Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add and return PermissionDenied error when not root #5

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extern crate regex;
extern crate nix;
extern crate regex;

use std::{fmt, error, io, convert, num};
use std::{convert, error, fmt, io, num};

/// Defines the general error type of iptables crate
#[derive(Debug)]
Expand All @@ -10,6 +10,7 @@ pub enum IPTError {
Regex(regex::Error),
Nix(nix::Error),
Parse(num::ParseIntError),
BadExitStatus(i32),
Other(&'static str),
}

Expand All @@ -23,6 +24,7 @@ impl fmt::Display for IPTError {
IPTError::Regex(ref err) => write!(f, "{}", err),
IPTError::Nix(ref err) => write!(f, "{}", err),
IPTError::Parse(ref err) => write!(f, "{}", err),
IPTError::BadExitStatus(i) => write!(f, "{}", i),
IPTError::Other(ref message) => write!(f, "{}", message),
}
}
Expand All @@ -35,6 +37,7 @@ impl error::Error for IPTError {
IPTError::Regex(ref err) => err.description(),
IPTError::Nix(ref err) => err.description(),
IPTError::Parse(ref err) => err.description(),
IPTError::BadExitStatus(_) => "iptables exited with a non-zero status",
IPTError::Other(ref message) => message,
}
}
Expand Down
202 changes: 115 additions & 87 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@

#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate nix;
extern crate regex;

pub mod error;

use std::process::{Command, Output};
use error::{IPTError, IPTResult};
use nix::fcntl::{flock, FlockArg};
use regex::{Match, Regex};
use error::{IPTResult, IPTError};
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use nix::fcntl::{flock, FlockArg};
use std::process::{Command, Output};
use std::vec::Vec;
use std::ffi::OsStr;

// List of built-in chains taken from: man 8 iptables
const BUILTIN_CHAINS_FILTER: &'static [&'static str] = &["INPUT", "FORWARD", "OUTPUT"];
Expand Down Expand Up @@ -97,24 +97,38 @@ pub fn new(is_ipv6: bool) -> IPTResult<IPTables> {
/// Creates a new `IPTables` Result with the command of 'iptables' if `is_ipv6` is `false`, otherwise the command is 'ip6tables'.
#[cfg(target_os = "linux")]
pub fn new(is_ipv6: bool) -> IPTResult<IPTables> {
let cmd = if is_ipv6 {
"ip6tables"
} else {
"iptables"
};
let cmd = if is_ipv6 { "ip6tables" } else { "iptables" };

let version_output = Command::new(cmd).arg("--version").output()?;
let re = Regex::new(r"v(\d+)\.(\d+)\.(\d+)")?;
let version_string = String::from_utf8_lossy(&version_output.stdout).into_owned();
let versions = re.captures(&version_string).ok_or("invalid version number")?;
let v_major = versions.get(1).ok_or("unable to get major version number")?.as_str().parse::<i32>()?;
let v_minor = versions.get(2).ok_or("unable to get minor version number")?.as_str().parse::<i32>()?;
let v_patch = versions.get(3).ok_or("unable to get patch version number")?.as_str().parse::<i32>()?;
let versions = re
.captures(&version_string)
.ok_or("invalid version number")?;
let v_major = versions
.get(1)
.ok_or("unable to get major version number")?
.as_str()
.parse::<i32>()?;
let v_minor = versions
.get(2)
.ok_or("unable to get minor version number")?
.as_str()
.parse::<i32>()?;
let v_patch = versions
.get(3)
.ok_or("unable to get patch version number")?
.as_str()
.parse::<i32>()?;

Ok(IPTables {
cmd: cmd,
has_check: (v_major > 1) || (v_major == 1 && v_minor > 4) || (v_major == 1 && v_minor == 4 && v_patch > 10),
has_wait: (v_major > 1) || (v_major == 1 && v_minor > 4) || (v_major == 1 && v_minor == 4 && v_patch > 19),
has_check: (v_major > 1)
|| (v_major == 1 && v_minor > 4)
|| (v_major == 1 && v_minor == 4 && v_patch > 10),
has_wait: (v_major > 1)
|| (v_major == 1 && v_minor > 4)
|| (v_major == 1 && v_minor == 4 && v_patch > 19),
})
}

Expand All @@ -123,31 +137,35 @@ impl IPTables {
pub fn get_policy(&self, table: &str, chain: &str) -> IPTResult<String> {
let builtin_chains = get_builtin_chains(table)?;
if !builtin_chains.iter().as_slice().contains(&chain) {
return Err(IPTError::Other("given chain is not a default chain in the given table, can't get policy"));
return Err(IPTError::Other(
"given chain is not a default chain in the given table, can't get policy",
));
}

let output = String::from_utf8_lossy(&self.run(&["-t", table, "-L", chain])?.stdout)
.into_owned();
let output =
String::from_utf8_lossy(&self.run(&["-t", table, "-L", chain])?.stdout).into_owned();
for item in output.trim().split("\n") {
let fields = item.split(" ").collect::<Vec<&str>>();
if fields.len() > 1 && fields[0] == "Chain" && fields[1] == chain {
return Ok(fields[3].replace(")", ""));
}
}
Err(IPTError::Other("could not find the default policy for table and chain"))
Err(IPTError::Other(
"could not find the default policy for table and chain",
))
}

/// Set the default policy for a table/chain.
pub fn set_policy(&self, table: &str, chain: &str, policy: &str) -> IPTResult<bool> {
pub fn set_policy(&self, table: &str, chain: &str, policy: &str) -> IPTResult<()> {
let builtin_chains = get_builtin_chains(table)?;
if !builtin_chains.iter().as_slice().contains(&chain) {
return Err(IPTError::Other("given chain is not a default chain in the given table, can't set policy"));
return Err(IPTError::Other(
"given chain is not a default chain in the given table, can't set policy",
));
}

match self.run(&["-t", table, "-P", chain, policy]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
self.run(&["-t", table, "-P", chain, policy])
.and_then(|_| Ok(()))
}

/// Executes a given `command` on the chain.
Expand All @@ -165,7 +183,9 @@ impl IPTables {
}

match self.run(&[&["-t", table, "-C", chain], rule.split_quoted().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Ok(_) => Ok(true),
Err(IPTError::BadExitStatus(1)) => Ok(false),
Err(IPTError::BadExitStatus(2)) => Ok(false),
Err(err) => Err(err),
}
}
Expand All @@ -175,61 +195,74 @@ impl IPTables {
#[cfg(target_os = "linux")]
pub fn chain_exists(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-L", chain]) {
Ok(output) => Ok(output.status.success()),
Ok(_) => Ok(true),
Err(IPTError::BadExitStatus(1)) => Ok(false),
Err(err) => Err(err),
}
}

/// Inserts `rule` in the `position` to the table/chain.
/// Returns `true` if the rule is inserted.
pub fn insert(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-I", chain, &position.to_string()], rule.split_quoted().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn insert(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<()> {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an example where the result type should maybe be IPResult<bool> instead. Thoughts?

self.run(
&[
&["-t", table, "-I", chain, &position.to_string()],
rule.split_quoted().as_slice(),
]
.concat(),
)
.and_then(|_| Ok(()))
}

/// Inserts `rule` in the `position` to the table/chain if it does not exist.
/// Returns `true` if the rule is inserted.
pub fn insert_unique(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<bool> {
pub fn insert_unique(
&self,
table: &str,
chain: &str,
rule: &str,
position: i32,
) -> IPTResult<()> {
if self.exists(table, chain, rule)? {
return Err(IPTError::Other("the rule exists in the table/chain"))
return Err(IPTError::Other("the rule exists in the table/chain"));
}

self.insert(table, chain, rule, position)
}

/// Replaces `rule` in the `position` to the table/chain.
/// Returns `true` if the rule is replaced.
pub fn replace(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-R", chain, &position.to_string()], rule.split_quoted().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn replace(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<()> {
self.run(
&[
&["-t", table, "-R", chain, &position.to_string()],
rule.split_quoted().as_slice(),
]
.concat(),
)
.and_then(|_| Ok(()))
}

/// Appends `rule` to the table/chain.
/// Returns `true` if the rule is appended.
pub fn append(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-A", chain], rule.split_quoted().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn append(&self, table: &str, chain: &str, rule: &str) -> IPTResult<()> {
self.run(&[&["-t", table, "-A", chain], rule.split_quoted().as_slice()].concat())
.and_then(|_| Ok(()))
}

/// Appends `rule` to the table/chain if it does not exist.
/// Returns `true` if the rule is appended.
pub fn append_unique(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
pub fn append_unique(&self, table: &str, chain: &str, rule: &str) -> IPTResult<()> {
if self.exists(table, chain, rule)? {
return Err(IPTError::Other("the rule exists in the table/chain"))
return Err(IPTError::Other("the rule exists in the table/chain"));
}

self.append(table, chain, rule)
}

/// Appends or replaces `rule` to the table/chain if it does not exist.
/// Returns `true` if the rule is appended or replaced.
pub fn append_replace(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
pub fn append_replace(&self, table: &str, chain: &str, rule: &str) -> IPTResult<()> {
if self.exists(table, chain, rule)? {
self.delete(table, chain, rule)?;
}
Expand All @@ -239,11 +272,9 @@ impl IPTables {

/// Deletes `rule` from the table/chain.
/// Returns `true` if the rule is deleted.
pub fn delete(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-D", chain], rule.split_quoted().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn delete(&self, table: &str, chain: &str, rule: &str) -> IPTResult<()> {
self.run(&[&["-t", table, "-D", chain], rule.split_quoted().as_slice()].concat())
.and_then(|_| Ok(()))
}

/// Deletes all repetition of the `rule` from the table/chain.
Expand Down Expand Up @@ -280,52 +311,40 @@ impl IPTables {

/// Creates a new user-defined chain.
/// Returns `true` if the chain is created.
pub fn new_chain(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-N", chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn new_chain(&self, table: &str, chain: &str) -> IPTResult<()> {
self.run(&["-t", table, "-N", chain]).and_then(|_| Ok(()))
}

/// Flushes (deletes all rules) a chain.
/// Returns `true` if the chain is flushed.
pub fn flush_chain(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-F", chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn flush_chain(&self, table: &str, chain: &str) -> IPTResult<()> {
self.run(&["-t", table, "-F", chain]).and_then(|_| Ok(()))
}

/// Renames a chain in the table.
/// Returns `true` if the chain is renamed.
pub fn rename_chain(&self, table: &str, old_chain: &str, new_chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-E", old_chain, new_chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn rename_chain(&self, table: &str, old_chain: &str, new_chain: &str) -> IPTResult<()> {
self.run(&["-t", table, "-E", old_chain, new_chain])
.and_then(|_| Ok(()))
}

/// Deletes a user-defined chain in the table.
/// Returns `true` if the chain is deleted.
pub fn delete_chain(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-X", chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn delete_chain(&self, table: &str, chain: &str) -> IPTResult<()> {
self.run(&["-t", table, "-X", chain]).and_then(|_| Ok(()))
}

/// Flushes all chains in a table.
/// Returns `true` if the chains are flushed.
pub fn flush_table(&self, table: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-F"]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
pub fn flush_table(&self, table: &str) -> IPTResult<()> {
self.run(&["-t", table, "-F"]).and_then(|_| Ok(()))
}

fn exists_old_version(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-S"]) {
Ok(output) => Ok(String::from_utf8_lossy(&output.stdout).into_owned().contains(&format!("-A {} {}", chain, rule))),
Ok(output) => Ok(String::from_utf8_lossy(&output.stdout)
.into_owned()
.contains(&format!("-A {} {}", chain, rule))),
Err(err) => Err(err),
}
}
Expand All @@ -352,14 +371,19 @@ impl IPTables {

let mut need_retry = true;
while need_retry {
match flock(file_lock.as_ref().unwrap().as_raw_fd(), FlockArg::LockExclusiveNonblock) {
match flock(
file_lock.as_ref().unwrap().as_raw_fd(),
FlockArg::LockExclusiveNonblock,
) {
Ok(_) => need_retry = false,
Err(e) => if e.errno() == nix::errno::EAGAIN {
// FIXME: may cause infinite loop
need_retry = true;
} else {
return Err(IPTError::Nix(e));
},
Err(e) => {
if e.errno() == nix::errno::EAGAIN {
// FIXME: may cause infinite loop
need_retry = true;
} else {
return Err(IPTError::Nix(e));
}
}
}
}
output = output_cmd.args(args).output()?;
Expand All @@ -372,6 +396,10 @@ impl IPTables {
};
}

Ok(output)
match output.status.code() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

output.status.code().and_then(|i| if i == 0 { Ok(output) } else { Err(IPTError::BadExitStatus(i)) })

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK this is invalid: and_then return type Option.

None => Ok(output),
Some(0) => Ok(output),
Some(i) => Err(IPTError::BadExitStatus(i)),
}
}
}
Loading