summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--TODO.md30
-rw-r--r--src/client/encrypted.rs205
-rw-r--r--src/client/mod.rs38
-rw-r--r--src/client/unencrypted.rs180
-rw-r--r--src/connection.rs162
-rw-r--r--src/error.rs19
-rw-r--r--src/jabber.rs296
-rw-r--r--src/lib.rs30
-rw-r--r--src/stanza/mod.rs4
-rw-r--r--src/stanza/stream.rs19
11 files changed, 391 insertions, 593 deletions
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<Element> 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<BufReader<ReadHalf<TlsStream<TcpStream>>>>,
- pub writer: Writer<WriteHalf<TlsStream<TcpStream>>>,
- jabber: &'j mut Jabber<'j>,
-}
-
-impl<'j> JabberClient<'j> {
- pub fn new(
- reader: NsReader<BufReader<ReadHalf<TlsStream<TcpStream>>>>,
- writer: Writer<WriteHalf<TlsStream<TcpStream>>>,
- 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<Vec<StreamFeature>> {
- 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<String>) -> 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<Vec<u8>> = 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::<Element>::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::<Element>::into(auth).write(&mut self.writer).await?;
-
- let server_response = Element::read(&mut self.reader).await?;
- println!("server_response: {:#?}", server_response);
- match TryInto::<Challenge>::try_into(&server_response) {
- Ok(challenge) => data = Some(challenge.sasl_data.to_owned()),
- Err(_) => {
- let success = TryInto::<Success>::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::<Element>::into(response)
- .write(&mut self.writer)
- .await?;
-
- let server_response = Element::read(&mut self.reader).await?;
- println!("server_response: {:?}", server_response);
- match TryInto::<Challenge>::try_into(&server_response) {
- Ok(challenge) => data = Some(challenge.sasl_data.to_owned()),
- Err(_) => {
- let success = TryInto::<Success>::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<encrypted::JabberClient<'j>> {
- 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<Vec<StreamFeatures>>;
-// }
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<BufReader<ReadHalf<TcpStream>>>,
- writer: Writer<WriteHalf<TcpStream>>,
- jabber: &'j mut Jabber<'j>,
-}
-
-impl<'j> JabberClient<'j> {
- pub fn new(
- reader: NsReader<BufReader<ReadHalf<TcpStream>>>,
- writer: Writer<WriteHalf<TcpStream>>,
- 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<Vec<StreamFeature>> {
- // Element::read(&mut self.reader).await?.try_into()
- // }
-
- // pub async fn starttls(mut self) -> Result<super::encrypted::JabberClient<'j>> {
- // 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<TcpStream>;
+pub type Unencrypted = TcpStream;
+
+pub enum Connection {
+ Encrypted(Jabber<Tls>),
+ Unencrypted(Jabber<Unencrypted>),
+}
+
+impl Connection {
+ pub async fn ensure_tls(self) -> Result<Jabber<Tls>> {
+ match self {
+ Connection::Encrypted(j) => Ok(j),
+ Connection::Unencrypted(j) => Ok(j.starttls().await?),
+ }
+ }
+
+ // pub async fn connect_user<J: TryInto<JID>>(jid: J, password: String) -> Result<Self> {
+ // 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<Self> {
+ 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<Tls> {
+ 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<Unencrypted> {
+ 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<quick_xml::Error> for JabberError {
}
}
-impl From<stanza::ElementError<'static>> for JabberError {
- fn from(e: stanza::ElementError<'static>) -> Self {
- Self::Element(e)
- }
-}
-
impl From<AttrError> for JabberError {
fn from(e: AttrError) -> Self {
Self::XML(e.into())
@@ -88,9 +77,3 @@ impl From<ParseError> for JabberError {
Self::JID(e)
}
}
-
-impl From<ElementParseError> 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<SASLConfig>,
- pub server: String,
- _marker: PhantomData<&'j ()>,
+pub struct Jabber<S>
+where
+ S: AsyncRead + AsyncWrite + Unpin,
+{
+ reader: NsReader<BufReader<ReadHalf<S>>>,
+ writer: Writer<WriteHalf<S>>,
+ jid: Option<JID>,
+ auth: Option<Arc<SASLConfig>>,
+ server: String,
}
-impl<'j> Jabber<'j> {
- pub fn user(jid: JID, password: String) -> Result<Self> {
- let server = jid.domainpart.clone();
- let auth = SASLConfig::with_credentials(None, jid.localpart.clone().unwrap(), password)?;
- println!("auth: {:?}", auth);
- Ok(Self {
+impl<S> Jabber<S>
+where
+ S: AsyncRead + AsyncWrite + Unpin,
+{
+ pub fn new(
+ reader: ReadHalf<S>,
+ writer: WriteHalf<S>,
+ jid: Option<JID>,
+ auth: Option<Arc<SASLConfig>>,
+ 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<JabberClient<'j>> {
- 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();
+impl<S> Jabber<S>
+where
+ S: AsyncRead + AsyncWrite + Unpin,
+ Writer<tokio::io::WriteHalf<S>>: AsyncWriteExt,
+ Writer<tokio::io::WriteHalf<S>>: AsyncWrite,
+{
+ pub async fn start_stream(&mut self) -> Result<()> {
+ // client to server
- // if it's a socket/ip then just return that
+ // declaration
+ self.writer.write_event_async(DECLARATION.clone()).await?;
- // 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)),
- }
+ // 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());
- 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;
- }
+ // server to client
- // 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))
- }
- });
+ // 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?
}
}
-
- // 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<JabberClientType> {
- 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));
- }
+ // 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!(),
}
- Err(JabberError::Connection)
}
}
+
+// pub async fn get_features(&mut self) -> Result<Vec<StreamFeature>> {
+// Element::read(&mut self.reader).await?.try_into()
+// }
+
+impl Jabber<Unencrypted> {
+ pub async fn starttls(mut self) -> Result<Jabber<Tls>> {
+ 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<T> = std::result::Result<T, JabberError>;
+pub async fn login<J: TryInto<JID>, P: AsRef<str>>(jid: J, password: P) -> Result<Connection> {
+ 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),