diff --git a/Cargo.lock b/Cargo.lock index 69c4337..ecbdae6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -754,6 +754,7 @@ version = "0.1.0" dependencies = [ "anyhow", "assert_matches", + "base64", "derive_more", "figment", "futures-util", diff --git a/Cargo.toml b/Cargo.toml index ddabb38..b3b4d66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ quick-xml = { version = "0.30.0", features = ["async-tokio"] } derive_more = "0.99.17" uuid = { version = "1.3.0", features = ["v4"] } sqlx = { version = "0.7.0-alpha.2", features = ["sqlite", "runtime-tokio-rustls", "migrate"] } +base64 = "0.21.3" [dev-dependencies] assert_matches = "1.5.0" diff --git a/src/main.rs b/src/main.rs index a623441..9452c8c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,7 @@ async fn main() -> Result<()> { let telemetry_terminator = util::telemetry::launch(telemetry_config, metrics.clone(), rooms.clone()).await?; let irc = projections::irc::launch(irc_config, players.clone(), rooms.clone(), metrics.clone(), storage.clone()).await?; - let xmpp = projections::xmpp::launch(xmpp_config, players.clone(), rooms.clone(), metrics.clone()).await?; + let xmpp = projections::xmpp::launch(xmpp_config, players.clone(), rooms.clone(), metrics.clone(), storage.clone()).await?; tracing::info!("Started"); sleep.await; diff --git a/src/projections/xmpp/mod.rs b/src/projections/xmpp/mod.rs index a02ceca..6a4386d 100644 --- a/src/projections/xmpp/mod.rs +++ b/src/projections/xmpp/mod.rs @@ -20,6 +20,7 @@ use tokio_rustls::rustls::{Certificate, PrivateKey}; use tokio_rustls::TlsAcceptor; use crate::core::player::{PlayerConnection, PlayerId, PlayerRegistry}; +use crate::core::repo::Storage; use crate::core::room::{RoomId, RoomRegistry}; use crate::prelude::*; use crate::protos::xmpp; @@ -27,6 +28,7 @@ use crate::protos::xmpp::bind::{BindResponse, Jid, Name, Resource, Server}; use crate::protos::xmpp::client::{Iq, Message, MessageType, Presence}; use crate::protos::xmpp::disco::*; use crate::protos::xmpp::roster::RosterQuery; +use crate::protos::xmpp::sasl::AuthBody; use crate::protos::xmpp::session::Session; use crate::protos::xmpp::stream::*; use crate::util::xml::{Continuation, FromXml, Parser, ToXml}; @@ -58,6 +60,7 @@ pub async fn launch( players: PlayerRegistry, rooms: RoomRegistry, metrics: MetricsRegistry, + storage: Storage, ) -> Result { log::info!("Starting XMPP projection"); @@ -97,11 +100,12 @@ pub async fn launch( } let players = players.clone(); let rooms = rooms.clone(); + let storage = storage.clone(); let terminator = Terminator::spawn(|termination| { let stopped_tx = stopped_tx.clone(); let loaded_config = loaded_config.clone(); async move { - match handle_socket(loaded_config, stream, &socket_addr, players, rooms, termination).await { + match handle_socket(loaded_config, stream, &socket_addr, players, rooms, storage, termination).await { Ok(_) => log::info!("Connection terminated"), Err(err) => log::warn!("Connection failed: {err}"), } @@ -133,7 +137,7 @@ pub async fn launch( } }), ) - .await; + .await; log::info!("Stopped XMPP projection"); Ok(()) }); @@ -147,6 +151,7 @@ async fn handle_socket( socket_addr: &SocketAddr, mut players: PlayerRegistry, rooms: RoomRegistry, + storage: Storage, termination: Deferred<()>, // TODO use it to stop the connection gracefully ) -> Result<()> { log::debug!("Received an XMPP connection from {socket_addr}"); @@ -171,7 +176,8 @@ async fn handle_socket( let mut xml_reader = NsReader::from_reader(BufReader::new(a)); let mut xml_writer = Writer::new(b); - let authenticated = socket_auth(&mut xml_reader, &mut xml_writer, &mut reader_buf).await?; + let mut storage = storage.clone(); + let authenticated = socket_auth(&mut xml_reader, &mut xml_writer, &mut reader_buf, &mut storage).await?; let mut connection = players .connect_to_player(authenticated.player_id.clone()) .await; @@ -183,7 +189,7 @@ async fn handle_socket( &mut connection, &rooms, ) - .await?; + .await?; let a = xml_reader.into_inner().into_inner(); let b = xml_writer.into_inner(); @@ -229,6 +235,7 @@ async fn socket_auth( xml_reader: &mut NsReader<(impl AsyncBufRead + Unpin)>, xml_writer: &mut Writer<(impl AsyncWrite + Unpin)>, reader_buf: &mut Vec, + storage: &mut Storage, ) -> Result { read_xml_header(xml_reader, reader_buf).await?; let _ = ClientStreamStart::parse(xml_reader, reader_buf).await?; @@ -242,27 +249,52 @@ async fn socket_auth( id: uuid::Uuid::new_v4().to_string(), version: "1.0".into(), } - .write_xml(xml_writer) - .await?; + .write_xml(xml_writer) + .await?; Features { start_tls: false, mechanisms: true, bind: false, } - .write_xml(xml_writer) - .await?; + .write_xml(xml_writer) + .await?; xml_writer.get_mut().flush().await?; - let _ = xmpp::sasl::Auth::parse(xml_reader, reader_buf).await?; + let auth: xmpp::sasl::Auth = xmpp::sasl::Auth::parse(xml_reader, reader_buf).await?; xmpp::sasl::Success.write_xml(xml_writer).await?; - let name: Str = "darova".into(); - Ok(Authenticated { - player_id: PlayerId::from("darova")?, - xmpp_name: Name(name.clone()), - xmpp_resource: Resource(name.clone()), - xmpp_muc_name: Resource(name), - }) + let auth_body = AuthBody::from_str(auth.body); + + match auth_body { + Ok(logopass) => { + let name = &logopass.login; + let stored_user = storage.retrieve_user_by_name(name).await?; + + let stored_user = match stored_user { + Some(u) => u, + None => { + log::info!("User '{}' not found", name); + return Err(fail("no user found")); + } + }; + if stored_user.password.is_none() { + log::info!("Password not defined for user '{}'", name); + return Err(fail("password is not defined")); + } + if stored_user.password.as_deref() != Some(&logopass.password) { + log::info!("Incorrect password supplied for user '{}'", name); + return Err(fail("passwords do not match")); + } + + Ok(Authenticated { + player_id: PlayerId::from("darova")?, + xmpp_name: Name(name.to_string().into()), + xmpp_resource: Resource(name.to_string().into()), + xmpp_muc_name: Resource(name.to_string().into()), + }) + } + Err(e) => Err(e) + } } async fn socket_final( @@ -285,15 +317,15 @@ async fn socket_final( id: uuid::Uuid::new_v4().to_string(), version: "1.0".into(), } - .write_xml(xml_writer) - .await?; + .write_xml(xml_writer) + .await?; Features { start_tls: false, mechanisms: false, bind: true, } - .write_xml(xml_writer) - .await?; + .write_xml(xml_writer) + .await?; xml_writer.get_mut().flush().await?; let mut parser = proto::ClientPacket::parse(); @@ -390,10 +422,10 @@ async fn handle_packet( } proto::ClientPacket::Message(m) => { if let Some(Jid { - name: Some(name), - server, - resource: _, - }) = m.to + name: Some(name), + server, + resource: _, + }) = m.to { if server.0.as_ref() == "rooms.localhost" && m.r#type == MessageType::Groupchat { user_handle @@ -419,7 +451,7 @@ async fn handle_packet( subject: None, body: m.body.clone(), } - .serialize(output); + .serialize(output); false } else { todo!() @@ -444,10 +476,10 @@ async fn handle_packet( ..Default::default() } } else if let Some(Jid { - name: Some(name), - server, - resource: Some(resource), - }) = p.to + name: Some(name), + server, + resource: Some(resource), + }) = p.to { let a = user_handle .join_room(RoomId::from(name.0.clone())?) diff --git a/src/protos/xmpp/sasl.rs b/src/protos/xmpp/sasl.rs index e78f605..7cd21e2 100644 --- a/src/protos/xmpp/sasl.rs +++ b/src/protos/xmpp/sasl.rs @@ -12,6 +12,7 @@ use crate::prelude::*; pub enum Mechanism { Plain, } + impl Mechanism { pub fn to_str(&self) -> &'static str { match self { @@ -27,10 +28,30 @@ impl Mechanism { } } +pub struct AuthBody { + pub login: String, + pub password: String, +} + +use base64::{Engine as _, engine::general_purpose}; + +impl AuthBody { + pub fn from_str(input: Vec) -> Result { + let decoded_body = general_purpose::STANDARD.decode(input).unwrap(); + let parsed_to_string = String::from_utf8(decoded_body).unwrap(); + let separated_words: Vec<&str> = parsed_to_string.split("\x00").filter(|&part| !part.is_empty()).collect::>().clone(); + if &separated_words.len() == &2_usize { + Ok(AuthBody { login: separated_words[0].to_string(), password: separated_words[1].to_string() }) + } else { Err(fail("Incorrect auth format")) } + } +} + + pub struct Auth { pub mechanism: Mechanism, pub body: Vec, } + impl Auth { pub async fn parse( reader: &mut NsReader, @@ -69,6 +90,7 @@ impl Auth { } pub struct Success; + impl Success { pub async fn write_xml(&self, writer: &mut Writer) -> Result<()> { let event = BytesStart::new(r#"success xmlns="urn:ietf:params:xml:ns:xmpp-sasl""#);