From 6320aac24a575fd9b201120cc1d691e104133b8a Mon Sep 17 00:00:00 2001 From: gngpp Date: Thu, 4 May 2023 15:15:43 +0000 Subject: [PATCH 1/8] Fix No such file or directory --- openwrt/xunlei/files/xunlei.init | 1 + 1 file changed, 1 insertion(+) diff --git a/openwrt/xunlei/files/xunlei.init b/openwrt/xunlei/files/xunlei.init index 9376da4..a90c866 100755 --- a/openwrt/xunlei/files/xunlei.init +++ b/openwrt/xunlei/files/xunlei.init @@ -31,6 +31,7 @@ start_service() { fi rm -rf /var/packages/pan-xunlei-com + mkdir -p /var/packages/pan-xunlei-com ln -s /usr/share/xunlei /var/packages/pan-xunlei-com ln -s /usr/share/xunlei/target/host/etc/synoinfo.conf /etc/synoinfo.conf ln -s /usr/share/xunlei/target/host/usr/syno/synoman/webman/modules/authenticate.cgi /usr/syno/synoman/webman/modules/authenticate.cgi From 044cacf93cdd424ebd78aacdec6aff51bac4a6a3 Mon Sep 17 00:00:00 2001 From: gngpp Date: Thu, 4 May 2023 15:19:00 +0000 Subject: [PATCH 2/8] Update --- Cargo.lock | 158 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 ++- examples/login.rs | 189 ++++++++++++++++++++++++++++++++++++++++++++++ examples/rsa.rs | 26 +++++++ src/launch.rs | 84 ++++++++++++++++++++- src/libc_asset.rs | 2 +- src/main.rs | 14 +++- src/systemd.rs | 4 +- 8 files changed, 474 insertions(+), 12 deletions(-) create mode 100644 examples/login.rs create mode 100644 examples/rsa.rs diff --git a/Cargo.lock b/Cargo.lock index 706ec86..f0adaeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -172,6 +178,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.79" @@ -276,6 +288,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -364,6 +382,17 @@ dependencies = [ "gzip-header", ] +[[package]] +name = "der" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e58dffcdcc8ee7b22f0c1f71a69243d7c2d9ad87b5a14361f2424a1565c219" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "digest" version = "0.10.6" @@ -371,6 +400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", + "const-oid", "crypto-common", ] @@ -621,6 +651,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -628,6 +661,12 @@ version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + [[package]] name = "link-cplusplus" version = "1.0.8" @@ -701,6 +740,23 @@ dependencies = [ "twoway", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -711,6 +767,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -718,6 +785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -751,12 +819,42 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "portable-atomic" version = "0.3.19" @@ -897,6 +995,27 @@ dependencies = [ "url", ] +[[package]] +name = "rsa" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd2017d3e6d67384f301f8b06fbf4567afc576430a61624d845eb04d2b30a72" +dependencies = [ + "byteorder", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "subtle", + "zeroize", +] + [[package]] name = "rust-embed" version = "6.6.1" @@ -1058,18 +1177,50 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spki" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1580,7 +1731,14 @@ dependencies = [ "log", "rand", "rouille", + "rsa", "rust-embed", "signal-hook", "ureq", ] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index b6ed7f2..604f15b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,21 +19,26 @@ rust-embed = "6.6.0" libc = "0.2.140" rand = "0.8.5" ureq = "2.6.2" +rsa = "0.9.0" indicatif = "0.17.3" rouille= "3.6.2" signal-hook = "0.3.15" clap = { version = "4.2.5", features = ["derive"] } [features] -default = ["launch", "systemd"] -embed = ["launch", "systemd"] +default = ["launch", "systemd", "auth"] +embed = ["launch", "systemd", "auth"] launch = [] systemd = [] +auth = [] [[bin]] name = "xunlei" path = "src/main.rs" +[profile.dev] +opt-level = 'z' + [profile.release] lto = true opt-level = 'z' diff --git a/examples/login.rs b/examples/login.rs new file mode 100644 index 0000000..2187981 --- /dev/null +++ b/examples/login.rs @@ -0,0 +1,189 @@ +#![allow(unreachable_code)] +#[macro_use] +extern crate rouille; + +use rouille::Request; +use rouille::Response; +use std::collections::HashMap; +use std::io; +use std::sync::Mutex; + +// This struct contains the data that we store on the server about each client. +#[derive(Debug, Clone)] +struct SessionData { + login: String, +} + +fn main() { + println!("Now listening on localhost:8000"); + let sessions_storage: Mutex> = Mutex::new(HashMap::new()); + + rouille::start_server("0.0.0.0:8000", move |request| { + rouille::log(&request, io::stdout(), || { + rouille::session::session(request, "SID", 3600, |session| { + let mut session_data = if session.client_has_sid() { + if let Some(data) = sessions_storage.lock().unwrap().get(session.id()) { + Some(data.clone()) + } else { + None + } + } else { + None + }; + + let response = handle_route(&request, &mut session_data); + + if let Some(d) = session_data { + sessions_storage + .lock() + .unwrap() + .insert(session.id().to_owned(), d); + } else if session.client_has_sid() { + sessions_storage.lock().unwrap().remove(session.id()); + } + + response + }) + }) + }); +} + +fn handle_route(request: &Request, session_data: &mut Option) -> Response { + router!(request, + (POST) (/login) => { + + let data = try_or_400!(post_input!(request, { + login: String, + password: String, + })); + + println!("Login attempt with login {:?} and password {:?}", data.login, data.password); + + if data.password.starts_with("b") { + *session_data = Some(SessionData { login: data.login }); + return Response::redirect_303("/"); + + } else { + return Response::html("Wrong login/password"); + } + }, + + _ => () + ); + + if let Some(session_data) = session_data.as_ref() { + // Logged in. + handle_route_logged_in(request, session_data) + } else { + // Not logged in. + router!(request, + (GET) (/) => { + Response::html(r#" + + Login + + + + + + "#) + + }, + + _ => { + // If the user tries to access any other route, redirect them to the login form. + // + // You may wonder: if I want to make some parts of my site public and some other + // parts private, should I put all my public routes here? The answer is no. The way + // this example is structured is appropriate for a website that is entirely + // private. Don't hesitate to structure it in a different way, for example by + // having a function that is dedicated only to public routes. + Response::redirect_303("/") + } + ) + } +} + +// This function handles the routes that are accessible only if the user is logged in. +fn handle_route_logged_in(request: &Request, _session_data: &SessionData) -> Response { + router!(request, + (GET) (/) => { + // Show some greetings with a dummy response. + Response::html(r#"You are now logged in. If you close your tab and open it again, + you will still be logged in.
+ Click here for the private area +
+
"#) + }, + + (GET) (/private) => { + // This route is here to demonstrate that the client can go to `/private` only if + // they are successfully logged in. + Response::html(r#"You are in the private area! Go back."#) + }, + + _ => Response::empty_404() + ) +} diff --git a/examples/rsa.rs b/examples/rsa.rs new file mode 100644 index 0000000..e1fd7ec --- /dev/null +++ b/examples/rsa.rs @@ -0,0 +1,26 @@ +use rsa::pkcs1::EncodeRsaPublicKey; + +fn main() { + use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; + + let mut rng = rand::thread_rng(); + let bits = 2048; + let priv_key: RsaPrivateKey = + RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); + let pub_key = RsaPublicKey::from(&priv_key); + let p = pub_key.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF).unwrap(); + println!("{}", p); + // Encrypt + let data = b"hello world"; + let enc_data = pub_key + .encrypt(&mut rng, Pkcs1v15Encrypt, &data[..]) + .expect("failed to encrypt"); + assert_ne!(&data[..], &enc_data[..]); + + // Decrypt + let dec_data = priv_key + .decrypt(Pkcs1v15Encrypt, &enc_data) + .expect("failed to decrypt"); + assert_eq!(&data[..], &dec_data[..]); + println!("{}", String::from_utf8_lossy(dec_data.as_ref())) +} diff --git a/src/launch.rs b/src/launch.rs index 1f56e21..dd4b6c8 100644 --- a/src/launch.rs +++ b/src/launch.rs @@ -1,4 +1,5 @@ use anyhow::Context; +use rsa::pkcs1::EncodeRsaPublicKey; use signal_hook::iterator::Signals; use crate::{standard, Config, Running}; @@ -12,6 +13,8 @@ use std::{ }; pub struct XunleiLauncher { + username: Option, + password: Option, host: std::net::IpAddr, port: u16, download_path: PathBuf, @@ -21,6 +24,8 @@ pub struct XunleiLauncher { impl From for XunleiLauncher { fn from(config: Config) -> Self { Self { + username: config.username, + password: config.password, host: config.host, port: config.port, download_path: config.download_path, @@ -238,7 +243,7 @@ impl XunleiLauncher { } impl Running for XunleiLauncher { - fn launch(&self) -> anyhow::Result<()> { + fn run(&self) -> anyhow::Result<()> { use std::thread::{Builder, JoinHandle}; let mut signals = Signals::new([ @@ -271,11 +276,19 @@ impl Running for XunleiLauncher { }) .expect("[XunleiLauncher] Failed to start backend thread"); - let host = self.host.to_string(); + let username = self.username.clone(); + let password = self.password.clone(); + let host = self.host.clone(); let port = self.port; // run webui service std::thread::spawn(move || { - XunleiLauncher::run_ui(host, port, ui_envs); + // XunleiLauncher::run_ui(host, port, ui_envs); + match XunleiWebUIServer::new(username, password, host, port, ui_envs).run() { + Ok(_) => {} + Err(e) => { + log::error!("[XunleiWebUIServer] error: {}", e) + } + } }); backend_thread @@ -286,3 +299,68 @@ impl Running for XunleiLauncher { Ok(()) } } + +struct XunleiWebUIServer { + username: Option, + password: Option, + host: std::net::IpAddr, + port: u16, + enc: Encrypt, + envs: HashMap, +} + +impl XunleiWebUIServer { + fn new( + username: Option, + password: Option, + host: std::net::IpAddr, + port: u16, + envs: HashMap, + ) -> Self { + Self { + host, + port, + envs, + username, + password, + enc: Encrypt::new(), + } + } +} + +impl Running for XunleiWebUIServer { + fn run(&self) -> anyhow::Result<()> { + todo!() + } +} + +struct Encrypt { + pub_key: rsa::RsaPublicKey, + pri_key: rsa::RsaPrivateKey, +} + +impl Encrypt { + fn new() -> Self { + let private_key = + rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).expect("failed to generate a key"); + Self { + pub_key: rsa::RsaPublicKey::from(&private_key), + pri_key: private_key, + } + } + + fn verify(&mut self, raw_data: &[u8], encode_data: &[u8]) -> anyhow::Result<()> { + let decode_data = self + .pri_key + .decrypt(rsa::Pkcs1v15Encrypt, &encode_data[..]) + .context("[Encrypt Failed to decrypt] ")?; + if decode_data.as_slice().eq(raw_data) { + return Ok(()); + } + anyhow::bail!("[Encrypt] wrong password") + } + + fn pub_key(&self) -> anyhow::Result { + Ok(self.pub_key.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF)?) + } +} diff --git a/src/libc_asset.rs b/src/libc_asset.rs index 3efd748..848b66e 100644 --- a/src/libc_asset.rs +++ b/src/libc_asset.rs @@ -27,9 +27,9 @@ pub(crate) fn ld_env(envs: &mut std::collections::HashMap) -> an .map(|v| v.into_owned()) .collect::>() { - let file = Asset::get(&filename).context("Failed to get bin asset")?; let target_file = libc_path.join(filename); if !target_file.exists() { + let file = Asset::get(&filename).context("Failed to get bin asset")?; standard::write_file(&target_file, file.data, 0o755)?; } } diff --git a/src/main.rs b/src/main.rs index a7badef..8721b5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use std::io::Write; use std::path::PathBuf; pub trait Running { - fn launch(&self) -> anyhow::Result<()>; + fn run(&self) -> anyhow::Result<()>; } #[derive(Parser)] @@ -43,6 +43,12 @@ pub enum Commands { #[derive(Args)] pub struct Config { + /// Xunlei panel username + #[clap(short, long)] + username: Option, + /// Xunlei panel password + #[clap(short, long)] + password: Option, /// Xunlei Listen host #[clap(short, long, default_value = "0.0.0.0", value_parser = parser_host)] host: std::net::IpAddr, @@ -63,15 +69,15 @@ fn main() -> anyhow::Result<()> { match opt.commands { #[cfg(feature = "systemd")] Commands::Install(config) => { - systemd::XunleiInstall::from(config).launch()?; + systemd::XunleiInstall::from(config).run()?; } #[cfg(feature = "systemd")] Commands::Uninstall => { - systemd::XunleiUninstall {}.launch()?; + systemd::XunleiUninstall {}.run()?; } #[cfg(feature = "launch")] Commands::Launch(config) => { - launch::XunleiLauncher::from(config).launch()?; + launch::XunleiLauncher::from(config).run()?; } } Ok(()) diff --git a/src/systemd.rs b/src/systemd.rs index fbd5a48..c31a6c1 100644 --- a/src/systemd.rs +++ b/src/systemd.rs @@ -234,7 +234,7 @@ impl XunleiInstall { } impl Running for XunleiInstall { - fn launch(&self) -> anyhow::Result<()> { + fn run(&self) -> anyhow::Result<()> { self.config()?; self.systemd(self.install()?) } @@ -262,7 +262,7 @@ impl XunleiUninstall { } impl Running for XunleiUninstall { - fn launch(&self) -> anyhow::Result<()> { + fn run(&self) -> anyhow::Result<()> { if Systemd::support() { Systemd::systemctl(["disable", standard::APP_NAME])?; Systemd::systemctl(["stop", standard::APP_NAME])?; From b6aa15d8765abab67102627875626cb3ec3799d0 Mon Sep 17 00:00:00 2001 From: gngpp Date: Sat, 6 May 2023 02:29:19 +0000 Subject: [PATCH 3/8] Support aarch64 a53/a72 --- .github/workflows/Release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 6baa00c..1be784f 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -163,6 +163,10 @@ jobs: target: - arch: "aarch64_generic" sdk: "https://downloads.openwrt.org/releases/22.03.2/targets/rockchip/armv8/openwrt-sdk-22.03.2-rockchip-armv8_gcc-11.2.0_musl.Linux-x86_64.tar.xz" + - arch: "aarch64_cortex-a53" + sdk: "https://downloads.openwrt.org/releases/22.03.2/targets/bcm27xx/bcm2710/openwrt-sdk-22.03.2-bcm27xx-bcm2710_gcc-11.2.0_musl.Linux-x86_64.tar.xz" + - arch: "aarch64_cortex-a72" + sdk: "https://downloads.openwrt.org/releases/22.03.2/targets/bcm27xx/bcm2711/openwrt-sdk-22.03.2-bcm27xx-bcm2711_gcc-11.2.0_musl.Linux-x86_64.tar.xz" - arch: "x86_64" sdk: "https://downloads.openwrt.org/releases/18.06.9/targets/x86/64/openwrt-sdk-18.06.9-x86-64_gcc-7.3.0_musl.Linux-x86_64.tar.xz" steps: From 295721da90fb5cc9fcb10d05ae371bffe396bc9e Mon Sep 17 00:00:00 2001 From: gngpp Date: Sat, 6 May 2023 02:33:29 +0000 Subject: [PATCH 4/8] Update Update.yml --- .github/workflows/Update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Update.yml b/.github/workflows/Update.yml index ed5e748..5c720d8 100644 --- a/.github/workflows/Update.yml +++ b/.github/workflows/Update.yml @@ -19,7 +19,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.TOKEN }} run: | dir=$(pwd) - bash +x $dir/unpack.sh + bash +x $dir/unpack.sh x86_64 current_version=$(git describe --tags --abbrev=0 | sed 's/^v//') new_version=$(cat bin/version) export new_version From d064dd5bbf96c7d93a881f8d3c102a0e3238026d Mon Sep 17 00:00:00 2001 From: gngpp Date: Sat, 6 May 2023 11:10:30 +0000 Subject: [PATCH 5/8] add login static files --- static/login.html | 109 +++++++++++++++++++++++++++++++++++++++++++++ static/sha3.min.js | 9 ++++ 2 files changed, 118 insertions(+) create mode 100644 static/login.html create mode 100644 static/sha3.min.js diff --git a/static/login.html b/static/login.html new file mode 100644 index 0000000..d970e53 --- /dev/null +++ b/static/login.html @@ -0,0 +1,109 @@ + + + + + +Login + + + + + + + + + \ No newline at end of file diff --git a/static/sha3.min.js b/static/sha3.min.js new file mode 100644 index 0000000..965b779 --- /dev/null +++ b/static/sha3.min.js @@ -0,0 +1,9 @@ +/** + * [js-sha3]{@link https://github.com/emn178/js-sha3} + * + * @version 0.8.0 + * @author Chen, Yi-Cyuan [emn178@gmail.com] + * @copyright Chen, Yi-Cyuan 2015-2018 + * @license MIT + */ +!function(){"use strict";function t(t,e,r){this.blocks=[],this.s=[],this.padding=e,this.outputBits=r,this.reset=!0,this.finalized=!1,this.block=0,this.start=0,this.blockCount=1600-(t<<1)>>5,this.byteCount=this.blockCount<<2,this.outputBlocks=r>>5,this.extraBytes=(31&r)>>3;for(var n=0;n<50;++n)this.s[n]=0}function e(e,r,n){t.call(this,e,r,n)}var r="input is invalid type",n="object"==typeof window,i=n?window:{};i.JS_SHA3_NO_WINDOW&&(n=!1);var o=!n&&"object"==typeof self;!i.JS_SHA3_NO_NODE_JS&&"object"==typeof process&&process.versions&&process.versions.node?i=global:o&&(i=self);var a=!i.JS_SHA3_NO_COMMON_JS&&"object"==typeof module&&module.exports,s="function"==typeof define&&define.amd,u=!i.JS_SHA3_NO_ARRAY_BUFFER&&"undefined"!=typeof ArrayBuffer,f="0123456789abcdef".split(""),c=[4,1024,262144,67108864],h=[0,8,16,24],p=[1,0,32898,0,32906,2147483648,2147516416,2147483648,32907,0,2147483649,0,2147516545,2147483648,32777,2147483648,138,0,136,0,2147516425,0,2147483658,0,2147516555,0,139,2147483648,32905,2147483648,32771,2147483648,32770,2147483648,128,2147483648,32778,0,2147483658,2147483648,2147516545,2147483648,32896,2147483648,2147483649,0,2147516424,2147483648],d=[224,256,384,512],l=[128,256],y=["hex","buffer","arrayBuffer","array","digest"],b={128:168,256:136};!i.JS_SHA3_NO_NODE_JS&&Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),!u||!i.JS_SHA3_NO_ARRAY_BUFFER_IS_VIEW&&ArrayBuffer.isView||(ArrayBuffer.isView=function(t){return"object"==typeof t&&t.buffer&&t.buffer.constructor===ArrayBuffer});for(var A=function(e,r,n){return function(i){return new t(e,r,e).update(i)[n]()}},w=function(e,r,n){return function(i,o){return new t(e,r,o).update(i)[n]()}},v=function(t,e,r){return function(e,n,i,o){return S["cshake"+t].update(e,n,i,o)[r]()}},B=function(t,e,r){return function(e,n,i,o){return S["kmac"+t].update(e,n,i,o)[r]()}},g=function(t,e,r,n){for(var i=0;i>2]|=t[p]<>2]|=o<>2]|=(192|o>>6)<>2]|=(128|63&o)<=57344?(a[i>>2]|=(224|o>>12)<>2]|=(128|o>>6&63)<>2]|=(128|63&o)<>2]|=(240|o>>18)<>2]|=(128|o>>12&63)<>2]|=(128|o>>6&63)<>2]|=(128|63&o)<=s){for(this.start=i-s,this.block=a[c],i=0;i>=8);r>0;)i.unshift(r),r=255&(t>>=8),++n;return e?i.push(n):i.unshift(n),this.update(i),i.length},t.prototype.encodeString=function(t){var e,n=typeof t;if("string"!==n){if("object"!==n)throw new Error(r);if(null===t)throw new Error(r);if(u&&t.constructor===ArrayBuffer)t=new Uint8Array(t);else if(!(Array.isArray(t)||u&&ArrayBuffer.isView(t)))throw new Error(r);e=!0}var i=0,o=t.length;if(e)i=o;else for(var a=0;a=57344?i+=3:(s=65536+((1023&s)<<10|1023&t.charCodeAt(++a)),i+=4)}return i+=this.encode(8*i),this.update(t),i},t.prototype.bytepad=function(t,e){for(var r=this.encode(e),n=0;n>2]|=this.padding[3&e],this.lastByteIndex===this.byteCount)for(t[0]=t[r],e=1;e>4&15]+f[15&t]+f[t>>12&15]+f[t>>8&15]+f[t>>20&15]+f[t>>16&15]+f[t>>28&15]+f[t>>24&15];a%e==0&&(j(r),o=0)}return i&&(t=r[o],s+=f[t>>4&15]+f[15&t],i>1&&(s+=f[t>>12&15]+f[t>>8&15]),i>2&&(s+=f[t>>20&15]+f[t>>16&15])),s},t.prototype.arrayBuffer=function(){this.finalize();var t,e=this.blockCount,r=this.s,n=this.outputBlocks,i=this.extraBytes,o=0,a=0,s=this.outputBits>>3;t=i?new ArrayBuffer(n+1<<2):new ArrayBuffer(s);for(var u=new Uint32Array(t);a>8&255,u[t+2]=e>>16&255,u[t+3]=e>>24&255;s%r==0&&j(n)}return o&&(t=s<<2,e=n[a],u[t]=255&e,o>1&&(u[t+1]=e>>8&255),o>2&&(u[t+2]=e>>16&255)),u},(e.prototype=new t).finalize=function(){return this.encode(this.outputBits,!0),t.prototype.finalize.call(this)};var j=function(t){var e,r,n,i,o,a,s,u,f,c,h,d,l,y,b,A,w,v,B,g,_,k,S,C,x,m,E,O,z,N,j,J,M,H,I,R,U,V,F,D,W,Y,K,q,G,L,P,Q,T,X,Z,$,tt,et,rt,nt,it,ot,at,st,ut,ft,ct;for(n=0;n<48;n+=2)i=t[0]^t[10]^t[20]^t[30]^t[40],o=t[1]^t[11]^t[21]^t[31]^t[41],a=t[2]^t[12]^t[22]^t[32]^t[42],s=t[3]^t[13]^t[23]^t[33]^t[43],u=t[4]^t[14]^t[24]^t[34]^t[44],f=t[5]^t[15]^t[25]^t[35]^t[45],c=t[6]^t[16]^t[26]^t[36]^t[46],h=t[7]^t[17]^t[27]^t[37]^t[47],e=(d=t[8]^t[18]^t[28]^t[38]^t[48])^(a<<1|s>>>31),r=(l=t[9]^t[19]^t[29]^t[39]^t[49])^(s<<1|a>>>31),t[0]^=e,t[1]^=r,t[10]^=e,t[11]^=r,t[20]^=e,t[21]^=r,t[30]^=e,t[31]^=r,t[40]^=e,t[41]^=r,e=i^(u<<1|f>>>31),r=o^(f<<1|u>>>31),t[2]^=e,t[3]^=r,t[12]^=e,t[13]^=r,t[22]^=e,t[23]^=r,t[32]^=e,t[33]^=r,t[42]^=e,t[43]^=r,e=a^(c<<1|h>>>31),r=s^(h<<1|c>>>31),t[4]^=e,t[5]^=r,t[14]^=e,t[15]^=r,t[24]^=e,t[25]^=r,t[34]^=e,t[35]^=r,t[44]^=e,t[45]^=r,e=u^(d<<1|l>>>31),r=f^(l<<1|d>>>31),t[6]^=e,t[7]^=r,t[16]^=e,t[17]^=r,t[26]^=e,t[27]^=r,t[36]^=e,t[37]^=r,t[46]^=e,t[47]^=r,e=c^(i<<1|o>>>31),r=h^(o<<1|i>>>31),t[8]^=e,t[9]^=r,t[18]^=e,t[19]^=r,t[28]^=e,t[29]^=r,t[38]^=e,t[39]^=r,t[48]^=e,t[49]^=r,y=t[0],b=t[1],L=t[11]<<4|t[10]>>>28,P=t[10]<<4|t[11]>>>28,O=t[20]<<3|t[21]>>>29,z=t[21]<<3|t[20]>>>29,st=t[31]<<9|t[30]>>>23,ut=t[30]<<9|t[31]>>>23,Y=t[40]<<18|t[41]>>>14,K=t[41]<<18|t[40]>>>14,H=t[2]<<1|t[3]>>>31,I=t[3]<<1|t[2]>>>31,A=t[13]<<12|t[12]>>>20,w=t[12]<<12|t[13]>>>20,Q=t[22]<<10|t[23]>>>22,T=t[23]<<10|t[22]>>>22,N=t[33]<<13|t[32]>>>19,j=t[32]<<13|t[33]>>>19,ft=t[42]<<2|t[43]>>>30,ct=t[43]<<2|t[42]>>>30,et=t[5]<<30|t[4]>>>2,rt=t[4]<<30|t[5]>>>2,R=t[14]<<6|t[15]>>>26,U=t[15]<<6|t[14]>>>26,v=t[25]<<11|t[24]>>>21,B=t[24]<<11|t[25]>>>21,X=t[34]<<15|t[35]>>>17,Z=t[35]<<15|t[34]>>>17,J=t[45]<<29|t[44]>>>3,M=t[44]<<29|t[45]>>>3,C=t[6]<<28|t[7]>>>4,x=t[7]<<28|t[6]>>>4,nt=t[17]<<23|t[16]>>>9,it=t[16]<<23|t[17]>>>9,V=t[26]<<25|t[27]>>>7,F=t[27]<<25|t[26]>>>7,g=t[36]<<21|t[37]>>>11,_=t[37]<<21|t[36]>>>11,$=t[47]<<24|t[46]>>>8,tt=t[46]<<24|t[47]>>>8,q=t[8]<<27|t[9]>>>5,G=t[9]<<27|t[8]>>>5,m=t[18]<<20|t[19]>>>12,E=t[19]<<20|t[18]>>>12,ot=t[29]<<7|t[28]>>>25,at=t[28]<<7|t[29]>>>25,D=t[38]<<8|t[39]>>>24,W=t[39]<<8|t[38]>>>24,k=t[48]<<14|t[49]>>>18,S=t[49]<<14|t[48]>>>18,t[0]=y^~A&v,t[1]=b^~w&B,t[10]=C^~m&O,t[11]=x^~E&z,t[20]=H^~R&V,t[21]=I^~U&F,t[30]=q^~L&Q,t[31]=G^~P&T,t[40]=et^~nt&ot,t[41]=rt^~it&at,t[2]=A^~v&g,t[3]=w^~B&_,t[12]=m^~O&N,t[13]=E^~z&j,t[22]=R^~V&D,t[23]=U^~F&W,t[32]=L^~Q&X,t[33]=P^~T&Z,t[42]=nt^~ot&st,t[43]=it^~at&ut,t[4]=v^~g&k,t[5]=B^~_&S,t[14]=O^~N&J,t[15]=z^~j&M,t[24]=V^~D&Y,t[25]=F^~W&K,t[34]=Q^~X&$,t[35]=T^~Z&tt,t[44]=ot^~st&ft,t[45]=at^~ut&ct,t[6]=g^~k&y,t[7]=_^~S&b,t[16]=N^~J&C,t[17]=j^~M&x,t[26]=D^~Y&H,t[27]=W^~K&I,t[36]=X^~$&q,t[37]=Z^~tt&G,t[46]=st^~ft&et,t[47]=ut^~ct&rt,t[8]=k^~y&A,t[9]=S^~b&w,t[18]=J^~C&m,t[19]=M^~x&E,t[28]=Y^~H&R,t[29]=K^~I&U,t[38]=$^~q&L,t[39]=tt^~G&P,t[48]=ft^~et&nt,t[49]=ct^~rt&it,t[0]^=p[n],t[1]^=p[n+1]};if(a)module.exports=S;else{for(x=0;x Date: Sat, 6 May 2023 11:40:03 +0000 Subject: [PATCH 6/8] Supports panel authentication for login --- src/launch.rs | 426 +++++++++++++++++++++++++++++-------------------- src/main.rs | 19 +-- src/systemd.rs | 21 ++- 3 files changed, 282 insertions(+), 184 deletions(-) diff --git a/src/launch.rs b/src/launch.rs index dd4b6c8..0a8bfad 100644 --- a/src/launch.rs +++ b/src/launch.rs @@ -1,10 +1,15 @@ +use rouille::router; +use rouille::Request; +use rouille::Response; +use std::collections::HashMap; +use std::io; +use std::sync::Mutex; + use anyhow::Context; -use rsa::pkcs1::EncodeRsaPublicKey; use signal_hook::iterator::Signals; use crate::{standard, Config, Running}; use std::{ - collections::HashMap, io::Read, ops::Not, os::unix::prelude::PermissionsExt, @@ -12,9 +17,21 @@ use std::{ process::Stdio, }; +const HTML_LOGIN: &str = include_str!("../static/login.html"); +const JS_SHA3: &str = include_str!("../static/sha3.min.js"); + +// hasher auth message +fn hasher_auth_message(s: &str) -> String { + use sha3::{Digest, Sha3_512}; + let mut hasher = Sha3_512::new(); + hasher.update(s); + format!("{:x}", hasher.finalize()) +} + +#[derive(Clone)] pub struct XunleiLauncher { - username: Option, - password: Option, + auth_user: Option, + auth_password: Option, host: std::net::IpAddr, port: u16, download_path: PathBuf, @@ -23,9 +40,18 @@ pub struct XunleiLauncher { impl From for XunleiLauncher { fn from(config: Config) -> Self { + let auth_user = match config.auth_user { + Some(auth_user) => Some(hasher_auth_message(&auth_user.as_str())), + None => None, + }; + + let auth_password = match config.auth_password { + Some(auth_password) => Some(hasher_auth_message(&auth_password.as_str())), + None => None, + }; Self { - username: config.username, - password: config.password, + auth_user, + auth_password, host: config.host, port: config.port, download_path: config.download_path, @@ -60,117 +86,6 @@ impl XunleiLauncher { Ok(child_process) } - fn run_ui(host: String, port: u16, envs: HashMap) { - log::info!("[XunleiLauncher] Start Xunlei Engine UI"); - rouille::start_server(format!("{}:{}", host, port), move |request| { - rouille::router!(request, - (GET) ["/webman/login.cgi"] => { - rouille::Response::json(&String::from(r#"{"SynoToken", ""}"#)) - .with_additional_header("Content-Type", "application/json; charset=utf-8") - .with_status_code(200) - }, - (GET) ["/"] => { - rouille::Response::redirect_307(standard::SYNOPKG_WEB_UI_HOME) - }, - (GET) ["/webman/"] => { - rouille::Response::redirect_307(standard::SYNOPKG_WEB_UI_HOME) - }, - (GET) ["/webman/3rdparty/pan-xunlei-com"] => { - rouille::Response::redirect_307(standard::SYNOPKG_WEB_UI_HOME) - }, - _ => { - let mut cmd = std::process::Command::new(standard::SYNOPKG_CLI_WEB); - cmd.current_dir(standard::SYNOPKG_PKGDEST); - cmd.envs(&envs) - .env("SERVER_SOFTWARE", "rust") - .env("SERVER_PROTOCOL", "HTTP/1.1") - .env("HTTP_HOST", &request.remote_addr().to_string()) - .env("GATEWAY_INTERFACE", "CGI/1.1") - .env("REQUEST_METHOD", request.method()) - .env("QUERY_STRING", request.raw_query_string()) - .env("REQUEST_URI", request.raw_url()) - .env("PATH_INFO", &request.url()) - .env("SCRIPT_NAME", ".") - .env("SCRIPT_FILENAME", &request.url()) - .env("SERVER_PORT", port.to_string()) - .env("REMOTE_ADDR", request.remote_addr().to_string()) - .env("SERVER_NAME", request.remote_addr().to_string()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .stdin(Stdio::piped()); - - for ele in request.headers() { - let k = ele.0.to_uppercase(); - let v = ele.1; - if k == "PROXY" { - continue - } - if v.is_empty().not() { - cmd.env(format!("HTTP_{}", k), v); - } - } - - if request.header("Content-Type").unwrap_or_default().is_empty().not() { - cmd.env( - "CONTENT_TYPE", - request.header("Content-Type").unwrap(), - ); - } - - if request.header("content-type").unwrap_or_default().is_empty().not() { - cmd.env( - "CONTENT_TYPE", - request.header("content-type").unwrap(), - ); - } - - if request.header("Content-Length").unwrap_or_default().is_empty().not() { - cmd.env( - "CONTENT_LENGTH", - request.header("Content-Length").unwrap(), - ); - } - - let mut child = cmd.spawn().unwrap(); - - if let Some(mut body) = request.data() { - std::io::copy(&mut body, child.stdin.as_mut().unwrap()).unwrap(); - } - - { - let mut stdout = std::io::BufReader::new(child.stdout.unwrap()); - - let mut headers = Vec::new(); - let mut status_code = 200; - for header in std::io::BufRead::lines(stdout.by_ref()) { - let header = header.unwrap(); - if header.is_empty() { - break; - } - - let (header, val) = header.split_once(':').unwrap(); - let val = &val[1..]; - - if header == "Status" { - status_code = val[0..3] - .parse() - .expect("Status returned by CGI program is invalid"); - } else { - headers.push((header.to_owned().into(), val.to_owned().into())); - } - } - rouille::Response { - status_code, - headers, - data: rouille::ResponseBody::from_reader(stdout), - upgrade: None, - } - } - } - ) - }); - } - fn envs(&self) -> anyhow::Result> { let mut envs = HashMap::new(); envs.insert( @@ -243,7 +158,7 @@ impl XunleiLauncher { } impl Running for XunleiLauncher { - fn run(&self) -> anyhow::Result<()> { + fn run(self) -> anyhow::Result<()> { use std::thread::{Builder, JoinHandle}; let mut signals = Signals::new([ @@ -252,8 +167,8 @@ impl Running for XunleiLauncher { signal_hook::consts::SIGTERM, ])?; - let ui_envs = self.envs()?; - let backend_envs = ui_envs.clone(); + let envs = self.envs()?; + let backend_envs = envs.clone(); let backend_thread: JoinHandle<_> = Builder::new() .name("backend".to_string()) .spawn(move || { @@ -276,14 +191,11 @@ impl Running for XunleiLauncher { }) .expect("[XunleiLauncher] Failed to start backend thread"); - let username = self.username.clone(); - let password = self.password.clone(); - let host = self.host.clone(); - let port = self.port; + let args = (self.clone(), envs); // run webui service std::thread::spawn(move || { // XunleiLauncher::run_ui(host, port, ui_envs); - match XunleiWebUIServer::new(username, password, host, port, ui_envs).run() { + match XunleiPanelServer::from(args).run() { Ok(_) => {} Err(e) => { log::error!("[XunleiWebUIServer] error: {}", e) @@ -300,67 +212,237 @@ impl Running for XunleiLauncher { } } -struct XunleiWebUIServer { - username: Option, - password: Option, +// This struct contains the data that we store on the server about each client. +#[derive(Debug, Clone)] +struct Session; + +#[macro_export] +macro_rules! try_or_400 { + ($result:expr) => { + match $result { + Ok(r) => r, + Err(err) => { + let json = rouille::try_or_400::ErrJson::from_err(&err); + return Ok(rouille::Response::json(&json).with_status_code(400)); + } + } + }; +} + +struct XunleiPanelServer { + auth_user: Option, + auth_password: Option, host: std::net::IpAddr, port: u16, - enc: Encrypt, envs: HashMap, } -impl XunleiWebUIServer { - fn new( - username: Option, - password: Option, - host: std::net::IpAddr, - port: u16, - envs: HashMap, - ) -> Self { - Self { - host, - port, - envs, - username, - password, - enc: Encrypt::new(), +impl XunleiPanelServer { + fn authentication(&self, auth_user: String, auth_password: String) -> bool { + let raw_auth_user = self.auth_user.clone().unwrap_or_default(); + let raw_auth_password = self.auth_password.clone().unwrap_or_default(); + auth_user.eq(&raw_auth_user) && auth_password.eq(&raw_auth_password) + } + + #[allow(unreachable_code)] + fn handle_route( + &self, + request: &Request, + session_data: &mut Option, + ) -> anyhow::Result { + if self.auth_user.is_none() || self.auth_password.is_none() { + *session_data = Some(Session {}); + } + + rouille::router!(request, + (POST) (/login) => { + let data = try_or_400!(rouille::post_input!(request, { + auth_user: String, + auth_password: String, + })); + if self.authentication(data.auth_user, data.auth_password) { + *session_data = Some(Session{}); + return Ok(Response::redirect_303("/")); + } else { + return Ok(Response::html("Wrong login/password")); + } + }, + _ => () + ); + + if let Some(_session_data) = session_data.as_ref() { + // Logged in. + self.handle_route_logged_in(request) + } else { + // Not logged in. + router!(request, + (GET) ["/login"] => { + Ok(Response::html(HTML_LOGIN)) + }, + (GET) ["/js/sha3.min.js"] => { + Ok(Response::html(JS_SHA3)) + }, + _ => { + Ok(Response::redirect_303("/login")) + } + ) } } -} -impl Running for XunleiWebUIServer { - fn run(&self) -> anyhow::Result<()> { - todo!() + // This function handles the routes that are accessible only if the user is logged in. + fn handle_route_logged_in(&self, request: &Request) -> anyhow::Result { + rouille::router!(request, + (GET) ["/webman/login.cgi"] => { + Ok(rouille::Response::json(&String::from(r#"{"SynoToken", ""}"#)).with_additional_header("Content-Type","application/json; charset=utf-8").with_status_code(200)) + }, + (GET) ["/"] => { + Ok(rouille::Response::redirect_307(standard::SYNOPKG_WEB_UI_HOME)) + }, + (GET) ["/webman/"] => { + Ok(rouille::Response::redirect_307(standard::SYNOPKG_WEB_UI_HOME)) + }, + (GET) ["/webman/3rdparty/pan-xunlei-com"] => { + Ok(rouille::Response::redirect_307(standard::SYNOPKG_WEB_UI_HOME)) + }, + _ => { + let mut cmd = std::process::Command::new(standard::SYNOPKG_CLI_WEB); + cmd.current_dir(standard::SYNOPKG_PKGDEST); + cmd.envs(&self.envs) + .env("SERVER_SOFTWARE", "rust") + .env("SERVER_PROTOCOL", "HTTP/1.1") + .env("HTTP_HOST", &request.remote_addr().to_string()) + .env("GATEWAY_INTERFACE", "CGI/1.1") + .env("REQUEST_METHOD", request.method()) + .env("QUERY_STRING", request.raw_query_string()) + .env("REQUEST_URI", request.raw_url()) + .env("PATH_INFO", &request.url()) + .env("SCRIPT_NAME", ".") + .env("SCRIPT_FILENAME", &request.url()) + .env("SERVER_PORT", self.port.to_string()) + .env("REMOTE_ADDR", request.remote_addr().to_string()) + .env("SERVER_NAME", request.remote_addr().to_string()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .stdin(Stdio::piped()); + + for ele in request.headers() { + let k = ele.0.to_uppercase(); + let v = ele.1; + if k == "PROXY" { + continue + } + if v.is_empty().not() { + cmd.env(format!("HTTP_{}", k), v); + } + } + + if request.header("Content-Type").unwrap_or_default().is_empty().not() { + cmd.env( + "CONTENT_TYPE", + request.header("Content-Type").unwrap(), + ); + } + + if request.header("content-type").unwrap_or_default().is_empty().not() { + cmd.env( + "CONTENT_TYPE", + request.header("content-type").unwrap(), + ); + } + + if request.header("Content-Length").unwrap_or_default().is_empty().not() { + cmd.env( + "CONTENT_LENGTH", + request.header("Content-Length").unwrap(), + ); + } + + let mut child = cmd.spawn().unwrap(); + + if let Some(mut body) = request.data() { + std::io::copy(&mut body, child.stdin.as_mut().unwrap()).unwrap(); + } + + { + let mut stdout = std::io::BufReader::new(child.stdout.unwrap()); + + let mut headers = Vec::new(); + let mut status_code = 200; + for header in std::io::BufRead::lines(stdout.by_ref()) { + let header = header.unwrap(); + if header.is_empty() { + break; + } + + let (header, val) = header.split_once(':').unwrap(); + let val = &val[1..]; + + if header == "Status" { + status_code = val[0..3] + .parse() + .expect("Status returned by CGI program is invalid"); + } else { + headers.push((header.to_owned().into(), val.to_owned().into())); + } + } + Ok(rouille::Response{status_code,headers,data:rouille::ResponseBody::from_reader(stdout),upgrade:None,}) + } + } + ) } } -struct Encrypt { - pub_key: rsa::RsaPublicKey, - pri_key: rsa::RsaPrivateKey, -} +impl Running for XunleiPanelServer { + fn run(self) -> anyhow::Result<()> { + let sessions_storage: Mutex> = Mutex::new(HashMap::new()); + let listen = format!("{}:{}", self.host.to_string(), self.port); + log::info!( + "[XunleiLauncher] Start Xunlei Pannel UI, listening on {}", + listen + ); + rouille::start_server(listen, move |request| { + rouille::log(&request, io::stdout(), || { + rouille::session::session(request, "XUNLEI_SID", 3600, |session| { + let mut session_data = if session.client_has_sid() { + if let Some(data) = sessions_storage.lock().unwrap().get(session.id()) { + Some(data.clone()) + } else { + None + } + } else { + None + }; + + let response = self.handle_route(&request, &mut session_data); + + if let Some(d) = session_data { + sessions_storage + .lock() + .unwrap() + .insert(session.id().to_owned(), d); + } else if session.client_has_sid() { + sessions_storage.lock().unwrap().remove(session.id()); + } -impl Encrypt { - fn new() -> Self { - let private_key = - rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).expect("failed to generate a key"); - Self { - pub_key: rsa::RsaPublicKey::from(&private_key), - pri_key: private_key, - } + match response { + Ok(res) => res, + Err(e) => Response::text(format!("An error occurred {}", e)), + } + }) + }) + }); } +} - fn verify(&mut self, raw_data: &[u8], encode_data: &[u8]) -> anyhow::Result<()> { - let decode_data = self - .pri_key - .decrypt(rsa::Pkcs1v15Encrypt, &encode_data[..]) - .context("[Encrypt Failed to decrypt] ")?; - if decode_data.as_slice().eq(raw_data) { - return Ok(()); +impl From<(XunleiLauncher, HashMap)> for XunleiPanelServer { + fn from(value: (XunleiLauncher, HashMap)) -> Self { + let launch = value.0; + Self { + auth_user: launch.auth_user.clone(), + auth_password: launch.auth_password.clone(), + host: launch.host.clone(), + port: launch.port.clone(), + envs: value.1, } - anyhow::bail!("[Encrypt] wrong password") - } - - fn pub_key(&self) -> anyhow::Result { - Ok(self.pub_key.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF)?) } } diff --git a/src/main.rs b/src/main.rs index 8721b5a..4edf83c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,11 +14,12 @@ use std::io::Write; use std::path::PathBuf; pub trait Running { - fn run(&self) -> anyhow::Result<()>; + fn run(self) -> anyhow::Result<()>; } #[derive(Parser)] #[clap(author, version, about, arg_required_else_help = true)] +#[command(args_conflicts_with_subcommands = true)] struct Opt { /// Enable debug #[clap(short, long, global = true)] @@ -43,12 +44,12 @@ pub enum Commands { #[derive(Args)] pub struct Config { - /// Xunlei panel username - #[clap(short, long)] - username: Option, - /// Xunlei panel password - #[clap(short, long)] - password: Option, + /// Xunlei authentication username + #[arg(short = 'U', long)] + auth_user: Option, + /// Xunlei authentication password + #[arg(short = 'W', long)] + auth_password: Option, /// Xunlei Listen host #[clap(short, long, default_value = "0.0.0.0", value_parser = parser_host)] host: std::net::IpAddr, @@ -105,7 +106,7 @@ fn init_log(debug: bool) { const PORT_RANGE: std::ops::RangeInclusive = 1024..=65535; // port range parser -pub(crate) fn parser_port_in_range(s: &str) -> anyhow::Result { +fn parser_port_in_range(s: &str) -> anyhow::Result { let port: usize = s .parse() .map_err(|_| anyhow::anyhow!(format!("`{}` isn't a port number", s)))?; @@ -120,7 +121,7 @@ pub(crate) fn parser_port_in_range(s: &str) -> anyhow::Result { } // address parser -pub(crate) fn parser_host(s: &str) -> anyhow::Result { +fn parser_host(s: &str) -> anyhow::Result { let addr = s .parse::() .map_err(|_| anyhow::anyhow!(format!("`{}` isn't a ip address", s)))?; diff --git a/src/systemd.rs b/src/systemd.rs index c31a6c1..880370d 100644 --- a/src/systemd.rs +++ b/src/systemd.rs @@ -17,6 +17,8 @@ use crate::Running; pub struct XunleiInstall { description: &'static str, + auth_user: Option, + auth_password: Option, host: std::net::IpAddr, port: u16, download_path: PathBuf, @@ -37,6 +39,8 @@ impl From for XunleiInstall { config_path: config.config_path, uid, gid, + auth_user: config.auth_user, + auth_password: config.auth_password, } } } @@ -196,6 +200,16 @@ impl XunleiInstall { if Systemd::support().not() { return Ok(()); } + + let auth = match self.auth_user.is_some() && self.auth_password.is_some() { + true => format!( + "-U {} -W {}", + self.auth_user.clone().unwrap_or_default(), + self.auth_password.clone().unwrap_or_default() + ), + false => "".to_string(), + }; + let systemctl_unit = format!( r#"[Unit] Description={} @@ -204,7 +218,7 @@ impl XunleiInstall { [Service] Type=simple - ExecStart={} launch -h {} -p {} -d {} -c {} + ExecStart={} launch -h {} -p {} -d {} -c {} {} LimitNOFILE=1024 LimitNPROC=512 User={} @@ -217,6 +231,7 @@ impl XunleiInstall { self.port, self.download_path.display(), self.config_path.display(), + auth, self.uid ); @@ -234,7 +249,7 @@ impl XunleiInstall { } impl Running for XunleiInstall { - fn run(&self) -> anyhow::Result<()> { + fn run(self) -> anyhow::Result<()> { self.config()?; self.systemd(self.install()?) } @@ -262,7 +277,7 @@ impl XunleiUninstall { } impl Running for XunleiUninstall { - fn run(&self) -> anyhow::Result<()> { + fn run(self) -> anyhow::Result<()> { if Systemd::support() { Systemd::systemctl(["disable", standard::APP_NAME])?; Systemd::systemctl(["stop", standard::APP_NAME])?; From 8c1805bd48606b5ce34e74b231d86c76d5bc0ce1 Mon Sep 17 00:00:00 2001 From: gngpp Date: Sat, 6 May 2023 11:40:19 +0000 Subject: [PATCH 7/8] Update Cargo --- Cargo.lock | 180 +++++++---------------------------------------------- Cargo.toml | 2 +- 2 files changed, 22 insertions(+), 160 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0adaeb..7c98f83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,12 +120,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bitflags" version = "1.3.2" @@ -178,12 +172,6 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "cc" version = "1.0.79" @@ -288,12 +276,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "const-oid" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -382,17 +364,6 @@ dependencies = [ "gzip-header", ] -[[package]] -name = "der" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e58dffcdcc8ee7b22f0c1f71a69243d7c2d9ad87b5a14361f2424a1565c219" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "digest" version = "0.10.6" @@ -400,7 +371,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", - "const-oid", "crypto-common", ] @@ -646,14 +616,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -661,12 +637,6 @@ version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - [[package]] name = "link-cplusplus" version = "1.0.8" @@ -740,23 +710,6 @@ dependencies = [ "twoway", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -767,17 +720,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -785,7 +727,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -819,42 +760,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "portable-atomic" version = "0.3.19" @@ -995,27 +906,6 @@ dependencies = [ "url", ] -[[package]] -name = "rsa" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dd2017d3e6d67384f301f8b06fbf4567afc576430a61624d845eb04d2b30a72" -dependencies = [ - "byteorder", - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-iter", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "subtle", - "zeroize", -] - [[package]] name = "rust-embed" version = "6.6.1" @@ -1158,6 +1048,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "signal-hook" version = "0.3.15" @@ -1177,50 +1077,18 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" -dependencies = [ - "digest", - "rand_core", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spki" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - [[package]] name = "syn" version = "1.0.109" @@ -1720,7 +1588,7 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "xunlei" -version = "3.5.2" +version = "3.5.2-1" dependencies = [ "anyhow", "chrono", @@ -1731,14 +1599,8 @@ dependencies = [ "log", "rand", "rouille", - "rsa", "rust-embed", + "sha3", "signal-hook", "ureq", ] - -[[package]] -name = "zeroize" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 2974bed..11178e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ rust-embed = "6.6.0" libc = "0.2.140" rand = "0.8.5" ureq = "2.6.2" -rsa = "0.9.0" +sha3 = "0.10.8" indicatif = "0.17.3" rouille= "3.6.2" signal-hook = "0.3.15" From bbcef7729afd35f89ce1d7670bcc4a43f1566592 Mon Sep 17 00:00:00 2001 From: gngpp Date: Sat, 6 May 2023 11:40:32 +0000 Subject: [PATCH 8/8] Delete examples --- examples/login.rs | 189 ---------------------------------------------- examples/rsa.rs | 26 ------- 2 files changed, 215 deletions(-) delete mode 100644 examples/login.rs delete mode 100644 examples/rsa.rs diff --git a/examples/login.rs b/examples/login.rs deleted file mode 100644 index 2187981..0000000 --- a/examples/login.rs +++ /dev/null @@ -1,189 +0,0 @@ -#![allow(unreachable_code)] -#[macro_use] -extern crate rouille; - -use rouille::Request; -use rouille::Response; -use std::collections::HashMap; -use std::io; -use std::sync::Mutex; - -// This struct contains the data that we store on the server about each client. -#[derive(Debug, Clone)] -struct SessionData { - login: String, -} - -fn main() { - println!("Now listening on localhost:8000"); - let sessions_storage: Mutex> = Mutex::new(HashMap::new()); - - rouille::start_server("0.0.0.0:8000", move |request| { - rouille::log(&request, io::stdout(), || { - rouille::session::session(request, "SID", 3600, |session| { - let mut session_data = if session.client_has_sid() { - if let Some(data) = sessions_storage.lock().unwrap().get(session.id()) { - Some(data.clone()) - } else { - None - } - } else { - None - }; - - let response = handle_route(&request, &mut session_data); - - if let Some(d) = session_data { - sessions_storage - .lock() - .unwrap() - .insert(session.id().to_owned(), d); - } else if session.client_has_sid() { - sessions_storage.lock().unwrap().remove(session.id()); - } - - response - }) - }) - }); -} - -fn handle_route(request: &Request, session_data: &mut Option) -> Response { - router!(request, - (POST) (/login) => { - - let data = try_or_400!(post_input!(request, { - login: String, - password: String, - })); - - println!("Login attempt with login {:?} and password {:?}", data.login, data.password); - - if data.password.starts_with("b") { - *session_data = Some(SessionData { login: data.login }); - return Response::redirect_303("/"); - - } else { - return Response::html("Wrong login/password"); - } - }, - - _ => () - ); - - if let Some(session_data) = session_data.as_ref() { - // Logged in. - handle_route_logged_in(request, session_data) - } else { - // Not logged in. - router!(request, - (GET) (/) => { - Response::html(r#" - - Login - - - - - - "#) - - }, - - _ => { - // If the user tries to access any other route, redirect them to the login form. - // - // You may wonder: if I want to make some parts of my site public and some other - // parts private, should I put all my public routes here? The answer is no. The way - // this example is structured is appropriate for a website that is entirely - // private. Don't hesitate to structure it in a different way, for example by - // having a function that is dedicated only to public routes. - Response::redirect_303("/") - } - ) - } -} - -// This function handles the routes that are accessible only if the user is logged in. -fn handle_route_logged_in(request: &Request, _session_data: &SessionData) -> Response { - router!(request, - (GET) (/) => { - // Show some greetings with a dummy response. - Response::html(r#"You are now logged in. If you close your tab and open it again, - you will still be logged in.
- Click here for the private area -
-
"#) - }, - - (GET) (/private) => { - // This route is here to demonstrate that the client can go to `/private` only if - // they are successfully logged in. - Response::html(r#"You are in the private area! Go back."#) - }, - - _ => Response::empty_404() - ) -} diff --git a/examples/rsa.rs b/examples/rsa.rs deleted file mode 100644 index e1fd7ec..0000000 --- a/examples/rsa.rs +++ /dev/null @@ -1,26 +0,0 @@ -use rsa::pkcs1::EncodeRsaPublicKey; - -fn main() { - use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; - - let mut rng = rand::thread_rng(); - let bits = 2048; - let priv_key: RsaPrivateKey = - RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); - let pub_key = RsaPublicKey::from(&priv_key); - let p = pub_key.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF).unwrap(); - println!("{}", p); - // Encrypt - let data = b"hello world"; - let enc_data = pub_key - .encrypt(&mut rng, Pkcs1v15Encrypt, &data[..]) - .expect("failed to encrypt"); - assert_ne!(&data[..], &enc_data[..]); - - // Decrypt - let dec_data = priv_key - .decrypt(Pkcs1v15Encrypt, &enc_data) - .expect("failed to decrypt"); - assert_eq!(&data[..], &dec_data[..]); - println!("{}", String::from_utf8_lossy(dec_data.as_ref())) -}