From 65b2b8e04d2e3b90b9f270b5dfd80a2d3ccbbcdc Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Mon, 26 Apr 2021 16:29:26 +0200 Subject: [PATCH] Switching to libmdns --- Cargo.toml | 14 ++--- src/discovery.rs | 30 +++------- src/frontend/index.html | 3 + src/frontend/libs/linkify-html.min.js | 1 - src/main.rs | 79 +++++++++++---------------- src/session_manager/mod.rs | 37 ++++++++----- 6 files changed, 72 insertions(+), 92 deletions(-) delete mode 100644 src/frontend/libs/linkify-html.min.js diff --git a/Cargo.toml b/Cargo.toml index 2cffacd..ca611bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" [dependencies] rand-8 = {package = "rand", version = "0.8.3"} rand-7 = {package = "rand", version = "0.7.3"} -tokio = {version = "1", features = ["full"]} +tokio = {version = "1", features = ["rt", "rt-multi-thread", "macros", "net", "io-util"]} lazy_static = "1.4" socket2 = "0.4.0" -mio = "0.7" +rusqlite = {version = "0.25.1", features = ["bundled"]} ed25519-dalek = "1" #for singing x25519-dalek = "1.1" #for shared secret sha2 = "0.9.3" @@ -26,16 +26,12 @@ tungstenite = "0.13.0" #websocket serde = "1.0.124" #serialization html-escape = "0.2.7" dirs = "3.0" -uuid = { version = "0.8", features = ["v4"] } +uuid = {version = "0.8", features = ["v4"]} webbrowser = "0.5.5" -astro-dnssd = "0.2.0" #mDNS advertiser +libmdns = "0.6" #mDNS advertiser multicast_dns = "0.5" #mDNS browser base64 = "0.13.0" time = "0.2.25" aes-gcm-siv = "0.9.0" scrypt = "0.6.3" -zeroize = "1.2.0" - -[dependencies.rusqlite] -version = "0.25.0" -features = ["bundled"] \ No newline at end of file +zeroize = "1.2.0" \ No newline at end of file diff --git a/src/discovery.rs b/src/discovery.rs index e3dfdb3..eed0d30 100644 --- a/src/discovery.rs +++ b/src/discovery.rs @@ -1,29 +1,17 @@ -use std::{thread, net::IpAddr}; -use astro_dnssd::register::DNSServiceBuilder; +use std::{net::IpAddr}; +use libmdns::{Responder, Service}; use multicast_dns::discovery::{DiscoveryManager, DiscoveryListeners, ResolveListeners}; use crate::{constants, print_error}; const SERVICE_TYPE: &str = "_aira._tcp"; -pub fn advertise_me(){ - thread::spawn(||{ - let mut service = DNSServiceBuilder::new(SERVICE_TYPE) - .with_name("AIRA Node") - .with_port(constants::PORT) - .build() - .unwrap(); - match service.register(|reply| match reply { - Ok(_) => {}, - Err(e) => print_error!("Error registering: {:?}", e) - }) { - Ok(_) => { - loop { - service.process_result(); - } - } - Err(e) => print_error!("Unable to register mDNS service. You won't be discoverable by others peers. {}", e) - }; - }); +pub fn advertise_me() -> Service { + Responder::new().unwrap().register( + SERVICE_TYPE.to_string(), + "AIRA Node".to_string(), + constants::PORT, + &[] + ) } pub fn discover_peers(on_service_discovered: F) { diff --git a/src/frontend/index.html b/src/frontend/index.html index d704333..925f505 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -303,6 +303,9 @@ #msg_log li p { margin-top: 0; } + #msg_log a { + color: #238cf5; + } #msg_log .file { display: flex; align-items: end; diff --git a/src/frontend/libs/linkify-html.min.js b/src/frontend/libs/linkify-html.min.js deleted file mode 100644 index 0b78baa..0000000 --- a/src/frontend/libs/linkify-html.min.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";!function(t,i){var s=function(t){function i(t){this.a=t}function s(t){return v.test(t)}function e(t){return k.test(t)}function h(t){return t.replace(A,"\n")}function r(t,i){this.b=t,this.c=i,this.d=null,this.input=null,this.e=-1,this.f=-1,this.g=-1,this.h=-1,this.i=-1,this.j()}function n(t,i){this.k=null,this.startLine=1,this.startColumn=0,this.options=i||{},this.tokenizer=new r(this,t)}function a(t,s){var e=new n(new i(p),s);return e.tokenize(t)}function o(t){var i,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},e=z.tokenize(t),h=[],r=[];for(s=new D(s),i=0;i0){var g=d(l.attributes);m+=" "+g.join(" ")}m+=">",r.push(m);break;case C:r.push("");break;case T:r.push(b(l.chars));break;case F:r.push("")}}return r.join("")}function u(i,s){for(var e=t.tokenize(i),h=[],r=0;r0;){var r=i[s];r.type===y&&r.tagName.toUpperCase()===t?h++:r.type===C&&r.tagName.toUpperCase()===t&&h--,e.push(r),s++}return e}function b(t){return t}function c(t){return t.replace(/"/g,""")}function d(t){for(var i=[],s=0;s"===t?(this.b.z(),this.d="beforeData"):(this.b.A(t),this.d="comment")},commentStartDash:function(){var t=this.r();"-"===t?this.d="commentEnd":">"===t?(this.b.z(),this.d="beforeData"):(this.b.A("-"),this.d="comment")},comment:function(){var t=this.r();"-"===t?this.d="commentEndDash":this.b.A(t)},commentEndDash:function(){var t=this.r();"-"===t?this.d="commentEnd":(this.b.A("-"+t),this.d="comment")},commentEnd:function(){var t=this.r();">"===t?(this.b.z(),this.d="beforeData"):(this.b.A("--"+t),this.d="comment")},tagName:function(){var t=this.r();s(t)?this.d="beforeAttributeName":"/"===t?this.d="selfClosingStartTag":">"===t?(this.b.B(),this.d="beforeData"):this.b.x(t)},beforeAttributeName:function(){var t=this.q();return s(t)?void this.r():void("/"===t?(this.d="selfClosingStartTag",this.r()):">"===t?(this.r(),this.b.B(),this.d="beforeData"):(this.d="attributeName",this.b.C(),this.r(),this.b.D(t)))},attributeName:function(){var t=this.q();s(t)?(this.d="afterAttributeName",this.r()):"/"===t?(this.b.F(!1),this.b.G(),this.r(),this.d="selfClosingStartTag"):"="===t?(this.d="beforeAttributeValue",this.r()):">"===t?(this.b.F(!1),this.b.G(),this.r(),this.b.B(),this.d="beforeData"):(this.r(),this.b.D(t))},afterAttributeName:function(){var t=this.q();return s(t)?void this.r():void("/"===t?(this.b.F(!1),this.b.G(),this.r(),this.d="selfClosingStartTag"):"="===t?(this.r(),this.d="beforeAttributeValue"):">"===t?(this.b.F(!1),this.b.G(),this.r(),this.b.B(),this.d="beforeData"):(this.b.F(!1),this.b.G(),this.r(),this.d="attributeName",this.b.C(),this.b.D(t)))},beforeAttributeValue:function(){var t=this.q();s(t)?this.r():'"'===t?(this.d="attributeValueDoubleQuoted",this.b.F(!0),this.r()):"'"===t?(this.d="attributeValueSingleQuoted",this.b.F(!0),this.r()):">"===t?(this.b.F(!1),this.b.G(),this.r(),this.b.B(),this.d="beforeData"):(this.d="attributeValueUnquoted",this.b.F(!1),this.r(),this.b.H(t))},attributeValueDoubleQuoted:function(){var t=this.r();'"'===t?(this.b.G(),this.d="afterAttributeValueQuoted"):"&"===t?this.b.H(this.s('"')||"&"):this.b.H(t)},attributeValueSingleQuoted:function(){var t=this.r();"'"===t?(this.b.G(),this.d="afterAttributeValueQuoted"):"&"===t?this.b.H(this.s("'")||"&"):this.b.H(t)},attributeValueUnquoted:function(){var t=this.q();s(t)?(this.b.G(),this.r(),this.d="beforeAttributeName"):"&"===t?(this.r(),this.b.H(this.s(">")||"&")):">"===t?(this.b.G(),this.r(),this.b.B(),this.d="beforeData"):(this.r(),this.b.H(t))},afterAttributeValueQuoted:function(){var t=this.q();s(t)?(this.r(),this.d="beforeAttributeName"):"/"===t?(this.r(),this.d="selfClosingStartTag"):">"===t?(this.r(),this.b.B(),this.d="beforeData"):this.d="beforeAttributeName"},selfClosingStartTag:function(){var t=this.q();">"===t?(this.r(),this.b.I(),this.b.B(),this.d="beforeData"):this.d="beforeAttributeName"},endTagOpen:function(){var t=this.r();e(t)&&(this.d="tagName",this.b.J(),this.b.x(t.toLowerCase()))}}},n.prototype={tokenize:function(t){return this.K=[],this.tokenizer.tokenize(t),this.K},tokenizePart:function(t){return this.K=[],this.tokenizer.tokenizePart(t),this.K},tokenizeEOF:function(){return this.K=[],this.tokenizer.tokenizeEOF(),this.K[0]},j:function(){this.k=null,this.startLine=1,this.startColumn=0},L:function(){this.options.M&&(this.k.M={start:{f:this.startLine,g:this.startColumn},N:{f:this.tokenizer.f,g:this.tokenizer.g}}),this.startLine=this.tokenizer.f,this.startColumn=this.tokenizer.g},u:function(){this.k={type:"Chars",chars:""},this.K.push(this.k)},v:function(t){this.k.chars+=t},p:function(){this.L()},y:function(){this.k={type:"Comment",chars:""},this.K.push(this.k)},A:function(t){this.k.chars+=t},z:function(){this.L()},w:function(){this.k={type:"StartTag",tagName:"",attributes:[],l:!1},this.K.push(this.k)},J:function(){this.k={type:"EndTag",tagName:""},this.K.push(this.k)},B:function(){this.L()},I:function(){this.k.l=!0},x:function(t){this.k.tagName+=t},C:function(){this._currentAttribute=["","",null],this.k.attributes.push(this._currentAttribute)},D:function(t){this._currentAttribute[0]+=t},F:function(t){this._currentAttribute[2]=t},H:function(t){this._currentAttribute[1]=this._currentAttribute[1]||"",this._currentAttribute[1]+=t},G:function(){}};var z={HTML5NamedCharRefs:p,EntityParser:i,EventedTokenizer:r,Tokenizer:n,tokenize:a},N=t.options,D=N.Options,y="StartTag",C="EndTag",T="Chars",F="Comment";return o}(i);t.linkifyHtml=s}(window,linkify); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 85286a2..91e5d49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ mod constants; mod discovery; use std::{env, fs, io, net::{SocketAddr, TcpStream}, path::Path, str::FromStr, sync::{Arc, RwLock}}; -use tokio::net::{TcpListener}; +use tokio::{net::TcpListener, runtime::Handle}; use actix_web::{App, HttpMessage, HttpRequest, HttpResponse, HttpServer, http::{header, CookieBuilder}, web, web::Data}; use actix_multipart::Multipart; use tungstenite::Message; @@ -87,17 +87,9 @@ async fn websocket_worker(websocket_strem: TcpStream, global_vars: Arc 0 { ui_connection.set_not_seen(not_seen); @@ -106,7 +98,7 @@ async fn websocket_worker(websocket_strem: TcpStream, global_vars: Arc { - let global_vars_read = global_vars.read().unwrap(); let (old_password, new_password) = if args.len() == 3 { (Some(base64::decode(args[1]).unwrap()), Some(base64::decode(args[2]).unwrap())) - } else if global_vars_read.is_identity_protected { //sent old_password + } else if Identity::is_protected().unwrap() { //sent old_password (Some(base64::decode(args[1]).unwrap()), None) } else { //sent new password (None, Some(base64::decode(args[1]).unwrap())) @@ -210,14 +200,7 @@ async fn websocket_worker(websocket_strem: TcpStream, global_vars: Arc { - ui_connection.password_changed(success, is_identity_protected); - if success && is_identity_protected != global_vars_read.is_identity_protected { - drop(global_vars_read); - let mut global_vars_write = global_vars.write().unwrap(); - global_vars_write.is_identity_protected = is_identity_protected; - } - } + Ok(success) => ui_connection.password_changed(success, is_identity_protected), Err(e) => print_error!(e) } } @@ -325,12 +308,11 @@ async fn handle_logout(req: HttpRequest) -> HttpResponse { Some(cookie) => { let global_vars = req.app_data::>>>().unwrap(); let mut global_vars_write = global_vars.write().unwrap(); - if global_vars_write.is_backend_running { + if global_vars_write.session_manager.is_identity_loaded() { global_vars_write.http_session_manager.remove(cookie.value()); global_vars_write.session_manager.stop().await; - global_vars_write.is_backend_running = false; } - if global_vars_write.is_identity_protected { + if Identity::is_protected().unwrap_or(true) { HttpResponse::Found().header(header::LOCATION, "/").finish() } else { HttpResponse::Ok().body(include_str!("frontend/logout.html")) @@ -340,11 +322,17 @@ async fn handle_logout(req: HttpRequest) -> HttpResponse { } } -async fn login(identity: Identity, global_vars: &Arc>) -> HttpResponse { +fn login(identity: Identity, global_vars: &Arc>) -> HttpResponse { let mut global_vars_write = global_vars.write().unwrap(); let cookie_value = global_vars_write.http_session_manager.register(); - if !global_vars_write.session_manager.is_identity_loaded() { - global_vars_write.session_manager.set_identity(Some(identity)).await; + let session_manager = global_vars_write.session_manager.clone(); + if !session_manager.is_identity_loaded() { + global_vars_write.session_manager.set_identity(Some(identity)); + global_vars_write.tokio_handle.clone().spawn(async move { + if SessionManager::start_listener(session_manager.clone()).await.is_err() { + print_error!("You won't be able to receive incomming connections from other peers."); + } + }); } let cookie = CookieBuilder::new(constants::HTTP_COOKIE_NAME, cookie_value) .http_only(true) @@ -353,19 +341,19 @@ async fn login(identity: Identity, global_vars: &Arc>) -> Htt HttpResponse::Found().header(header::LOCATION, "/").set_header(header::SET_COOKIE, cookie.to_string()).finish() } -async fn on_identity_loaded(identity: Identity, global_vars: &Arc>) -> HttpResponse { +fn on_identity_loaded(identity: Identity, global_vars: &Arc>) -> HttpResponse { match Identity::clear_temporary_files() { Ok(_) => {}, Err(e) => print_error!(e) } - login(identity, global_vars).await + login(identity, global_vars) } -async fn handle_login(req: HttpRequest, mut params: web::Form) -> HttpResponse { +fn handle_login(req: HttpRequest, mut params: web::Form) -> HttpResponse { let response = match Identity::load_identity(Some(params.password.as_bytes())) { Ok(identity) => { let global_vars = req.app_data::>>>().unwrap(); - on_identity_loaded(identity, global_vars).await + on_identity_loaded(identity, global_vars) } Err(e) => generate_login_response(Some(&e.to_string())) }; @@ -412,7 +400,7 @@ async fn handle_create(req: HttpRequest, mut params: web::Form) -> ) { Ok(identity) => { let global_vars = req.app_data::>>>().unwrap(); - login(identity, global_vars.get_ref()).await + login(identity, global_vars.get_ref()) } Err(e) => { print_error!(e); @@ -427,15 +415,12 @@ async fn handle_create(req: HttpRequest, mut params: web::Form) -> response } -async fn index_not_logged_in(global_vars: &Arc>) -> HttpResponse { - let global_vars_read = global_vars.read().unwrap(); - let is_protected = global_vars_read.is_identity_protected; - drop(global_vars_read); - if is_protected { +fn index_not_logged_in(global_vars: &Arc>) -> HttpResponse { + if Identity::is_protected().unwrap_or(true) { generate_login_response(None) } else { match Identity::load_identity(None) { - Ok(identity) => on_identity_loaded(identity, global_vars).await, + Ok(identity) => on_identity_loaded(identity, global_vars), Err(_) => generate_login_response(None) //assuming no identity } } @@ -450,14 +435,14 @@ async fn handle_index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok().body( include_str!("frontend/index.html") .replace("WEBSOCKET_PORT", &global_vars_read.websocket_port.to_string()) - .replace("IS_IDENTITY_PROTECTED", &global_vars_read.is_identity_protected.to_string()) + .replace("IS_IDENTITY_PROTECTED", &Identity::is_protected().unwrap().to_string()) ) } else { drop(global_vars_read); - index_not_logged_in(global_vars).await + index_not_logged_in(global_vars) } } - None => index_not_logged_in(global_vars).await + None => index_not_logged_in(global_vars) } } @@ -609,10 +594,9 @@ impl HttpSessionsManager { struct GlobalVars { session_manager: Arc, - is_backend_running: bool, websocket_port: u16, - is_identity_protected: bool, http_session_manager: HttpSessionsManager, + tokio_handle: Handle, } #[tokio::main] @@ -627,10 +611,9 @@ async fn main() { } let global_vars = Arc::new(RwLock::new(GlobalVars { session_manager: Arc::new(SessionManager::new()), - is_backend_running: false, websocket_port: 0, - is_identity_protected: Identity::is_protected().unwrap_or(false), http_session_manager: HttpSessionsManager::new(), + tokio_handle: Handle::current(), })); let websocket_port = start_websocket_server(global_vars.clone()).await; global_vars.write().unwrap().websocket_port = websocket_port; diff --git a/src/session_manager/mod.rs b/src/session_manager/mod.rs index 8dff254..340ed75 100644 --- a/src/session_manager/mod.rs +++ b/src/session_manager/mod.rs @@ -3,11 +3,12 @@ pub mod protocol; use std::{collections::HashMap, net::{IpAddr, SocketAddr}, io, sync::{Mutex, RwLock, Arc}}; use tokio::{net::{TcpListener, TcpStream}, sync::{mpsc, mpsc::Sender}}; -use session::Session; +use libmdns::Service; use strum_macros::Display; +use session::Session; use ed25519_dalek::PUBLIC_KEY_LENGTH; use uuid::Uuid; -use crate::{constants, identity::{Contact, Identity}, print_error}; +use crate::{constants, discovery, identity::{Contact, Identity}, print_error}; use crate::ui_interface::UiConnection; #[derive(Display, Debug, PartialEq, Eq)] @@ -36,6 +37,7 @@ pub struct SessionManager { pub last_loaded_msg_offsets: RwLock>, pub saved_msgs: Mutex)>>>, not_seen: RwLock>, + mdns_service: Mutex>, listener_stop_signal: Mutex>>, } @@ -178,16 +180,18 @@ impl SessionManager { Ok(buffer) => { if buffer[0] == protocol::Headers::ASK_NAME { let name = { - session_manager.identity.read().unwrap().as_ref().unwrap().name.clone() + session_manager.identity.read().unwrap().as_ref().and_then(|identity| Some(identity.name.clone())) }; - match session.encrypt_and_send(&protocol::tell_name(&name)).await { - Ok(_) => {} - Err(e) => { - print_error!(e); - session.close(); - break; + if name.is_some() { //can be None if we log out just before locking the identity mutex + match session.encrypt_and_send(&protocol::tell_name(&name.unwrap())).await { + Ok(_) => {} + Err(e) => { + print_error!(e); + session.close(); + break; + } } - } + } } else { let buffer = if buffer[0] == protocol::Headers::FILE { let file_name_len = u16::from_be_bytes([buffer[1], buffer[2]]) as usize; @@ -277,6 +281,7 @@ impl SessionManager { let server_v4 = TcpListener::bind(SocketAddr::new("0.0.0.0".parse().unwrap(), constants::PORT)).await?; let (sender, mut receiver) = mpsc::channel(1); *session_manager.listener_stop_signal.lock().unwrap() = Some(sender); + *session_manager.mdns_service.lock().unwrap() = Some(discovery::advertise_me()); tokio::spawn(async move { loop { let (stream, _addr) = (tokio::select! { @@ -422,8 +427,13 @@ impl SessionManager { #[allow(unused_must_use)] pub async fn stop(&self) { - self.listener_stop_signal.lock().unwrap().as_ref().unwrap().send(()).await; - self.set_identity(None).await; + *self.mdns_service.lock().unwrap() = None; //unregister mdns service + let mut sender = self.listener_stop_signal.lock().unwrap(); + if sender.is_some() { + sender.as_ref().unwrap().send(()).await; + *sender = None; + } + self.set_identity(None); for (_, _, sender) in self.sessions.read().unwrap().values() { sender.send(SessionCommand::Close).await; } @@ -438,7 +448,7 @@ impl SessionManager { } #[allow(unused_must_use)] - pub async fn set_identity(&self, identity: Option) { + pub fn set_identity(&self, identity: Option) { let mut identity_guard = self.identity.write().unwrap(); if identity.is_none() { //logout identity_guard.as_mut().unwrap().zeroize(); @@ -477,6 +487,7 @@ impl SessionManager { last_loaded_msg_offsets: RwLock::new(HashMap::new()), saved_msgs: Mutex::new(HashMap::new()), not_seen: RwLock::new(Vec::new()), + mdns_service: Mutex::new(None), listener_stop_signal: Mutex::new(None), } }