From a86fc4bd847144d406b1948306f0694a6e2e4ffe Mon Sep 17 00:00:00 2001 From: limingwei Date: Sun, 14 Apr 2024 17:37:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E7=BC=96=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +++ Cargo.toml | 22 ++++++++++ config.yml | 9 +++++ src/core/mod.rs | 2 + src/core/nal.rs | 67 +++++++++++++++++++++++++++++++ src/core/sangfor.rs | 67 +++++++++++++++++++++++++++++++ src/main.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++ src/test/mod.rs | 2 + src/test/nal.rs | 22 ++++++++++ src/test/rc4.rs | 39 ++++++++++++++++++ src/util/logs.rs | 56 ++++++++++++++++++++++++++ src/util/mod.rs | 2 + src/util/rc4.rs | 43 ++++++++++++++++++++ 13 files changed, 433 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 config.yml create mode 100644 src/core/mod.rs create mode 100644 src/core/nal.rs create mode 100644 src/core/sangfor.rs create mode 100644 src/main.rs create mode 100644 src/test/mod.rs create mode 100644 src/test/nal.rs create mode 100644 src/test/rc4.rs create mode 100644 src/util/logs.rs create mode 100644 src/util/mod.rs create mode 100644 src/util/rc4.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc60106 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +Cargo.lock + +/logs +.idea diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d4f981f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "Nal" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = { version = "0.11", features = ["json"] } +tokio = { version = "1", features = ["full"] } +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.114" +serde_yaml = "0.9.32" +#rc4 = "0.1.0" +log = "0.4.20" +async-trait = "0.1.77" +cron = "0.12.1" +chrono = "0.4.34" +#env_logger = "0.11.3" +fern = "0.6" +#log4rs = "1.3.0" +yaml-rust = "0.4.5" diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..52a4b68 --- /dev/null +++ b/config.yml @@ -0,0 +1,9 @@ +login: + username: 2336 #用户名(工号) + password: 2336 #密码 + +check: + interval: 10 #单位秒 + +log: + normal: false #是否显示操作正常的日志 \ No newline at end of file diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..ed9690a --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,2 @@ +pub mod nal; +pub mod sangfor; diff --git a/src/core/nal.rs b/src/core/nal.rs new file mode 100644 index 0000000..8d9a9ac --- /dev/null +++ b/src/core/nal.rs @@ -0,0 +1,67 @@ +use std::collections::HashMap; +use std::time::Duration; +use async_trait::async_trait; +use reqwest::{Client, Error}; +use serde::{Deserialize, Serialize}; +use serde::__private::de::Content::I16; +use serde_json::Value; +use tokio::time::timeout; + +/// 网络自动登录trait +#[async_trait] +pub trait Nal { + /// 登录网络 + async fn login_net(&self, config: &LoginConfig) -> Result; +} + +/// 网络类型 +#[derive(Debug, Serialize, Deserialize)] +pub enum NetType { + Sangfor, + Other, +} + +/// 登录配置 +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginConfig { + pub username: String, + pub password: String, +} + +impl LoginConfig { + pub fn new(username: &str, password: &str) -> Self { + Self { + username: String::from(username), + password: String::from(password), + } + } +} + +/// 网络状态检测相关参数 +#[derive(Debug, Serialize, Deserialize)] +pub struct NetStatusCheck { + /// 检测间隔时间,单位秒 + pub interval: u16, +} + + +/// 获取没有代理的客户端 +pub fn get_no_proxy_client() -> Client { + Client::builder() + .no_proxy() // 禁用代理 + .timeout(Duration::from_secs(30)) + .build().unwrap() +} + +/// 检测网络是否正常 +pub async fn check_net() -> bool { + get_no_proxy_client() + .get("http://baidu.com") + .send() + .await + .is_ok() +} + +pub async fn login(nal: &T, lc: &LoginConfig) -> Result { + nal.login_net(lc).await +} \ No newline at end of file diff --git a/src/core/sangfor.rs b/src/core/sangfor.rs new file mode 100644 index 0000000..71c8c3a --- /dev/null +++ b/src/core/sangfor.rs @@ -0,0 +1,67 @@ +use std::collections::HashMap; +use std::future::Future; +use std::time::{SystemTime, UNIX_EPOCH}; +use async_trait::async_trait; +use log::info; +use reqwest::{Error, Response}; +use crate::core::nal; +use crate::core::nal::{get_no_proxy_client, LoginConfig, Nal}; +use crate::util::rc4::RC4; + +/// 深信服 +pub struct Sangfor { + login_url: String, +} + +/// 登录接口 +const LOGIN_API: &str = "/ac_portal/login.php"; + +impl Sangfor { + pub fn new(addr: &str) -> Sangfor { + let mut string = addr.to_owned(); + string.push_str(LOGIN_API); + Self { + login_url: String::from(string), + } + } + + /// rc4编码 + fn rc4_encode(key: &str, pwd: &str) -> String { + let mut data = pwd.as_bytes().to_vec(); + + // let mut rc4 = Rc4::new(key.as_bytes().into()); + // rc4.apply_keystream(&mut data); + + let mut rc4 = RC4::new(key.as_bytes()); + rc4.apply_keystream(&mut data); + + data.iter().map(|b| format!("{:02x}", b)).collect::() + } +} + +#[async_trait] +impl Nal for Sangfor { + async fn login_net(&self, config: &LoginConfig) -> Result { + let client = get_no_proxy_client(); + + let mut params = HashMap::new(); + params.insert("opr", "pwdLogin"); + params.insert("userName", config.username.as_str()); + let timestamp = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64).to_string(); + let auth_tag = timestamp.as_str(); + params.insert("auth_tag", auth_tag); + let pwd = Self::rc4_encode(auth_tag, config.password.as_str()); + params.insert("pwd", pwd.as_str()); + params.insert("rememberPwd", "1"); + let lu = &self.login_url; + let rsp = client.post(lu) + .form(¶ms) + .send() + .await? + .json::() + .await?; + info!("login result: {rsp:#?}"); + // rsp.is_ok() + Ok(rsp["success"] == true) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d837b0b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,97 @@ +use std::fs; +use std::fs::{File, OpenOptions}; +use std::str::FromStr; +use tokio::time::{Duration, sleep}; +use chrono::{Datelike, DateTime, Local, Timelike, Utc}; +use cron::Schedule; +use log::{info, warn}; +use serde_json::Value::Bool; +use crate::core::nal; +use crate::core::nal::{login, LoginConfig, NetStatusCheck, NetType}; +use crate::core::sangfor::Sangfor; +use std::io::{Error, Write}; +use std::time::SystemTime; +use serde::{Deserialize, Serialize}; + +mod core; +mod test; +mod util; + + +/// NAL配置参数 +#[derive(Debug, Serialize, Deserialize)] +pub struct NalConfig { + pub net_type: Option, + pub login: LoginConfig, + pub check: NetStatusCheck, +} + +impl NalConfig { + pub fn default() -> Self { + NalConfig { + net_type: Option::from(NetType::Sangfor), + login: LoginConfig { username: "".to_string(), password: "".to_string() }, + check: NetStatusCheck { interval: 0 }, + } + } +} + +/// 初始化配置 +pub fn init_config() -> NalConfig { + let result = File::open("./config.yml"); + if result.is_err() { + //初始化配置 + return NalConfig::default(); + } + + //缺少字段会导致序列化出错 + let result1 = serde_yaml::from_reader(result.unwrap()); + if result1.is_err() { + let string = result1.err().unwrap().to_string(); + warn!("config serde_yaml error: {string}"); + NalConfig::default() + } else { + let mut yaml: NalConfig = result1.unwrap_or(NalConfig::default()); + if yaml.net_type.is_none() { + yaml.net_type = Option::from(NetType::Sangfor) + } + yaml + } +} + +#[tokio::main] +async fn main() { + util::logs::init("nal.log").expect("初始化日志出错"); + + let config = init_config(); + info!("config: {config:#?}"); + + /*let expression = "* 1 * * * * *"; + let schedule = Schedule::from_str(expression).unwrap(); + println!("All stars: Upcoming fire times for '{}':", expression); + for datetime in schedule.upcoming(Utc).take(10) { + let is_ok = nal::check_net().await; + println!("net isOk: -> {is_ok:}"); + }*/ + + loop { + //检测网络是否正常 + let is_ok = nal::check_net().await; + if !is_ok { + warn!("网络异常"); + //登录 + let sangfor = Sangfor::new("http://1.1.1.4"); + let login_ok = nal::login(&sangfor, &config.login).await; + if login_ok.unwrap_or_else(|e| { false }) { + info!("登录成功"); + } else { + info!("登录失败"); + } + } else { + info!("网络正常"); + }; + + // 延迟指定秒后再次执行 + sleep(Duration::from_secs(config.check.interval as u64)).await; + } +} \ No newline at end of file diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 0000000..25f13c5 --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1,2 @@ +mod rc4; +mod nal; \ No newline at end of file diff --git a/src/test/nal.rs b/src/test/nal.rs new file mode 100644 index 0000000..4eed6e2 --- /dev/null +++ b/src/test/nal.rs @@ -0,0 +1,22 @@ +use log::log; +use crate::core::nal; +use crate::core::nal::{LoginConfig, Nal}; +use crate::core::sangfor::Sangfor; + +// #[test] +#[tokio::test] +async fn sangfor() { + let nal: &dyn Nal = &Sangfor::new("http://1.1.1.4/ac_portal/login.php"); + let config = LoginConfig { + username: 2336.to_string(), + password: 13141.to_string(), + }; + let is_ok = nal.login_net(&config).await; + println!(" is_ok: {:?}", is_ok); +} + +#[tokio::test] +async fn check_net() { + let is_ok = nal::check_net().await; + println!(" is_ok: {:?}", is_ok); +} \ No newline at end of file diff --git a/src/test/rc4.rs b/src/test/rc4.rs new file mode 100644 index 0000000..6ac0ed2 --- /dev/null +++ b/src/test/rc4.rs @@ -0,0 +1,39 @@ +use std::string::String; +use std::str::{from_utf8, from_utf8_mut}; +// use rc4::{consts::*, KeyInit, StreamCipher}; +// use rc4::{Key, Rc4}; + +#[test] +fn custom_encode(){ + let key = b"secret_key"; + let mut rc4 = crate::util::rc4::RC4::new(key); + + let mut data = b"hello world".to_vec(); + println!("Original: {:?}", from_utf8(&*data)); + println!("Original: {:?}", data.iter().map(|b| format!("{:02x}", b)).collect::()); + + // let mut data = data.as_slice(); + rc4.apply_keystream(&mut data); + let hex_string = data.iter().map(|b| format!("{:02x}", b)).collect::(); + println!("Encrypted: {:?}", hex_string); + + // 每次使用需要重新创建,不然解密后的数据不对 + let mut rc4 = crate::util::rc4::RC4::new(key); + let mut encode_data = b"\x2b\x5a\xf7\xcd\xaf\x75\x9c\x49\x3b\x8c\xd0".to_vec(); // 加密后的数据 + rc4.apply_keystream(&mut encode_data); + println!("Decrypted: {:?}", encode_data.iter().map(|b| format!("{:02x}", b)).collect::()); +} + +// #rc4 = "0.1.0" +// #[test] +// fn rc4_encode(){ +// let mut rc4 = Rc4::new(b"secret_key".into()); +// let mut data = b"hello world".to_vec(); +// println!(" Original: {:?}", data.iter().map(|b| format!("{:02x}", b)).collect::()); +// rc4.apply_keystream(&mut data); +// println!("Encrypted: {:?}", data.iter().map(|b| format!("{:02x}", b)).collect::()); +// +// let mut rc4 = Rc4::new(b"secret_key".into()); +// rc4.apply_keystream(&mut data); +// println!("Decrypted: {:?}", data.iter().map(|b| format!("{:02x}", b)).collect::()); +// } \ No newline at end of file diff --git a/src/util/logs.rs b/src/util/logs.rs new file mode 100644 index 0000000..8a4b25a --- /dev/null +++ b/src/util/logs.rs @@ -0,0 +1,56 @@ +use std::fs; +use chrono::Local; + +/// 初始化日志 +pub fn init(log_file: &str) -> Result<(), fern::InitError> { + fs::create_dir_all("./logs").unwrap(); // 如果需要,创建日志目录 + let log_file_path = "./logs/".to_string() + log_file; + /* + /// 定义一个函数用于将DateTime对象转换为指定格式的字符串 + fn format_timestamp(dt: Timestamp) -> String { + let dt: DateTime = DateTime::from_str(&*dt.to_string()).unwrap(); + format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + dt.year(), dt.month(), dt.day(), + dt.hour(), dt.minute(), dt.second()) + } + // 设置全局日志级别,比如这里设置为info级别 + std::env::set_var("RUST_LOG", "info"); + // env_logger::init(); // 初始化一个默认的logger,如env_logger + env_logger::Builder::from_default_env() + .format(|buf, record| { + // let warn_style = buf.default_level_style(log::Level::Warn); + let timestamp = buf.timestamp(); + let time = format_timestamp(timestamp); + let level = record.level(); + let file = record.file().unwrap(); + let line = record.line().unwrap(); + let module_path = record.module_path().unwrap(); + writeln!( + buf, + // "My formatted log ({time}): {warn_style}{}{warn_style:#}", + "{time} [{level:05}] {file}|{module_path}|{line} : {}", + record.args() + ) + }) // 自定义格式化逻辑 + // .filter(Some("crate"), log::LevelFilter::Info) // 只记录指定crate的日志 + .target(log_file_path) + .init();*/ + + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{} {} {}:{}] {}", + Local::now().format("%Y-%m-%d %H:%M:%S"), + record.level(), + record.target(), + record.line().unwrap(), + message + )) + }) + .level(log::LevelFilter::Info) + .chain(std::io::stdout()) + + .chain(fern::log_file(log_file_path)?) + .apply()?; + Ok(()) +} \ No newline at end of file diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..1d65e48 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,2 @@ +pub mod logs; +pub mod rc4; diff --git a/src/util/rc4.rs b/src/util/rc4.rs new file mode 100644 index 0000000..20f5b2f --- /dev/null +++ b/src/util/rc4.rs @@ -0,0 +1,43 @@ +pub struct RC4 { + state: [u8; 256], + i: usize, + j: usize, +} + +impl RC4 { + pub fn new(key: &[u8]) -> RC4 { + let mut state = [0; 256]; + for i in 0..256 { + state[i] = i as u8; + } + + let mut j = 0; + for i in 0..256 { + j = (j + state[i] as usize + key[i % key.len()] as usize) % 256; + state.swap(i, j); + } + + RC4 { state, i: 0, j: 0 } + } + + pub fn apply_keystream(&mut self, data: &mut [u8]) { + for byte in data.iter_mut() { + self.i = (self.i + 1) % 256; + self.j = (self.j + self.state[self.i] as usize) % 256; + self.state.swap(self.i, self.j); + let k = self.state[(self.state[self.i] as usize + self.state[self.j] as usize) % 256]; + *byte ^= k; + } + /*let mut i = self.i; + let mut j = self.j; + for byte in data.iter_mut() { + i = (i + 1) % 256; + j = (j + self.state[i] as usize) % 256; + self.state.swap(i, j); + let t = (self.state[i] as usize + self.state[j] as usize) % 256; + *byte ^= self.state[t]; + } + self.i = i; + self.j = j;*/ + } +}