update to sasl-rs 0.3.0, process error stanzas
This commit is contained in:
parent
7692c262c8
commit
9d59bd2f7e
9 changed files with 305 additions and 14 deletions
|
|
@ -20,4 +20,7 @@ openssl = "0.9.7"
|
|||
base64 = "0.4.0"
|
||||
minidom = "0.1.0"
|
||||
jid = "0.1.0"
|
||||
sasl = "0.1.0"
|
||||
sasl = "0.3.0"
|
||||
|
||||
[features]
|
||||
insecure = []
|
||||
|
|
|
|||
|
|
@ -5,8 +5,14 @@ use ns;
|
|||
use plugin::{Plugin, PluginProxyBinding};
|
||||
use event::AbstractEvent;
|
||||
use connection::{Connection, C2S};
|
||||
use sasl::{SaslMechanism, SaslCredentials, SaslSecret};
|
||||
use sasl::{ Mechanism as SaslMechanism
|
||||
, Credentials as SaslCredentials
|
||||
, Secret as SaslSecret
|
||||
, ChannelBinding
|
||||
};
|
||||
use sasl::mechanisms::{Plain, Scram, Sha1, Sha256};
|
||||
use components::sasl_error::SaslError;
|
||||
use util::FromElement;
|
||||
|
||||
use base64;
|
||||
|
||||
|
|
@ -27,7 +33,7 @@ pub struct StreamFeatures {
|
|||
/// A builder for `Client`s.
|
||||
pub struct ClientBuilder {
|
||||
jid: Jid,
|
||||
credentials: Option<SaslCredentials>,
|
||||
credentials: SaslCredentials,
|
||||
host: Option<String>,
|
||||
port: u16,
|
||||
}
|
||||
|
|
@ -37,7 +43,7 @@ impl ClientBuilder {
|
|||
pub fn new(jid: Jid) -> ClientBuilder {
|
||||
ClientBuilder {
|
||||
jid: jid,
|
||||
credentials: None,
|
||||
credentials: SaslCredentials::default(),
|
||||
host: None,
|
||||
port: 5222,
|
||||
}
|
||||
|
|
@ -57,11 +63,11 @@ impl ClientBuilder {
|
|||
|
||||
/// Sets the password to use.
|
||||
pub fn password<P: Into<String>>(mut self, password: P) -> ClientBuilder {
|
||||
self.credentials = Some(SaslCredentials {
|
||||
username: self.jid.node.clone().expect("JID has no node"),
|
||||
self.credentials = SaslCredentials {
|
||||
username: Some(self.jid.node.clone().expect("JID has no node")),
|
||||
secret: SaslSecret::Password(password.into()),
|
||||
channel_binding: None,
|
||||
});
|
||||
channel_binding: ChannelBinding::None,
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +78,7 @@ impl ClientBuilder {
|
|||
C2S::init(&mut transport, &self.jid.domain, "before_sasl")?;
|
||||
let (sender_out, sender_in) = channel();
|
||||
let (dispatcher_out, dispatcher_in) = channel();
|
||||
let mut credentials = self.credentials.expect("can't connect without credentials");
|
||||
let mut credentials = self.credentials;
|
||||
credentials.channel_binding = transport.channel_bind();
|
||||
let mut client = Client {
|
||||
jid: self.jid,
|
||||
|
|
@ -147,15 +153,23 @@ impl Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn connect(&mut self, credentials: SaslCredentials) -> Result<(), Error> {
|
||||
fn connect(&mut self, mut credentials: SaslCredentials) -> Result<(), Error> {
|
||||
let features = self.wait_for_features()?;
|
||||
let ms = &features.sasl_mechanisms.ok_or(Error::SaslError(Some("no SASL mechanisms".to_owned())))?;
|
||||
fn wrap_err(err: String) -> Error { Error::SaslError(Some(err)) }
|
||||
// TODO: better way for selecting these, enabling anonymous auth
|
||||
let mut mechanism: Box<SaslMechanism> = if ms.contains("SCRAM-SHA-256") {
|
||||
let mut mechanism: Box<SaslMechanism> = if ms.contains("SCRAM-SHA-256-PLUS") {
|
||||
Box::new(Scram::<Sha256>::from_credentials(credentials).map_err(wrap_err)?)
|
||||
}
|
||||
else if ms.contains("SCRAM-SHA-1-PLUS") {
|
||||
Box::new(Scram::<Sha1>::from_credentials(credentials).map_err(wrap_err)?)
|
||||
}
|
||||
else if ms.contains("SCRAM-SHA-256") {
|
||||
credentials.channel_binding = ChannelBinding::Unsupported;
|
||||
Box::new(Scram::<Sha256>::from_credentials(credentials).map_err(wrap_err)?)
|
||||
}
|
||||
else if ms.contains("SCRAM-SHA-1") {
|
||||
credentials.channel_binding = ChannelBinding::Unsupported;
|
||||
Box::new(Scram::<Sha1>::from_credentials(credentials).map_err(wrap_err)?)
|
||||
}
|
||||
else if ms.contains("PLAIN") {
|
||||
|
|
@ -207,9 +221,8 @@ impl Client {
|
|||
return Ok(());
|
||||
}
|
||||
else if n.is("failure", ns::SASL) {
|
||||
let msg = n.text();
|
||||
let inner = if msg == "" { None } else { Some(msg) };
|
||||
return Err(Error::SaslError(inner));
|
||||
let inner = SaslError::from_element(&n).map_err(|_| Error::SaslError(None))?;
|
||||
return Err(Error::XmppSaslError(inner));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/components/mod.rs
Normal file
3
src/components/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod stanza_error;
|
||||
pub mod sasl_error;
|
||||
//pub mod stream_error;
|
||||
75
src/components/sasl_error.rs
Normal file
75
src/components/sasl_error.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use ns;
|
||||
use minidom::Element;
|
||||
use util::FromElement;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Condition {
|
||||
Aborted,
|
||||
AccountDisabled(Option<String>),
|
||||
CredentialsExpired,
|
||||
EncryptionRequired,
|
||||
IncorrectEncoding,
|
||||
InvalidAuthzid,
|
||||
InvalidMechanism,
|
||||
MalformedRequest,
|
||||
MechanismTooWeak,
|
||||
NotAuthorized,
|
||||
TemporaryAuthFailure,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SaslError {
|
||||
condition: Condition,
|
||||
text: Option<String>,
|
||||
}
|
||||
|
||||
impl FromElement for SaslError {
|
||||
type Err = ();
|
||||
|
||||
fn from_element(element: &Element) -> Result<SaslError, ()> {
|
||||
if !element.is("failure", ns::SASL) {
|
||||
return Err(());
|
||||
}
|
||||
let mut err = SaslError {
|
||||
condition: Condition::Unknown,
|
||||
text: None,
|
||||
};
|
||||
if let Some(text) = element.get_child("text", ns::SASL) {
|
||||
let desc = text.text();
|
||||
err.text = Some(desc);
|
||||
}
|
||||
if element.has_child("aborted", ns::SASL) {
|
||||
err.condition = Condition::Aborted;
|
||||
}
|
||||
else if let Some(account_disabled) = element.get_child("account-disabled", ns::SASL) {
|
||||
let text = account_disabled.text();
|
||||
err.condition = Condition::AccountDisabled(if text == "" { None } else { Some(text) });
|
||||
}
|
||||
else if element.has_child("credentials-expired", ns::SASL) {
|
||||
err.condition = Condition::CredentialsExpired;
|
||||
}
|
||||
else if element.has_child("encryption-required", ns::SASL) {
|
||||
err.condition = Condition::EncryptionRequired;
|
||||
}
|
||||
else if element.has_child("incorrect-encoding", ns::SASL) {
|
||||
err.condition = Condition::IncorrectEncoding;
|
||||
}
|
||||
else if element.has_child("invalid-authzid", ns::SASL) {
|
||||
err.condition = Condition::InvalidAuthzid;
|
||||
}
|
||||
else if element.has_child("malformed-request", ns::SASL) {
|
||||
err.condition = Condition::MalformedRequest;
|
||||
}
|
||||
else if element.has_child("mechanism-too-weak", ns::SASL) {
|
||||
err.condition = Condition::MechanismTooWeak;
|
||||
}
|
||||
else if element.has_child("not-authorized", ns::SASL) {
|
||||
err.condition = Condition::NotAuthorized;
|
||||
}
|
||||
else if element.has_child("temporary-auth-failure", ns::SASL) {
|
||||
err.condition = Condition::TemporaryAuthFailure;
|
||||
}
|
||||
Ok(err)
|
||||
}
|
||||
}
|
||||
172
src/components/stanza_error.rs
Normal file
172
src/components/stanza_error.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
use ns;
|
||||
use minidom::Element;
|
||||
use util::{FromElement, FromParentElement};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ErrorType {
|
||||
Auth,
|
||||
Cancel,
|
||||
Continue,
|
||||
Modify,
|
||||
Wait,
|
||||
}
|
||||
|
||||
impl FromStr for ErrorType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<ErrorType, ()> {
|
||||
Ok(match s {
|
||||
"auth" => ErrorType::Auth,
|
||||
"cancel" => ErrorType::Cancel,
|
||||
"continue" => ErrorType::Continue,
|
||||
"modify" => ErrorType::Modify,
|
||||
"wait" => ErrorType::Wait,
|
||||
_ => { return Err(()); },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Condition {
|
||||
BadRequest,
|
||||
Conflict,
|
||||
FeatureNotImplemented,
|
||||
Forbidden,
|
||||
Gone(Option<String>),
|
||||
InternalServerError,
|
||||
ItemNotFound,
|
||||
JidMalformed,
|
||||
NotAcceptable,
|
||||
NotAllowed,
|
||||
NotAuthorized,
|
||||
PolicyViolation,
|
||||
RecipientUnavailable,
|
||||
Redirect(Option<String>),
|
||||
RegistrationRequired,
|
||||
RemoteServerNotFound,
|
||||
RemoteServerTimeout,
|
||||
ResourceConstraint,
|
||||
ServiceUnavailable,
|
||||
SubscriptionRequired,
|
||||
UndefinedCondition,
|
||||
UnexpectedRequest,
|
||||
}
|
||||
|
||||
impl FromParentElement for Condition {
|
||||
type Err = ();
|
||||
|
||||
fn from_parent_element(elem: &Element) -> Result<Condition, ()> {
|
||||
if elem.has_child("bad-request", ns::STANZAS) {
|
||||
Ok(Condition::BadRequest)
|
||||
}
|
||||
else if elem.has_child("conflict", ns::STANZAS) {
|
||||
Ok(Condition::Conflict)
|
||||
}
|
||||
else if elem.has_child("feature-not-implemented", ns::STANZAS) {
|
||||
Ok(Condition::FeatureNotImplemented)
|
||||
}
|
||||
else if elem.has_child("forbidden", ns::STANZAS) {
|
||||
Ok(Condition::Forbidden)
|
||||
}
|
||||
else if let Some(alt) = elem.get_child("gone", ns::STANZAS) {
|
||||
let text = alt.text();
|
||||
let inner = if text == "" { None } else { Some(text) };
|
||||
Ok(Condition::Gone(inner))
|
||||
}
|
||||
else if elem.has_child("internal-server-error", ns::STANZAS) {
|
||||
Ok(Condition::InternalServerError)
|
||||
}
|
||||
else if elem.has_child("item-not-found", ns::STANZAS) {
|
||||
Ok(Condition::ItemNotFound)
|
||||
}
|
||||
else if elem.has_child("jid-malformed", ns::STANZAS) {
|
||||
Ok(Condition::JidMalformed)
|
||||
}
|
||||
else if elem.has_child("not-acceptable", ns::STANZAS) {
|
||||
Ok(Condition::NotAcceptable)
|
||||
}
|
||||
else if elem.has_child("not-allowed", ns::STANZAS) {
|
||||
Ok(Condition::NotAllowed)
|
||||
}
|
||||
else if elem.has_child("not-authorized", ns::STANZAS) {
|
||||
Ok(Condition::NotAuthorized)
|
||||
}
|
||||
else if elem.has_child("policy-violation", ns::STANZAS) {
|
||||
Ok(Condition::PolicyViolation)
|
||||
}
|
||||
else if elem.has_child("recipient-unavailable", ns::STANZAS) {
|
||||
Ok(Condition::RecipientUnavailable)
|
||||
}
|
||||
else if let Some(alt) = elem.get_child("redirect", ns::STANZAS) {
|
||||
let text = alt.text();
|
||||
let inner = if text == "" { None } else { Some(text) };
|
||||
Ok(Condition::Redirect(inner))
|
||||
}
|
||||
else if elem.has_child("registration-required", ns::STANZAS) {
|
||||
Ok(Condition::RegistrationRequired)
|
||||
}
|
||||
else if elem.has_child("remote-server-not-found", ns::STANZAS) {
|
||||
Ok(Condition::RemoteServerNotFound)
|
||||
}
|
||||
else if elem.has_child("remote-server-timeout", ns::STANZAS) {
|
||||
Ok(Condition::RemoteServerTimeout)
|
||||
}
|
||||
else if elem.has_child("resource-constraint", ns::STANZAS) {
|
||||
Ok(Condition::ResourceConstraint)
|
||||
}
|
||||
else if elem.has_child("service-unavailable", ns::STANZAS) {
|
||||
Ok(Condition::ServiceUnavailable)
|
||||
}
|
||||
else if elem.has_child("subscription-required", ns::STANZAS) {
|
||||
Ok(Condition::SubscriptionRequired)
|
||||
}
|
||||
else if elem.has_child("undefined-condition", ns::STANZAS) {
|
||||
Ok(Condition::UndefinedCondition)
|
||||
}
|
||||
else if elem.has_child("unexpected-request", ns::STANZAS) {
|
||||
Ok(Condition::UnexpectedRequest)
|
||||
}
|
||||
else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StanzaError {
|
||||
error_type: ErrorType,
|
||||
text: Option<String>,
|
||||
condition: Condition,
|
||||
}
|
||||
|
||||
impl StanzaError {
|
||||
pub fn new(error_type: ErrorType, text: Option<String>, condition: Condition) -> StanzaError {
|
||||
StanzaError {
|
||||
error_type: error_type,
|
||||
text: text,
|
||||
condition: condition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromElement for StanzaError {
|
||||
type Err = ();
|
||||
|
||||
fn from_element(elem: &Element) -> Result<StanzaError, ()> {
|
||||
if elem.is("error", ns::STANZAS) {
|
||||
let error_type = elem.attr("type").ok_or(())?;
|
||||
let err: ErrorType = error_type.parse().map_err(|_| ())?;
|
||||
let condition: Condition = Condition::from_parent_element(elem)?;
|
||||
let text = elem.get_child("text", ns::STANZAS).map(|c| c.text());
|
||||
Ok(StanzaError {
|
||||
error_type: err,
|
||||
text: text,
|
||||
condition: condition,
|
||||
})
|
||||
}
|
||||
else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ use minidom::Error as MinidomError;
|
|||
|
||||
use base64::Base64Error;
|
||||
|
||||
use components::sasl_error::SaslError;
|
||||
|
||||
/// An error which wraps a bunch of errors from different crates and the stdlib.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
|
@ -25,6 +27,7 @@ pub enum Error {
|
|||
MinidomError(MinidomError),
|
||||
Base64Error(Base64Error),
|
||||
SaslError(Option<String>),
|
||||
XmppSaslError(SaslError),
|
||||
StreamError,
|
||||
EndOfDocument,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,7 @@ pub mod plugin;
|
|||
pub mod event;
|
||||
pub mod plugins;
|
||||
pub mod connection;
|
||||
pub mod util;
|
||||
pub mod components;
|
||||
|
||||
mod locked_io;
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ pub const STREAM: &'static str = "http://etherx.jabber.org/streams";
|
|||
pub const TLS: &'static str = "urn:ietf:params:xml:ns:xmpp-tls";
|
||||
pub const SASL: &'static str = "urn:ietf:params:xml:ns:xmpp-sasl";
|
||||
pub const BIND: &'static str = "urn:ietf:params:xml:ns:xmpp-bind";
|
||||
pub const STANZAS: &'static str = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||
|
|
|
|||
19
src/util.rs
Normal file
19
src/util.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use minidom::Element;
|
||||
|
||||
pub trait FromElement where Self: Sized {
|
||||
type Err;
|
||||
|
||||
fn from_element(elem: &Element) -> Result<Self, Self::Err>;
|
||||
}
|
||||
|
||||
pub trait FromParentElement where Self: Sized {
|
||||
type Err;
|
||||
|
||||
fn from_parent_element(elem: &Element) -> Result<Self, Self::Err>;
|
||||
}
|
||||
|
||||
pub trait ToElement where Self: Sized {
|
||||
type Err;
|
||||
|
||||
fn to_element(&self) -> Result<Element, Self::Err>;
|
||||
}
|
||||
Loading…
Reference in a new issue