Skip to content

Commit

Permalink
new: rest api
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsocket committed Jul 15, 2024
1 parent 696e38c commit 1eec65f
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 12 deletions.
15 changes: 14 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ shell-words = "1.1.0"
serde_yaml = "0.9.30"
actix-web = "4.8.0"
uuid = "1.10.0"
nix = { version = "0.29.0", features = ["signal"] }

[dev-dependencies]
tempfile = "3.8.0"
Expand Down
3 changes: 2 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api")
.service(session::start)
.service(session::stop)
.service(session::show)
.service(session::list),
);
Expand All @@ -49,7 +50,7 @@ pub(crate) async fn start(opts: Options) -> Result<(), Error> {
.app_data(web::Data::new(state.clone()))
.configure(config)
.default_service(web::route().to(not_found))
.wrap(actix_web::middleware::Logger::default())
//.wrap(actix_web::middleware::Logger::default())
})
.bind(&address)
.map_err(|e| e.to_string())?
Expand Down
14 changes: 14 additions & 0 deletions src/api/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ pub async fn show(path: web::Path<String>, state: web::Data<SharedState>) -> Htt
}
}

#[get("/session/{session_id}/stop")]
pub async fn stop(path: web::Path<String>, state: web::Data<SharedState>) -> HttpResponse {
let session_id = path.into_inner();
let session_id = match uuid::Uuid::parse_str(&session_id) {
Ok(uuid) => uuid,
Err(e) => return HttpResponse::BadRequest().body(e.to_string()),
};

match state.read().await.stop_session(&session_id) {
Ok(_) => HttpResponse::Ok().body("session stopping"),
Err(e) => HttpResponse::NotFound().body(e.to_string()),
}
}

#[post("/session/new")]
pub async fn start(
state: web::Data<SharedState>,
Expand Down
42 changes: 33 additions & 9 deletions src/api/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ impl Wrapper {
session_id: uuid::Uuid,
argv: Vec<String>,
) -> Result<Self, Error> {
// make sure the args are correct
// TODO: change all errors and results to anyhow
let _ = Options::try_parse_from(&argv).map_err(|e| e.to_string())?;
let app = get_current_exe()?;

// https://stackoverflow.com/questions/49245907/how-to-read-subprocess-output-asynchronously
Expand All @@ -97,7 +94,11 @@ impl Wrapper {

let process_id = child.id().unwrap();

log::info!("spawned '{} {:?}' as {process_id}", &app, &argv);
log::info!(
"[{session_id}] started '{} {:?}' as process {process_id}",
&app,
&argv
);

// read stdout
let output = Arc::new(Mutex::new(vec![]));
Expand All @@ -114,12 +115,16 @@ impl Wrapper {
tokio::task::spawn(async move {
match child.wait().await {
Ok(code) => {
log::info!("child process {process_id} completed with code {code}");
log::info!(
"[{session_id}] child process {process_id} completed with code {code}"
);
*child_completed.lock().unwrap() =
Some(Completion::with_status(code.code().unwrap()));
Some(Completion::with_status(code.code().unwrap_or(-1)));
}
Err(error) => {
log::error!("child process {process_id} completed with error {error}");
log::error!(
"[{session_id}] child process {process_id} completed with error {error}"
);
*child_completed.lock().unwrap() =
Some(Completion::with_error(error.to_string()));
}
Expand All @@ -136,6 +141,14 @@ impl Wrapper {
output,
})
}

pub fn stop(&self) -> Result<(), Error> {
nix::sys::signal::kill(
nix::unistd::Pid::from_raw(self.process_id as nix::libc::pid_t),
nix::sys::signal::Signal::SIGTERM,
)
.map_err(|e| e.to_string())
}
}

#[derive(Serialize)]
Expand All @@ -154,10 +167,13 @@ impl State {
client: String,
argv: Vec<String>,
) -> Result<uuid::Uuid, Error> {
let session_id = uuid::Uuid::new_v4();
// TODO: change all errors and results to anyhow

log::info!("starting session {} for {:?} ...", &session_id, &client);
// validate argv
let _ = Options::try_parse_from(&argv).map_err(|e| e.to_string())?;
let session_id = uuid::Uuid::new_v4();

// add to active sessions
self.sessions.insert(
session_id.clone(),
Wrapper::start(client, session_id, argv).await?,
Expand All @@ -166,6 +182,14 @@ impl State {
Ok(session_id)
}

pub fn stop_session(&self, id: &uuid::Uuid) -> Result<(), Error> {
let session = match self.sessions.get(id) {
Some(s) => s,
None => return Err(format!("session {id} not found")),
};
session.stop()
}

pub fn active_sessions(&self) -> &HashMap<uuid::Uuid, Wrapper> {
&self.sessions
}
Expand Down
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ pub(crate) use crate::session::Session;
fn setup() -> Result<Options, session::Error> {
if env::var_os("RUST_LOG").is_none() {
// set `RUST_LOG=debug` to see debug logs
env::set_var("RUST_LOG", "info,blocking=off,pavao=off,fast_socks5=off");
env::set_var(
"RUST_LOG",
"info,blocking=off,pavao=off,fast_socks5=off,actix_server=warn",
);
}

env_logger::builder()
Expand Down
50 changes: 50 additions & 0 deletions test-servers/test-api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import requests
import time
import os
import sys

# simulate a session stop if -k is passed
do_kill = '-k' in sys.argv

# start a new dns enumeration session
api_server = 'http://localhost:8666'
args = ["dns", "--target", "something.com", "--payloads", "data/1000.txt"]

resp = requests.post(f'{api_server}/api/session/new', json=args)
if not resp.ok:
print(f"ERROR: {resp}")
quit()

session_id = resp.json()

print(f"session {session_id} started ...")

# list sessions
print(requests.get(f'{api_server}/api/sessions').json())
quit()
num = 0

while True:
time.sleep(1)
# get session status
resp = requests.get(f'{api_server}/api/session/{session_id}')
if resp.ok:
os.system("clear")
session = resp.json()
print(f"started_at={session['started_at']}")
print('\n'.join(session['output']))

if 'completed' in session and session['completed'] is not None:
break

if do_kill and num >= 2:
print("killing ...")
# stop the session
resp = requests.get(f'{api_server}/api/session/{session_id}/stop')
print(resp.text)

num += 1

print("\n")
print(session)

0 comments on commit 1eec65f

Please sign in to comment.