client.rs

  1use jid::Jid;
  2use transport::{Transport, SslTransport};
  3use error::Error;
  4use ns;
  5use plugin::{Plugin, PluginProxyBinding};
  6use event::AbstractEvent;
  7use connection::{Connection, C2S};
  8use sasl::{SaslMechanism, SaslCredentials, SaslSecret};
  9use sasl::mechanisms::{Plain, Scram, Sha1, Sha256};
 10
 11use base64;
 12
 13use minidom::Element;
 14
 15use xml::reader::XmlEvent as ReaderEvent;
 16
 17use std::sync::mpsc::{Receiver, channel};
 18
 19use std::collections::HashSet;
 20
 21/// Struct that should be moved somewhere else and cleaned up.
 22#[derive(Debug)]
 23pub struct StreamFeatures {
 24    pub sasl_mechanisms: Option<HashSet<String>>,
 25}
 26
 27/// A builder for `Client`s.
 28pub struct ClientBuilder {
 29    jid: Jid,
 30    credentials: Option<SaslCredentials>,
 31    host: Option<String>,
 32    port: u16,
 33}
 34
 35impl ClientBuilder {
 36    /// Creates a new builder for an XMPP client that will connect to `jid` with default parameters.
 37    pub fn new(jid: Jid) -> ClientBuilder {
 38        ClientBuilder {
 39            jid: jid,
 40            credentials: None,
 41            host: None,
 42            port: 5222,
 43        }
 44    }
 45
 46    /// Sets the host to connect to.
 47    pub fn host(mut self, host: String) -> ClientBuilder {
 48        self.host = Some(host);
 49        self
 50    }
 51
 52    /// Sets the port to connect to.
 53    pub fn port(mut self, port: u16) -> ClientBuilder {
 54        self.port = port;
 55        self
 56    }
 57
 58    /// Sets the password to use.
 59    pub fn password<P: Into<String>>(mut self, password: P) -> ClientBuilder {
 60        self.credentials = Some(SaslCredentials {
 61            username: self.jid.node.clone().expect("JID has no node"),
 62            secret: SaslSecret::Password(password.into()),
 63            channel_binding: None,
 64        });
 65        self
 66    }
 67
 68    /// Connects to the server and returns a `Client` when succesful.
 69    pub fn connect(self) -> Result<Client, Error> {
 70        let host = &self.host.unwrap_or(self.jid.domain.clone());
 71        let mut transport = SslTransport::connect(host, self.port)?;
 72        C2S::init(&mut transport, &self.jid.domain, "before_sasl")?;
 73        let (sender_out, sender_in) = channel();
 74        let (dispatcher_out, dispatcher_in) = channel();
 75        let mut credentials = self.credentials.expect("can't connect without credentials");
 76        credentials.channel_binding = transport.channel_bind();
 77        let mut client = Client {
 78            jid: self.jid,
 79            transport: transport,
 80            plugins: Vec::new(),
 81            binding: PluginProxyBinding::new(sender_out, dispatcher_out),
 82            sender_in: sender_in,
 83            dispatcher_in: dispatcher_in,
 84        };
 85        client.connect(credentials)?;
 86        client.bind()?;
 87        Ok(client)
 88    }
 89}
 90
 91/// An XMPP client.
 92pub struct Client {
 93    jid: Jid,
 94    transport: SslTransport,
 95    plugins: Vec<Box<Plugin>>,
 96    binding: PluginProxyBinding,
 97    sender_in: Receiver<Element>,
 98    dispatcher_in: Receiver<AbstractEvent>,
 99}
100
101impl Client {
102    /// Returns a reference to the `Jid` associated with this `Client`.
103    pub fn jid(&self) -> &Jid {
104        &self.jid
105    }
106
107    /// Registers a plugin.
108    pub fn register_plugin<P: Plugin + 'static>(&mut self, mut plugin: P) {
109        plugin.bind(self.binding.clone());
110        self.plugins.push(Box::new(plugin));
111    }
112
113    /// Returns the plugin given by the type parameter, if it exists, else panics.
114    pub fn plugin<P: Plugin>(&self) -> &P {
115        for plugin in &self.plugins {
116            let any = plugin.as_any();
117            if let Some(ret) = any.downcast_ref::<P>() {
118                return ret;
119            }
120        }
121        panic!("plugin does not exist!");
122    }
123
124    /// Returns the next event and flush the send queue.
125    pub fn next_event(&mut self) -> Result<AbstractEvent, Error> {
126        self.flush_send_queue()?;
127        loop {
128            if let Ok(evt) = self.dispatcher_in.try_recv() {
129                return Ok(evt);
130            }
131            let elem = self.transport.read_element()?;
132            for plugin in self.plugins.iter_mut() {
133                plugin.handle(&elem);
134                // TODO: handle plugin return
135            }
136            self.flush_send_queue()?;
137        }
138    }
139
140    /// Flushes the send queue, sending all queued up stanzas.
141    pub fn flush_send_queue(&mut self) -> Result<(), Error> { // TODO: not sure how great of an
142                                                              //       idea it is to flush in this
143                                                              //       manner…
144        while let Ok(elem) = self.sender_in.try_recv() {
145            self.transport.write_element(&elem)?;
146        }
147        Ok(())
148    }
149
150    fn connect(&mut self, credentials: SaslCredentials) -> Result<(), Error> {
151        let features = self.wait_for_features()?;
152        let ms = &features.sasl_mechanisms.ok_or(Error::SaslError(Some("no SASL mechanisms".to_owned())))?;
153        fn wrap_err(err: String) -> Error { Error::SaslError(Some(err)) }
154        // TODO: better way for selecting these, enabling anonymous auth
155        let mut mechanism: Box<SaslMechanism> = if ms.contains("SCRAM-SHA-256") {
156            Box::new(Scram::<Sha256>::from_credentials(credentials).map_err(wrap_err)?)
157        }
158        else if ms.contains("SCRAM-SHA-1") {
159            Box::new(Scram::<Sha1>::from_credentials(credentials).map_err(wrap_err)?)
160        }
161        else if ms.contains("PLAIN") {
162            Box::new(Plain::from_credentials(credentials).map_err(wrap_err)?)
163        }
164        else {
165            return Err(Error::SaslError(Some("can't find a SASL mechanism to use".to_owned())));
166        };
167        let auth = mechanism.initial().map_err(|x| Error::SaslError(Some(x)))?;
168        let mut elem = Element::builder("auth")
169                               .ns(ns::SASL)
170                               .attr("mechanism", mechanism.name())
171                               .build();
172        if !auth.is_empty() {
173            elem.append_text_node(base64::encode(&auth));
174        }
175        self.transport.write_element(&elem)?;
176        loop {
177            let n = self.transport.read_element()?;
178            if n.is("challenge", ns::SASL) {
179                let text = n.text();
180                let challenge = if text == "" {
181                    Vec::new()
182                }
183                else {
184                    base64::decode(&text)?
185                };
186                let response = mechanism.response(&challenge).map_err(|x| Error::SaslError(Some(x)))?;
187                let mut elem = Element::builder("response")
188                                       .ns(ns::SASL)
189                                       .build();
190                if !response.is_empty() {
191                    elem.append_text_node(base64::encode(&response));
192                }
193                self.transport.write_element(&elem)?;
194            }
195            else if n.is("success", ns::SASL) {
196                let text = n.text();
197                let data = if text == "" {
198                    Vec::new()
199                }
200                else {
201                    base64::decode(&text)?
202                };
203                mechanism.success(&data).map_err(|x| Error::SaslError(Some(x)))?;
204                self.transport.reset_stream();
205                C2S::init(&mut self.transport, &self.jid.domain, "after_sasl")?;
206                self.wait_for_features()?;
207                return Ok(());
208            }
209            else if n.is("failure", ns::SASL) {
210                let msg = n.text();
211                let inner = if msg == "" { None } else { Some(msg) };
212                return Err(Error::SaslError(inner));
213            }
214        }
215    }
216
217    fn bind(&mut self) -> Result<(), Error> {
218        let mut elem = Element::builder("iq")
219                               .attr("id", "bind")
220                               .attr("type", "set")
221                               .build();
222        let mut bind = Element::builder("bind")
223                               .ns(ns::BIND)
224                               .build();
225        if let Some(ref resource) = self.jid.resource {
226            let res = Element::builder("resource")
227                              .ns(ns::BIND)
228                              .text(resource.to_owned())
229                              .build();
230            bind.append_child(res);
231        }
232        elem.append_child(bind);
233        self.transport.write_element(&elem)?;
234        loop {
235            let n = self.transport.read_element()?;
236            if n.is("iq", ns::CLIENT) && n.has_child("bind", ns::BIND) {
237                return Ok(());
238            }
239        }
240    }
241
242    fn wait_for_features(&mut self) -> Result<StreamFeatures, Error> {
243        // TODO: this is very ugly
244        loop {
245            let e = self.transport.read_event()?;
246            match e {
247                ReaderEvent::StartElement { .. } => {
248                    break;
249                },
250                _ => (),
251            }
252        }
253        loop {
254            let n = self.transport.read_element()?;
255            if n.is("features", ns::STREAM) {
256                let mut features = StreamFeatures {
257                    sasl_mechanisms: None,
258                };
259                if let Some(ms) = n.get_child("mechanisms", ns::SASL) {
260                    let mut res = HashSet::new();
261                    for cld in ms.children() {
262                        res.insert(cld.text());
263                    }
264                    features.sasl_mechanisms = Some(res);
265                }
266                return Ok(features);
267            }
268        }
269    }
270}