From d84eb375001f5d2010826723594e3bdbfb631a15 Mon Sep 17 00:00:00 2001 From: Luis Cruz Date: Mon, 23 Dec 2024 13:12:38 +0000 Subject: [PATCH 1/6] teliod: Rename api documentation --- clis/teliod/{QNAP.md => API.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename clis/teliod/{QNAP.md => API.md} (100%) diff --git a/clis/teliod/QNAP.md b/clis/teliod/API.md similarity index 100% rename from clis/teliod/QNAP.md rename to clis/teliod/API.md From 5049fc450a5d7b633ad8012eab91c719b63e9b93 Mon Sep 17 00:00:00 2001 From: Luis Cruz Date: Mon, 23 Dec 2024 14:39:57 +0000 Subject: [PATCH 2/6] teliod: Make qnap feature a subset of cgi feature --- clis/teliod/Cargo.toml | 4 +++- clis/teliod/src/main.rs | 14 +++++++------- qnap/shared/teliod.cgi | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/clis/teliod/Cargo.toml b/clis/teliod/Cargo.toml index 57e4be793..b793ab955 100644 --- a/clis/teliod/Cargo.toml +++ b/clis/teliod/Cargo.toml @@ -42,4 +42,6 @@ rand = "0.8.5" serial_test = "3.2.0" [features] -qnap = ["const_format", "rust-cgi"] +cgi = ["const_format", "rust-cgi"] +qnap = ["cgi"] + diff --git a/clis/teliod/src/main.rs b/clis/teliod/src/main.rs index e9021c5ba..32ac16708 100644 --- a/clis/teliod/src/main.rs +++ b/clis/teliod/src/main.rs @@ -19,8 +19,8 @@ mod configure_interface; mod core_api; mod daemon; mod nc; -#[cfg(feature = "qnap")] -mod qnap; +#[cfg(feature = "cgi")] +mod cgi; use crate::{ command_listener::CommandResponse, @@ -50,9 +50,9 @@ enum Cmd { Daemon { config_path: String }, #[clap(flatten)] Client(ClientCmd), - #[cfg(feature = "qnap")] + #[cfg(feature = "cgi")] #[clap(about = "Receive and parse http requests")] - QnapCgi, + Cgi, } #[derive(Debug, ThisError)] @@ -149,9 +149,9 @@ async fn main() -> Result<(), TeliodError> { Err(TeliodError::DaemonIsNotRunning) } } - #[cfg(feature = "qnap")] - Cmd::QnapCgi => { - rust_cgi::handle(qnap::handle_request); + #[cfg(feature = "cgi")] + Cmd::Cgi => { + rust_cgi::handle(cgi::handle_request); Ok(()) } } diff --git a/qnap/shared/teliod.cgi b/qnap/shared/teliod.cgi index 260e2959e..74abddd09 100755 --- a/qnap/shared/teliod.cgi +++ b/qnap/shared/teliod.cgi @@ -5,4 +5,4 @@ CONF=/etc/config/qpkg.conf QPKG_NAME="NordSecurityMeshnet" QPKG_ROOT=`/sbin/getcfg $QPKG_NAME Install_Path -f ${CONF}` -exec "${QPKG_ROOT}/teliod" qnap-cgi +exec "${QPKG_ROOT}/teliod" cgi From d5e518e52e984d8899efb0235f81ef010dd10f95 Mon Sep 17 00:00:00 2001 From: Luis Cruz Date: Mon, 23 Dec 2024 14:41:01 +0000 Subject: [PATCH 3/6] teliod: Add http test server to host cgi --- Justfile | 5 +++++ contrib/http_root/.gitignore | 1 + contrib/http_root/cgi-bin/teliod.cgi | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 contrib/http_root/.gitignore create mode 100644 contrib/http_root/cgi-bin/teliod.cgi diff --git a/Justfile b/Justfile index d4cefcd69..a9bcf69fb 100644 --- a/Justfile +++ b/Justfile @@ -67,6 +67,11 @@ black fix="false": pylint: docker run --rm -t -v$(pwd):/code 'ubuntu:22.04' sh -c "apt-get update && apt-get -y install python3-pip && cd code && pip3 install --no-deps -r requirements.txt && pipenv install --system && cd nat-lab && pipenv install --system && pylint -f colorized . --ignore telio_bindings.py" +# Start a dev web cgi server, for local teliod cgi development +web: + @echo "Go to http://127.0.0.1:8080/cgi-bin/teliod.cgi" + python3 -m http.server --cgi -d $(pwd)/contrib/http_root/ -b 127.0.0.1 8080 + _udeps-install: _nightly-install cargo +{{ nightly }} install cargo-udeps@0.1.47 --locked diff --git a/contrib/http_root/.gitignore b/contrib/http_root/.gitignore new file mode 100644 index 000000000..a98849924 --- /dev/null +++ b/contrib/http_root/.gitignore @@ -0,0 +1 @@ +**/*.log diff --git a/contrib/http_root/cgi-bin/teliod.cgi b/contrib/http_root/cgi-bin/teliod.cgi new file mode 100644 index 000000000..ab7f09ad4 --- /dev/null +++ b/contrib/http_root/cgi-bin/teliod.cgi @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +fail() { + cat $DEV_DIR/cargo.log + exit 0 +} + +DEV_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BIN_DIR="$(realpath $DEV_DIR/../../../target/debug/)" + +cargo build -F cgi -p teliod >"$DEV_DIR/cargo.log" 2>&1 || fail + +export PATH=$BIN_DIR:$PATH + +exec teliod cgi From 409cd3cd8c9df527212b9fcb242a1f7adb694172 Mon Sep 17 00:00:00 2001 From: Luis Cruz Date: Mon, 23 Dec 2024 14:47:55 +0000 Subject: [PATCH 4/6] teliod: Refactor qnap component for a cgi focused approach --- .unreleased/LLT-5882 | 0 clis/teliod/src/{qnap.rs => cgi/api.rs} | 48 ++++++++-------- clis/teliod/src/cgi/constants.rs | 15 +++++ clis/teliod/src/cgi/mod.rs | 73 +++++++++++++++++++++++++ clis/teliod/src/main.rs | 6 +- 5 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 .unreleased/LLT-5882 rename clis/teliod/src/{qnap.rs => cgi/api.rs} (92%) create mode 100644 clis/teliod/src/cgi/constants.rs create mode 100644 clis/teliod/src/cgi/mod.rs diff --git a/.unreleased/LLT-5882 b/.unreleased/LLT-5882 new file mode 100644 index 000000000..e69de29bb diff --git a/clis/teliod/src/qnap.rs b/clis/teliod/src/cgi/api.rs similarity index 92% rename from clis/teliod/src/qnap.rs rename to clis/teliod/src/cgi/api.rs index a93dc6810..b2769215c 100644 --- a/clis/teliod/src/qnap.rs +++ b/clis/teliod/src/cgi/api.rs @@ -1,12 +1,14 @@ -use core::str; use std::{ fs, io::Write, process::{Command, Stdio}, + str, }; -use const_format::concatcp; -use rust_cgi::{http::Method, http::StatusCode, text_response, Request, Response}; +use rust_cgi::{ + http::{Method, StatusCode}, + text_response, Response, +}; use crate::{ command_listener::CommandResponse, @@ -14,15 +16,10 @@ use crate::{ ClientCmd, DaemonSocket, TeliodError, TIMEOUT_SEC, }; -const QPKG_DIR: &str = "/share/CACHEDEV1_DATA/.qpkg/NordSecurityMeshnet"; -const TELIOD_BIN: &str = concatcp!(QPKG_DIR, "/teliod"); -const MESHNET_LOG: &str = concatcp!(QPKG_DIR, "/meshnet.log"); -const TELIOD_LOG: &str = "/var/log/teliod.log"; - -#[cfg(not(test))] -const TELIOD_CFG: &str = concatcp!(QPKG_DIR, "/teliod.cfg"); -#[cfg(test)] -use tests::TELIOD_CFG; +use super::{ + constants::{MESHNET_LOG, TELIOD_BIN, TELIOD_CFG, TELIOD_LOG}, + CgiRequest, +}; macro_rules! teliod_blocking_query { ($command:expr) => {{ @@ -37,26 +34,26 @@ macro_rules! teliod_blocking_query { }}; } -pub(crate) fn handle_request(request: Request) -> Response { - match (request.method(), request.uri().query()) { - (&Method::POST, _) => start_daemon(), - (&Method::DELETE, _) => stop_daemon(), - (&Method::PATCH, _) => { +pub(crate) fn handle_api(request: &CgiRequest) -> Option { + match (request.method(), request.route()) { + (&Method::POST, "" | "/") => Some(start_daemon()), + (&Method::DELETE, "" | "/") => Some(stop_daemon()), + (&Method::PATCH, "" | "/") => { let body = match str::from_utf8(request.body()) { Ok(body) => body, Err(error) => { - return text_response( + return Some(text_response( StatusCode::BAD_REQUEST, format!("Invalid UTF-8 in request body: {}", error), - ) + )) } }; - update_config(body) + Some(update_config(body)) } - (&Method::GET, Some("info=get-status")) => get_status(), - (&Method::GET, Some("info=get-teliod-logs")) => get_teliod_logs(), - (&Method::GET, Some("info=get-meshnet-logs")) => get_meshnet_logs(), - (_, _) => text_response(StatusCode::BAD_REQUEST, "Invalid request."), + (&Method::GET, "/get-status") => Some(get_status()), + (&Method::GET, "/get-teliod-logs") => Some(get_teliod_logs()), + (&Method::GET, "/get-meshnet-logs") => Some(get_meshnet_logs()), + (_, _) => Some(text_response(StatusCode::BAD_REQUEST, "Invalid request.")), } } @@ -237,12 +234,11 @@ mod tests { use super::{update_config, TeliodDaemonConfig}; use crate::{ + cgi::constants::TELIOD_CFG, config::{InterfaceConfig, MqttConfig, Percentage}, configure_interface::InterfaceConfigurationProvider, }; - pub const TELIOD_CFG: &str = "/tmp/teliod_config.json"; - #[test] #[serial] fn test_update_config() { diff --git a/clis/teliod/src/cgi/constants.rs b/clis/teliod/src/cgi/constants.rs new file mode 100644 index 000000000..81b08caa0 --- /dev/null +++ b/clis/teliod/src/cgi/constants.rs @@ -0,0 +1,15 @@ +use const_format::concatcp; + +#[cfg(feature = "qnap")] +pub const APP_DIR: &str = "/share/CACHEDEV1_DATA/.qpkg/NordSecurityMeshnet"; +#[cfg(not(feature = "qnap"))] +pub const APP_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/target/debug"); + +pub const TELIOD_BIN: &str = concatcp!(APP_DIR, "/teliod"); +pub const MESHNET_LOG: &str = concatcp!(APP_DIR, "/meshnet.log"); +pub const TELIOD_LOG: &str = "/var/log/teliod.log"; + +#[cfg(not(test))] +pub const TELIOD_CFG: &str = concatcp!(APP_DIR, "/teliod.cfg"); +#[cfg(test)] +pub const TELIOD_CFG: &str = "/tmp/teliod_config.json"; diff --git a/clis/teliod/src/cgi/mod.rs b/clis/teliod/src/cgi/mod.rs new file mode 100644 index 000000000..afa7f2f6e --- /dev/null +++ b/clis/teliod/src/cgi/mod.rs @@ -0,0 +1,73 @@ +use std::{env::var, ops::Deref}; + +use rust_cgi::{http::StatusCode, text_response, Request, Response}; + +mod api; +pub(crate) mod constants; + +pub struct CgiRequest { + pub inner: Request, + route: String, +} + +impl CgiRequest { + fn new(inner: Request) -> Self { + Self { + inner, + route: var("PATH_INFO").unwrap_or_default(), + } + } + + pub fn route(&self) -> &str { + &self.route + } +} + +impl Deref for CgiRequest { + type Target = Request; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +pub fn handle_request(request: Request) -> Response { + let request = CgiRequest::new(request); + + if let Some(response) = api::handle_api(&request) { + #[cfg(debug_assertions)] + let response = trace_request(&request, &response).unwrap_or(text_response( + StatusCode::INTERNAL_SERVER_ERROR, + "Tracing request failed.", + )); + return response; + } + + text_response(StatusCode::BAD_REQUEST, "Invalid request.") +} + +#[cfg(debug_assertions)] +fn trace_request(request: &CgiRequest, response: &Response) -> Option { + use std::{env::vars, fmt::Write}; + let mut msg = String::new(); + let _ = writeln!( + &mut msg, + "ENVIRONMENT:\n{:#?}\n", + vars().collect::>(), + ); + let _ = writeln!( + &mut msg, + "REQUEST:\nmethod: {:?}\nuri: {:?}\npath: {:?}\nroute: {:?}\nquery: {:?}\n", + request.method(), + request.uri(), + request.uri().path(), + request.route(), + request.uri().query(), + ); + let _ = writeln!( + &mut msg, + "RESPONSE:\nstatus_code: {:?}\nbody: {:?}\n", + response.status(), + std::str::from_utf8(response.body()).unwrap_or("[Error] Response with invalid UTF-8."), + ); + Some(text_response(StatusCode::OK, msg)) +} diff --git a/clis/teliod/src/main.rs b/clis/teliod/src/main.rs index 32ac16708..86881b06b 100644 --- a/clis/teliod/src/main.rs +++ b/clis/teliod/src/main.rs @@ -12,6 +12,8 @@ use tokio::{ }; use tracing::{debug, error}; +#[cfg(feature = "cgi")] +mod cgi; mod command_listener; mod comms; mod config; @@ -19,8 +21,6 @@ mod configure_interface; mod core_api; mod daemon; mod nc; -#[cfg(feature = "cgi")] -mod cgi; use crate::{ command_listener::CommandResponse, @@ -63,7 +63,7 @@ enum TeliodError { InvalidCommand(String), #[error("Invalid response received: {0}")] InvalidResponse(String), - #[error("Client failed to receive response in {TIMEOUT_SEC}")] + #[error("Client failed to receive response in {TIMEOUT_SEC}s")] ClientTimeoutError, #[error("Broken signal stream")] BrokenSignalStream, From d95632a59a86ae318bb021d6a18c8707eff7ba71 Mon Sep 17 00:00:00 2001 From: Luis Cruz Date: Thu, 2 Jan 2025 12:56:51 +0000 Subject: [PATCH 5/6] qnap: Rename launch script --- qnap/build_sign.csv | 2 +- qnap/qpkg.cfg | 2 +- qnap/shared/{NordSecMeshnet.sh => NordSecurityMeshnet.sh} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename qnap/shared/{NordSecMeshnet.sh => NordSecurityMeshnet.sh} (100%) mode change 100755 => 100644 diff --git a/qnap/build_sign.csv b/qnap/build_sign.csv index 3966cb267..65a4b7029 100644 --- a/qnap/build_sign.csv +++ b/qnap/build_sign.csv @@ -1 +1 @@ -,/NordSecMeshnet.sh, +,/NordSecurityMeshnet.sh, diff --git a/qnap/qpkg.cfg b/qnap/qpkg.cfg index 52be74d09..5f0da1604 100644 --- a/qnap/qpkg.cfg +++ b/qnap/qpkg.cfg @@ -14,7 +14,7 @@ QPKG_LICENSE="GPL-3.0-only" # Preferred number in start/stop sequence. QPKG_RC_NUM="101" # Init-script used to control the start and stop of the installed application. -QPKG_SERVICE_PROGRAM="NordSecMeshnet.sh" +QPKG_SERVICE_PROGRAM="NordSecurityMeshnet.sh" # Optional 1 is enable. Path of starting/ stopping shall script. (no start/stop on App Center) #QPKG_DISABLE_APPCENTER_UI_SERVICE=1 diff --git a/qnap/shared/NordSecMeshnet.sh b/qnap/shared/NordSecurityMeshnet.sh old mode 100755 new mode 100644 similarity index 100% rename from qnap/shared/NordSecMeshnet.sh rename to qnap/shared/NordSecurityMeshnet.sh From d558afa2aaaf2600f3fc0a6bf8e4ff6157262f19 Mon Sep 17 00:00:00 2001 From: Luis Cruz Date: Thu, 2 Jan 2025 12:59:18 +0000 Subject: [PATCH 6/6] qnap: Use socket instead of pid file to check if app is running --- qnap/shared/NordSecurityMeshnet.sh | 58 ++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/qnap/shared/NordSecurityMeshnet.sh b/qnap/shared/NordSecurityMeshnet.sh index 90377e8ed..505b0d299 100644 --- a/qnap/shared/NordSecurityMeshnet.sh +++ b/qnap/shared/NordSecurityMeshnet.sh @@ -8,42 +8,64 @@ 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 -NORDSECMESHNET_DIR=/tmp/nordsecuritymeshnet/ -TELIOD_PID_FILE=${NORDSECMESHNET_DIR}/teliod.pid TELIOD_CFG_FILE=${QPKG_ROOT}/teliod.cfg TELIOD_LOG_FILE="/var/log/teliod.log" +system_log() { + local log_level + case "$1" in + "INFO") log_level=0 ;; + "WARN") log_level=1 ;; + "ERROR") log_level=2 ;; + *) log_level=0 ;; + esac + /sbin/log_tool -u "NordSecurity" -t "$log_level" -a "$2" +} + +get_ipc_socket_path() { + if [ -d "/run" ]; then + echo "/run/teliod.sock" + elif [ -d "/var/run" ]; then + echo "/var/run/teliod.sock" + else + system_log ERROR "Neither /run/ nor /var/run/ exists" + exit 1 + fi +} + case "$1" in start) ENABLED=$(/sbin/getcfg $QPKG_NAME Enable -u -d FALSE -f $CONF) if [ "$ENABLED" != "TRUE" ]; then - echo "$QPKG_NAME is disabled." + system_log INFO "Package application is disabled." exit 1 fi - ln -s ${QPKG_ROOT}/web /home/Qhttpd/Web/NordSecurityMeshnet - ln -s ${QPKG_ROOT}/teliod.cgi /home/httpd/cgi-bin/qpkg/teliod.cgi - mkdir -p -m 0755 $NORDSECMESHNET_DIR + ln -fs ${QPKG_ROOT}/web /home/Qhttpd/Web/NordSecurityMeshnet + ln -fs ${QPKG_ROOT}/teliod.cgi /home/httpd/cgi-bin/qpkg/teliod.cgi - if [ -e ${TELIOD_PID_FILE} ]; then - PID=$(cat ${TELIOD_PID_FILE}) - if [ -d /proc/${PID}/ ]; then - echo "${QPKG_NAME} is already running." - exit 0 - fi + SOCKET_PATH=$(get_ipc_socket_path) + if [ -e "$SOCKET_PATH" ]; then + system_log INFO "Package application is already running." + exit 0 fi ${QPKG_ROOT}/teliod daemon $TELIOD_CFG_FILE > $TELIOD_LOG_FILE 2>&1 & - echo $! > ${TELIOD_PID_FILE} + system_log INFO "Teliod daemon started." ;; stop) - if [ -e ${TELIOD_PID_FILE} ]; then - PID=$(cat ${TELIOD_PID_FILE}) - kill -9 ${PID} || true - rm -f ${TELIOD_PID_FILE} + SOCKET_PATH=$(get_ipc_socket_path) + + ${QPKG_ROOT}/teliod quit-daemon || true + sleep 2 + + if [ -e "$SOCKET_PATH" ]; then + system_log WARN "Application socket still exist, forcing shutdown..." + killall -9 teliod || true + rm -f "$SOCKET_PATH" fi - rm -f /run/teliod.sock + system_log INFO "Teliod daemon stopped." ;; restart)