diff --git a/Cargo.lock b/Cargo.lock index 5e0c0b4..6b4e29f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "cryptoxide" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46212f5d1792f89c3e866fb10636139464060110c568edd7f73ab5e9f736c26d" + [[package]] name = "ct-logs" version = "0.6.0" @@ -514,6 +520,7 @@ dependencies = [ "protobuf-codegen-pure", "quinn", "rand", + "regex", "serde", "serde_derive", "serde_json", @@ -530,15 +537,20 @@ dependencies = [ name = "hbbs" version = "1.1.3" dependencies = [ - "cc", + "base64 0.13.0", "clap", + "cryptoxide", "hbb_common", "lazy_static", + "mac_address", + "machine-uid", + "minreq", "rocksdb", "rust-ini", "serde", "serde_derive", "serde_json", + "whoami", ] [[package]] @@ -687,12 +699,30 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "machine-uid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1595709b0a7386bcd56ba34d250d626e5503917d05d32cdccddcd68603e212" +dependencies = [ + "winreg", +] + [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "minreq" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781e56f7d29192378f0a04948b1e6aec67ce561273b2dd26ac510bbe88d7be70" +dependencies = [ + "punycode", +] + [[package]] name = "mio" version = "0.6.22" @@ -945,6 +975,12 @@ dependencies = [ "protobuf-codegen", ] +[[package]] +name = "punycode" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" + [[package]] name = "quick-error" version = "1.2.3" @@ -1549,6 +1585,12 @@ dependencies = [ "libc", ] +[[package]] +name = "whoami" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7884773ab69074615cb8f8425d0e53f11710786158704fca70f53e71b0e05504" + [[package]] name = "winapi" version = "0.2.8" @@ -1592,6 +1634,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index af34518..f48d688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,14 @@ serde_json = "1.0" lazy_static = "1.4" clap = "2.33" rust-ini = "0.16" +minreq = { version = "2.3.1", features = ["punycode"] } +machine-uid = "0.2" +mac_address = "1.1" +whoami = "0.9" +base64 = "0.13" +cryptoxide = "0.3" [build-dependencies] -cc = "1.0" hbb_common = { path = "libs/hbb_common" } [workspace] diff --git a/libs/hbb_common b/libs/hbb_common index 002939a..9948718 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 002939a1037c786d2651a779492a7c813ea4e54a +Subproject commit 99487187a6b25380b9a412f040f43f319ece7545 diff --git a/src/hbbr.rs b/src/hbbr.rs index c2ac21b..f0633a2 100644 --- a/src/hbbr.rs +++ b/src/hbbr.rs @@ -3,14 +3,17 @@ mod relay_server; use hbb_common::{env_logger::*, ResultType}; use relay_server::*; use std::sync::{Arc, Mutex}; +mod lic; fn main() -> ResultType<()> { init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); let args = format!( "-p, --port=[NUMBER(default={})] 'Sets the listening port' -k, --key=[KEY] 'Only allow the client with the same key' + {} ", - DEFAULT_PORT + DEFAULT_PORT, + lic::EMAIL_ARG ); let matches = App::new("hbbr") .version(hbbs::VERSION) @@ -18,6 +21,9 @@ fn main() -> ResultType<()> { .about("RustDesk Relay Server") .args_from_usage(&args) .get_matches(); + if !lic::check_lic(matches.value_of("email").unwrap_or("")) { + return Ok(()); + } let stop: Arc> = Default::default(); start( matches.value_of("port").unwrap_or(DEFAULT_PORT), diff --git a/src/lic.rs b/src/lic.rs new file mode 100644 index 0000000..2970c0d --- /dev/null +++ b/src/lic.rs @@ -0,0 +1,110 @@ +use hbb_common::{bail, log, ResultType}; +use serde_derive::{Deserialize, Serialize}; +use std::io::prelude::*; +use std::path::Path; + +#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)] +pub struct Machine { + #[serde(default)] + hostname: String, + #[serde(default)] + uid: String, + #[serde(default)] + mac: String, +} + +#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)] +pub struct Post { + #[serde(default)] + machine: String, + #[serde(default)] + email: String, + #[serde(default)] + status: String, +} + +const LICENSE_FILE: &'static str = ".license.txt"; + +pub fn check_lic(email: &str) -> bool { + let machine = get_lic(); + let path = Path::new(LICENSE_FILE); + if Path::is_file(&path) { + let contents = std::fs::read_to_string(&path).unwrap_or("".to_owned()); + if verify(&contents, &machine) { + return true; + } + } + + if email.is_empty() { + log::error!("Registered email required (-m option). Please visit https://rustdesk.com/server for more infomration."); + return false; + } + + match check_email(machine, email.to_owned()) { + Ok(v) => { + return v; + } + Err(err) => { + log::error!("{}", err); + return false; + } + } +} + +fn write_lic(lic: &str) { + if let Ok(mut f) = std::fs::File::create(LICENSE_FILE) { + f.write_all(lic.as_bytes()).ok(); + f.sync_all().ok(); + } +} + +fn check_email(machine: String, email: String) -> ResultType { + log::info!("Checking email with the server ..."); + let resp = minreq::post("http://rustdesk.com/api/check-email") + .with_body( + serde_json::to_string(&Post { + machine: machine.clone(), + email, + ..Default::default() + }) + .unwrap(), + ) + .send()?; + if resp.reason_phrase == "OK" { + let p: Post = serde_json::from_str(&resp.as_str()?)?; + if !p.status.is_empty() { + bail!("{}", p.status); + } + if !verify(&p.machine, &machine) { + bail!("Verification failure"); + } + write_lic(&p.machine); + } else { + bail!("Server error: {}", resp.reason_phrase); + } + Ok(true) +} + +fn get_lic() -> String { + let hostname = whoami::hostname(); + let uid = machine_uid::get().unwrap_or("".to_owned()); + let mac = if let Ok(Some(ma)) = mac_address::get_mac_address() { + base64::encode(ma.bytes()) + } else { + "".to_owned() + }; + serde_json::to_string(&Machine { hostname, uid, mac }).unwrap() +} + +fn verify(enc_str: &str, msg: &str) -> bool { + if let Ok(data) = base64::decode(enc_str) { + let key = + b"\xf1T\xc0\x1c\xffee\x86,S*\xd9.\x91\xcd\x85\x12:\xec\xa9 \x99:\x8a\xa2S\x1f Yy\x93R"; + cryptoxide::ed25519::verify(msg.as_bytes(), &key[..], &data) + } else { + false + } +} + +pub const EMAIL_ARG: &'static str = + "-m, --email=[EMAIL] 'Sets your email address registered with RustDesk'"; diff --git a/src/main.rs b/src/main.rs index 96e0376..808c8fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use clap::App; use hbb_common::{env_logger::*, log, ResultType}; use hbbs::*; +mod lic; use ini::Ini; use std::sync::{Arc, Mutex}; @@ -16,8 +17,11 @@ fn main() -> ResultType<()> { -R, --rendezvous-servers=[HOSTS] 'Sets rendezvous servers, seperated by colon' -u, --software-url=[URL] 'Sets download url of RustDesk software of newest version' -r, --relay-servers=[HOST] 'Sets the default relay servers, seperated by colon' + -C, --change-id=[BOOL(default=Y)] 'Sets if support to change id' + {} -k, --key=[KEY] 'Only allow the client with the same key'", DEFAULT_PORT, + lic::EMAIL_ARG ); let matches = App::new("hbbs") .version(crate::VERSION) @@ -43,6 +47,9 @@ fn main() -> ResultType<()> { } return default.to_owned(); }; + if !lic::check_lic(&get_arg("email", "")) { + return Ok(()); + } let port = get_arg("port", DEFAULT_PORT); let relay_servers: Vec = get_arg("relay-servers", "") .split(",") @@ -50,6 +57,7 @@ fn main() -> ResultType<()> { .map(|x| x.to_owned()) .collect(); let serial: i32 = get_arg("serial", "").parse().unwrap_or(0); + let id_change_support: bool = get_arg("change-id", "Y").to_uppercase() == "Y"; let rendezvous_servers: Vec = get_arg("rendezvous-servers", "") .split(",") .filter(|x| !x.is_empty() && test_if_valid_server(x, "rendezvous-server").is_ok()) @@ -69,6 +77,7 @@ fn main() -> ResultType<()> { get_arg("software-url", ""), &get_arg("key", ""), stop, + id_change_support, )?; Ok(()) } diff --git a/src/rendezvous_server.rs b/src/rendezvous_server.rs index 75e670e..dff17e6 100644 --- a/src/rendezvous_server.rs +++ b/src/rendezvous_server.rs @@ -164,6 +164,7 @@ impl RendezvousServer { software_url: String, key: &str, stop: Arc>, + id_change_support: bool, ) -> ResultType<()> { if !key.is_empty() { log::info!("Key: {}", key); @@ -171,6 +172,7 @@ impl RendezvousServer { log::info!("Listening on tcp/udp {}", addr); log::info!("Listening on tcp {}, extra port for NAT test", addr2); log::info!("relay-servers={:?}", relay_servers); + log::info!("change-id={:?}", id_change_support); let mut socket = FramedSocket::new(addr).await?; let (tx, mut rx) = mpsc::unbounded_channel::<(RendezvousMessage, SocketAddr)>(); let version = hbb_common::get_version_from_url(&software_url); @@ -202,6 +204,7 @@ impl RendezvousServer { &mut socket, key, stop.clone(), + id_change_support, ) .await; } @@ -215,6 +218,7 @@ impl RendezvousServer { socket: &mut FramedSocket, key: &str, stop: Arc>, + id_change_support: bool, ) { let mut timer = interval(Duration::from_millis(100)); loop { @@ -321,6 +325,31 @@ impl RendezvousServer { } break; } + Some(rendezvous_message::Union::register_pk(rk)) => { + if rk.uuid.is_empty() { + break; + } + let mut res = register_pk_response::Result::OK; + if !id_change_support { + res = register_pk_response::Result::NOT_SUPPORT; + } else if !hbb_common::is_valid_custom_id(&rk.id) { + res = register_pk_response::Result::INVALID_ID_FORMAT; + } else if let Some(peer) = rs.pm.get(&rk.id).await { + if peer.uuid != rk.uuid { + res = register_pk_response::Result::ID_EXISTS; + } + } + let mut msg_out = RendezvousMessage::new(); + msg_out.set_register_pk_response(RegisterPkResponse { + result: res.into(), + ..Default::default() + }); + if let Some(tcp) = sender.as_mut() { + if let Ok(bytes) = msg_out.write_to_bytes() { + allow_err!(tcp.send(Bytes::from(bytes)).await); + } + } + } _ => { break; } @@ -372,7 +401,7 @@ impl RendezvousServer { let id = rk.id; let mut res = register_pk_response::Result::OK; if let Some(peer) = self.pm.get(&id).await { - if !peer.uuid.is_empty() && peer.uuid != rk.uuid { + if peer.uuid != rk.uuid { log::warn!( "Peer {} uuid mismatch: {:?} vs {:?}", id, @@ -380,7 +409,7 @@ impl RendezvousServer { peer.uuid ); res = register_pk_response::Result::UUID_MISMATCH; - } else if peer.uuid.is_empty() || peer.pk != rk.pk { + } else if peer.pk != rk.pk { self.pm.update_pk(id, addr, rk.uuid, rk.pk); } } else { @@ -611,6 +640,17 @@ impl RendezvousServer { }, }; let socket_addr = AddrMangle::encode(addr); + let relay_server = { + if self.relay_servers.is_empty() { + "".to_owned() + } else { + let i = unsafe { + ROTATION_RELAY_SERVER += 1; + ROTATION_RELAY_SERVER % self.relay_servers.len() + }; + self.relay_servers[i].clone() + } + }; if same_intranet { log::debug!( "Fetch local addr {:?} {:?} request from {:?}", @@ -618,13 +658,9 @@ impl RendezvousServer { &peer.socket_addr, &addr ); - let i = unsafe { - ROTATION_RELAY_SERVER += 1; - ROTATION_RELAY_SERVER % self.relay_servers.len() - }; msg_out.set_fetch_local_addr(FetchLocalAddr { socket_addr, - relay_server: self.relay_servers[i].clone(), + relay_server, ..Default::default() }); } else { @@ -634,14 +670,10 @@ impl RendezvousServer { &peer.socket_addr, &addr ); - let i = unsafe { - ROTATION_RELAY_SERVER += 1; - ROTATION_RELAY_SERVER % self.relay_servers.len() - }; msg_out.set_punch_hole(PunchHole { socket_addr, nat_type: ph.nat_type, - relay_server: self.relay_servers[i].clone(), + relay_server, ..Default::default() }); }