Skip to content

Commit

Permalink
qnap: Implement cgi logic in teliod
Browse files Browse the repository at this point in the history
  • Loading branch information
lcruz99 committed Dec 4, 2024
1 parent 4a870ce commit e95f37f
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 131 deletions.
Empty file added .unreleased/LLT-5831
Empty file.
1 change: 1 addition & 0 deletions 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 clis/teliod/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ anyhow.workspace = true
smart-default = "0.7.1"
base64 = "0.22.1"
dirs = "4.0.0"
lazy_static.workspace = true

[dev-dependencies]
rand = "0.8.5"
7 changes: 7 additions & 0 deletions clis/teliod/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod configure_interface;
mod core_api;
mod daemon;
mod nc;
mod qnap;

use crate::{
command_listener::CommandResponse,
Expand All @@ -44,6 +45,8 @@ enum Cmd {
Daemon { config_path: String },
#[clap(flatten)]
Client(ClientCmd),
#[clap(about = "Receive and parse http requests")]
Cgi,
}

#[derive(Debug, ThisError)]
Expand Down Expand Up @@ -134,5 +137,9 @@ async fn main() -> Result<(), TeliodError> {
Err(TeliodError::DaemonIsNotRunning)
}
}
Cmd::Cgi => {
qnap::parse_cgi();
Ok(())
}
}
}
226 changes: 226 additions & 0 deletions clis/teliod/src/qnap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
use std::{
env, fs,
io::{self, Read},
process::{Command, Stdio},
};

use serde_json::json;

struct PathConfig;

impl PathConfig {
const APP_TMP_DIR: &'static str = "/tmp/nordsecuritymeshnet";
const QPKG_DIR: &'static str = "/share/CACHEDEV1_DATA/.qpkg/NordSecurityMeshnet";

// Static methods for derived paths
pub fn teliod_bin() -> String {
format!("{}/teliod", Self::QPKG_DIR)
}

pub fn teliod_cfg() -> String {
format!("{}/teliod.cfg", Self::QPKG_DIR)
}

pub fn teliod_log() -> String {
"/var/log/teliod.log".to_owned()
}

pub fn meshnet_log() -> String {
format!("{}/meshnet.log", Self::QPKG_DIR)
}

pub fn pid() -> String {
format!("{}/teliod.pid", Self::APP_TMP_DIR)
}
}

#[derive(Debug)]
enum HttpMethod {
Get,
Post,
Patch,
Unsupported,
}

#[derive(Debug)]
enum Action {
Start,
Stop,
UpdateConfig,
GetStatus,
GetTeliodLogs,
GetMeshnetLogs,
Invalid,
}

impl Action {
fn from_query(query: &str) -> Self {
match query {
"action=start" => Action::Start,
"action=stop" => Action::Stop,
"action=update-config" => Action::UpdateConfig,
"action=get-status" => Action::GetStatus,
"action=get-teliod-logs" => Action::GetTeliodLogs,
"action=get-meshnet-logs" => Action::GetMeshnetLogs,
_ => Action::Invalid,
}
}
}

pub(crate) fn parse_cgi() {
println!("Content-Type: application/json\n");

let method = match env::var("REQUEST_METHOD").as_deref() {
Ok("GET") => HttpMethod::Get,
Ok("POST") => HttpMethod::Post,
Ok("PATCH") => HttpMethod::Patch,
_ => HttpMethod::Unsupported,
};

let mut post_data = String::new();
if matches!(method, HttpMethod::Post | HttpMethod::Patch) {
let content_length = env::var("CONTENT_LENGTH")
.ok()
.and_then(|len| len.parse::<usize>().ok());
if let Some(length) = content_length {
let _ = io::stdin()
.take(length as u64)
.read_to_string(&mut post_data); // TODO: handle failure
}
}

let action = {
let query = env::var("QUERY_STRING").unwrap_or_default();
Action::from_query(&query)
};

match (method, action) {
(HttpMethod::Post, Action::Start) => start_daemon(),
(HttpMethod::Post, Action::Stop) => stop_daemon(),
(HttpMethod::Patch, Action::UpdateConfig) => update_config(&post_data),
(HttpMethod::Get, Action::GetStatus) => get_status(),
(HttpMethod::Get, Action::GetMeshnetLogs) => get_meshnet_logs(),
(HttpMethod::Get, Action::GetTeliodLogs) => get_teliod_logs(),
(HttpMethod::Unsupported, _) => send_response(405, "Method not allowed."),
(_, Action::Invalid) => send_response(400, "Invalid action."),
_ => send_response(400, "Bad request."),
}
}

fn is_application_running() -> bool {
if let Ok(pid) = fs::read_to_string(PathConfig::pid()) {
if fs::metadata(format!("/proc/{}", pid.trim())).is_ok() {
return true;
}
}
false
}

fn send_response<T: serde::Serialize>(code: u16, message: T) {
let response = json!({ "code": code, "message": message });
println!("{}", response);
}

fn start_daemon() {
if is_application_running() {
send_response(400, "Application is already running.");
return;
}

let teliod_log_file = match fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(PathConfig::teliod_log())
{
Ok(file) => file,
Err(_) => {
send_response(500, "Failed to open teliod log file.");
return;
}
};

match Command::new("setsid")
.arg(PathConfig::teliod_bin())
.arg("daemon")
.arg(PathConfig::teliod_cfg())
.stdout(Stdio::from(
teliod_log_file
.try_clone()
.expect("Failed to open teliod log file."),
))
.stderr(Stdio::from(teliod_log_file))
.spawn()
{
Ok(child) => {
let pid = child.id();
if fs::write(PathConfig::pid(), pid.to_string()).is_ok() {
send_response(200, "Application started successfully.");
} else {
send_response(500, "Failed to save PID.");
}
}
Err(_) => send_response(500, "Failed to start the application."),
}
}

fn stop_daemon() {
if let Ok(pid) = fs::read_to_string(PathConfig::pid()) {
if Command::new("kill").arg(pid.trim()).status().is_ok() {
let _ = fs::remove_file(PathConfig::pid());
send_response(200, "Application stopped successfully.");
} else {
send_response(500, "Failed to stop the application.");
}
} else {
send_response(400, "Application is not running.");
}
}

// TODO: LLT-5712
fn update_config(post_data: &str) {
// match serde_json::from_str::<serde_json::Value>(post_data)
// .ok()
// .and_then(|json| json.get("config").and_then(|c| c.as_str()))
// {
// Some(new_config) => {
// if fs::write(PathConfig::cfg(), new_config).is_ok() {
// send_response(200, "Configuration updated successfully.");
// } else {
// send_response(500, "Failed to update configuration.");
// }
// }
// None => send_response(400, "Invalid configuration data."),
// }
}

fn get_status() {
if is_application_running() {
match Command::new(PathConfig::teliod_bin())
.arg("get-status")
.output()
{
Ok(output) => {
let status = String::from_utf8_lossy(&output.stdout).to_string();
send_response(200, json!({ "status-report": status }));
}
Err(_) => send_response(500, "Failed to retrieve status."),
}
} else {
send_response(400, "Application is not running.");
}
}

fn get_teliod_logs() {
match fs::read_to_string(PathConfig::teliod_log()) {
Ok(logs) => send_response(200, json!({ "teliod-logs": logs })),
Err(_) => send_response(404, "Log file not found."),
}
}

fn get_meshnet_logs() {
match fs::read_to_string(PathConfig::meshnet_log()) {
Ok(logs) => send_response(200, json!({ "meshnet-logs": logs })),
Err(_) => send_response(404, "Log file not found."),
}
}
2 changes: 1 addition & 1 deletion qnap/package_routines
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
#}"
#
PKG_POST_REMOVE="{
$CMD_RM -rf /tmp/nordsecmeshnet
$CMD_RM -rf /tmp/nordsecuritymeshnet
$CMD_RM -f /home/Qhttpd/Web/NordSecurityMeshnet
$CMD_RM -f /home/httpd/cgi-bin/qpkg/teliod.cgi
$CMD_RM -f /var/log/teliod.log
Expand Down
1 change: 0 additions & 1 deletion qnap/shared/NordSecMeshnet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
CONF=/etc/config/qpkg.conf
QPKG_NAME="NordSecurityMeshnet"


QPKG_ROOT=`/sbin/getcfg $QPKG_NAME Install_Path -f ${CONF}`
APACHE_ROOT=`/sbin/getcfg SHARE_DEF defWeb -d Qweb -f /etc/config/def_share.info`
export QNAP_QPKG=$QPKG_NAME
Expand Down
Loading

0 comments on commit e95f37f

Please sign in to comment.