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