use crate::busybox;
use clap;
use crate::Error;
use crate::ErrorKind;
use crate::ctrl::{Interface, PromptError};
pub use crate::ffi::ForeignCommand;
use std::io;
use std::io::prelude::*;
use std::sync::Arc;
use std::sync::Mutex;
use std::collections::HashMap;
#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(unix)]
use crate::ffi::daemonize;
#[derive(Clone)]
pub enum Command {
Native(NativeCommand),
Foreign(ForeignCommand),
}
pub type NativeCommand = fn(&mut Shell, Vec<String>) -> Result<(), Error>;
impl Command {
pub fn run(&self, mut sh: &mut Shell, args: Vec<String>) -> Result<(), Error> {
use self::Command::*;
match *self {
Native(ref func) => func(&mut sh, args),
Foreign(ref func) => func.run(args),
}
}
#[cfg(unix)]
pub fn daemonized(&self, sh: &Shell, args: Vec<String>) -> Result<(), Error> {
daemonize(sh.daemon_clone(), self.clone(), args)
}
#[cfg(not(unix))]
pub fn daemonized(&self, sh: &Shell, args: Vec<String>) -> Result<(), Error> {
self.run(&mut sh.daemon_clone(), args)
}
}
impl From<NativeCommand> for Command {
fn from(cmd: NativeCommand) -> Command {
Command::Native(cmd)
}
}
impl From<ForeignCommand> for Command {
fn from(cmd: ForeignCommand) -> Command {
Command::Foreign(cmd)
}
}
#[derive(Default)]
pub struct Toolbox(HashMap<String, Command>);
impl Toolbox {
#[inline]
pub fn empty() -> Toolbox {
Toolbox(HashMap::new())
}
#[inline]
pub fn new() -> Toolbox {
let mut toolbox = Toolbox::empty();
toolbox.insert_many_native(vec![
("cat" , busybox::cat),
("cd" , busybox::cd),
("downgrade" , busybox::downgrade),
("echo" , busybox::echo),
("exec" , busybox::exec),
("grep" , busybox::grep),
("help" , busybox::help),
("ls" , busybox::ls),
("mkdir" , busybox::mkdir),
("pwd" , busybox::pwd),
("rm" , busybox::rm),
]);
#[cfg(unix)]
toolbox.insert_many_native(vec![
("chmod" , busybox::chmod),
("chown" , busybox::chown),
("chroot" , busybox::chroot),
("fchdir" , busybox::fchdir),
("fds" , busybox::fds),
("jit" , busybox::jit),
("id" , busybox::id),
("setgroups" , busybox::setgroups),
("setgid" , busybox::setgid),
("setuid" , busybox::setuid),
("seteuid" , busybox::seteuid),
]);
#[cfg(target_os="linux")]
toolbox.insert_many_native(vec![
("caps" , busybox::caps),
("mount" , busybox::mount),
("keepcaps" , busybox::keepcaps),
("setresgid" , busybox::setresgid),
("setresuid" , busybox::setresuid),
("setreuid" , busybox::setreuid),
]);
#[cfg(target_os="openbsd")]
toolbox.insert_many_native(vec![
("pledge" , busybox::pledge),
]);
#[cfg(feature="archives")]
toolbox.insert_many_native(vec![
("tar" , busybox::tar),
]);
#[cfg(feature="network")]
toolbox.insert_many_native(vec![
("curl" , busybox::curl),
("revshell" , busybox::revshell),
#[cfg(unix)]
("ipcshell" , busybox::ipcshell),
]);
toolbox
}
#[inline]
pub fn get(&self, key: &str) -> Option<&Command> {
self.0.get(key)
}
#[inline]
pub fn keys(&self) -> Vec<String> {
self.0
.keys()
.map(|x| x.to_owned())
.collect()
}
#[inline]
pub fn insert<I: Into<String>>(&mut self, key: I, func: Command) {
self.0.insert(key.into(), func);
}
#[inline]
pub fn insert_many<I: Into<String>>(&mut self, commands: Vec<(I, Command)>) {
for (key, func) in commands {
self.insert(key, func);
}
}
#[inline]
pub fn insert_many_native<I: Into<String>>(&mut self, commands: Vec<(I, NativeCommand)>) {
for (key, func) in commands {
self.insert(key, func.into());
}
}
#[inline]
pub fn with<I: Into<String>>(mut self, commands: Vec<(I, NativeCommand)>) -> Toolbox {
self.insert_many_native(commands);
self
}
}
pub struct Shell {
ui: Interface,
toolbox: Arc<Mutex<Toolbox>>,
}
impl Read for Shell {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.ui.read(buf)
}
}
impl Write for Shell {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.ui.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.ui.flush()
}
}
impl Shell {
pub fn new(toolbox: Toolbox) -> Shell {
let toolbox = Arc::new(Mutex::new(toolbox));
let ui = Interface::default(&toolbox);
Shell {
ui,
toolbox,
}
}
#[inline]
pub fn downgrade(&mut self) {
match self.ui {
#[cfg(feature="readline")]
Interface::Fancy(_) => {
self.ui = Interface::stdio();
},
_ => shprintln!(self, "[-] interface is already downgraded"),
}
}
#[inline]
pub fn list_commands(&self) -> Vec<String> {
let toolbox = self.toolbox.lock().unwrap();
toolbox.keys()
}
#[inline]
pub fn hotswap(&mut self, ui: Interface) {
self.ui = ui;
}
#[cfg(unix)]
#[inline]
pub fn pipe(&mut self) -> Option<(RawFd, RawFd, RawFd)> {
self.ui.pipe()
}
#[inline]
pub fn insert<I: Into<String>>(&mut self, name: I, command: Command) {
let mut toolbox = self.toolbox.lock().unwrap();
toolbox.insert(name, command);
}
fn process(&mut self, cmd: InputCmd) {
debug!("cmd: {:?}", cmd);
let result: Option<Command> = {
let toolbox = self.toolbox.lock().unwrap();
match toolbox.get(&cmd.prog) {
Some(x) => Some(x.clone()),
None => None,
}
};
let result = match (result, cmd.bg) {
(Some(func), true) => func.daemonized(&self, cmd.args),
(Some(func), false) => func.run(self, cmd.args),
(None, _) => Err(ErrorKind::Args(clap::Error {
message: String::from("\u{1b}[1;31merror:\u{1b}[0m unknown command"),
kind: clap::ErrorKind::MissingRequiredArgument,
info: None,
}).into()),
};
if let Err(err) = result {
match *err.kind() {
ErrorKind::Args(ref err) => shprintln!(self, "{}", err.message),
_ => shprintln!(self, "error: {:?}", err),
}
}
}
fn daemon_clone(&self) -> Shell {
let toolbox = self.toolbox.clone();
let ui = Interface::default(&toolbox);
Shell {
ui,
toolbox,
}
}
#[inline]
fn prompt(&mut self) -> Result<String, PromptError> {
self.ui.readline(" [%]> ")
}
#[inline]
fn get_line(&mut self) -> Result<Option<InputCmd>, ()> {
let readline = self.prompt();
match readline {
Ok(line) => {
#[cfg(feature="readline")]
self.ui.add_history_entry(line.as_ref());
Ok(parse_line(&line))
},
Err(_) => Err(()),
}
}
#[inline]
pub fn exec_once(&mut self, line: &str) {
if let Some(cmd) = parse_line(line) {
self.process(cmd);
}
}
pub fn run(&mut self) {
loop {
match self.get_line() {
Ok(Some(cmd)) => self.process(cmd),
Ok(None) => (),
Err(_) => break,
}
}
}
}
#[inline]
fn tokenize(line: &str) -> Vec<String> {
let mut cmd = Vec::new();
let mut token = String::new();
let mut escape = false;
for x in line.chars() {
if escape {
token.push(x);
escape = false;
continue;
}
match x {
' ' | '\n' => {
if !token.is_empty() {
cmd.push(token);
token = String::new();
}
},
'\\' => {
escape = true;
},
x => {
token.push(x);
},
}
}
if !token.is_empty() {
cmd.push(token);
}
cmd
}
#[derive(Debug, PartialEq)]
pub struct InputCmd {
prog: String,
args: Vec<String>,
bg: bool,
}
#[inline]
fn parse_line(line: &str) -> Option<InputCmd> {
trace!("line: {:?}", line);
if is_comment(&line) {
return None;
}
let (bg, line) = if line.ends_with(" &") {
(true, &line[..line.len()-2])
} else {
(false, line)
};
let cmd = tokenize(&line);
debug!("got {:?}", cmd);
if cmd.is_empty() {
None
} else {
let prog = cmd[0].clone();
Some(InputCmd {
prog,
args: cmd,
bg,
})
}
}
#[inline]
fn is_comment(line: &str) -> bool {
for x in line.chars() {
match x {
'#' => return true,
' ' => (),
_ => return false,
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse() {
let cmd = tokenize("foo\\ \\\\bar");
assert_eq!(cmd, vec!["foo ", "\\bar"]);
}
#[test]
fn test_empty() {
let cmd = tokenize("");
let expected: Vec<String> = Vec::new();
assert_eq!(expected, cmd);
}
#[test]
fn test_newline() {
let cmd = tokenize("\n");
let expected: Vec<String> = Vec::new();
assert_eq!(expected, cmd);
}
#[test]
fn test_is_comment() {
assert_eq!(false, is_comment("hello world"));
assert_eq!(true, is_comment("#hello world"));
assert_eq!(false, is_comment("hello #world"));
assert_eq!(false, is_comment(""));
assert_eq!(false, is_comment(" "));
assert_eq!(true, is_comment(" # x"));
}
#[test]
fn test_bg() {
let cmd = parse_line("foo bar &");
assert_eq!(Some(InputCmd {
prog: "foo".to_string(),
args: vec!["foo".to_string(), "bar".to_string()],
bg: true,
}), cmd);
}
}