Compare commits

..

No commits in common. "4886396044356d2676a77c3900af796fe7641f42" and "7c2577d196c059ab6e2d5b0efe5e036bdad75be7" have entirely different histories.

7 changed files with 287 additions and 532 deletions

View File

@ -19,7 +19,6 @@ tracing = "0.1.40"
trust-dns-resolver = "0.22.0" trust-dns-resolver = "0.22.0"
try_map = "0.3.1" try_map = "0.3.1"
peanuts = { version = "0.1.0", path = "../peanuts" } peanuts = { version = "0.1.0", path = "../peanuts" }
futures = "0.3.31"
[dev-dependencies] [dev-dependencies]
test-log = { version = "0.2", features = ["trace"] } test-log = { version = "0.2", features = ["trace"] }

View File

@ -1,266 +0,0 @@
use std::{pin::pin, sync::Arc, task::Poll};
use futures::{Sink, Stream, StreamExt};
use rsasl::config::SASLConfig;
use crate::{
connection::{Tls, Unencrypted},
jid::ParseError,
stanza::{
client::Stanza,
sasl::Mechanisms,
stream::{Feature, Features},
},
Connection, Error, JabberStream, Result, JID,
};
// feed it client stanzas, receive client stanzas
pub struct JabberClient {
connection: ConnectionState,
jid: JID,
password: Arc<SASLConfig>,
server: String,
}
impl JabberClient {
pub fn new(
jid: impl TryInto<JID, Error = ParseError>,
password: impl ToString,
) -> Result<JabberClient> {
let jid = jid.try_into()?;
let sasl_config = SASLConfig::with_credentials(
None,
jid.localpart.clone().ok_or(Error::NoLocalpart)?,
password.to_string(),
)?;
Ok(JabberClient {
connection: ConnectionState::Disconnected,
jid: jid.clone(),
password: sasl_config,
server: jid.domainpart,
})
}
pub async fn connect(&mut self) -> Result<()> {
match &self.connection {
ConnectionState::Disconnected => {
self.connection = ConnectionState::Disconnected
.connect(&mut self.jid, self.password.clone(), &mut self.server)
.await?;
Ok(())
}
ConnectionState::Connecting(_connecting) => Err(Error::AlreadyConnecting),
ConnectionState::Connected(_jabber_stream) => Ok(()),
}
}
}
impl Stream for JabberClient {
type Item = Result<Stanza>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let mut client = pin!(self);
match &mut client.connection {
ConnectionState::Disconnected => Poll::Pending,
ConnectionState::Connecting(_connecting) => Poll::Pending,
ConnectionState::Connected(jabber_stream) => jabber_stream.poll_next_unpin(cx),
}
}
}
pub enum ConnectionState {
Disconnected,
Connecting(Connecting),
Connected(JabberStream<Tls>),
}
impl ConnectionState {
pub async fn connect(
mut self,
jid: &mut JID,
auth: Arc<SASLConfig>,
server: &mut String,
) -> Result<Self> {
loop {
match self {
ConnectionState::Disconnected => {
self = ConnectionState::Connecting(Connecting::start(&server).await?);
}
ConnectionState::Connecting(connecting) => match connecting {
Connecting::InsecureConnectionEstablised(tcp_stream) => {
self = ConnectionState::Connecting(Connecting::InsecureStreamStarted(
JabberStream::start_stream(tcp_stream, server).await?,
))
}
Connecting::InsecureStreamStarted(jabber_stream) => {
self = ConnectionState::Connecting(Connecting::InsecureGotFeatures(
jabber_stream.get_features().await?,
))
}
Connecting::InsecureGotFeatures((features, jabber_stream)) => {
match features.negotiate()? {
Feature::StartTls(_start_tls) => {
self =
ConnectionState::Connecting(Connecting::StartTls(jabber_stream))
}
// TODO: better error
_ => return Err(Error::TlsRequired),
}
}
Connecting::StartTls(jabber_stream) => {
self = ConnectionState::Connecting(Connecting::ConnectionEstablished(
jabber_stream.starttls(&server).await?,
))
}
Connecting::ConnectionEstablished(tls_stream) => {
self = ConnectionState::Connecting(Connecting::StreamStarted(
JabberStream::start_stream(tls_stream, server).await?,
))
}
Connecting::StreamStarted(jabber_stream) => {
self = ConnectionState::Connecting(Connecting::GotFeatures(
jabber_stream.get_features().await?,
))
}
Connecting::GotFeatures((features, jabber_stream)) => {
match features.negotiate()? {
Feature::StartTls(_start_tls) => return Err(Error::AlreadyTls),
Feature::Sasl(mechanisms) => {
self = ConnectionState::Connecting(Connecting::Sasl(
mechanisms,
jabber_stream,
))
}
Feature::Bind => {
self = ConnectionState::Connecting(Connecting::Bind(jabber_stream))
}
Feature::Unknown => return Err(Error::Unsupported),
}
}
Connecting::Sasl(mechanisms, jabber_stream) => {
self = ConnectionState::Connecting(Connecting::ConnectionEstablished(
jabber_stream.sasl(mechanisms, auth.clone()).await?,
))
}
Connecting::Bind(jabber_stream) => {
self = ConnectionState::Connected(jabber_stream.bind(jid).await?)
}
},
connected => return Ok(connected),
}
}
}
}
pub enum Connecting {
InsecureConnectionEstablised(Unencrypted),
InsecureStreamStarted(JabberStream<Unencrypted>),
InsecureGotFeatures((Features, JabberStream<Unencrypted>)),
StartTls(JabberStream<Unencrypted>),
ConnectionEstablished(Tls),
StreamStarted(JabberStream<Tls>),
GotFeatures((Features, JabberStream<Tls>)),
Sasl(Mechanisms, JabberStream<Tls>),
Bind(JabberStream<Tls>),
}
impl Connecting {
pub async fn start(server: &str) -> Result<Self> {
match Connection::connect(server).await? {
Connection::Encrypted(tls_stream) => Ok(Connecting::ConnectionEstablished(tls_stream)),
Connection::Unencrypted(tcp_stream) => {
Ok(Connecting::InsecureConnectionEstablised(tcp_stream))
}
}
}
}
impl Features {
pub fn negotiate(self) -> Result<Feature> {
if let Some(Feature::StartTls(s)) = self
.features
.iter()
.find(|feature| matches!(feature, Feature::StartTls(_s)))
{
// TODO: avoid clone
return Ok(Feature::StartTls(s.clone()));
} else if let Some(Feature::Sasl(mechanisms)) = self
.features
.iter()
.find(|feature| matches!(feature, Feature::Sasl(_)))
{
// TODO: avoid clone
return Ok(Feature::Sasl(mechanisms.clone()));
} else if let Some(Feature::Bind) = self
.features
.into_iter()
.find(|feature| matches!(feature, Feature::Bind))
{
Ok(Feature::Bind)
} else {
// TODO: better error
return Err(Error::Negotiation);
}
}
}
pub enum InsecureConnecting {
Disconnected,
ConnectionEstablished(Connection),
PreStarttls(JabberStream<Unencrypted>),
PreAuthenticated(JabberStream<Tls>),
Authenticated(Tls),
PreBound(JabberStream<Tls>),
Bound(JabberStream<Tls>),
}
impl Sink<Stanza> for JabberClient {
type Error = Error;
fn poll_ready(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::result::Result<(), Self::Error>> {
todo!()
}
fn start_send(
self: std::pin::Pin<&mut Self>,
item: Stanza,
) -> std::result::Result<(), Self::Error> {
todo!()
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::result::Result<(), Self::Error>> {
todo!()
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::result::Result<(), Self::Error>> {
todo!()
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::JabberClient;
use test_log::test;
use tokio::time::sleep;
#[test(tokio::test)]
async fn login() {
let mut client = JabberClient::new("test@blos.sm", "slayed").unwrap();
client.connect().await.unwrap();
sleep(Duration::from_secs(5)).await
}
}

View File

@ -10,6 +10,7 @@ use tokio_native_tls::native_tls::TlsConnector;
use tokio_native_tls::TlsStream; use tokio_native_tls::TlsStream;
use tracing::{debug, info, instrument, trace}; use tracing::{debug, info, instrument, trace};
use crate::Jabber;
use crate::Result; use crate::Result;
use crate::{Error, JID}; use crate::{Error, JID};
@ -18,51 +19,69 @@ pub type Unencrypted = TcpStream;
#[derive(Debug)] #[derive(Debug)]
pub enum Connection { pub enum Connection {
Encrypted(Tls), Encrypted(Jabber<Tls>),
Unencrypted(Unencrypted), Unencrypted(Jabber<Unencrypted>),
} }
impl Connection { impl Connection {
// #[instrument] #[instrument]
/// stream not started /// stream not started
// pub async fn ensure_tls(self) -> Result<Jabber<Tls>> { pub async fn ensure_tls(self) -> Result<Jabber<Tls>> {
// match self { match self {
// Connection::Encrypted(j) => Ok(j), Connection::Encrypted(j) => Ok(j),
// Connection::Unencrypted(mut j) => { Connection::Unencrypted(mut j) => {
// j.start_stream().await?; j.start_stream().await?;
// info!("upgrading connection to tls"); info!("upgrading connection to tls");
// j.get_features().await?; j.get_features().await?;
// let j = j.starttls().await?; let j = j.starttls().await?;
// Ok(j) Ok(j)
// } }
// } }
// } }
pub async fn connect_user(jid: impl AsRef<str>) -> Result<Self> { pub async fn connect_user(jid: impl AsRef<str>, password: String) -> Result<Self> {
let jid: JID = JID::from_str(jid.as_ref())?; let jid: JID = JID::from_str(jid.as_ref())?;
let server = jid.domainpart.clone(); let server = jid.domainpart.clone();
Self::connect(&server).await let auth = SASLConfig::with_credentials(None, jid.localpart.clone().unwrap(), password)?;
println!("auth: {:?}", auth);
Self::connect(&server, Some(jid), Some(auth)).await
} }
#[instrument] #[instrument]
pub async fn connect(server: impl AsRef<str> + std::fmt::Debug) -> Result<Self> { pub async fn connect(
info!("connecting to {}", server.as_ref()); server: &str,
let sockets = Self::get_sockets(server.as_ref()).await; jid: Option<JID>,
auth: Option<Arc<SASLConfig>>,
) -> Result<Self> {
info!("connecting to {}", server);
let sockets = Self::get_sockets(&server).await;
debug!("discovered sockets: {:?}", sockets); debug!("discovered sockets: {:?}", sockets);
for (socket_addr, tls) in sockets { for (socket_addr, tls) in sockets {
match tls { match tls {
true => { true => {
if let Ok(connection) = Self::connect_tls(socket_addr, server.as_ref()).await { if let Ok(connection) = Self::connect_tls(socket_addr, &server).await {
info!("connected via encrypted stream to {}", socket_addr); info!("connected via encrypted stream to {}", socket_addr);
// let (readhalf, writehalf) = tokio::io::split(connection); let (readhalf, writehalf) = tokio::io::split(connection);
return Ok(Self::Encrypted(connection)); return Ok(Self::Encrypted(Jabber::new(
readhalf,
writehalf,
jid,
auth,
server.to_owned(),
)));
} }
} }
false => { false => {
if let Ok(connection) = Self::connect_unencrypted(socket_addr).await { if let Ok(connection) = Self::connect_unencrypted(socket_addr).await {
info!("connected via unencrypted stream to {}", socket_addr); info!("connected via unencrypted stream to {}", socket_addr);
// let (readhalf, writehalf) = tokio::io::split(connection); let (readhalf, writehalf) = tokio::io::split(connection);
return Ok(Self::Unencrypted(connection)); return Ok(Self::Unencrypted(Jabber::new(
readhalf,
writehalf,
jid,
auth,
server.to_owned(),
)));
} }
} }
} }
@ -169,16 +188,16 @@ mod tests {
#[test(tokio::test)] #[test(tokio::test)]
async fn connect() { async fn connect() {
Connection::connect("blos.sm").await.unwrap(); Connection::connect("blos.sm", None, None).await.unwrap();
} }
// #[test(tokio::test)] #[test(tokio::test)]
// async fn test_tls() { async fn test_tls() {
// Connection::connect("blos.sm", None, None) Connection::connect("blos.sm", None, None)
// .await .await
// .unwrap() .unwrap()
// .ensure_tls() .ensure_tls()
// .await .await
// .unwrap(); .unwrap();
// } }
} }

View File

@ -8,16 +8,24 @@ use crate::{jid::ParseError, stanza::sasl::Failure};
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
Connection, Connection,
BadStream,
StartTlsUnavailable,
TlsNegotiation,
Utf8Decode, Utf8Decode,
NoFeatures,
UnknownNamespace,
UnknownAttribute,
NoID,
NoType,
IDMismatch,
BindError,
ParseError,
Negotiation, Negotiation,
TlsRequired, TlsRequired,
AlreadyTls, UnexpectedEnd,
Unsupported,
NoLocalpart,
AlreadyConnecting,
UnexpectedElement(peanuts::Element), UnexpectedElement(peanuts::Element),
UnexpectedText,
XML(peanuts::Error), XML(peanuts::Error),
Deserialization(peanuts::DeserializeError),
SASL(SASLError), SASL(SASLError),
JID(ParseError), JID(ParseError),
Authentication(Failure), Authentication(Failure),
@ -29,6 +37,8 @@ pub enum Error {
pub enum SASLError { pub enum SASLError {
SASL(rsasl::prelude::SASLError), SASL(rsasl::prelude::SASLError),
MechanismName(MechanismNameError), MechanismName(MechanismNameError),
NoChallenge,
NoSuccess,
} }
impl From<rsasl::prelude::SASLError> for Error { impl From<rsasl::prelude::SASLError> for Error {
@ -37,12 +47,6 @@ impl From<rsasl::prelude::SASLError> for Error {
} }
} }
impl From<peanuts::DeserializeError> for Error {
fn from(e: peanuts::DeserializeError) -> Self {
Error::Deserialization(e)
}
}
impl From<MechanismNameError> for Error { impl From<MechanismNameError> for Error {
fn from(e: MechanismNameError) -> Self { fn from(e: MechanismNameError) -> Self {
Self::SASL(SASLError::MechanismName(e)) Self::SASL(SASLError::MechanismName(e))

View File

@ -1,10 +1,8 @@
use std::pin::pin; use std::str;
use std::str::{self, FromStr};
use std::sync::Arc; use std::sync::Arc;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use futures::StreamExt; use peanuts::element::IntoElement;
use peanuts::element::{FromContent, IntoElement};
use peanuts::{Reader, Writer}; use peanuts::{Reader, Writer};
use rsasl::prelude::{Mechname, SASLClient, SASLConfig}; use rsasl::prelude::{Mechname, SASLClient, SASLConfig};
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf}; use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
@ -15,7 +13,6 @@ use crate::connection::{Tls, Unencrypted};
use crate::error::Error; use crate::error::Error;
use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType}; use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType};
use crate::stanza::client::iq::{Iq, IqType, Query}; use crate::stanza::client::iq::{Iq, IqType, Query};
use crate::stanza::client::Stanza;
use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse}; use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};
use crate::stanza::starttls::{Proceed, StartTls}; use crate::stanza::starttls::{Proceed, StartTls};
use crate::stanza::stream::{Feature, Features, Stream}; use crate::stanza::stream::{Feature, Features, Stream};
@ -23,34 +20,47 @@ use crate::stanza::XML_VERSION;
use crate::JID; use crate::JID;
use crate::{Connection, Result}; use crate::{Connection, Result};
// open stream (streams started) pub struct Jabber<S> {
pub struct JabberStream<S> {
reader: Reader<ReadHalf<S>>, reader: Reader<ReadHalf<S>>,
writer: Writer<WriteHalf<S>>, writer: Writer<WriteHalf<S>>,
jid: Option<JID>,
auth: Option<Arc<SASLConfig>>,
server: String,
} }
impl<S: AsyncRead> futures::Stream for JabberStream<S> { impl<S> Jabber<S>
type Item = Result<Stanza>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
pin!(self).reader.poll_next_unpin(cx).map(|content| {
content.map(|content| -> Result<Stanza> {
let stanza = content.map(|content| Stanza::from_content(content))?;
Ok(stanza?)
})
})
}
}
impl<S> JabberStream<S>
where where
S: AsyncRead + AsyncWrite + Unpin + Send + std::fmt::Debug, S: AsyncRead + AsyncWrite + Unpin,
JabberStream<S>: std::fmt::Debug,
{ {
pub async fn sasl(mut self, mechanisms: Mechanisms, sasl_config: Arc<SASLConfig>) -> Result<S> { pub fn new(
reader: ReadHalf<S>,
writer: WriteHalf<S>,
jid: Option<JID>,
auth: Option<Arc<SASLConfig>>,
server: String,
) -> Self {
let reader = Reader::new(reader);
let writer = Writer::new(writer);
Self {
reader,
writer,
jid,
auth,
server,
}
}
}
impl<S> Jabber<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send,
Jabber<S>: std::fmt::Debug,
{
pub async fn sasl(
&mut self,
mechanisms: Mechanisms,
sasl_config: Arc<SASLConfig>,
) -> Result<()> {
let sasl = SASLClient::new(sasl_config); let sasl = SASLClient::new(sasl_config);
let mut offered_mechs: Vec<&Mechname> = Vec::new(); let mut offered_mechs: Vec<&Mechname> = Vec::new();
for mechanism in &mechanisms.mechanisms { for mechanism in &mechanisms.mechanisms {
@ -133,15 +143,12 @@ where
} }
} }
} }
let writer = self.writer.into_inner(); Ok(())
let reader = self.reader.into_inner();
let stream = reader.unsplit(writer);
Ok(stream)
} }
pub async fn bind(mut self, jid: &mut JID) -> Result<Self> { pub async fn bind(&mut self) -> Result<()> {
let iq_id = nanoid::nanoid!(); let iq_id = nanoid::nanoid!();
if let Some(resource) = &jid.resourcepart { if let Some(resource) = self.jid.clone().unwrap().resourcepart {
let iq = Iq { let iq = Iq {
from: None, from: None,
id: iq_id.clone(), id: iq_id.clone(),
@ -149,7 +156,7 @@ where
r#type: IqType::Set, r#type: IqType::Set,
lang: None, lang: None,
query: Some(Query::Bind(Bind { query: Some(Query::Bind(Bind {
r#type: Some(BindType::Resource(ResourceType(resource.to_string()))), r#type: Some(BindType::Resource(ResourceType(resource))),
})), })),
errors: Vec::new(), errors: Vec::new(),
}; };
@ -164,12 +171,12 @@ where
lang: _, lang: _,
query: query:
Some(Query::Bind(Bind { Some(Query::Bind(Bind {
r#type: Some(BindType::Jid(FullJidType(new_jid))), r#type: Some(BindType::Jid(FullJidType(jid))),
})), })),
errors: _, errors: _,
} if id == iq_id => { } if id == iq_id => {
*jid = new_jid; self.jid = Some(jid);
return Ok(self); return Ok(());
} }
Iq { Iq {
from: _, from: _,
@ -207,12 +214,12 @@ where
lang: _, lang: _,
query: query:
Some(Query::Bind(Bind { Some(Query::Bind(Bind {
r#type: Some(BindType::Jid(FullJidType(new_jid))), r#type: Some(BindType::Jid(FullJidType(jid))),
})), })),
errors: _, errors: _,
} if id == iq_id => { } if id == iq_id => {
*jid = new_jid; self.jid = Some(jid);
return Ok(self); return Ok(());
} }
Iq { Iq {
from: _, from: _,
@ -233,44 +240,39 @@ where
} }
#[instrument] #[instrument]
pub async fn start_stream(connection: S, server: &mut String) -> Result<Self> { pub async fn start_stream(&mut self) -> Result<()> {
// client to server // client to server
let (reader, writer) = tokio::io::split(connection);
let mut reader = Reader::new(reader);
let mut writer = Writer::new(writer);
// declaration // declaration
writer.write_declaration(XML_VERSION).await?; self.writer.write_declaration(XML_VERSION).await?;
// opening stream element // opening stream element
let stream = Stream::new_client( let server = self.server.clone().try_into()?;
None, let stream = Stream::new_client(None, server, None, "en".to_string());
JID::from_str(server.as_ref())?, self.writer.write_start(&stream).await?;
None,
"en".to_string(),
);
writer.write_start(&stream).await?;
// server to client // server to client
// may or may not send a declaration // may or may not send a declaration
let _decl = reader.read_prolog().await?; let _decl = self.reader.read_prolog().await?;
// receive stream element and validate // receive stream element and validate
let stream: Stream = reader.read_start().await?; let text = str::from_utf8(self.reader.buffer.data()).unwrap();
debug!("data: {}", text);
let stream: Stream = self.reader.read_start().await?;
debug!("got stream: {:?}", stream); debug!("got stream: {:?}", stream);
if let Some(from) = stream.from { if let Some(from) = stream.from {
*server = from.to_string(); self.server = from.to_string()
} }
Ok(Self { reader, writer }) Ok(())
} }
pub async fn get_features(mut self) -> Result<(Features, Self)> { pub async fn get_features(&mut self) -> Result<Features> {
debug!("getting features"); debug!("getting features");
let features: Features = self.reader.read().await?; let features: Features = self.reader.read().await?;
debug!("got features: {:?}", features); debug!("got features: {:?}", features);
Ok((features, self)) Ok(features)
} }
pub fn into_inner(self) -> S { pub fn into_inner(self) -> S {
@ -278,89 +280,89 @@ where
} }
} }
impl JabberStream<Unencrypted> { impl Jabber<Unencrypted> {
// pub async fn negotiate<S: AsyncRead + AsyncWrite + Unpin>( pub async fn negotiate<S: AsyncRead + AsyncWrite + Unpin>(mut self) -> Result<Jabber<Tls>> {
// mut self, self.start_stream().await?;
// features: Features, // TODO: timeout
// ) -> Result<Feature> { let features = self.get_features().await?.features;
// // TODO: timeout if let Some(Feature::StartTls(_)) = features
// if let Some(Feature::StartTls(_)) = features .iter()
// .features .find(|feature| matches!(feature, Feature::StartTls(_s)))
// .iter() {
// .find(|feature| matches!(feature, Feature::StartTls(_s))) let jabber = self.starttls().await?;
// { let jabber = jabber.negotiate().await?;
// return Ok(self); return Ok(jabber);
// } else { } else {
// // TODO: better error // TODO: better error
// return Err(Error::TlsRequired); return Err(Error::TlsRequired);
// } }
// }
// #[async_recursion]
// pub async fn negotiate_tls_optional(mut self) -> Result<Connection> {
// self.start_stream().await?;
// // TODO: timeout
// let features = self.get_features().await?.features;
// if let Some(Feature::StartTls(_)) = features
// .iter()
// .find(|feature| matches!(feature, Feature::StartTls(_s)))
// {
// let jabber = self.starttls().await?;
// let jabber = jabber.negotiate().await?;
// return Ok(Connection::Encrypted(jabber));
// } else if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
// self.auth.clone(),
// features
// .iter()
// .find(|feature| matches!(feature, Feature::Sasl(_))),
// ) {
// self.sasl(mechanisms.clone(), sasl_config).await?;
// let jabber = self.negotiate_tls_optional().await?;
// Ok(jabber)
// } else if let Some(Feature::Bind) = features
// .iter()
// .find(|feature| matches!(feature, Feature::Bind))
// {
// self.bind().await?;
// Ok(Connection::Unencrypted(self))
// } else {
// // TODO: better error
// return Err(Error::Negotiation);
// }
// }
} }
impl JabberStream<Tls> { #[async_recursion]
// #[async_recursion] pub async fn negotiate_tls_optional(mut self) -> Result<Connection> {
// pub async fn negotiate(mut self) -> Result<JabberStream<Tls>> { self.start_stream().await?;
// self.start_stream().await?; // TODO: timeout
// let features = self.get_features().await?.features; let features = self.get_features().await?.features;
if let Some(Feature::StartTls(_)) = features
// if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = ( .iter()
// self.auth.clone(), .find(|feature| matches!(feature, Feature::StartTls(_s)))
// features {
// .iter() let jabber = self.starttls().await?;
// .find(|feature| matches!(feature, Feature::Sasl(_))), let jabber = jabber.negotiate().await?;
// ) { return Ok(Connection::Encrypted(jabber));
// // TODO: avoid clone } else if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
// self.sasl(mechanisms.clone(), sasl_config).await?; self.auth.clone(),
// let jabber = self.negotiate().await?; features
// Ok(jabber) .iter()
// } else if let Some(Feature::Bind) = features .find(|feature| matches!(feature, Feature::Sasl(_))),
// .iter() ) {
// .find(|feature| matches!(feature, Feature::Bind)) self.sasl(mechanisms.clone(), sasl_config).await?;
// { let jabber = self.negotiate_tls_optional().await?;
// self.bind().await?; Ok(jabber)
// Ok(self) } else if let Some(Feature::Bind) = features
// } else { .iter()
// // TODO: better error .find(|feature| matches!(feature, Feature::Bind))
// return Err(Error::Negotiation); {
// } self.bind().await?;
// } Ok(Connection::Unencrypted(self))
} else {
// TODO: better error
return Err(Error::Negotiation);
}
}
} }
impl JabberStream<Unencrypted> { impl Jabber<Tls> {
pub async fn starttls(mut self, domain: impl AsRef<str>) -> Result<Tls> { #[async_recursion]
pub async fn negotiate(mut self) -> Result<Jabber<Tls>> {
self.start_stream().await?;
let features = self.get_features().await?.features;
if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
self.auth.clone(),
features
.iter()
.find(|feature| matches!(feature, Feature::Sasl(_))),
) {
// TODO: avoid clone
self.sasl(mechanisms.clone(), sasl_config).await?;
let jabber = self.negotiate().await?;
Ok(jabber)
} else if let Some(Feature::Bind) = features
.iter()
.find(|feature| matches!(feature, Feature::Bind))
{
self.bind().await?;
Ok(self)
} else {
// TODO: better error
return Err(Error::Negotiation);
}
}
}
impl Jabber<Unencrypted> {
pub async fn starttls(mut self) -> Result<Jabber<Tls>> {
self.writer self.writer
.write_full(&StartTls { required: false }) .write_full(&StartTls { required: false })
.await?; .await?;
@ -368,31 +370,43 @@ impl JabberStream<Unencrypted> {
debug!("got proceed: {:?}", proceed); debug!("got proceed: {:?}", proceed);
let connector = TlsConnector::new().unwrap(); let connector = TlsConnector::new().unwrap();
let stream = self.reader.into_inner().unsplit(self.writer.into_inner()); let stream = self.reader.into_inner().unsplit(self.writer.into_inner());
if let Ok(tls_stream) = tokio_native_tls::TlsConnector::from(connector) if let Ok(tlsstream) = tokio_native_tls::TlsConnector::from(connector)
.connect(domain.as_ref(), stream) .connect(&self.server, stream)
.await .await
{ {
// let (read, write) = tokio::io::split(tlsstream); let (read, write) = tokio::io::split(tlsstream);
// let client = JabberStream::new(read, write); let client = Jabber::new(
return Ok(tls_stream); read,
write,
self.jid.to_owned(),
self.auth.to_owned(),
self.server.to_owned(),
);
return Ok(client);
} else { } else {
return Err(Error::Connection); return Err(Error::Connection);
} }
} }
} }
impl std::fmt::Debug for JabberStream<Tls> { impl std::fmt::Debug for Jabber<Tls> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Jabber") f.debug_struct("Jabber")
.field("connection", &"tls") .field("connection", &"tls")
.field("jid", &self.jid)
.field("auth", &self.auth)
.field("server", &self.server)
.finish() .finish()
} }
} }
impl std::fmt::Debug for JabberStream<Unencrypted> { impl std::fmt::Debug for Jabber<Unencrypted> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Jabber") f.debug_struct("Jabber")
.field("connection", &"unencrypted") .field("connection", &"unencrypted")
.field("jid", &self.jid)
.field("auth", &self.auth)
.field("server", &self.server)
.finish() .finish()
} }
} }
@ -408,61 +422,61 @@ mod tests {
#[test(tokio::test)] #[test(tokio::test)]
async fn start_stream() { async fn start_stream() {
// let connection = Connection::connect("blos.sm", None, None).await.unwrap(); let connection = Connection::connect("blos.sm", None, None).await.unwrap();
// match connection { match connection {
// Connection::Encrypted(mut c) => c.start_stream().await.unwrap(), Connection::Encrypted(mut c) => c.start_stream().await.unwrap(),
// Connection::Unencrypted(mut c) => c.start_stream().await.unwrap(), Connection::Unencrypted(mut c) => c.start_stream().await.unwrap(),
// } }
} }
#[test(tokio::test)] #[test(tokio::test)]
async fn sasl() { async fn sasl() {
// let mut jabber = Connection::connect_user("test@blos.sm", "slayed".to_string()) let mut jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
// .await .await
// .unwrap() .unwrap()
// .ensure_tls() .ensure_tls()
// .await .await
// .unwrap(); .unwrap();
// let text = str::from_utf8(jabber.reader.buffer.data()).unwrap(); let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
// println!("data: {}", text); println!("data: {}", text);
// jabber.start_stream().await.unwrap(); jabber.start_stream().await.unwrap();
// let text = str::from_utf8(jabber.reader.buffer.data()).unwrap(); let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
// println!("data: {}", text); println!("data: {}", text);
// jabber.reader.read_buf().await.unwrap(); jabber.reader.read_buf().await.unwrap();
// let text = str::from_utf8(jabber.reader.buffer.data()).unwrap(); let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
// println!("data: {}", text); println!("data: {}", text);
// let features = jabber.get_features().await.unwrap(); let features = jabber.get_features().await.unwrap();
// let (sasl_config, feature) = ( let (sasl_config, feature) = (
// jabber.auth.clone().unwrap(), jabber.auth.clone().unwrap(),
// features features
// .features .features
// .iter() .iter()
// .find(|feature| matches!(feature, Feature::Sasl(_))) .find(|feature| matches!(feature, Feature::Sasl(_)))
// .unwrap(), .unwrap(),
// ); );
// match feature { match feature {
// Feature::StartTls(_start_tls) => todo!(), Feature::StartTls(_start_tls) => todo!(),
// Feature::Sasl(mechanisms) => { Feature::Sasl(mechanisms) => {
// jabber.sasl(mechanisms.clone(), sasl_config).await.unwrap(); jabber.sasl(mechanisms.clone(), sasl_config).await.unwrap();
// } }
// Feature::Bind => todo!(), Feature::Bind => todo!(),
// Feature::Unknown => todo!(), Feature::Unknown => todo!(),
// } }
} }
#[tokio::test] #[tokio::test]
async fn negotiate() { async fn negotiate() {
// let _jabber = Connection::connect_user("test@blos.sm", "slayed".to_string()) let _jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
// .await .await
// .unwrap() .unwrap()
// .ensure_tls() .ensure_tls()
// .await .await
// .unwrap() .unwrap()
// .negotiate() .negotiate()
// .await .await
// .unwrap(); .unwrap();
// sleep(Duration::from_secs(5)).await sleep(Duration::from_secs(5)).await
} }
} }

View File

@ -2,7 +2,6 @@
// #![feature(let_chains)] // #![feature(let_chains)]
// TODO: logging (dropped errors) // TODO: logging (dropped errors)
pub mod client;
pub mod connection; pub mod connection;
pub mod error; pub mod error;
pub mod jabber; pub mod jabber;
@ -12,25 +11,24 @@ pub mod stanza;
pub use connection::Connection; pub use connection::Connection;
use connection::Tls; use connection::Tls;
pub use error::Error; pub use error::Error;
pub use jabber::JabberStream; pub use jabber::Jabber;
pub use jid::JID; pub use jid::JID;
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
pub async fn login<J: AsRef<str>, P: AsRef<str>>(jid: J, password: P) -> Result<JabberStream<Tls>> { pub async fn login<J: AsRef<str>, P: AsRef<str>>(jid: J, password: P) -> Result<Jabber<Tls>> {
todo!() Ok(Connection::connect_user(jid, password.as_ref().to_string())
// Ok(Connection::connect_user(jid, password.as_ref().to_string()) .await?
// .await? .ensure_tls()
// .ensure_tls() .await?
// .await? .negotiate()
// .negotiate() .await?)
// .await?)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// #[tokio::test] #[tokio::test]
// async fn test_login() { async fn test_login() {
// crate::login("test@blos.sm/clown", "slayed").await.unwrap(); crate::login("test@blos.sm/clown", "slayed").await.unwrap();
// } }
} }

View File

@ -1,7 +1,7 @@
use iq::Iq; use iq::Iq;
use message::Message; use message::Message;
use peanuts::{ use peanuts::{
element::{Content, ContentBuilder, FromContent, FromElement, IntoContent, IntoElement}, element::{FromElement, IntoElement},
DeserializeError, DeserializeError,
}; };
use presence::Presence; use presence::Presence;
@ -20,18 +20,6 @@ pub enum Stanza {
Presence(Presence), Presence(Presence),
Iq(Iq), Iq(Iq),
Error(StreamError), Error(StreamError),
OtherContent(Content),
}
impl FromContent for Stanza {
fn from_content(content: Content) -> peanuts::element::DeserializeResult<Self> {
match content {
Content::Element(element) => Ok(Stanza::from_element(element)?),
Content::Text(_) => Ok(Stanza::OtherContent(content)),
Content::PI => Ok(Stanza::OtherContent(content)),
Content::Comment(_) => Ok(Stanza::OtherContent(content)),
}
}
} }
impl FromElement for Stanza { impl FromElement for Stanza {
@ -48,14 +36,13 @@ impl FromElement for Stanza {
} }
} }
impl IntoContent for Stanza { impl IntoElement for Stanza {
fn builder(&self) -> peanuts::element::ContentBuilder { fn builder(&self) -> peanuts::element::ElementBuilder {
match self { match self {
Stanza::Message(message) => <Message as IntoContent>::builder(message), Stanza::Message(message) => message.builder(),
Stanza::Presence(presence) => <Presence as IntoContent>::builder(presence), Stanza::Presence(presence) => presence.builder(),
Stanza::Iq(iq) => <Iq as IntoContent>::builder(iq), Stanza::Iq(iq) => iq.builder(),
Stanza::Error(error) => <StreamError as IntoContent>::builder(error), Stanza::Error(error) => error.builder(),
Stanza::OtherContent(_content) => ContentBuilder::Comment("other-content".to_string()),
} }
} }
} }