/*************************************************************************** * Copyright (C) 2004 - 2005 by Raphael Langerhorst * * raphael-langerhorst@gmx.at * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be * * included in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.* * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * * OTHER DEALINGS IN THE SOFTWARE. * ***************************************************************************/ #include "GXmppNetwork.h" #include #include #include #include #include //TODO implement all methods namespace GWE { class QNetworkSendEvent : public QCustomEvent { private: QString Message; public: QNetworkSendEvent(QString message) : QCustomEvent(65001),Message(message) {} virtual ~QNetworkSendEvent() {} QString message() { return Message; } }; class GXmppMessageQueue { private: QMap Queues; public: void add(QString server, QString message); /** * The order is from front to back (start at begin()). * Queue is automatically removed from Queues. */ QStringList takeMessagesForServer(QString server); bool hasMessages(QString server); }; void GXmppMessageQueue::add(QString server, QString message) { QStringList queue; if (Queues.contains(server)) { queue = Queues[server]; } queue.append(message); Queues.insert(server,queue); } QStringList GXmppMessageQueue::takeMessagesForServer(QString server) { QStringList msgs; if (Queues.contains(server)) { msgs = Queues[server]; Queues.remove(server); } return msgs; } bool GXmppMessageQueue::hasMessages(QString server) { return Queues.contains(server); } GXmppNetwork::GXmppNetwork(QString full_jid, QObject *parent, const char *name) : GXmlNetwork(parent,name), Active(FALSE), Connected(FALSE), Connecting(FALSE), XmppLayerCreated(FALSE), XmppConnector(NULL), Tls(NULL), TlsHandler(NULL), Stream(NULL), NetworkId(full_jid), MessageQueue(new GXmppMessageQueue()), StayConnected(FALSE) { connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(closeNetwork())); // QTimer* heartbeat_timer = new QTimer(this,"heartbeat timer"); // connect(heartbeat_timer,SIGNAL(timeout()),this,SLOT(sendHeartbeat())); // heartbeat_timer->start(55000); // see XMPP::ClientStream::setNoopTime() which is now used } GXmppNetwork::~GXmppNetwork() { deleteXmppLayer(true); if (MessageQueue) { delete MessageQueue; MessageQueue=NULL; } } bool GXmppNetwork::createXmppLayer() { if (XmppLayerCreated) { qWarning("XMPP Layer already created! Destroying it first..."); this->deleteXmppLayer(true); qWarning("XMPP Layer now destroyed"); } //XMPP related setup try { qDebug("Creating XMPP layer"); XmppConnector = new XMPP::AdvancedConnector(); Q_CHECK_PTR(XmppConnector); //@todo something causes a segfault if the jabber server is not found. if (XmppConnector==NULL) { throw std::exception(); } connect(XmppConnector,SIGNAL(srvLookup(const QString &)),this,SLOT(connectorServerLookup(const QString&))); connect(XmppConnector,SIGNAL(srvResult(bool)),this,SLOT(connectorServerResult(bool))); connect(XmppConnector,SIGNAL(httpSyncStarted()),this,SLOT(connectorHttpSyncStarted())); connect(XmppConnector,SIGNAL(httpSyncFinished()),this,SLOT(connectorHttpSyncFinished())); if (QCA::isSupported(QCA::CAP_TLS)) { qDebug("CAP TLS supported"); Tls = new QCA::TLS(); Q_CHECK_PTR(Tls); if (Tls==NULL) { throw std::exception(); } TlsHandler = new XMPP::QCATLSHandler(Tls); Q_CHECK_PTR(TlsHandler); if (TlsHandler == NULL) { throw std::exception(); } connect(TlsHandler,SIGNAL(tlsHandshaken()),this,SLOT(tlsHandshaken())); } this->Stream = new XMPP::ClientStream(this->XmppConnector,this->TlsHandler); Q_CHECK_PTR(Stream); if (Stream == NULL) { throw std::exception(); } connect(Stream,SIGNAL(connected()),SLOT(clientStreamConnected())); connect(Stream,SIGNAL(securityLayerActivated(int)),SLOT(clientStreamSecurityLayerActivated(int))); connect(Stream,SIGNAL(needAuthParams(bool,bool,bool)),SLOT(clientStreamNeedAuthenticationParameters(bool,bool,bool))); connect(Stream,SIGNAL(authenticated()),SLOT(clientStreamAuthenticated())); connect(Stream,SIGNAL(connectionClosed()),SLOT(clientStreamConnectionClosed())); connect(Stream,SIGNAL(delayedCloseFinished()),SLOT(clientStreamDelayedCloseFinished())); connect(Stream,SIGNAL(readyRead()),SLOT(clientStreamReadyRead())); connect(Stream,SIGNAL(stanzaWritten()),SLOT(clientStreamStanzaWritten())); connect(Stream,SIGNAL(warning(int)),SLOT(clientStreamWarning(int))); connect(Stream,SIGNAL(error(int)),SLOT(clientStreamError(int))); this->XmppLayerCreated = true; qDebug("XMPP Layer now created"); return true; } catch (std::exception e) { deleteXmppLayer(true); return false; } //active == false && connected == false, they are set in initNetwork() } bool GXmppNetwork::deleteXmppLayer(bool force) { if (force==FALSE) { if (!XmppLayerCreated) { qWarning("XMPP layer not even created! Nothing to delete."); return true; } if (Active) { qWarning("XMPP network is active! Not deleting objects!"); return false; } if (Connected) { qWarning("XMPP network is still connected! Not deleting objects!"); return false; } } if (Stream) { delete Stream; Stream = NULL; } if (TlsHandler) { delete TlsHandler; TlsHandler = NULL; } if (Tls) { delete Tls; Tls = NULL; } if (XmppConnector) { delete XmppConnector; XmppConnector=NULL; } this->Connected = false; this->Connecting = false; this->Active = false; this->XmppLayerCreated = false; return true; } void GXmppNetwork::customEvent(QCustomEvent* event) { if (event->type()==65001) { if (!this->isConnected()) { qWarning("Can't send data because network is not connected!"); } else { // qDebug("sending message:"); // qDebug(((QNetworkSendEvent*)event)->message()); Stream->writeDirect(((QNetworkSendEvent*)event)->message()); } } else qWarning(QString("Received unknown custom event type %1").arg(QString::number(event->type()))); } bool GXmppNetwork::isSubscribed(QString destination) { if (this->SubscriptionStates.contains(destination)) { if (this->SubscriptionStates[destination]=="subscribed") return true; else return false; } else return false; } void GXmppNetwork::sendQueuedMessages(QString destination) { QStringList msgs = MessageQueue->takeMessagesForServer(destination); QStringList::iterator it; for (it = msgs.begin(); it != msgs.end(); ++it) { this->send(*it); } } void GXmppNetwork::sendHeartbeat() { if (this->isConnected()) this->send(""); } // GXmlNetwork implementation BEGIN // bool GXmppNetwork::initNetwork() { if (this->Connecting) { qWarning("Already in the process of connecting the network"); return false; } this->Connecting = true; QMutexLocker lock(this); if (!this->createXmppLayer()) { qWarning("Failed to initialize XMPP layer"); return false; } if (Active) { qWarning("XMPP layer already active"); return true; } Active = true; // HACK don't use XMPP 1.0 even if advertised (jabberd 1.4.4 is bugged) this->Stream->setOldOnly(true); //XMPP initialization - without SSL this->XmppConnector->setOptHostPort(NetworkId.domain(),5222); //@todo get option from a parameter! this->XmppConnector->setOptSSL(false); this->Stream->setAllowPlain(true); this->Stream->setResourceBinding(true); //for use with SSL // this->XmppConnector->setOptHostPort(NetworkId.domain(),5223); // this->XmppConnector->setOptSSL(true); if (this->Tls) { QPtrList certStore; Tls->setCertificateStore(certStore); } Stream->setNoopTime(55000); // every 55 seconds qDebug("Connecting XMPP network with JID " + this->getNetworkId()); qDebug("Warning: if you get a segmentation fault next, this probably means"); qDebug(" that the server you want to connect to (the domain part"); qDebug(" of the JID) does not have an XMPP/Jabber server running."); qDebug(" In this case make sure the JID is correct and/or contact"); qDebug(" the G System Team."); this->Stream->connectToServer(this->NetworkId); return true; } void GXmppNetwork::reconnectNetwork() { QTimer::singleShot(100,this,SLOT(initNetwork())); } bool GXmppNetwork::closeNetwork() { QMutexLocker lock(this); this->StayConnected = false; if (this->Connected) { qDebug("Disconnecting XMPP Stream..."); this->Stream->close(); this->Connected = false; this->Active = false; // correct place? this->Connecting = false; return true; } else { qWarning("Not closing network: not connected"); return true; //it's fine, not connected after all } } QString GXmppNetwork::getNetworkId() const { return this->NetworkId.full(); } void GXmppNetwork::setPassword(const QString& password) { this->Password = password; } bool GXmppNetwork::send(QDomElement data, const QString& destination) { if (destination.isEmpty()) { qWarning("Destination is empty, not sending message!"); return false; } //@todo this doesn't seem to be functional(??) XMPP::Jid destjid(destination); if (!destjid.isValid()) { qWarning(QString("Destination %1 not a valid JID, not sending message!").arg(destination)); return false; } // qDebug(QString("type range for user events: %1 to %2").arg(QString::number(QEvent::User)).arg(QString::number(QEvent::MaxUser))); QDomDocument d; QDomElement e = d.createElement("message"); d.appendChild(e); e.setAttribute("to",destination); e.appendChild(d.importNode(data,"true")); if (!this->isSubscribed(destination) || !this->isConnected()) { if (!this->isConnected() && this->StayConnected) { qWarning("Network is not connected! Reconnecting..."); this->reconnectNetwork(); } else { qWarning(QString("Delaying sending of message, destination %1 is not yet available").arg(destination)); this->makeDestinationAvailable(destination); } this->MessageQueue->add(destination,d.toString()); } else { this->send(d.toString()); // QApplication::postEvent(this,new QNetworkSendEvent(d.toString())); } return true; } bool GXmppNetwork::send(const QString& data) { QApplication::postEvent(this,new QNetworkSendEvent(data)); return true; } bool GXmppNetwork::flushOutput() { //assume everything is fine (I don't know about a method to flush output) //@todo implement return true; } void GXmppNetwork::makeDestinationAvailable(const QString& destination) { if (this->SubscriptionStates.contains(destination)) { if (this->SubscriptionStates[destination] == "subscribed") { // XMPP::Stanza s = this->Stream->createStanza(XMPP::Stanza::Presence, destination, "probe"); // QString msg = s.toString(); QString msg = QString("").arg(destination); this->send(msg); } else { // XMPP::Stanza s = this->Stream->createStanza(XMPP::Stanza::Presence, destination, "subscribe"); // QString msg = s.toString(); QString msg = QString("").arg(destination); this->send(msg); } } else { // XMPP::Stanza s = this->Stream->createStanza(XMPP::Stanza::Presence, destination, "subscribe"); // QString msg = s.toString(); this->SubscriptionStates.insert(destination,"unsubscribed"); // use this for XMPP 1.0 QString msg = QString("").arg(destination); this->send(msg); // use this HACK for pre XMPP 1.0 msg = QString("").arg(destination); this->send(msg); } } bool GXmppNetwork::isConnected() { if (this->Active && this->Connected && Stream->isAuthenticated() && Stream->isActive()) return true; else return false; } //END GXmlNetwork implementation // //BEGIN XMPP slots and signals for network management // // CONNECTOR SLOTS // void GXmppNetwork::connectorServerLookup(const QString& server) { qDebug("XMPP Server Lookup: " + server); } void GXmppNetwork::connectorServerResult(bool success) { if (success) { qDebug("XMPP Server Result OK"); } else { qDebug("XMPP Server Result FAILURE"); } } void GXmppNetwork::connectorHttpSyncStarted() { qDebug("XMPP Connector HTTP sync started"); } void GXmppNetwork::connectorHttpSyncFinished() { qDebug("XMPP Connector HTTP sync finished"); } // TLS SLOTS // void GXmppNetwork::tlsHandshaken() { qDebug("XMPP TLS handshake complete"); } // STREAM SLOTS // void GXmppNetwork::clientStreamConnected() { this->Connecting = false; this->Connected = true; this->StayConnected = true; qDebug("XMPP Stream connected"); } void GXmppNetwork::clientStreamSecurityLayerActivated(int l) { qDebug("XMPP Stream Security Layer " + QString::number(l) + " activated"); } void GXmppNetwork::clientStreamNeedAuthenticationParameters(bool a,bool b,bool c) { qDebug("XMPP needs authentication " + QString::number(a) + " " + QString::number(b) + " " + QString::number(c)); qDebug("Continuing to authenticate"); if (this->Stream) { if (a) { qDebug("username: " + this->NetworkId.full()); this->Stream->setUsername(this->NetworkId.full()); } if (b) { // qDebug("password: (not shown)"); this->Stream->setPassword(this->Password); } if (c) { qDebug("domain: " + this->NetworkId.domain()); this->Stream->setRealm(this->NetworkId.domain()); } this->Stream->continueAfterParams(); } } void GXmppNetwork::clientStreamAuthenticated() { qDebug("XMPP Stream authenticated"); //well, just let everyone know we are online this->Stream->writeDirect(""); emit this->networkConnected(); //@todo: send an apropriate message to the master (define a server-server-protocol!!) } void GXmppNetwork::clientStreamConnectionClosed() { qDebug("XMPP Stream connection closed."); if (this->StayConnected) { qDebug("Using timer for delayed recreation of the XMPP layer..."); this->reconnectNetwork(); } } void GXmppNetwork::clientStreamDelayedCloseFinished() { qDebug("XMPP Stream delayed close finished"); } void GXmppNetwork::clientStreamReadyRead() { qDebug("XMPP Stream ready read"); bool stanza_printed = false; while(Stream->stanzaAvailable()) { this->lock(); XMPP::Stanza s = Stream->read(); this->unlock(); // qDebug(s.toString()); // stanza_printed=true; QDomElement data = s.element(); QString from = s.from().full(); QString type = s.type(); // qDebug(QString("XMPP message received, data tag name: %1").arg(data.tagName())); if (data.tagName() == "message") //extract all child elements { //make sure presence is subscribed if (this->SubscriptionStates.contains(from)) { if (this->SubscriptionStates[from] != "subscribed") this->makeDestinationAvailable(from); } else this->makeDestinationAvailable(from); //@todo improve detection of unneccessary offline messages, // more clearly state which offline messages should go through (body does for now) bool discard = false; bool isbody = false; if (data.elementsByTagName("error").count()>0 || type == "error") discard=true; else if (data.elementsByTagName("body").count()>0) //do not emit body tags when error tags are included - thus else if isbody=true; else //filter out all other delayed messages { QDomNodeList dom_list = data.elementsByTagName("x"); for (unsigned int i=0; idataAvailable(node.toElement(),from); } else { qDebug("x tag received, ignoring!"); if (!stanza_printed) { qDebug("Stanza data:"); qDebug(s.toString()); stanza_printed=true; } } } node = node.nextSibling(); } } else { qWarning("Discarding offline message"); } } else if (data.tagName() == "presence") { // qDebug(QString("Received presence stanza from %1").arg(from)); if (!data.hasAttribute("type")) { //this is a HACK to work with pre XMPP 1.0 (old) servers if (!this->isSubscribed(from)) { QString msg = QString("").arg(from); this->send(msg); this->SubscriptionStates.insert(from,"subscribed"); qDebug(QString("%1 is now subscribed and available.").arg(from)); } this->sendQueuedMessages(from); emit this->presenceChanged(from,true); } else if (type == "error") { //@todo errors should be communicated to the data controller, somehow qWarning("Presence error:"); if (!stanza_printed) { qDebug("Stanza data:"); qDebug(s.toString()); stanza_printed=true; } } else { if (type == "unavailable") { qDebug(QString("%1 is unavailable.").arg(from)); //this is a HACK to work with pre XMPP 1.0 (old) servers this->SubscriptionStates.remove(from); emit this->presenceChanged(from,false); } else if (type == "subscribe") { qDebug(QString("%1 wants to subscribe.").arg(from)); QString msg = QString("").arg(from); this->send(msg); if (!this->SubscriptionStates.contains(from)) { this->SubscriptionStates.insert(from,"unsubscribed"); } if (this->SubscriptionStates[from] == "unsubscribed") { msg = QString("").arg(this->getNetworkId()).arg(from); this->send(msg); } } else if (type == "subscribed") { qDebug(QString("Subscribed to %1.").arg(from)); this->SubscriptionStates.insert(from,"subscribed"); this->makeDestinationAvailable(from); } else if (type == "unsubscribe") { qDebug(QString("Unsubscribing presence to %1").arg(from)); QString msg = QString("").arg(from); this->send(msg); if (this->SubscriptionStates.contains(from)) { if (this->SubscriptionStates[from] != "unsubscribed") { msg = QString("").arg(from); this->send(msg); } } } else if (type == "unsubscribed") { qDebug(QString("Unsubscribed from %1.").arg(from)); this->SubscriptionStates.insert(from,"unsubscribed"); // qDebug(QString("Unsubscribed presence from %1").arg(from)); emit this->presenceChanged(from,false); } } } else { qDebug(QString("%1 stanza received, ignoring!").arg(data.tagName())); if (!stanza_printed) { qDebug("Stanza data:"); qDebug(s.toString()); stanza_printed=true; } // emit this->dataAvailable(data,from); //if it's not a "message", pass everything on } } } void GXmppNetwork::clientStreamStanzaWritten() { qDebug("XMPP Stream Stanza written"); } void GXmppNetwork::clientStreamWarning(int w) { qDebug("XMPP Stream warning " + QString::number(w)); qDebug("continuing after warning"); if (this->Stream) this->Stream->continueAfterWarning(); } void GXmppNetwork::clientStreamError(int err) { // copied with small adaptions from the IRIS conntest example // LGPL license, (c) Justin Karneges // http://delta.affinix.com/iris/ QString s; if(err == XMPP::ClientStream::ErrParse) { s = "XML parsing error"; } else if(err == XMPP::ClientStream::ErrProtocol) { s = "XMPP protocol error"; } else if(err == XMPP::ClientStream::ErrStream) { int x = Stream->errorCondition(); if(x == XMPP::Stream::GenericStreamError) s = "generic stream error"; else if(x == XMPP::ClientStream::Conflict) s = "conflict (remote login replacing this one)"; else if(x == XMPP::ClientStream::ConnectionTimeout) s = "timed out from inactivity"; else if(x == XMPP::ClientStream::InternalServerError) s = "internal server error"; else if(x == XMPP::ClientStream::InvalidXml) s = "invalid XML"; else if(x == XMPP::ClientStream::PolicyViolation) s = "policy violation. go to jail!"; else if(x == XMPP::ClientStream::ResourceConstraint) s = "server out of resources"; else if(x == XMPP::ClientStream::SystemShutdown) s = "system is shutting down NOW"; s = QString("XMPP stream error: ") + s; } else if(err == XMPP::ClientStream::ErrConnection) { int x = XmppConnector->errorCode(); QString s; if(x == XMPP::AdvancedConnector::ErrConnectionRefused) s = "unable to connect to server"; else if(x == XMPP::AdvancedConnector::ErrHostNotFound) s = "host not found"; else if(x == XMPP::AdvancedConnector::ErrProxyConnect) s = "proxy connect"; else if(x == XMPP::AdvancedConnector::ErrProxyNeg) s = "proxy negotiating"; else if(x == XMPP::AdvancedConnector::ErrProxyAuth) s = "proxy authorization"; else if(x == XMPP::AdvancedConnector::ErrStream) s = "stream error"; s = QString("Connection error: ") + s; } else if(err == XMPP::ClientStream::ErrNeg) { int x = Stream->errorCondition(); QString s; if(x == XMPP::ClientStream::HostGone) s = "host no longer hosted"; else if(x == XMPP::ClientStream::HostUnknown) s = "host unknown"; else if(x == XMPP::ClientStream::RemoteConnectionFailed) s = "a required remote connection failed"; else if(x == XMPP::ClientStream::SeeOtherHost) s = QString("see other host: [%1]").arg(Stream->errorText()); else if(x == XMPP::ClientStream::UnsupportedVersion) s = "server does not support proper xmpp version"; s = QString("Stream negotiation error: ") + s; } else if(err == XMPP::ClientStream::ErrTLS) { int x = Stream->errorCondition(); QString s; if(x == XMPP::ClientStream::TLSStart) s = "server rejected STARTTLS"; else if(x == XMPP::ClientStream::TLSFail) { int t = TlsHandler->tlsError(); if(t == QCA::TLS::ErrHandshake) s = "TLS handshake error"; else s = "broken security layer (TLS)"; } //nothing to add } else if(err == XMPP::ClientStream::ErrAuth) { int x = Stream->errorCondition(); if(x == XMPP::ClientStream::GenericAuthError) s = "unable to login"; else if(x == XMPP::ClientStream::NoMech) s = "no appropriate auth mechanism available for given security settings"; else if(x == XMPP::ClientStream::BadProto) s = "bad server response"; else if(x == XMPP::ClientStream::BadServ) s = "server failed mutual authentication"; else if(x == XMPP::ClientStream::EncryptionRequired) s = "encryption required for chosen SASL mechanism"; else if(x == XMPP::ClientStream::InvalidAuthzid) s = "invalid authzid"; else if(x == XMPP::ClientStream::InvalidMech) s = "invalid SASL mechanism"; else if(x == XMPP::ClientStream::InvalidRealm) s = "invalid realm"; else if(x == XMPP::ClientStream::MechTooWeak) s = "SASL mechanism too weak for authzid"; else if(x == XMPP::ClientStream::NotAuthorized) s = "not authorized"; else if(x == XMPP::ClientStream::TemporaryAuthFailure) s = "temporary auth failure"; s = QString("Authentication error: ") + s; } else if(err == XMPP::ClientStream::ErrSecurityLayer) s = "broken security layer (SASL)"; qWarning(s); if (!this->isConnected() && this->StayConnected) { qWarning("XMPP Stream got disconnected, reconnecting..."); QTimer::singleShot(100,this,SLOT(initNetwork())); } } //END XMPP slots and signals for network management // }