diff --git a/Cargo.lock b/Cargo.lock index 2745fde..f03b74c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + [[package]] name = "bitflags" version = "1.3.2" @@ -46,6 +52,15 @@ dependencies = [ "generic-array 0.12.4", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -77,8 +92,12 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" name = "calllog" version = "0.1.0" dependencies = [ + "base16", "html_parser", "hyper", + "json", + "rand", + "sha2", "srp", "tokio", ] @@ -104,6 +123,15 @@ dependencies = [ "vec_map", ] +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + [[package]] name = "digest" version = "0.8.1" @@ -193,6 +221,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "h2" version = "0.3.4" @@ -334,6 +373,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "lazy_static" version = "1.4.0" @@ -459,6 +504,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "parking_lot" version = "0.11.2" @@ -539,6 +590,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -581,6 +638,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -639,10 +736,23 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ - "block-buffer", + "block-buffer 0.7.3", "digest 0.8.1", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -887,6 +997,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 2ac0981..f09a36b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,8 @@ edition = "2018" hyper = { version = "0.14", features = ["full"] } tokio = { version = "1", features = ["full"] } html_parser = { version = "0.6.2" } -srp = { version = "0.5.0" } \ No newline at end of file +srp = { version = "0.5.0" } +sha2 = { version = "0.9.8" } +rand = { version = "0.8.4" } +base16 = { version = "0.2.1" } +json = { version = "*" } diff --git a/src/login.rs b/src/login.rs new file mode 100644 index 0000000..e301ed0 --- /dev/null +++ b/src/login.rs @@ -0,0 +1,183 @@ +use std::{fmt::format, io::Read}; + +use hyper::{body::HttpBody, Body, Client, Method, Request, Uri}; +use json::JsonValue; +use rand::Fill; +use sha2::Sha512; +use srp::{ + client::{srp_private_key, SrpClient}, + groups::G_2048, +}; + +const AUTH_URI: &'static str = "http://192.168.1.1/authenticate"; + +fn get_username<'a>() -> &'a str { + "admin" +} + +fn get_password<'a>() -> &'a str { + "admin" +} + +pub async fn try_login(cookie: &str, csrf_token: &str) -> Result<(), ()> { + let mut a = [0u8; 64]; + a.try_fill(&mut rand::thread_rng()).unwrap(); + let srp_client = SrpClient::::new(&a, &G_2048); + + let a_pub = srp_client.get_a_pub(); + let a_pub = base16::encode_lower(&a_pub); + + let client = Client::new(); + + let uri: Uri = AUTH_URI.parse().unwrap(); + + let req_body = format!("CSRFtoken={}&I={}&A={}", csrf_token, get_username(), a_pub); + + println!("{}", &req_body); + + let request = Request::builder() + .method(Method::POST) + .uri(uri.clone()) + .header("Accept", "application/json, text/javascript, */*; q=0.01") + .header( + "Content-Type", + "application/x-www-form-urlencoded; charset=UTF-8", + ) + .header("Cookie", format!("sessionID={}", cookie).as_str()) + .header("Host", "192.168.1.1") + .header("Origin", "http://192.168.1.1") + .header("Pragma", "no-cache") + .header("Referer", "http://192.168.1.1/") + .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52") + .header("X-Requested-With", "XMLHttpRequest") + .body(Body::from(req_body)) + .unwrap(); + + match client.request(request).await { + Ok(mut resp) => { + let mut buffer: Vec = Vec::new(); + + while let Some(chunk) = resp.body_mut().data().await { + match chunk { + Ok(bytes) => buffer.append(&mut bytes.to_vec()), + Err(e) => panic!("{}", e.to_string()), + } + } + + // TODO: handle errors + let buffer = String::from_utf8(buffer).unwrap(); + println!("{:?}", buffer); + //let resp = json::parse(&buffer).unwrap(); + + if let Some((s, b_pub)) = get_salt_and_b_pub(&buffer) { + let s = base16::decode(s.as_bytes()).unwrap(); + + let b_pub = base16::decode(b_pub.as_bytes()).unwrap(); + + let private_key = srp_private_key::( + get_username().as_bytes(), + get_password().as_bytes(), + &s, + ); + + let verifier = srp_client.process_reply(&private_key, &b_pub).unwrap(); + + let user_proof = verifier.get_proof(); + + let m = base16::encode_lower(&user_proof); + + let req_body = format!("CSRFtoken={}&M={}", csrf_token, m); + let request = Request::builder() + .method(Method::POST) + .uri(uri) + .header("Accept", "application/json, text/javascript, */*; q=0.01") + .header( + "Content-Type", + "application/x-www-form-urlencoded; charset=UTF-8", + ) + .header("Cookie", format!("sessionID={}", cookie).as_str()) + .header("Host", "192.168.1.1") + .header("Origin", "http://192.168.1.1") + .header("Pragma", "no-cache") + .header("Referer", "http://192.168.1.1/") + .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52") + .header("X-Requested-With", "XMLHttpRequest") + .body(Body::from(req_body)) + .unwrap(); + + match client.request(request).await { + Ok(mut resp) => { + let mut buffer: Vec = Vec::new(); + + while let Some(chunk) = resp.body_mut().data().await { + match chunk { + Ok(bytes) => buffer.append(&mut bytes.to_vec()), + Err(e) => panic!("{}", e.to_string()), + } + } + + // TODO: handle errors + let buffer = String::from_utf8(buffer).unwrap(); + println!("final resp: {:?}", buffer); + } + Err(e) => panic!("{}", e.to_string()), + } + //let server_proof = conn.send_proof(user_proof); + //let key = verifier.verify_server(server_proof).unwrap(); + } else { + println!("{:?}", resp); + } + } + Err(e) => panic!("{}", e.to_string()), + } + + todo!() +} + +fn get_salt_and_b_pub(msg: &str) -> Option<(String, String)> { + let msg = json::parse(&msg).unwrap(); + + //_get_salt_and_b_pub(&msg, None, None) + if let JsonValue::Object(obj) = msg { + println!("{:?}", obj); + + let mut s = String::new(); + let mut b = String::new(); + + for (name, val) in obj.iter() { + if name == "s" { + s = get_json_string(val).unwrap(); + } else if name == "B" { + b = get_json_string(val).unwrap(); + } + } + + Some((s, b)) + } else { + None + } +} + +fn get_json_string(val: &JsonValue) -> Option { + match val { + JsonValue::Short(val) => Some(val.to_string()), + JsonValue::String(val) => Some(val.to_string()), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use crate::login::get_salt_and_b_pub; + + #[test] + fn parse_json_resp() { + let msg = "{ \"s\":\"0DEC99D3\", \"B\":\"2FCDB529BB3F7F02BAD0ED15C62995CB9E76AC0352D91F64AD02A1EF06C533565F64A0B25D7235682E5D732CA8FD1C675231CA205D83FF317E8D7BFBC48D863822A7E9A8ADC7BBD54B54A7D59502479872E4926C6169BDDED2E7FD7BFD1714ECA3FE22511C4F48F8FA33240DB8DD289933FC19A1B7DC0EEB4A56337ADB67FFC08684822D05D2A5EC95A3B21E84B4CB0F2C6195454D1D3B59548987485555E200B5E23B7E89E87C60AF3C2AA5834B284DD182C9A508FE479357BA341750385837241ACA56AAC5E380AB3876F80761E4BE33FAA8BBE60863AFC96FEBDB2AC64636AF3035777624EF2ECC581CA7266D3F11BB30CDEF21E9802973CCC7899E375F3E\" }"; + println!("{:?}", json::parse(&msg)); + + assert_eq!( + get_salt_and_b_pub(msg), + Some((String::from("0DEC99D3"), String::from("2FCDB529BB3F7F02BAD0ED15C62995CB9E76AC0352D91F64AD02A1EF06C533565F64A0B25D7235682E5D732CA8FD1C675231CA205D83FF317E8D7BFBC48D863822A7E9A8ADC7BBD54B54A7D59502479872E4926C6169BDDED2E7FD7BFD1714ECA3FE22511C4F48F8FA33240DB8DD289933FC19A1B7DC0EEB4A56337ADB67FFC08684822D05D2A5EC95A3B21E84B4CB0F2C6195454D1D3B59548987485555E200B5E23B7E89E87C60AF3C2AA5834B284DD182C9A508FE479357BA341750385837241ACA56AAC5E380AB3876F80761E4BE33FAA8BBE60863AFC96FEBDB2AC64636AF3035777624EF2ECC581CA7266D3F11BB30CDEF21E9802973CCC7899E375F3E"))) + ) + } +} diff --git a/src/main.rs b/src/main.rs index ec8b5cc..4968349 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod login; mod parsing; mod request; diff --git a/src/request.rs b/src/request.rs index 4c7e52a..a447ac7 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,15 +1,15 @@ -use std::{io::Read, time::Instant}; +use std::time::Instant; -use hyper::{body::HttpBody, Client, Response}; -use tokio::sync::mpsc::{Receiver, Sender}; use html_parser::Dom; +use hyper::{body::HttpBody, Body, Client, Method, Request, Uri}; +use tokio::sync::mpsc::{Receiver, Sender}; -use crate::parsing::extract_csrf_token_from_node; +use crate::{login::try_login, parsing::extract_csrf_token_from_node}; const CALLLOG_URI: &'static str = "http://192.168.1.1/modals/mmpbx-log-modal.lp"; enum ResponseParse { - NotLoggedIn { csrf_token: String }, + NotLoggedIn { cookie: String, csrf_token: String }, CallLog(CallLog), Error(String), } @@ -29,9 +29,13 @@ pub async fn fetch_thread(channel: Receiver>) { }; let mut calllog = loop { - match try_fetch_updated_data().await { - ResponseParse::NotLoggedIn { csrf_token } => { - println!("{}", csrf_token); + match try_fetch_updated_data(request_cache.cookie.map_or(String::new(), |s| s.clone())) + .await + { + ResponseParse::NotLoggedIn { cookie, csrf_token } => { + // TODO: handle login error + let _res = try_login(&cookie, &csrf_token).await; + todo!() } ResponseParse::CallLog(log) => break log, @@ -49,13 +53,44 @@ pub async fn fetch_thread(channel: Receiver>) { } } -async fn try_fetch_updated_data() -> ResponseParse { +async fn try_fetch_updated_data(cookie: String) -> ResponseParse { let client = Client::new(); - let uri = CALLLOG_URI.parse().unwrap(); - - match client.get(uri).await { + let uri: Uri = CALLLOG_URI.parse().unwrap(); + let mut cookie = cookie; + + let request = Request::builder() + .method(Method::GET) + .uri(uri) + .header("Cookie", format!("sessionID={}", cookie).as_str()) + .header("Host", "192.168.1.1") + .header("Origin", "http://192.168.1.1") + .header("Pragma", "no-cache") + .header("Referer", "http://192.168.1.1/") + .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52") + .header("X-Requested-With", "XMLHttpRequest") + .body(Body::empty()) + .unwrap(); + + match client.request(request).await { Ok(mut resp) => { + if let Some(new_cookie) = resp.headers().get(hyper::header::SET_COOKIE) { + println!("Set-Cookie: {:?}", new_cookie); + let new_cookie = new_cookie + .to_str() + .unwrap() + .split(';') + .map(|s| s.trim()) + .find(|s| s.starts_with("sessionID=")) + .map_or(String::new(), |s| { + s.split('=') + .skip(1) + .next() + .map_or(String::new(), |s| s.to_owned()) + }); + println!("Set-Cookie: {:?}", new_cookie); + cookie = new_cookie; + } let mut buffer: Vec = Vec::new(); while let Some(chunk) = resp.body_mut().data().await { @@ -71,7 +106,7 @@ async fn try_fetch_updated_data() -> ResponseParse { ResponseParse::CallLog(log) } else { if let Some(csrf_token) = extract_csfr_token_from_body(&body) { - ResponseParse::NotLoggedIn { csrf_token } + ResponseParse::NotLoggedIn { cookie, csrf_token } } else { todo!() } @@ -90,6 +125,8 @@ fn extract_calllog_from_body(body: &str) -> Option { } fn extract_csfr_token_from_body(body: &str) -> Option { + println!("{}", body); + let dom = Dom::parse(body); if let Ok(dom) = dom { for element in dom.children.iter() {