diff options
Diffstat (limited to 'kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cpp')
-rw-r--r-- | kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cpp | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cpp b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cpp new file mode 100644 index 00000000..b64c444a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cpp @@ -0,0 +1,545 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/p2p/client/sessionclient.h" +#include "talk/p2p/base/helpers.h" +#include "talk/base/logging.h" +#include "talk/xmllite/qname.h" +#include "talk/xmpp/constants.h" +#include "talk/xmllite/xmlprinter.h" +#include <iostream> +#undef SetPort + +namespace { + +// We only allow usernames to be this many characters or fewer. +const size_t kMaxUsernameSize = 16; + +} + +namespace cricket { + +#if 0 +>>>>>> +<iq from="..." to="..." type="set" id="27"> + <session xmlns="http://www.google.com/session" type="initiate" id="Dr45JU8A34DF" initiator="..."> + <description xmlns="http://www.whoever.com/whatever"> + ... + </description> + </session> +</iq> + +<<<<<< +<iq from="..." to="..." type="result" id="27"/> + +>>>>>> +<iq from="..." to="..." type="set" id="28"> + <session xmlns="http://www.google.com/session" type="candidates" id="Dr45JU8A34DF" initiator="..."> + <candidate name="rtp" address="X.X.X.X" port="NNN" username="asdf" password="asdf" preference="1.0" type="udp" network="bleh"/> + <candidate name="rtp" address="X.X.X.X" port="NNN" username="asdf" password="asdf" preference="1.0" type="udp" network="bleh"/> + <candidate name="rtp" address="X.X.X.X" port="NNN" username="asdf" password="asdf" preference="1.0" type="udp" network="bleh"/> + </session> +</iq>> + +<<<<<< +<iq from="..." to="..." type="result" id="28"/> + +#endif + +const std::string NS_GOOGLESESSION("http://www.google.com/session"); +const buzz::TQName TQN_GOOGLESESSION_SESSION(true, NS_GOOGLESESSION, "session"); +const buzz::TQName TQN_GOOGLESESSION_CANDIDATE(true, NS_GOOGLESESSION, "candidate"); +const buzz::TQName TQN_GOOGLESESSION_TARGET(true, NS_GOOGLESESSION, "target"); +const buzz::TQName TQN_GOOGLESESSION_COOKIE(true, NS_GOOGLESESSION, "cookie"); +const buzz::TQName TQN_GOOGLESESSION_REGARDING(true, NS_GOOGLESESSION, "regarding"); + +const buzz::TQName TQN_TYPE(true, buzz::STR_EMPTY, "type"); +const buzz::TQName TQN_ID(true, buzz::STR_EMPTY, "id"); +const buzz::TQName TQN_INITIATOR(true, buzz::STR_EMPTY, "initiator"); +const buzz::TQName TQN_NAME(true, buzz::STR_EMPTY, "name"); +const buzz::TQName TQN_PORT(true, buzz::STR_EMPTY, "port"); +const buzz::TQName TQN_NETWORK(true, buzz::STR_EMPTY, "network"); +const buzz::TQName TQN_GENERATION(true, buzz::STR_EMPTY, "generation"); +const buzz::TQName TQN_ADDRESS(true, buzz::STR_EMPTY, "address"); +const buzz::TQName TQN_USERNAME(true, buzz::STR_EMPTY, "username"); +const buzz::TQName TQN_PASSWORD(true, buzz::STR_EMPTY, "password"); +const buzz::TQName TQN_PREFERENCE(true, buzz::STR_EMPTY, "preference"); +const buzz::TQName TQN_PROTOCOL(true, buzz::STR_EMPTY, "protocol"); +const buzz::TQName TQN_KEY(true, buzz::STR_EMPTY, "key"); + +class XmlCookie: public SessionMessage::Cookie { +public: + XmlCookie(const buzz::XmlElement* elem) + : elem_(new buzz::XmlElement(*elem)) { + } + + virtual ~XmlCookie() { + delete elem_; + } + + const buzz::XmlElement* elem() const { return elem_; } + + virtual Cookie* Copy() { + return new XmlCookie(elem_); + } + +private: + buzz::XmlElement* elem_; +}; + +SessionClient::SessionClient(SessionManager *session_manager) { + session_manager_ = session_manager; + session_manager_->SignalSessionCreate.connect(this, &SessionClient::OnSessionCreateSlot); + session_manager_->SignalSessionDestroy.connect(this, &SessionClient::OnSessionDestroySlot); +} + +SessionClient::~SessionClient() { +} + +void SessionClient::OnSessionCreateSlot(Session *session, bool received_initiate) { + // Does this session belong to this session client? + if (session->name() == GetSessionDescriptionName()) { + session->SignalOutgoingMessage.connect(this, &SessionClient::OnOutgoingMessage); + OnSessionCreate(session, received_initiate); + } +} + +void SessionClient::OnSessionDestroySlot(Session *session) { + if (session->name() == GetSessionDescriptionName()) { + session->SignalOutgoingMessage.disconnect(this); + OnSessionDestroy(session); + } +} + +bool SessionClient::IsClientStanza(const buzz::XmlElement *stanza) { + // Is it a IQ set stanza? + if (stanza->Name() != buzz::TQN_IQ) + return false; + if (stanza->Attr(buzz::TQN_TYPE) != buzz::STR_SET) + return false; + + // Make sure it has the right child element + const buzz::XmlElement* element + = stanza->FirstNamed(TQN_GOOGLESESSION_SESSION); + if (element == NULL) + return false; + + // Is it one of the allowed types? + std::string type; + if (element->HasAttr(TQN_TYPE)) { + type = element->Attr(TQN_TYPE); + if (type != "initiate" && type != "accept" && type != "modify" && + type != "candidates" && type != "reject" && type != "redirect" && + type != "terminate") { + return false; + } + } + + // Does this client own the session description namespace? + buzz::TQName qn_session_desc(GetSessionDescriptionName(), "description"); + const buzz::XmlElement* description = element->FirstNamed(qn_session_desc); + if (type == "initiate" || type == "accept" || type == "modify") { + if (description == NULL) + return false; + } else { + if (description != NULL) + return false; + } + + // It's good + return true; +} + +void SessionClient::OnIncomingStanza(const buzz::XmlElement *stanza) { + SessionMessage message; + if (!ParseIncomingMessage(stanza, message)) + return; + + session_manager_->OnIncomingMessage(message); +} + +void SessionClient::OnFailedSend(const buzz::XmlElement *original_stanza, + const buzz::XmlElement *failure_stanza) { + SessionMessage message; + if (!ParseIncomingMessage(original_stanza, message)) + return; + + // Note the from/to represents the *original* stanza and not the from/to + // on any return path + session_manager_->OnIncomingError(message); +} + +bool SessionClient::ParseIncomingMessage(const buzz::XmlElement *stanza, + SessionMessage& message) { + // Parse stanza into SessionMessage + const buzz::XmlElement* element + = stanza->FirstNamed(TQN_GOOGLESESSION_SESSION); + + std::string type = element->Attr(TQN_TYPE); + if (type == "initiate" || type == "accept" || type == "modify") { + ParseInitiateAcceptModify(stanza, message); + } else if (type == "candidates") { + ParseCandidates(stanza, message); + } else if (type == "reject" || type == "terminate") { + ParseRejectTerminate(stanza, message); + } else if (type == "redirect") { + ParseRedirect(stanza, message); + } else { + return false; + } + + return true; +} + +void SessionClient::ParseHeader(const buzz::XmlElement *stanza, SessionMessage &message) { + if (stanza->HasAttr(buzz::TQN_FROM)) + message.set_from(stanza->Attr(buzz::TQN_FROM)); + if (stanza->HasAttr(buzz::TQN_TO)) + message.set_to(stanza->Attr(buzz::TQN_TO)); + + const buzz::XmlElement *element + = stanza->FirstNamed(TQN_GOOGLESESSION_SESSION); + if (element->HasAttr(TQN_ID)) + message.session_id().set_id_str(element->Attr(TQN_ID)); + + if (element->HasAttr(TQN_INITIATOR)) + message.session_id().set_initiator(element->Attr(TQN_INITIATOR)); + + std::string type = element->Attr(TQN_TYPE); + if (type == "initiate") { + message.set_type(SessionMessage::TYPE_INITIATE); + } else if (type == "accept") { + message.set_type(SessionMessage::TYPE_ACCEPT); + } else if (type == "modify") { + message.set_type(SessionMessage::TYPE_MODIFY); + } else if (type == "candidates") { + message.set_type(SessionMessage::TYPE_CANDIDATES); + } else if (type == "reject") { + message.set_type(SessionMessage::TYPE_REJECT); + } else if (type == "redirect") { + message.set_type(SessionMessage::TYPE_REDIRECT); + } else if (type == "terminate") { + message.set_type(SessionMessage::TYPE_TERMINATE); + } else { + assert(false); + } +} + +void SessionClient::ParseInitiateAcceptModify(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + + // Parse session description + const buzz::XmlElement *session + = stanza->FirstNamed(TQN_GOOGLESESSION_SESSION); + buzz::TQName qn_session_desc(GetSessionDescriptionName(), "description"); + const buzz::XmlElement* desc_elem = session->FirstNamed(qn_session_desc); + const SessionDescription *description = NULL; + if (desc_elem) + description = CreateSessionDescription(desc_elem); + message.set_name(GetSessionDescriptionName()); + message.set_description(description); +} + +void SessionClient::ParseCandidates(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + + // Parse candidates and session description + std::vector<Candidate> candidates; + const buzz::XmlElement *element + = stanza->FirstNamed(TQN_GOOGLESESSION_SESSION); + const buzz::XmlElement *child = element->FirstElement(); + while (child != NULL) { + if (child->Name() == TQN_GOOGLESESSION_CANDIDATE) { + Candidate candidate; + if (ParseCandidate(child, &candidate)) + candidates.push_back(candidate); + } + child = child->NextElement(); + } + message.set_name(GetSessionDescriptionName()); + message.set_candidates(candidates); +} + +void SessionClient::ParseRejectTerminate(const buzz::XmlElement *stanza, SessionMessage &message) { + // Reject and terminate are very simple + ParseHeader(stanza, message); +} + +bool SessionClient::ParseCandidate(const buzz::XmlElement *child, + Candidate* candidate) { + // Check for all of the required attributes. + if (!child->HasAttr(TQN_NAME) || + !child->HasAttr(TQN_ADDRESS) || + !child->HasAttr(TQN_PORT) || + !child->HasAttr(TQN_USERNAME) || + !child->HasAttr(TQN_PREFERENCE) || + !child->HasAttr(TQN_PROTOCOL) || + !child->HasAttr(TQN_GENERATION)) { + LOG(LERROR) << "Candidate missing required attribute"; + return false; + } + + SocketAddress address; + address.SetIP(child->Attr(TQN_ADDRESS)); + std::istringstream ist(child->Attr(TQN_PORT)); + int port; + ist >> port; + address.SetPort(port); + + if (address.IsAny()) { + LOG(LERROR) << "Candidate has address 0"; + return false; + } + + // Always disallow addresses that refer to the local host. + if (address.IsLocalIP()) { + LOG(LERROR) << "Candidate has local IP address"; + return false; + } + + // Disallow all ports below 1024, except for 80 and 443 on public addresses. + if (port < 1024) { + if ((port != 80) && (port != 443)) { + LOG(LERROR) << "Candidate has port below 1024, not 80 or 443"; + return false; + } + if (address.IsPrivateIP()) { + LOG(LERROR) << "Candidate has port of 80 or 443 with private IP address"; + return false; + } + } + + candidate->set_name(child->Attr(TQN_NAME)); + candidate->set_address(address); + candidate->set_username(child->Attr(TQN_USERNAME)); + candidate->set_preference_str(child->Attr(TQN_PREFERENCE)); + candidate->set_protocol(child->Attr(TQN_PROTOCOL)); + candidate->set_generation_str(child->Attr(TQN_GENERATION)); + + // Check that the username is not too long and does not use any bad chars. + if (candidate->username().size() > kMaxUsernameSize) { + LOG(LERROR) << "Candidate username is too long"; + return false; + } + if (!IsBase64Encoded(candidate->username())) { + LOG(LERROR) << "Candidate username has non-base64 encoded characters"; + return false; + } + + // Look for the non-required attributes. + if (child->HasAttr(TQN_PASSWORD)) + candidate->set_password(child->Attr(TQN_PASSWORD)); + if (child->HasAttr(TQN_TYPE)) + candidate->set_type(child->Attr(TQN_TYPE)); + if (child->HasAttr(TQN_NETWORK)) + candidate->set_network_name(child->Attr(TQN_NETWORK)); + + return true; +} + +void SessionClient::ParseRedirect(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + const buzz::XmlElement *session = stanza->FirstNamed(TQN_GOOGLESESSION_SESSION); + + // Parse the target and cookie. + + const buzz::XmlElement* target = session->FirstNamed(TQN_GOOGLESESSION_TARGET); + if (target) + message.set_redirect_target(target->Attr(TQN_NAME)); + + const buzz::XmlElement* cookie = session->FirstNamed(TQN_GOOGLESESSION_COOKIE); + if (cookie) + message.set_redirect_cookie(new XmlCookie(cookie)); +} + +void SessionClient::OnOutgoingMessage(Session *session, const SessionMessage &message) { + // Translate the message into an XMPP stanza + + buzz::XmlElement *result = NULL; + switch (message.type()) { + case SessionMessage::TYPE_INITIATE: + case SessionMessage::TYPE_ACCEPT: + case SessionMessage::TYPE_MODIFY: + result = TranslateInitiateAcceptModify(message); + break; + + case SessionMessage::TYPE_CANDIDATES: + result = TranslateCandidates(message); + break; + + case SessionMessage::TYPE_REJECT: + case SessionMessage::TYPE_TERMINATE: + result = TranslateRejectTerminate(message); + break; + + case SessionMessage::TYPE_REDIRECT: + result = TranslateRedirect(message); + break; + } + + // Send the stanza. Note that SessionClient is passing on ownership + // of result. + if (result != NULL) { + SignalSendStanza(this, result); + } +} + +buzz::XmlElement *SessionClient::TranslateHeader(const SessionMessage &message) { + buzz::XmlElement *result = new buzz::XmlElement(buzz::TQN_IQ); + result->AddAttr(buzz::TQN_TO, message.to()); + result->AddAttr(buzz::TQN_TYPE, buzz::STR_SET); + buzz::XmlElement *session = new buzz::XmlElement(TQN_GOOGLESESSION_SESSION, true); + result->AddElement(session); + switch (message.type()) { + case SessionMessage::TYPE_INITIATE: + session->AddAttr(TQN_TYPE, "initiate"); + break; + case SessionMessage::TYPE_ACCEPT: + session->AddAttr(TQN_TYPE, "accept"); + break; + case SessionMessage::TYPE_MODIFY: + session->AddAttr(TQN_TYPE, "modify"); + break; + case SessionMessage::TYPE_CANDIDATES: + session->AddAttr(TQN_TYPE, "candidates"); + break; + case SessionMessage::TYPE_REJECT: + session->AddAttr(TQN_TYPE, "reject"); + break; + case SessionMessage::TYPE_REDIRECT: + session->AddAttr(TQN_TYPE, "redirect"); + break; + case SessionMessage::TYPE_TERMINATE: + session->AddAttr(TQN_TYPE, "terminate"); + break; + } + session->AddAttr(TQN_ID, message.session_id().id_str()); + session->AddAttr(TQN_INITIATOR, message.session_id().initiator()); + return result; +} + +buzz::XmlElement *SessionClient::TranslateCandidate(const Candidate &candidate) { + buzz::XmlElement *result = new buzz::XmlElement(TQN_GOOGLESESSION_CANDIDATE); + result->AddAttr(TQN_NAME, candidate.name()); + result->AddAttr(TQN_ADDRESS, candidate.address().IPAsString()); + result->AddAttr(TQN_PORT, candidate.address().PortAsString()); + result->AddAttr(TQN_USERNAME, candidate.username()); + result->AddAttr(TQN_PASSWORD, candidate.password()); + result->AddAttr(TQN_PREFERENCE, candidate.preference_str()); + result->AddAttr(TQN_PROTOCOL, candidate.protocol()); + result->AddAttr(TQN_TYPE, candidate.type()); + result->AddAttr(TQN_NETWORK, candidate.network_name()); + result->AddAttr(TQN_GENERATION, candidate.generation_str()); + return result; +} + +buzz::XmlElement *SessionClient::TranslateInitiateAcceptModify(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(TQN_GOOGLESESSION_SESSION); + + // Candidates + assert(message.candidates().size() == 0); + + // Session Description + buzz::XmlElement* description = TranslateSessionDescription(message.description()); + assert(description->Name().LocalPart() == "description"); + assert(description->Name().Namespace() == GetSessionDescriptionName()); + session->AddElement(description); + + if (message.redirect_cookie() != NULL) { + const buzz::XmlElement* cookie = + reinterpret_cast<XmlCookie*>(message.redirect_cookie())->elem(); + for (const buzz::XmlElement* elem = cookie->FirstElement(); elem; elem = elem->NextElement()) + session->AddElement(new buzz::XmlElement(*elem)); + } + + return result; +} + +buzz::XmlElement *SessionClient::TranslateCandidates(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(TQN_GOOGLESESSION_SESSION); + + // Candidates + std::vector<Candidate>::const_iterator it; + for (it = message.candidates().begin(); it != message.candidates().end(); it++) + session->AddElement(TranslateCandidate(*it)); + + return result; +} + +buzz::XmlElement *SessionClient::TranslateRejectTerminate(const SessionMessage &message) { + // These messages are simple, and only have a header + return TranslateHeader(message); +} + +buzz::XmlElement *SessionClient::TranslateRedirect(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(TQN_GOOGLESESSION_SESSION); + + assert(message.candidates().size() == 0); + assert(message.description() == NULL); + + assert(message.redirect_target().size() > 0); + buzz::XmlElement* target = new buzz::XmlElement(TQN_GOOGLESESSION_TARGET); + target->AddAttr(TQN_NAME, message.redirect_target()); + session->AddElement(target); + + buzz::XmlElement* cookie = new buzz::XmlElement(TQN_GOOGLESESSION_COOKIE); + session->AddElement(cookie); + + // If the message does not have a redirect cookie, then this is a redirect + // initiated by us. We will automatically add a regarding cookie. + if (message.redirect_cookie() == NULL) { + buzz::XmlElement* regarding = new buzz::XmlElement(TQN_GOOGLESESSION_REGARDING); + regarding->AddAttr(TQN_NAME, GetJid().BareJid().Str()); + cookie->AddElement(regarding); + } else { + const buzz::XmlElement* cookie_elem = + reinterpret_cast<const XmlCookie*>(message.redirect_cookie())->elem(); + const buzz::XmlElement* elem; + for (elem = cookie_elem->FirstElement(); elem; elem = elem->NextElement()) + cookie->AddElement(new buzz::XmlElement(*elem)); + } + + return result; +} + +SessionManager *SessionClient::session_manager() { + return session_manager_; +} + +} // namespace cricket |