From e893869df974ebb7afcc318119840c53f8f377cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cel=20=F0=9F=8C=B8?= Date: Sat, 21 Oct 2023 01:28:54 +0100 Subject: [PATCH] implement connection --- Cargo.toml | 1 + TODO.md | 30 ++-- src/client/encrypted.rs | 205 ------------------------ src/client/mod.rs | 38 ----- src/client/unencrypted.rs | 180 --------------------- src/connection.rs | 162 +++++++++++++++++++ src/error.rs | 19 +-- src/jabber.rs | 320 +++++++++++++++++++++++--------------- src/lib.rs | 30 ++-- src/stanza/mod.rs | 4 +- src/stanza/stream.rs | 19 ++- 11 files changed, 403 insertions(+), 605 deletions(-) delete mode 100644 src/client/encrypted.rs delete mode 100644 src/client/mod.rs delete mode 100644 src/client/unencrypted.rs create mode 100644 src/connection.rs diff --git a/Cargo.toml b/Cargo.toml index bb68f96..ffd2931 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] async-recursion = "1.0.4" async-trait = "0.1.68" +lazy_static = "1.4.0" nanoid = "0.4.0" quick-xml = { git = "https://github.com/tafia/quick-xml.git", features = ["async-tokio", "serialize"] } # TODO: remove unneeded features diff --git a/TODO.md b/TODO.md index 22d656a..31f8012 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,23 @@ # TODO -[ ] recognise starttls required -[ ] logging -[ ] documentation -[ ] error handling - [ ] remove unwraps - [ ] proper error types - [ ] stream error type -[ ] change stanzas from owned to borrowed types with lifetimes -[ ] Into trait with event() and content() functions +## next + +ci/cd: doc generation +feature: error handling on stream according to rfc6120 +docs: jid +docs: jabber +docs: starttls +docs: sasl +docs: resource binding +feature: logging +feature: starttls +feature: sasl +feature: resource binding + +## in progress + + +## done + +feature: jabber client connection +feature: jid diff --git a/src/client/encrypted.rs b/src/client/encrypted.rs deleted file mode 100644 index 263d5ff..0000000 --- a/src/client/encrypted.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::{collections::BTreeMap, str}; - -use quick_xml::{ - events::{BytesDecl, Event}, - NsReader, Writer, -}; -use rsasl::prelude::{Mechname, SASLClient}; -use tokio::io::{BufReader, ReadHalf, WriteHalf}; -use tokio::net::TcpStream; -use tokio_native_tls::TlsStream; - -use crate::Jabber; -use crate::JabberError; -use crate::Result; - -pub struct JabberClient<'j> { - pub reader: NsReader>>>, - pub writer: Writer>>, - jabber: &'j mut Jabber<'j>, -} - -impl<'j> JabberClient<'j> { - pub fn new( - reader: NsReader>>>, - writer: Writer>>, - jabber: &'j mut Jabber<'j>, - ) -> Self { - Self { - reader, - writer, - jabber, - } - } - - pub async fn start_stream(&mut self) -> Result<()> { - // client to server - let declaration = BytesDecl::new("1.0", None, None); - let server = &self.jabber.server.to_owned().try_into()?; - let stream_element = - Stream::new_client(&self.jabber.jid, server, None, Some("en".to_string())); - self.writer - .write_event_async(Event::Decl(declaration)) - .await; - let stream_element: Element<'_> = stream_element.into(); - stream_element - .write_start(&mut self.writer, &BTreeMap::new()) - .await?; - // server to client - let mut buf = Vec::new(); - self.reader.read_event_into_async(&mut buf).await?; - let _stream_response = Element::read_start(&mut self.reader).await?; - Ok(()) - } - - pub async fn get_features(&mut self) -> Result> { - Element::read(&mut self.reader).await?.try_into() - } - - pub async fn negotiate(&mut self) -> Result<()> { - loop { - println!("negotiate loop"); - let features = self.get_features().await?; - println!("features: {:?}", features); - - match &features[0] { - StreamFeature::Sasl(sasl) => { - println!("sasl?"); - self.sasl(&sasl).await?; - } - StreamFeature::Bind => { - self.bind().await?; - return Ok(()); - } - x => println!("{:?}", x), - } - } - } - - pub async fn watch(&mut self) -> Result<()> { - loop { - let element = Element::read(&mut self.reader).await?; - println!("{:#?}", element); - } - } - - pub async fn sasl(&mut self, mechanisms: &Vec) -> Result<()> { - println!("{:?}", mechanisms); - let sasl = SASLClient::new(self.jabber.auth.clone()); - let mut offered_mechs: Vec<&Mechname> = Vec::new(); - for mechanism in mechanisms { - offered_mechs.push(Mechname::parse(mechanism.as_bytes())?) - } - println!("{:?}", offered_mechs); - let mut session = sasl.start_suggested(&offered_mechs)?; - let selected_mechanism = session.get_mechname().as_str().to_owned(); - println!("selected mech: {:?}", selected_mechanism); - let mut data: Option> = None; - if !session.are_we_first() { - // if not first mention the mechanism then get challenge data - // mention mechanism - let auth = Auth { - mechanism: selected_mechanism.as_str(), - sasl_data: "=", - }; - Into::::into(auth).write(&mut self.writer).await?; - // get challenge data - let challenge = &Element::read(&mut self.reader).await?; - let challenge: Challenge = challenge.try_into()?; - println!("challenge: {:?}", challenge); - data = Some(challenge.sasl_data.to_owned()); - println!("we didn't go first"); - } else { - // if first, mention mechanism and send data - let mut sasl_data = Vec::new(); - session.step64(None, &mut sasl_data).unwrap(); - let auth = Auth { - mechanism: selected_mechanism.as_str(), - sasl_data: str::from_utf8(&sasl_data)?, - }; - println!("{:?}", auth); - Into::::into(auth).write(&mut self.writer).await?; - - let server_response = Element::read(&mut self.reader).await?; - println!("server_response: {:#?}", server_response); - match TryInto::::try_into(&server_response) { - Ok(challenge) => data = Some(challenge.sasl_data.to_owned()), - Err(_) => { - let success = TryInto::::try_into(&server_response)?; - if let Some(sasl_data) = success.sasl_data { - data = Some(sasl_data.to_owned()) - } - } - } - println!("we went first"); - } - - // stepping the authentication exchange to completion - if data != None { - println!("data: {:?}", data); - let mut sasl_data = Vec::new(); - while { - // decide if need to send more data over - let state = session - .step64(data.as_deref(), &mut sasl_data) - .expect("step errored!"); - state.is_running() - } { - // While we aren't finished, receive more data from the other party - let response = Response { - sasl_data: str::from_utf8(&sasl_data)?, - }; - println!("response: {:?}", response); - Into::::into(response) - .write(&mut self.writer) - .await?; - - let server_response = Element::read(&mut self.reader).await?; - println!("server_response: {:?}", server_response); - match TryInto::::try_into(&server_response) { - Ok(challenge) => data = Some(challenge.sasl_data.to_owned()), - Err(_) => { - let success = TryInto::::try_into(&server_response)?; - if let Some(sasl_data) = success.sasl_data { - data = Some(sasl_data.to_owned()) - } - } - } - } - } - self.start_stream().await?; - Ok(()) - } - - pub async fn bind(&mut self) -> Result<()> { - match &self.jabber.jid.resourcepart { - Some(resource) => { - println!("setting resource"); - let bind = Bind { - resource: Some(resource.clone()), - jid: None, - }; - let result: Bind = IQ::set(self, None, None, bind).await?.try_into()?; - if let Some(jid) = result.jid { - println!("{}", jid); - self.jabber.jid = jid; - return Ok(()); - } - } - None => { - println!("not setting resource"); - let bind = Bind { - resource: None, - jid: None, - }; - let result: Bind = IQ::set(self, None, None, bind).await?.try_into()?; - if let Some(jid) = result.jid { - println!("{}", jid); - self.jabber.jid = jid; - return Ok(()); - } - } - } - Err(JabberError::BindError) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index 01df4a4..0000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -// pub mod encrypted; -pub mod unencrypted; - -// use async_trait::async_trait; - -// use crate::stanza::stream::StreamFeature; -use crate::JabberError; -use crate::Result; - -pub enum JabberClientType<'j> { - // Encrypted(encrypted::JabberClient<'j>), - Unencrypted(unencrypted::JabberClient<'j>), -} - -impl<'j> JabberClientType<'j> { - /// ensures an encrypted jabber client - pub async fn ensure_tls(self) -> Result> { - match self { - Self::Encrypted(c) => Ok(c), - Self::Unencrypted(mut c) => { - c.start_stream().await?; - let features = c.get_features().await?; - if features.contains(&StreamFeature::StartTls) { - Ok(c.starttls().await?) - } else { - Err(JabberError::StartTlsUnavailable) - } - } - } - } -} - -// TODO: jabber client trait over both client types using macro -// #[async_trait] -// pub trait JabberTrait { -// async fn start_stream(&mut self) -> Result<()>; -// async fn get_features(&self) -> Result>; -// } diff --git a/src/client/unencrypted.rs b/src/client/unencrypted.rs deleted file mode 100644 index 4aa9c63..0000000 --- a/src/client/unencrypted.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::str; - -use quick_xml::{ - events::{BytesStart, Event}, - name::QName, - se, NsReader, Writer, -}; -use tokio::io::{BufReader, ReadHalf, WriteHalf}; -use tokio::net::TcpStream; -use tokio_native_tls::native_tls::TlsConnector; -use try_map::FallibleMapExt; - -use crate::error::JabberError; -use crate::stanza::stream::Stream; -use crate::stanza::DECLARATION; -use crate::Jabber; -use crate::Result; - -pub struct JabberClient<'j> { - reader: NsReader>>, - writer: Writer>, - jabber: &'j mut Jabber<'j>, -} - -impl<'j> JabberClient<'j> { - pub fn new( - reader: NsReader>>, - writer: Writer>, - jabber: &'j mut Jabber<'j>, - ) -> Self { - Self { - reader, - writer, - jabber, - } - } - - pub async fn start_stream(&mut self) -> Result<()> { - // client to server - - // declaration - self.writer.write_event_async(DECLARATION).await?; - - // opening stream element - let server = &self.jabber.server.to_owned().try_into()?; - let stream_element = Stream::new_client(None, server, None, "en"); - se::to_writer_with_root(&mut self.writer, "stream:stream", &stream_element); - - // server to client - - // may or may not send a declaration - let buf = Vec::new(); - let mut first_event = self.reader.read_resolved_event_into_async(&mut buf).await?; - match first_event { - (quick_xml::name::ResolveResult::Unbound, Event::Decl(e)) => { - if let Ok(version) = e.version() { - if version.as_ref() == b"1.0" { - first_event = self.reader.read_resolved_event_into_async(&mut buf).await? - } else { - // todo: error - todo!() - } - } else { - first_event = self.reader.read_resolved_event_into_async(&mut buf).await? - } - } - _ => (), - } - - // receive stream element and validate - let stream_response: Stream; - match first_event { - (quick_xml::name::ResolveResult::Bound(ns), Event::Start(e)) => { - if ns.0 == crate::stanza::stream::XMLNS.as_bytes() { - // stream_response = Stream::new( - // e.try_get_attribute("from")?.try_map(|attribute| { - // str::from_utf8(attribute.value.as_ref())? - // .try_into()? - // .as_ref() - // })?, - // e.try_get_attribute("to")?.try_map(|attribute| { - // str::from_utf8(attribute.value.as_ref())? - // .try_into()? - // .as_ref() - // })?, - // e.try_get_attribute("id")?.try_map(|attribute| { - // str::from_utf8(attribute.value.as_ref())? - // .try_into()? - // .as_ref() - // })?, - // e.try_get_attribute("version")?.try_map(|attribute| { - // str::from_utf8(attribute.value.as_ref())? - // .try_into()? - // .as_ref() - // })?, - // e.try_get_attribute("lang")?.try_map(|attribute| { - // str::from_utf8(attribute.value.as_ref())? - // .try_into()? - // .as_ref() - // })?, - // ); - return Ok(()); - } else { - return Err(JabberError::BadStream); - } - } - // TODO: errors for incorrect namespace - (quick_xml::name::ResolveResult::Unbound, Event::Decl(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::Start(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::End(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::Empty(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::Text(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::CData(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::Comment(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::Decl(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::PI(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::DocType(_)) => todo!(), - (quick_xml::name::ResolveResult::Unknown(_), Event::Eof) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::Start(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::End(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::Empty(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::Text(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::CData(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::Comment(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::PI(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::DocType(_)) => todo!(), - (quick_xml::name::ResolveResult::Unbound, Event::Eof) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::End(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::Empty(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::Text(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::CData(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::Comment(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::Decl(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::PI(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::DocType(_)) => todo!(), - (quick_xml::name::ResolveResult::Bound(_), Event::Eof) => todo!(), - } - } - - // pub async fn get_features(&mut self) -> Result> { - // Element::read(&mut self.reader).await?.try_into() - // } - - // pub async fn starttls(mut self) -> Result> { - // let mut starttls_element = BytesStart::new("starttls"); - // starttls_element.push_attribute(("xmlns", "urn:ietf:params:xml:ns:xmpp-tls")); - // self.writer - // .write_event_async(Event::Empty(starttls_element)) - // .await - // .unwrap(); - // let mut buf = Vec::new(); - // match self.reader.read_event_into_async(&mut buf).await.unwrap() { - // Event::Empty(e) => match e.name() { - // QName(b"proceed") => { - // let connector = TlsConnector::new().unwrap(); - // let stream = self - // .reader - // .into_inner() - // .into_inner() - // .unsplit(self.writer.into_inner()); - // if let Ok(tlsstream) = tokio_native_tls::TlsConnector::from(connector) - // .connect(&self.jabber.server, stream) - // .await - // { - // let (read, write) = tokio::io::split(tlsstream); - // let reader = Reader::from_reader(BufReader::new(read)); - // let writer = Writer::new(write); - // let mut client = - // super::encrypted::JabberClient::new(reader, writer, self.jabber); - // client.start_stream().await?; - // return Ok(client); - // } - // } - // QName(_) => return Err(JabberError::TlsNegotiation), - // }, - // _ => return Err(JabberError::TlsNegotiation), - // } - // Err(JabberError::TlsNegotiation) - // } -} diff --git a/src/connection.rs b/src/connection.rs new file mode 100644 index 0000000..24f7745 --- /dev/null +++ b/src/connection.rs @@ -0,0 +1,162 @@ +use std::net::{IpAddr, SocketAddr}; +use std::str; +use std::str::FromStr; + +use tokio::net::TcpStream; +use tokio_native_tls::native_tls::TlsConnector; +// TODO: use rustls +use tokio_native_tls::TlsStream; + +use crate::Jabber; +use crate::JabberError; +use crate::Result; + +pub type Tls = TlsStream; +pub type Unencrypted = TcpStream; + +pub enum Connection { + Encrypted(Jabber), + Unencrypted(Jabber), +} + +impl Connection { + pub async fn ensure_tls(self) -> Result> { + match self { + Connection::Encrypted(j) => Ok(j), + Connection::Unencrypted(j) => Ok(j.starttls().await?), + } + } + + // pub async fn connect_user>(jid: J, password: String) -> Result { + // let server = jid.domainpart.clone(); + // let auth = SASLConfig::with_credentials(None, jid.localpart.clone().unwrap(), password)?; + // println!("auth: {:?}", auth); + // Self::connect(&server, jid.try_into()?, Some(auth)).await + // } + + async fn connect(server: &str) -> Result { + let sockets = Self::get_sockets(&server).await; + for (socket_addr, tls) in sockets { + match tls { + true => { + if let Ok(connection) = Self::connect_tls(socket_addr, &server).await { + let (readhalf, writehalf) = tokio::io::split(connection); + return Ok(Self::Encrypted(Jabber::new( + readhalf, + writehalf, + None, + None, + server.to_owned(), + ))); + } + } + false => { + if let Ok(connection) = Self::connect_unencrypted(socket_addr).await { + let (readhalf, writehalf) = tokio::io::split(connection); + return Ok(Self::Unencrypted(Jabber::new( + readhalf, + writehalf, + None, + None, + server.to_owned(), + ))); + } + } + } + } + Err(JabberError::Connection) + } + + async fn get_sockets(domain: &str) -> Vec<(SocketAddr, bool)> { + let mut socket_addrs = Vec::new(); + + // if it's a socket/ip then just return that + + // socket + if let Ok(socket_addr) = SocketAddr::from_str(domain) { + match socket_addr.port() { + 5223 => socket_addrs.push((socket_addr, true)), + _ => socket_addrs.push((socket_addr, false)), + } + + return socket_addrs; + } + // ip + if let Ok(ip) = IpAddr::from_str(domain) { + socket_addrs.push((SocketAddr::new(ip, 5222), false)); + socket_addrs.push((SocketAddr::new(ip, 5223), true)); + return socket_addrs; + } + + // otherwise resolve + if let Ok(resolver) = trust_dns_resolver::AsyncResolver::tokio_from_system_conf() { + if let Ok(lookup) = resolver + .srv_lookup(format!("_xmpp-client._tcp.{}", domain)) + .await + { + for srv in lookup { + resolver + .lookup_ip(srv.target().to_owned()) + .await + .map(|ips| { + for ip in ips { + socket_addrs.push((SocketAddr::new(ip, srv.port()), false)) + } + }); + } + } + if let Ok(lookup) = resolver + .srv_lookup(format!("_xmpps-client._tcp.{}", domain)) + .await + { + for srv in lookup { + resolver + .lookup_ip(srv.target().to_owned()) + .await + .map(|ips| { + for ip in ips { + socket_addrs.push((SocketAddr::new(ip, srv.port()), true)) + } + }); + } + } + + // in case cannot connect through SRV records + resolver.lookup_ip(domain).await.map(|ips| { + for ip in ips { + socket_addrs.push((SocketAddr::new(ip, 5222), false)); + socket_addrs.push((SocketAddr::new(ip, 5223), true)); + } + }); + } + socket_addrs + } + + /// establishes a connection to the server + pub async fn connect_tls(socket_addr: SocketAddr, domain_name: &str) -> Result { + let socket = TcpStream::connect(socket_addr) + .await + .map_err(|_| JabberError::Connection)?; + let connector = TlsConnector::new().map_err(|_| JabberError::Connection)?; + tokio_native_tls::TlsConnector::from(connector) + .connect(domain_name, socket) + .await + .map_err(|_| JabberError::Connection) + } + + pub async fn connect_unencrypted(socket_addr: SocketAddr) -> Result { + TcpStream::connect(socket_addr) + .await + .map_err(|_| JabberError::Connection) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn connect() { + Connection::connect("blos.sm").await.unwrap(); + } +} diff --git a/src/error.rs b/src/error.rs index 9278f1b..b12914c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,10 +3,7 @@ use std::str::Utf8Error; use quick_xml::events::attributes::AttrError; use rsasl::mechname::MechanismNameError; -use crate::{ - jid::ParseError, - stanza::{self, ElementError, ElementParseError}, -}; +use crate::jid::ParseError; #[derive(Debug)] pub enum JabberError { @@ -22,14 +19,12 @@ pub enum JabberError { NoType, IDMismatch, BindError, - ElementParse(ElementParseError), ParseError, UnexpectedEnd, UnexpectedElement, UnexpectedText, XML(quick_xml::Error), SASL(SASLError), - Element(ElementError<'static>), JID(ParseError), } @@ -71,12 +66,6 @@ impl From for JabberError { } } -impl From> for JabberError { - fn from(e: stanza::ElementError<'static>) -> Self { - Self::Element(e) - } -} - impl From for JabberError { fn from(e: AttrError) -> Self { Self::XML(e.into()) @@ -88,9 +77,3 @@ impl From for JabberError { Self::JID(e) } } - -impl From for JabberError { - fn from(e: ElementParseError) -> Self { - Self::ElementParse(e) - } -} diff --git a/src/jabber.rs b/src/jabber.rs index d48eb9c..3583d19 100644 --- a/src/jabber.rs +++ b/src/jabber.rs @@ -1,141 +1,205 @@ -use std::marker::PhantomData; -use std::net::{IpAddr, SocketAddr}; -use std::str::FromStr; use std::sync::Arc; -use quick_xml::{NsReader, Writer}; +use quick_xml::{events::Event, se::Serializer, NsReader, Writer}; use rsasl::prelude::SASLConfig; -use tokio::io::BufReader; -use tokio::net::TcpStream; -use tokio_native_tls::native_tls::TlsConnector; +use serde::Serialize; +use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, ReadHalf, WriteHalf}; -use crate::client::JabberClientType; -use crate::jid::JID; -use crate::{client, JabberClient}; -use crate::{JabberError, Result}; +use crate::connection::{Tls, Unencrypted}; +use crate::error::JabberError; +use crate::stanza::stream::Stream; +use crate::stanza::DECLARATION; +use crate::Result; +use crate::JID; -pub struct Jabber<'j> { - pub jid: JID, - pub auth: Arc, - pub server: String, - _marker: PhantomData<&'j ()>, +pub struct Jabber +where + S: AsyncRead + AsyncWrite + Unpin, +{ + reader: NsReader>>, + writer: Writer>, + jid: Option, + auth: Option>, + server: String, } -impl<'j> Jabber<'j> { - pub fn user(jid: JID, password: String) -> Result { - let server = jid.domainpart.clone(); - let auth = SASLConfig::with_credentials(None, jid.localpart.clone().unwrap(), password)?; - println!("auth: {:?}", auth); - Ok(Self { +impl Jabber +where + S: AsyncRead + AsyncWrite + Unpin, +{ + pub fn new( + reader: ReadHalf, + writer: WriteHalf, + jid: Option, + auth: Option>, + server: String, + ) -> Self { + let reader = NsReader::from_reader(BufReader::new(reader)); + let writer = Writer::new(writer); + Self { + reader, + writer, jid, auth, server, - _marker: PhantomData, - }) - } - - pub async fn login(&'j mut self) -> Result> { - let mut client = self.connect().await?.ensure_tls().await?; - client.start_stream().await?; - client.negotiate().await?; - Ok(client) - } - - async fn get_sockets(&self) -> Vec<(SocketAddr, bool)> { - let mut socket_addrs = Vec::new(); - - // if it's a socket/ip then just return that - - // socket - if let Ok(socket_addr) = SocketAddr::from_str(&self.jid.domainpart) { - match socket_addr.port() { - 5223 => socket_addrs.push((socket_addr, true)), - _ => socket_addrs.push((socket_addr, false)), - } - - return socket_addrs; } - // ip - if let Ok(ip) = IpAddr::from_str(&self.jid.domainpart) { - socket_addrs.push((SocketAddr::new(ip, 5222), false)); - socket_addrs.push((SocketAddr::new(ip, 5223), true)); - return socket_addrs; - } - - // otherwise resolve - if let Ok(resolver) = trust_dns_resolver::AsyncResolver::tokio_from_system_conf() { - if let Ok(lookup) = resolver - .srv_lookup(format!("_xmpp-client._tcp.{}", self.jid.domainpart)) - .await - { - for srv in lookup { - resolver - .lookup_ip(srv.target().to_owned()) - .await - .map(|ips| { - for ip in ips { - socket_addrs.push((SocketAddr::new(ip, srv.port()), false)) - } - }); - } - } - if let Ok(lookup) = resolver - .srv_lookup(format!("_xmpps-client._tcp.{}", self.jid.domainpart)) - .await - { - for srv in lookup { - resolver - .lookup_ip(srv.target().to_owned()) - .await - .map(|ips| { - for ip in ips { - socket_addrs.push((SocketAddr::new(ip, srv.port()), true)) - } - }); - } - } - - // in case cannot connect through SRV records - resolver.lookup_ip(&self.jid.domainpart).await.map(|ips| { - for ip in ips { - socket_addrs.push((SocketAddr::new(ip, 5222), false)); - socket_addrs.push((SocketAddr::new(ip, 5223), true)); - } - }); - } - socket_addrs - } - - /// establishes a connection to the server - pub async fn connect(&'j mut self) -> Result { - for (socket_addr, is_tls) in self.get_sockets().await { - println!("trying {}", socket_addr); - match is_tls { - true => { - let socket = TcpStream::connect(socket_addr).await.unwrap(); - let connector = TlsConnector::new().unwrap(); - if let Ok(stream) = tokio_native_tls::TlsConnector::from(connector) - .connect(&self.server, socket) - .await - { - let (read, write) = tokio::io::split(stream); - let reader = NsReader::from_reader(BufReader::new(read)); - let writer = Writer::new(write); - let client = client::encrypted::JabberClient::new(reader, writer, self); - return Ok(JabberClientType::Encrypted(client)); - } - } - false => { - if let Ok(stream) = TcpStream::connect(socket_addr).await { - let (read, write) = tokio::io::split(stream); - let reader = NsReader::from_reader(BufReader::new(read)); - let writer = Writer::new(write); - let client = client::unencrypted::JabberClient::new(reader, writer, self); - return Ok(JabberClientType::Unencrypted(client)); - } - } - } - } - Err(JabberError::Connection) } } + +impl Jabber +where + S: AsyncRead + AsyncWrite + Unpin, + Writer>: AsyncWriteExt, + Writer>: AsyncWrite, +{ + pub async fn start_stream(&mut self) -> Result<()> { + // client to server + + // declaration + self.writer.write_event_async(DECLARATION.clone()).await?; + + // opening stream element + let server = &self.server.to_owned().try_into()?; + let stream_element = Stream::new_client(None, server, None, "en"); + // TODO: nicer function to serialize to xml writer + let mut buffer = String::new(); + let ser = Serializer::new(&mut buffer); + stream_element.serialize(ser).unwrap(); + self.writer.write_all(buffer.as_bytes()); + + // server to client + + // may or may not send a declaration + let mut buf = Vec::new(); + let mut first_event = self.reader.read_resolved_event_into_async(&mut buf).await?; + match first_event { + (quick_xml::name::ResolveResult::Unbound, Event::Decl(e)) => { + if let Ok(version) = e.version() { + if version.as_ref() == b"1.0" { + first_event = self.reader.read_resolved_event_into_async(&mut buf).await? + } else { + // todo: error + todo!() + } + } else { + first_event = self.reader.read_resolved_event_into_async(&mut buf).await? + } + } + _ => (), + } + + // receive stream element and validate + let stream_response: Stream; + match first_event { + (quick_xml::name::ResolveResult::Bound(ns), Event::Start(e)) => { + if ns.0 == crate::stanza::stream::XMLNS.as_bytes() { + // stream_response = Stream::new( + // e.try_get_attribute("from")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("to")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("id")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("version")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // e.try_get_attribute("lang")?.try_map(|attribute| { + // str::from_utf8(attribute.value.as_ref())? + // .try_into()? + // .as_ref() + // })?, + // ); + return Ok(()); + } else { + return Err(JabberError::BadStream); + } + } + // TODO: errors for incorrect namespace + (quick_xml::name::ResolveResult::Unbound, Event::Decl(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Start(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::End(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Empty(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Text(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::CData(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Comment(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Decl(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::PI(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::DocType(_)) => todo!(), + (quick_xml::name::ResolveResult::Unknown(_), Event::Eof) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Start(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::End(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Empty(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Text(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::CData(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Comment(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::PI(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::DocType(_)) => todo!(), + (quick_xml::name::ResolveResult::Unbound, Event::Eof) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::End(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Empty(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Text(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::CData(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Comment(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Decl(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::PI(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::DocType(_)) => todo!(), + (quick_xml::name::ResolveResult::Bound(_), Event::Eof) => todo!(), + } + } +} + +// pub async fn get_features(&mut self) -> Result> { +// Element::read(&mut self.reader).await?.try_into() +// } + +impl Jabber { + pub async fn starttls(mut self) -> Result> { + todo!() + } + // let mut starttls_element = BytesStart::new("starttls"); + // starttls_element.push_attribute(("xmlns", "urn:ietf:params:xml:ns:xmpp-tls")); + // self.writer + // .write_event_async(Event::Empty(starttls_element)) + // .await + // .unwrap(); + // let mut buf = Vec::new(); + // match self.reader.read_event_into_async(&mut buf).await.unwrap() { + // Event::Empty(e) => match e.name() { + // QName(b"proceed") => { + // let connector = TlsConnector::new().unwrap(); + // let stream = self + // .reader + // .into_inner() + // .into_inner() + // .unsplit(self.writer.into_inner()); + // if let Ok(tlsstream) = tokio_native_tls::TlsConnector::from(connector) + // .connect(&self.jabber.server, stream) + // .await + // { + // let (read, write) = tokio::io::split(tlsstream); + // let reader = Reader::from_reader(BufReader::new(read)); + // let writer = Writer::new(write); + // let mut client = + // super::encrypted::JabberClient::new(reader, writer, self.jabber); + // client.start_stream().await?; + // return Ok(client); + // } + // } + // QName(_) => return Err(JabberError::TlsNegotiation), + // }, + // _ => return Err(JabberError::TlsNegotiation), + // } + // Err(JabberError::TlsNegotiation) + // } +} diff --git a/src/lib.rs b/src/lib.rs index 86da83d..738735d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,38 +2,30 @@ #![feature(let_chains)] // TODO: logging (dropped errors) -pub mod client; +pub mod connection; pub mod error; pub mod jabber; pub mod jid; pub mod stanza; -// pub use client::encrypted::JabberClient; +#[macro_use] +extern crate lazy_static; + +pub use connection::Connection; pub use error::JabberError; pub use jabber::Jabber; pub use jid::JID; pub type Result = std::result::Result; +pub async fn login, P: AsRef>(jid: J, password: P) -> Result { + todo!() +} + #[cfg(test)] mod tests { - use std::str::FromStr; - - use crate::Jabber; - use crate::JID; - #[tokio::test] - async fn login() { - Jabber::user( - JID::from_str("test@blos.sm/clown").unwrap(), - "slayed".to_owned(), - ) - .unwrap() - .login() - .await - .unwrap() - .watch() - .await - .unwrap(); + async fn test_login() { + crate::login("test@blos.sm/clown", "slayed").await.unwrap(); } } diff --git a/src/stanza/mod.rs b/src/stanza/mod.rs index c5a6da3..e4f080f 100644 --- a/src/stanza/mod.rs +++ b/src/stanza/mod.rs @@ -8,4 +8,6 @@ pub mod stream; use quick_xml::events::{BytesDecl, Event}; -pub static DECLARATION: Event = Event::Decl(BytesDecl::new("1.0", None, None)); +lazy_static! { + pub static ref DECLARATION: Event<'static> = Event::Decl(BytesDecl::new("1.0", None, None)); +} diff --git a/src/stanza/stream.rs b/src/stanza/stream.rs index 07f7e6e..9a21373 100644 --- a/src/stanza/stream.rs +++ b/src/stanza/stream.rs @@ -25,13 +25,13 @@ pub struct Stream<'s> { xmlns_stream: &'s str, } -impl Stream { +impl<'s> Stream<'s> { pub fn new( - from: Option<&JID>, - to: Option<&JID>, - id: Option<&str>, - version: Option<&str>, - lang: Option<&str>, + from: Option<&'s JID>, + to: Option<&'s JID>, + id: Option<&'s str>, + version: Option<&'s str>, + lang: Option<&'s str>, ) -> Self { Self { from, @@ -46,7 +46,12 @@ impl Stream { /// For initial stream headers, the initiating entity SHOULD include the 'xml:lang' attribute. /// For privacy, it is better to not set `from` when sending a client stanza over an unencrypted connection. - pub fn new_client(from: Option<&JID>, to: &JID, id: Option<&str>, lang: &str) -> Self { + pub fn new_client( + from: Option<&'s JID>, + to: &'s JID, + id: Option<&'s str>, + lang: &'s str, + ) -> Self { Self { from, to: Some(to),