1/*
2 *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/libjingle/xmpp/xmppengineimpl.h"
12
13#include <algorithm>
14#include <sstream>
15#include <vector>
16
17#include "webrtc/libjingle/xmllite/xmlelement.h"
18#include "webrtc/libjingle/xmllite/xmlprinter.h"
19#include "webrtc/libjingle/xmpp/constants.h"
20#include "webrtc/libjingle/xmpp/saslhandler.h"
21#include "webrtc/libjingle/xmpp/xmpplogintask.h"
22#include "webrtc/base/common.h"
23
24namespace buzz {
25
26XmppEngine* XmppEngine::Create() {
27  return new XmppEngineImpl();
28}
29
30
31XmppEngineImpl::XmppEngineImpl()
32    : stanza_parse_handler_(this),
33      stanza_parser_(&stanza_parse_handler_),
34      engine_entered_(0),
35      password_(),
36      requested_resource_(STR_EMPTY),
37      tls_option_(buzz::TLS_REQUIRED),
38      login_task_(new XmppLoginTask(this)),
39      next_id_(0),
40      state_(STATE_START),
41      encrypted_(false),
42      error_code_(ERROR_NONE),
43      subcode_(0),
44      stream_error_(),
45      raised_reset_(false),
46      output_handler_(NULL),
47      session_handler_(NULL),
48      iq_entries_(new IqEntryVector()),
49      sasl_handler_(),
50      output_(new std::stringstream()) {
51  for (int i = 0; i < HL_COUNT; i+= 1) {
52    stanza_handlers_[i].reset(new StanzaHandlerVector());
53  }
54
55  // Add XMPP namespaces to XML namespaces stack.
56  xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams");
57  xmlns_stack_.AddXmlns("", "jabber:client");
58}
59
60XmppEngineImpl::~XmppEngineImpl() {
61  DeleteIqCookies();
62}
63
64XmppReturnStatus XmppEngineImpl::SetOutputHandler(
65    XmppOutputHandler* output_handler) {
66  if (state_ != STATE_START)
67    return XMPP_RETURN_BADSTATE;
68
69  output_handler_ = output_handler;
70
71  return XMPP_RETURN_OK;
72}
73
74XmppReturnStatus XmppEngineImpl::SetSessionHandler(
75    XmppSessionHandler* session_handler) {
76  if (state_ != STATE_START)
77    return XMPP_RETURN_BADSTATE;
78
79  session_handler_ = session_handler;
80
81  return XMPP_RETURN_OK;
82}
83
84XmppReturnStatus XmppEngineImpl::HandleInput(
85    const char* bytes, size_t len) {
86  if (state_ < STATE_OPENING || state_ > STATE_OPEN)
87    return XMPP_RETURN_BADSTATE;
88
89  EnterExit ee(this);
90
91  // TODO: The return value of the xml parser is not checked.
92  stanza_parser_.Parse(bytes, len, false);
93
94  return XMPP_RETURN_OK;
95}
96
97XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) {
98  if (state_ != STATE_CLOSED) {
99    EnterExit ee(this);
100    // If told that connection closed and not already closed,
101    // then connection was unpexectedly dropped.
102    if (subcode) {
103      SignalError(ERROR_SOCKET, subcode);
104    } else {
105      SignalError(ERROR_CONNECTION_CLOSED, 0);  // no subcode
106    }
107  }
108  return XMPP_RETURN_OK;
109}
110
111XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) {
112  if (state_ != STATE_START)
113    return XMPP_RETURN_BADSTATE;
114  tls_option_ = use_tls;
115  return XMPP_RETURN_OK;
116}
117
118XmppReturnStatus XmppEngineImpl::SetTlsServer(
119    const std::string& tls_server_hostname,
120    const std::string& tls_server_domain) {
121  if (state_ != STATE_START)
122    return XMPP_RETURN_BADSTATE;
123
124  tls_server_hostname_ = tls_server_hostname;
125  tls_server_domain_= tls_server_domain;
126
127  return XMPP_RETURN_OK;
128}
129
130TlsOptions XmppEngineImpl::GetTls() {
131  return tls_option_;
132}
133
134XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) {
135  if (state_ != STATE_START)
136    return XMPP_RETURN_BADSTATE;
137
138  user_jid_ = jid;
139
140  return XMPP_RETURN_OK;
141}
142
143const Jid& XmppEngineImpl::GetUser() {
144  return user_jid_;
145}
146
147XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) {
148  if (state_ != STATE_START)
149    return XMPP_RETURN_BADSTATE;
150
151  sasl_handler_.reset(sasl_handler);
152  return XMPP_RETURN_OK;
153}
154
155XmppReturnStatus XmppEngineImpl::SetRequestedResource(
156    const std::string& resource) {
157  if (state_ != STATE_START)
158    return XMPP_RETURN_BADSTATE;
159
160  requested_resource_ = resource;
161
162  return XMPP_RETURN_OK;
163}
164
165const std::string& XmppEngineImpl::GetRequestedResource() {
166  return requested_resource_;
167}
168
169XmppReturnStatus XmppEngineImpl::AddStanzaHandler(
170    XmppStanzaHandler* stanza_handler,
171    XmppEngine::HandlerLevel level) {
172  if (state_ == STATE_CLOSED)
173    return XMPP_RETURN_BADSTATE;
174
175  stanza_handlers_[level]->push_back(stanza_handler);
176
177  return XMPP_RETURN_OK;
178}
179
180XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(
181    XmppStanzaHandler* stanza_handler) {
182  bool found = false;
183
184  for (int level = 0; level < HL_COUNT; level += 1) {
185    StanzaHandlerVector::iterator new_end =
186      std::remove(stanza_handlers_[level]->begin(),
187      stanza_handlers_[level]->end(),
188      stanza_handler);
189
190    if (new_end != stanza_handlers_[level]->end()) {
191      stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
192      found = true;
193    }
194  }
195
196  if (!found)
197    return XMPP_RETURN_BADARGUMENT;
198
199  return XMPP_RETURN_OK;
200}
201
202XmppReturnStatus XmppEngineImpl::Connect() {
203  if (state_ != STATE_START)
204    return XMPP_RETURN_BADSTATE;
205
206  EnterExit ee(this);
207
208  // get the login task started
209  state_ = STATE_OPENING;
210  if (login_task_) {
211    login_task_->IncomingStanza(NULL, false);
212    if (login_task_->IsDone())
213      login_task_.reset();
214  }
215
216  return XMPP_RETURN_OK;
217}
218
219XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) {
220  if (state_ == STATE_CLOSED)
221    return XMPP_RETURN_BADSTATE;
222
223  EnterExit ee(this);
224
225  if (login_task_) {
226    // still handshaking - then outbound stanzas are queued
227    login_task_->OutgoingStanza(element);
228  } else {
229    // handshake done - send straight through
230    InternalSendStanza(element);
231  }
232
233  return XMPP_RETURN_OK;
234}
235
236XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) {
237  if (state_ == STATE_CLOSED || login_task_)
238    return XMPP_RETURN_BADSTATE;
239
240  EnterExit ee(this);
241
242  (*output_) << text;
243
244  return XMPP_RETURN_OK;
245}
246
247std::string XmppEngineImpl::NextId() {
248  std::stringstream ss;
249  ss << next_id_++;
250  return ss.str();
251}
252
253XmppReturnStatus XmppEngineImpl::Disconnect() {
254  if (state_ != STATE_CLOSED) {
255    EnterExit ee(this);
256    if (state_ == STATE_OPEN)
257      *output_ << "</stream:stream>";
258    state_ = STATE_CLOSED;
259  }
260
261  return XMPP_RETURN_OK;
262}
263
264void XmppEngineImpl::IncomingStart(const XmlElement* start) {
265  if (HasError() || raised_reset_)
266    return;
267
268  if (login_task_) {
269    // start-stream should go to login task
270    login_task_->IncomingStanza(start, true);
271    if (login_task_->IsDone())
272      login_task_.reset();
273  }
274  else {
275    // if not logging in, it's an error to see a start
276    SignalError(ERROR_XML, 0);
277  }
278}
279
280void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) {
281  if (HasError() || raised_reset_)
282    return;
283
284  if (stanza->Name() == QN_STREAM_ERROR) {
285    // Explicit XMPP stream error
286    SignalStreamError(stanza);
287  } else if (login_task_) {
288    // Handle login handshake
289    login_task_->IncomingStanza(stanza, false);
290    if (login_task_->IsDone())
291      login_task_.reset();
292  } else if (HandleIqResponse(stanza)) {
293    // iq is handled by above call
294  } else {
295    // give every "peek" handler a shot at all stanzas
296    for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
297      (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
298    }
299
300    // give other handlers a shot in precedence order, stopping after handled
301    for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
302      for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
303        if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
304          return;
305      }
306    }
307
308    // If nobody wants to handle a stanza then send back an error.
309    // Only do this for IQ stanzas as messages should probably just be dropped
310    // and presence stanzas should certainly be dropped.
311    std::string type = stanza->Attr(QN_TYPE);
312    if (stanza->Name() == QN_IQ &&
313        !(type == "error" || type == "result")) {
314      SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
315    }
316  }
317}
318
319void XmppEngineImpl::IncomingEnd(bool isError) {
320  if (HasError() || raised_reset_)
321    return;
322
323  SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
324}
325
326void XmppEngineImpl::InternalSendStart(const std::string& to) {
327  std::string hostname = tls_server_hostname_;
328  if (hostname.empty())
329    hostname = to;
330
331  // If not language is specified, the spec says use *
332  std::string lang = lang_;
333  if (lang.length() == 0)
334    lang = "*";
335
336  // send stream-beginning
337  // note, we put a \r\n at tne end fo the first line to cause non-XMPP
338  // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
339  *output_ << "<stream:stream to=\"" << hostname << "\" "
340           << "xml:lang=\"" << lang << "\" "
341           << "version=\"1.0\" "
342           << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
343           << "xmlns=\"jabber:client\">\r\n";
344}
345
346void XmppEngineImpl::InternalSendStanza(const XmlElement* element) {
347  // It should really never be necessary to set a FROM attribute on a stanza.
348  // It is implied by the bind on the stream and if you get it wrong
349  // (by flipping from/to on a message?) the server will close the stream.
350  ASSERT(!element->HasAttr(QN_FROM));
351
352  XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_);
353}
354
355std::string XmppEngineImpl::ChooseBestSaslMechanism(
356    const std::vector<std::string>& mechanisms, bool encrypted) {
357  return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
358}
359
360SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) {
361  return sasl_handler_->CreateSaslMechanism(name);
362}
363
364void XmppEngineImpl::SignalBound(const Jid& fullJid) {
365  if (state_ == STATE_OPENING) {
366    bound_jid_ = fullJid;
367    state_ = STATE_OPEN;
368  }
369}
370
371void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) {
372  if (state_ != STATE_CLOSED) {
373    stream_error_.reset(new XmlElement(*stream_error));
374    SignalError(ERROR_STREAM, 0);
375  }
376}
377
378void XmppEngineImpl::SignalError(Error error_code, int sub_code) {
379  if (state_ != STATE_CLOSED) {
380    error_code_ = error_code;
381    subcode_ = sub_code;
382    state_ = STATE_CLOSED;
383  }
384}
385
386bool XmppEngineImpl::HasError() {
387  return error_code_ != ERROR_NONE;
388}
389
390void XmppEngineImpl::StartTls(const std::string& domain) {
391  if (output_handler_) {
392    // As substitute for the real (login jid's) domain, we permit
393    // verifying a tls_server_domain_ instead, if one was passed.
394    // This allows us to avoid running a proxy that needs to handle
395    // valuable certificates.
396    output_handler_->StartTls(
397      tls_server_domain_.empty() ? domain : tls_server_domain_);
398    encrypted_ = true;
399  }
400}
401
402XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
403    : engine_(engine),
404      state_(engine->state_) {
405  engine->engine_entered_ += 1;
406}
407
408XmppEngineImpl::EnterExit::~EnterExit()  {
409 XmppEngineImpl* engine = engine_;
410
411 engine->engine_entered_ -= 1;
412
413 bool closing = (engine->state_ != state_ &&
414       engine->state_ == STATE_CLOSED);
415 bool flushing = closing || (engine->engine_entered_ == 0);
416
417 if (engine->output_handler_ && flushing) {
418   std::string output = engine->output_->str();
419   if (output.length() > 0)
420     engine->output_handler_->WriteOutput(output.c_str(), output.length());
421   engine->output_->str("");
422
423   if (closing) {
424     engine->output_handler_->CloseConnection();
425     engine->output_handler_ = 0;
426   }
427 }
428
429 if (engine->engine_entered_)
430   return;
431
432 if (engine->raised_reset_) {
433   engine->stanza_parser_.Reset();
434   engine->raised_reset_ = false;
435 }
436
437 if (engine->session_handler_) {
438   if (engine->state_ != state_)
439     engine->session_handler_->OnStateChange(engine->state_);
440   // Note: Handling of OnStateChange(CLOSED) should allow for the
441   // deletion of the engine, so no members should be accessed
442   // after this line.
443 }
444}
445
446}  // namespace buzz
447