Prepare for merge: Move all tokio-xmpp files into tokio-xmpp/

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2019-10-18 14:16:01 +02:00
commit 34aa710366
No known key found for this signature in database
GPG key ID: DEDA74AEECA9D0F2
23 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,116 @@
use std::str::FromStr;
use std::collections::HashSet;
use std::convert::TryFrom;
use futures::{Future, Poll, Stream, future::{ok, err, IntoFuture}};
use sasl::client::mechanisms::{Anonymous, Plain, Scram};
use sasl::client::Mechanism;
use sasl::common::scram::{Sha1, Sha256};
use sasl::common::Credentials;
use tokio_io::{AsyncRead, AsyncWrite};
use xmpp_parsers::sasl::{Auth, Challenge, Failure, Mechanism as XMPPMechanism, Response, Success};
use crate::xmpp_codec::Packet;
use crate::xmpp_stream::XMPPStream;
use crate::{AuthError, Error, ProtocolError};
const NS_XMPP_SASL: &str = "urn:ietf:params:xml:ns:xmpp-sasl";
pub struct ClientAuth<S: AsyncRead + AsyncWrite> {
future: Box<dyn Future<Item = XMPPStream<S>, Error = Error>>,
}
impl<S: AsyncRead + AsyncWrite + 'static> ClientAuth<S> {
pub fn new(stream: XMPPStream<S>, creds: Credentials) -> Result<Self, Error> {
let local_mechs: Vec<Box<dyn Fn() -> Box<dyn Mechanism>>> = vec![
Box::new(|| Box::new(Scram::<Sha256>::from_credentials(creds.clone()).unwrap())),
Box::new(|| Box::new(Scram::<Sha1>::from_credentials(creds.clone()).unwrap())),
Box::new(|| Box::new(Plain::from_credentials(creds.clone()).unwrap())),
Box::new(|| Box::new(Anonymous::new())),
];
let remote_mechs: HashSet<String> = stream
.stream_features
.get_child("mechanisms", NS_XMPP_SASL)
.ok_or(AuthError::NoMechanism)?
.children()
.filter(|child| child.is("mechanism", NS_XMPP_SASL))
.map(|mech_el| mech_el.text())
.collect();
for local_mech in local_mechs {
let mut mechanism = local_mech();
if remote_mechs.contains(mechanism.name()) {
let initial = mechanism.initial().map_err(AuthError::Sasl)?;
let mechanism_name = XMPPMechanism::from_str(mechanism.name()).map_err(ProtocolError::Parsers)?;
let send_initial = Box::new(stream.send_stanza(Auth {
mechanism: mechanism_name,
data: initial,
}))
.map_err(Error::Io);
let future = Box::new(send_initial.and_then(
|stream| Self::handle_challenge(stream, mechanism)
).and_then(
|stream| stream.restart()
));
return Ok(ClientAuth {
future,
});
}
}
Err(AuthError::NoMechanism)?
}
fn handle_challenge(stream: XMPPStream<S>, mut mechanism: Box<dyn Mechanism>) -> Box<dyn Future<Item = XMPPStream<S>, Error = Error>> {
Box::new(
stream.into_future()
.map_err(|(e, _stream)| e.into())
.and_then(|(stanza, stream)| {
match stanza {
Some(Packet::Stanza(stanza)) => {
if let Ok(challenge) = Challenge::try_from(stanza.clone()) {
let response = mechanism
.response(&challenge.data);
Box::new(
response
.map_err(|e| AuthError::Sasl(e).into())
.into_future()
.and_then(|response| {
// Send response and loop
stream.send_stanza(Response { data: response })
.map_err(Error::Io)
.and_then(|stream| Self::handle_challenge(stream, mechanism))
})
)
} else if let Ok(_) = Success::try_from(stanza.clone()) {
Box::new(ok(stream))
} else if let Ok(failure) = Failure::try_from(stanza.clone()) {
Box::new(err(Error::Auth(AuthError::Fail(failure.defined_condition))))
} else if stanza.name() == "failure" {
// Workaround for https://gitlab.com/xmpp-rs/xmpp-parsers/merge_requests/1
Box::new(err(Error::Auth(AuthError::Sasl("failure".to_string()))))
} else {
// ignore and loop
Self::handle_challenge(stream, mechanism)
}
}
Some(_) => {
// ignore and loop
Self::handle_challenge(stream, mechanism)
}
None => Box::new(err(Error::Disconnected))
}
})
)
}
}
impl<S: AsyncRead + AsyncWrite> Future for ClientAuth<S> {
type Item = XMPPStream<S>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.future.poll()
}
}

View file

@ -0,0 +1,102 @@
use futures::{sink, Async, Future, Poll, Stream};
use std::convert::TryFrom;
use std::mem::replace;
use tokio_io::{AsyncRead, AsyncWrite};
use xmpp_parsers::Jid;
use xmpp_parsers::bind::{BindQuery, BindResponse};
use xmpp_parsers::iq::{Iq, IqType};
use crate::xmpp_codec::Packet;
use crate::xmpp_stream::XMPPStream;
use crate::{Error, ProtocolError};
const NS_XMPP_BIND: &str = "urn:ietf:params:xml:ns:xmpp-bind";
const BIND_REQ_ID: &str = "resource-bind";
pub enum ClientBind<S: AsyncWrite> {
Unsupported(XMPPStream<S>),
WaitSend(sink::Send<XMPPStream<S>>),
WaitRecv(XMPPStream<S>),
Invalid,
}
impl<S: AsyncWrite> ClientBind<S> {
/// Consumes and returns the stream to express that you cannot use
/// the stream for anything else until the resource binding
/// req/resp are done.
pub fn new(stream: XMPPStream<S>) -> Self {
match stream.stream_features.get_child("bind", NS_XMPP_BIND) {
None =>
// No resource binding available,
// return the (probably // usable) stream immediately
{
ClientBind::Unsupported(stream)
}
Some(_) => {
let resource;
if let Jid::Full(jid) = stream.jid.clone() {
resource = Some(jid.resource);
} else {
resource = None;
}
let iq = Iq::from_set(BIND_REQ_ID, BindQuery::new(resource));
let send = stream.send_stanza(iq);
ClientBind::WaitSend(send)
}
}
}
}
impl<S: AsyncRead + AsyncWrite> Future for ClientBind<S> {
type Item = XMPPStream<S>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let state = replace(self, ClientBind::Invalid);
match state {
ClientBind::Unsupported(stream) => Ok(Async::Ready(stream)),
ClientBind::WaitSend(mut send) => match send.poll() {
Ok(Async::Ready(stream)) => {
replace(self, ClientBind::WaitRecv(stream));
self.poll()
}
Ok(Async::NotReady) => {
replace(self, ClientBind::WaitSend(send));
Ok(Async::NotReady)
}
Err(e) => Err(e)?,
},
ClientBind::WaitRecv(mut stream) => match stream.poll() {
Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => match Iq::try_from(stanza) {
Ok(iq) => {
if iq.id == BIND_REQ_ID {
match iq.payload {
IqType::Result(payload) => {
payload
.and_then(|payload| BindResponse::try_from(payload).ok())
.map(|bind| stream.jid = bind.into());
Ok(Async::Ready(stream))
}
_ => Err(ProtocolError::InvalidBindResponse)?,
}
} else {
Ok(Async::NotReady)
}
}
_ => Ok(Async::NotReady),
},
Ok(Async::Ready(_)) => {
replace(self, ClientBind::WaitRecv(stream));
self.poll()
}
Ok(Async::NotReady) => {
replace(self, ClientBind::WaitRecv(stream));
Ok(Async::NotReady)
}
Err(e) => Err(e)?,
},
ClientBind::Invalid => unreachable!(),
}
}
}

View file

@ -0,0 +1,236 @@
use futures::{done, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
use idna;
use xmpp_parsers::{Jid, JidParseError};
use sasl::common::{ChannelBinding, Credentials};
use std::mem::replace;
use std::str::FromStr;
use tokio::net::TcpStream;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tls::TlsStream;
use super::event::Event;
use super::happy_eyeballs::Connecter;
use super::starttls::{StartTlsClient, NS_XMPP_TLS};
use super::xmpp_codec::Packet;
use super::xmpp_stream;
use super::{Error, ProtocolError};
mod auth;
use self::auth::ClientAuth;
mod bind;
use self::bind::ClientBind;
/// XMPP client connection and state
pub struct Client {
state: ClientState,
}
type XMPPStream = xmpp_stream::XMPPStream<TlsStream<TcpStream>>;
const NS_JABBER_CLIENT: &str = "jabber:client";
enum ClientState {
Invalid,
Disconnected,
Connecting(Box<dyn Future<Item = XMPPStream, Error = Error>>),
Connected(XMPPStream),
}
impl Client {
/// Start a new XMPP client
///
/// Start polling the returned instance so that it will connect
/// and yield events.
pub fn new(jid: &str, password: &str) -> Result<Self, JidParseError> {
let jid = Jid::from_str(jid)?;
let client = Self::new_with_jid(jid, password);
Ok(client)
}
/// Start a new client given that the JID is already parsed.
pub fn new_with_jid(jid: Jid, password: &str) -> Self {
let password = password.to_owned();
let connect = Self::make_connect(jid, password.clone());
let client = Client {
state: ClientState::Connecting(Box::new(connect)),
};
client
}
fn make_connect(jid: Jid, password: String) -> impl Future<Item = XMPPStream, Error = Error> {
let username = jid.clone().node().unwrap();
let jid1 = jid.clone();
let jid2 = jid.clone();
let password = password;
done(idna::domain_to_ascii(&jid.domain()))
.map_err(|_| Error::Idna)
.and_then(|domain| {
done(Connecter::from_lookup(
&domain,
Some("_xmpp-client._tcp"),
5222,
))
})
.flatten()
.and_then(move |tcp_stream| {
xmpp_stream::XMPPStream::start(tcp_stream, jid1, NS_JABBER_CLIENT.to_owned())
})
.and_then(|xmpp_stream| {
if Self::can_starttls(&xmpp_stream) {
Ok(Self::starttls(xmpp_stream))
} else {
Err(Error::Protocol(ProtocolError::NoTls))
}
})
.flatten()
.and_then(|tls_stream| XMPPStream::start(tls_stream, jid2, NS_JABBER_CLIENT.to_owned()))
.and_then(
move |xmpp_stream| done(Self::auth(xmpp_stream, username, password)), // TODO: flatten?
)
.and_then(|auth| auth)
.and_then(|xmpp_stream| Self::bind(xmpp_stream))
.and_then(|xmpp_stream| {
// println!("Bound to {}", xmpp_stream.jid);
Ok(xmpp_stream)
})
}
fn can_starttls<S>(stream: &xmpp_stream::XMPPStream<S>) -> bool {
stream
.stream_features
.get_child("starttls", NS_XMPP_TLS)
.is_some()
}
fn starttls<S: AsyncRead + AsyncWrite>(
stream: xmpp_stream::XMPPStream<S>,
) -> StartTlsClient<S> {
StartTlsClient::from_stream(stream)
}
fn auth<S: AsyncRead + AsyncWrite + 'static>(
stream: xmpp_stream::XMPPStream<S>,
username: String,
password: String,
) -> Result<ClientAuth<S>, Error> {
let creds = Credentials::default()
.with_username(username)
.with_password(password)
.with_channel_binding(ChannelBinding::None);
ClientAuth::new(stream, creds)
}
fn bind<S: AsyncWrite>(stream: xmpp_stream::XMPPStream<S>) -> ClientBind<S> {
ClientBind::new(stream)
}
/// Get the client's bound JID (the one reported by the XMPP
/// server).
pub fn bound_jid(&self) -> Option<&Jid> {
match self.state {
ClientState::Connected(ref stream) => Some(&stream.jid),
_ => None,
}
}
}
impl Stream for Client {
type Item = Event;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let state = replace(&mut self.state, ClientState::Invalid);
match state {
ClientState::Invalid => Err(Error::InvalidState),
ClientState::Disconnected => Ok(Async::Ready(None)),
ClientState::Connecting(mut connect) => match connect.poll() {
Ok(Async::Ready(stream)) => {
let jid = stream.jid.clone();
self.state = ClientState::Connected(stream);
Ok(Async::Ready(Some(Event::Online(jid))))
}
Ok(Async::NotReady) => {
self.state = ClientState::Connecting(connect);
Ok(Async::NotReady)
}
Err(e) => Err(e),
},
ClientState::Connected(mut stream) => {
// Poll sink
match stream.poll_complete() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(())) => (),
Err(e) => return Err(e)?,
};
// Poll stream
match stream.poll() {
Ok(Async::Ready(None)) => {
// EOF
self.state = ClientState::Disconnected;
Ok(Async::Ready(Some(Event::Disconnected)))
}
Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => {
// Receive stanza
self.state = ClientState::Connected(stream);
Ok(Async::Ready(Some(Event::Stanza(stanza))))
}
Ok(Async::Ready(Some(Packet::Text(_)))) => {
// Ignore text between stanzas
Ok(Async::NotReady)
}
Ok(Async::Ready(Some(Packet::StreamStart(_)))) => {
// <stream:stream>
Err(ProtocolError::InvalidStreamStart.into())
}
Ok(Async::Ready(Some(Packet::StreamEnd))) => {
// End of stream: </stream:stream>
Ok(Async::Ready(None))
}
Ok(Async::NotReady) => {
// Try again later
self.state = ClientState::Connected(stream);
Ok(Async::NotReady)
}
Err(e) => Err(e)?,
}
}
}
}
}
impl Sink for Client {
type SinkItem = Packet;
type SinkError = Error;
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
match self.state {
ClientState::Connected(ref mut stream) =>
Ok(stream.start_send(item)?),
_ =>
Ok(AsyncSink::NotReady(item)),
}
}
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
match self.state {
ClientState::Connected(ref mut stream) => stream.poll_complete().map_err(|e| e.into()),
_ => Ok(Async::Ready(())),
}
}
/// This closes the inner TCP stream.
///
/// To synchronize your shutdown with the server side, you should
/// first send `Packet::StreamEnd` and wait for the end of the
/// incoming stream before closing the connection.
fn close(&mut self) -> Poll<(), Self::SinkError> {
match self.state {
ClientState::Connected(ref mut stream) =>
stream.close()
.map_err(|e| e.into()),
_ =>
Ok(Async::Ready(())),
}
}
}