/* * stream.cpp - handles a client stream * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /* Notes: - For Non-SASL auth (JEP-0078), username and resource fields are required. TODO: - sasl needParams is totally jacked? PLAIN requires authzid, etc - server error handling - reply with protocol errors if the client send something wrong - don't necessarily disconnect on protocol error. prepare for more. - server function - deal with stream 'to' attribute dynamically - flag tls/sasl/binding support dynamically (have the ability to specify extra stream:features) - inform the caller about the user authentication information - sasl security settings - resource-binding interaction - timeouts - allow exchanges of non-standard stanzas - send even if we close prematurely? - ensure ClientStream and child classes are fully deletable after signals - xml:lang in root () element - sasl external - sasl anonymous */ #include"xmpp.h" #include #include #include #include #include #include"bytestream.h" #include"base64.h" #include"hash.h" #include"simplesasl.h" #include"securestream.h" #include"protocol.h" #ifdef XMPP_TEST #include"td.h" #endif //#define XMPP_DEBUG using namespace XMPP; static Debug *debug_ptr = 0; void XMPP::setDebug(Debug *p) { debug_ptr = p; } static TQByteArray randomArray(int size) { TQByteArray a(size); for(int n = 0; n < size; ++n) a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); return a; } static TQString genId() { // need SHA1 here if(!TQCA::isSupported(TQCA::CAP_SHA1)) TQCA::insertProvider(createProviderHash()); return TQCA::SHA1::hashToString(randomArray(128)); } //---------------------------------------------------------------------------- // Stanza //---------------------------------------------------------------------------- Stanza::Error::Error(int _type, int _condition, const TQString &_text, const TQDomElement &_appSpec) { type = _type; condition = _condition; text = _text; appSpec = _appSpec; } class Stanza::Private { public: struct ErrorTypeEntry { const char *str; int type; }; static ErrorTypeEntry errorTypeTable[]; struct ErrorCondEntry { const char *str; int cond; }; static ErrorCondEntry errorCondTable[]; static int stringToKind(const TQString &s) { if(s == "message") return Message; else if(s == "presence") return Presence; else if(s == "iq") return IQ; else return -1; } static TQString kindToString(Kind k) { if(k == Message) return "message"; else if(k == Presence) return "presence"; else return "iq"; } static int stringToErrorType(const TQString &s) { for(int n = 0; errorTypeTable[n].str; ++n) { if(s == errorTypeTable[n].str) return errorTypeTable[n].type; } return -1; } static TQString errorTypeToString(int x) { for(int n = 0; errorTypeTable[n].str; ++n) { if(x == errorTypeTable[n].type) return errorTypeTable[n].str; } return TQString(); } static int stringToErrorCond(const TQString &s) { for(int n = 0; errorCondTable[n].str; ++n) { if(s == errorCondTable[n].str) return errorCondTable[n].cond; } return -1; } static TQString errorCondToString(int x) { for(int n = 0; errorCondTable[n].str; ++n) { if(x == errorCondTable[n].cond) return errorCondTable[n].str; } return TQString(); } Stream *s; TQDomElement e; }; Stanza::Private::ErrorTypeEntry Stanza::Private::errorTypeTable[] = { { "cancel", Cancel }, { "continue", Continue }, { "modify", Modify }, { "auth", Auth }, { "wait", Wait }, { 0, 0 }, }; Stanza::Private::ErrorCondEntry Stanza::Private::errorCondTable[] = { { "bad-request", BadRequest }, { "conflict", Conflict }, { "feature-not-implemented", FeatureNotImplemented }, { "forbidden", Forbidden }, { "internal-server-error", InternalServerError }, { "item-not-found", ItemNotFound }, { "jid-malformed", JidMalformed }, { "not-allowed", NotAllowed }, { "payment-required", PaymentRequired }, { "recipient-unavailable", RecipientUnavailable }, { "registration-required", RegistrationRequired }, { "remote-server-not-found", ServerNotFound }, { "remote-server-timeout", ServerTimeout }, { "resource-constraint", ResourceConstraint }, { "service-unavailable", ServiceUnavailable }, { "subscription-required", SubscriptionRequired }, { "undefined-condition", UndefinedCondition }, { "unexpected-request", UnexpectedRequest }, { 0, 0 }, }; Stanza::Stanza() { d = 0; } Stanza::Stanza(Stream *s, Kind k, const Jid &to, const TQString &type, const TQString &id) { d = new Private; Kind kind; if(k == Message || k == Presence || k == IQ) kind = k; else kind = Message; d->s = s; d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); if(to.isValid()) setTo(to); if(!type.isEmpty()) setType(type); if(!id.isEmpty()) setId(id); } Stanza::Stanza(Stream *s, const TQDomElement &e) { d = 0; if(e.namespaceURI() != s->baseNS()) return; int x = Private::stringToKind(e.tagName()); if(x == -1) return; d = new Private; d->s = s; d->e = e; } Stanza::Stanza(const Stanza &from) { d = 0; *this = from; } Stanza & Stanza::operator=(const Stanza &from) { delete d; d = 0; if(from.d) d = new Private(*from.d); return *this; } Stanza::~Stanza() { delete d; } bool Stanza::isNull() const { return (d ? false: true); } TQDomElement Stanza::element() const { return d->e; } TQString Stanza::toString() const { return Stream::xmlToString(d->e); } TQDomDocument & Stanza::doc() const { return d->s->doc(); } TQString Stanza::baseNS() const { return d->s->baseNS(); } TQString Stanza::xhtmlImNS() const { return d->s->xhtmlImNS(); } TQString Stanza::xhtmlNS() const { return d->s->xhtmlNS(); } TQDomElement Stanza::createElement(const TQString &ns, const TQString &tagName) { return d->s->doc().createElementNS(ns, tagName); } TQDomElement Stanza::createTextElement(const TQString &ns, const TQString &tagName, const TQString &text) { TQDomElement e = d->s->doc().createElementNS(ns, tagName); e.appendChild(d->s->doc().createTextNode(text)); return e; } TQDomElement Stanza::createXHTMLElement(const TQString &xHTML) { TQDomDocument doc; doc.setContent(xHTML, true); TQDomElement root = doc.documentElement(); //TQDomElement e; return (root); } void Stanza::appendChild(const TQDomElement &e) { d->e.appendChild(e); } Stanza::Kind Stanza::kind() const { return (Kind)Private::stringToKind(d->e.tagName()); } void Stanza::setKind(Kind k) { d->e.setTagName(Private::kindToString(k)); } Jid Stanza::to() const { return Jid(d->e.attribute("to")); } Jid Stanza::from() const { return Jid(d->e.attribute("from")); } TQString Stanza::id() const { return d->e.attribute("id"); } TQString Stanza::type() const { return d->e.attribute("type"); } TQString Stanza::lang() const { return d->e.attributeNS(NS_XML, "lang", TQString()); } void Stanza::setTo(const Jid &j) { d->e.setAttribute("to", j.full()); } void Stanza::setFrom(const Jid &j) { d->e.setAttribute("from", j.full()); } void Stanza::setId(const TQString &id) { d->e.setAttribute("id", id); } void Stanza::setType(const TQString &type) { d->e.setAttribute("type", type); } void Stanza::setLang(const TQString &lang) { d->e.setAttribute("xml:lang", lang); } Stanza::Error Stanza::error() const { Error err; TQDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); if(e.isNull()) return err; // type int x = Private::stringToErrorType(e.attribute("type")); if(x != -1) err.type = x; // condition: find first element TQDomNodeList nl = e.childNodes(); TQDomElement t; uint n; for(n = 0; n < nl.count(); ++n) { TQDomNode i = nl.item(n); if(i.isElement()) { t = i.toElement(); break; } } if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { x = Private::stringToErrorCond(t.tagName()); if(x != -1) err.condition = x; } // text t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); if(!t.isNull()) err.text = t.text(); else err.text = e.text(); // appspec: find first non-standard namespaced element nl = e.childNodes(); for(n = 0; n < nl.count(); ++n) { TQDomNode i = nl.item(n); if(i.isElement() && i.namespaceURI() != NS_STANZAS) { err.appSpec = i.toElement(); break; } } return err; } void Stanza::setError(const Error &err) { // create the element if necessary TQDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); if(errElem.isNull()) { errElem = d->e.ownerDocument().createElementNS(d->s->baseNS(), "error"); d->e.appendChild(errElem); } // error type/condition if(d->s->old()) { errElem.setAttribute("code", TQString::number(err.condition)); } else { TQString stype = Private::errorTypeToString(err.type); if(stype.isEmpty()) return; TQString scond = Private::errorCondToString(err.condition); if(scond.isEmpty()) return; errElem.setAttribute("type", stype); errElem.appendChild(d->e.ownerDocument().createElementNS(d->s->baseNS(), scond)); } // text if(d->s->old()) { errElem.appendChild(d->e.ownerDocument().createTextNode(err.text)); } else { TQDomElement te = d->e.ownerDocument().createElementNS(d->s->baseNS(), "text"); te.appendChild(d->e.ownerDocument().createTextNode(err.text)); errElem.appendChild(te); } // application specific errElem.appendChild(err.appSpec); } void Stanza::clearError() { TQDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); if(!errElem.isNull()) d->e.removeChild(errElem); } //---------------------------------------------------------------------------- // Stream //---------------------------------------------------------------------------- static XmlProtocol *foo = 0; Stream::Stream(TQObject *parent) :TQObject(parent) { } Stream::~Stream() { } Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const TQString &type, const TQString &id) { return Stanza(this, k, to, type, id); } Stanza Stream::createStanza(const TQDomElement &e) { return Stanza(this, e); } TQString Stream::xmlToString(const TQDomElement &e, bool clip) { if(!foo) foo = new CoreProtocol; return foo->elementToString(e, clip); } //---------------------------------------------------------------------------- // ClientStream //---------------------------------------------------------------------------- enum { Idle, Connecting, WaitVersion, WaitTLS, NeedParams, Active, Closing }; enum { Client, Server }; class ClientStream::Private { public: Private() { conn = 0; bs = 0; ss = 0; tlsHandler = 0; tls = 0; sasl = 0; in.setAutoDelete(true); oldOnly = false; allowPlain = false; mutualAuth = false; haveLocalAddr = false; minimumSSF = 0; maximumSSF = 0; doBinding = true; in_rrsig = false; reset(); } void reset() { state = Idle; notify = 0; newStanzas = false; sasl_ssf = 0; tls_warned = false; using_tls = false; } Jid jid; TQString server; bool oldOnly; bool allowPlain, mutualAuth; bool haveLocalAddr; TQHostAddress localAddr; TQ_UINT16 localPort; int minimumSSF, maximumSSF; TQString sasl_mech; bool doBinding; bool in_rrsig; Connector *conn; ByteStream *bs; TLSHandler *tlsHandler; TQCA::TLS *tls; TQCA::SASL *sasl; SecureStream *ss; CoreProtocol client; CoreProtocol srv; TQString defRealm; int mode; int state; int notify; bool newStanzas; int sasl_ssf; bool tls_warned, using_tls; bool doAuth; TQStringList sasl_mechlist; int errCond; TQString errText; TQDomElement errAppSpec; TQPtrList in; TQTimer noopTimer; int noop_time; }; ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, TQObject *parent) :Stream(parent) { d = new Private; d->mode = Client; d->conn = conn; connect(d->conn, TQT_SIGNAL(connected()), TQT_SLOT(cr_connected())); connect(d->conn, TQT_SIGNAL(error()), TQT_SLOT(cr_error())); d->noop_time = 0; connect(&d->noopTimer, TQT_SIGNAL(timeout()), TQT_SLOT(doNoop())); d->tlsHandler = tlsHandler; } ClientStream::ClientStream(const TQString &host, const TQString &defRealm, ByteStream *bs, TQCA::TLS *tls, TQObject *parent) :Stream(parent) { d = new Private; d->mode = Server; d->bs = bs; connect(d->bs, TQT_SIGNAL(connectionClosed()), TQT_SLOT(bs_connectionClosed())); connect(d->bs, TQT_SIGNAL(delayedCloseFinished()), TQT_SLOT(bs_delayedCloseFinished())); connect(d->bs, TQT_SIGNAL(error(int)), TQT_SLOT(bs_error(int))); TQByteArray spare = d->bs->read(); d->ss = new SecureStream(d->bs); connect(d->ss, TQT_SIGNAL(readyRead()), TQT_SLOT(ss_readyRead())); connect(d->ss, TQT_SIGNAL(bytesWritten(int)), TQT_SLOT(ss_bytesWritten(int))); connect(d->ss, TQT_SIGNAL(tlsHandshaken()), TQT_SLOT(ss_tlsHandshaken())); connect(d->ss, TQT_SIGNAL(tlsClosed()), TQT_SLOT(ss_tlsClosed())); connect(d->ss, TQT_SIGNAL(error(int)), TQT_SLOT(ss_error(int))); d->server = host; d->defRealm = defRealm; d->tls = tls; d->srv.startClientIn(genId()); //d->srv.startServerIn(genId()); //d->state = Connecting; //d->jid = Jid(); //d->server = TQString(); } ClientStream::~ClientStream() { reset(); delete d; } void ClientStream::reset(bool all) { d->reset(); d->noopTimer.stop(); // delete securestream delete d->ss; d->ss = 0; // reset sasl delete d->sasl; d->sasl = 0; // client if(d->mode == Client) { // reset tls if(d->tlsHandler) d->tlsHandler->reset(); // reset connector if(d->bs) { d->bs->close(); d->bs = 0; } d->conn->done(); // reset state machine d->client.reset(); } // server else { if(d->tls) d->tls->reset(); if(d->bs) { d->bs->close(); d->bs = 0; } d->srv.reset(); } if(all) d->in.clear(); } Jid ClientStream::jid() const { return d->jid; } void ClientStream::connectToServer(const Jid &jid, bool auth) { reset(true); d->state = Connecting; d->jid = jid; d->doAuth = auth; d->server = d->jid.domain(); d->conn->connectToServer(d->server); } void ClientStream::continueAfterWarning() { if(d->state == WaitVersion) { // if we don't have TLS yet, then we're never going to get it if(!d->tls_warned && !d->using_tls) { d->tls_warned = true; d->state = WaitTLS; warning(WarnNoTLS); return; } d->state = Connecting; processNext(); } else if(d->state == WaitTLS) { d->state = Connecting; processNext(); } } void ClientStream::accept() { d->srv.host = d->server; processNext(); } bool ClientStream::isActive() const { return (d->state != Idle) ? true: false; } bool ClientStream::isAuthenticated() const { return (d->state == Active) ? true: false; } void ClientStream::setUsername(const TQString &s) { if(d->sasl) d->sasl->setUsername(s); } void ClientStream::setPassword(const TQString &s) { if(d->client.old) { d->client.setPassword(s); } else { if(d->sasl) d->sasl->setPassword(s); } } void ClientStream::setRealm(const TQString &s) { if(d->sasl) d->sasl->setRealm(s); } void ClientStream::continueAfterParams() { if(d->state == NeedParams) { d->state = Connecting; if(d->client.old) { processNext(); } else { if(d->sasl) d->sasl->continueAfterParams(); } } } void ClientStream::setResourceBinding(bool b) { d->doBinding = b; } void ClientStream::setNoopTime(int mills) { d->noop_time = mills; if(d->state != Active) return; if(d->noop_time == 0) { d->noopTimer.stop(); return; } d->noopTimer.start(d->noop_time); } TQString ClientStream::saslMechanism() const { return d->client.saslMech(); } int ClientStream::saslSSF() const { return d->sasl_ssf; } void ClientStream::setSASLMechanism(const TQString &s) { d->sasl_mech = s; } void ClientStream::setLocalAddr(const TQHostAddress &addr, TQ_UINT16 port) { d->haveLocalAddr = true; d->localAddr = addr; d->localPort = port; } int ClientStream::errorCondition() const { return d->errCond; } TQString ClientStream::errorText() const { return d->errText; } TQDomElement ClientStream::errorAppSpec() const { return d->errAppSpec; } bool ClientStream::old() const { return d->client.old; } void ClientStream::close() { if(d->state == Active) { d->state = Closing; d->client.shutdown(); processNext(); } else if(d->state != Idle && d->state != Closing) { reset(); } } TQDomDocument & ClientStream::doc() const { return d->client.doc; } TQString ClientStream::baseNS() const { return NS_CLIENT; } TQString ClientStream::xhtmlImNS() const { return NS_XHTML_IM; } TQString ClientStream::xhtmlNS() const { return NS_XHTML; } void ClientStream::setAllowPlain(bool b) { d->allowPlain = b; } void ClientStream::setRequireMutualAuth(bool b) { d->mutualAuth = b; } void ClientStream::setSSFRange(int low, int high) { d->minimumSSF = low; d->maximumSSF = high; } void ClientStream::setOldOnly(bool b) { d->oldOnly = b; } bool ClientStream::stanzaAvailable() const { return (!d->in.isEmpty()); } Stanza ClientStream::read() { if(d->in.isEmpty()) return Stanza(); else { Stanza *sp = d->in.getFirst(); Stanza s = *sp; d->in.removeRef(sp); return s; } } void ClientStream::write(const Stanza &s) { if(d->state == Active) { d->client.sendStanza(s.element()); processNext(); } } void ClientStream::cr_connected() { d->bs = d->conn->stream(); connect(d->bs, TQT_SIGNAL(connectionClosed()), TQT_SLOT(bs_connectionClosed())); connect(d->bs, TQT_SIGNAL(delayedCloseFinished()), TQT_SLOT(bs_delayedCloseFinished())); TQByteArray spare = d->bs->read(); d->ss = new SecureStream(d->bs); connect(d->ss, TQT_SIGNAL(readyRead()), TQT_SLOT(ss_readyRead())); connect(d->ss, TQT_SIGNAL(bytesWritten(int)), TQT_SLOT(ss_bytesWritten(int))); connect(d->ss, TQT_SIGNAL(tlsHandshaken()), TQT_SLOT(ss_tlsHandshaken())); connect(d->ss, TQT_SIGNAL(tlsClosed()), TQT_SLOT(ss_tlsClosed())); connect(d->ss, TQT_SIGNAL(error(int)), TQT_SLOT(ss_error(int))); //d->client.startDialbackOut("andbit.net", "im.pyxa.org"); //d->client.startServerOut(d->server); d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth); d->client.setAllowTLS(d->tlsHandler ? true: false); d->client.setAllowBind(d->doBinding); d->client.setAllowPlain(d->allowPlain); /*d->client.jid = d->jid; d->client.server = d->server; d->client.allowPlain = d->allowPlain; d->client.oldOnly = d->oldOnly; d->client.sasl_mech = d->sasl_mech; d->client.doTLS = d->tlsHandler ? true: false; d->client.doBinding = d->doBinding;*/ TQGuardedPtr self = this; connected(); if(!self) return; // immediate SSL? if(d->conn->useSSL()) { d->using_tls = true; d->ss->startTLSClient(d->tlsHandler, d->server, spare); } else { d->client.addIncomingData(spare); processNext(); } } void ClientStream::cr_error() { reset(); error(ErrConnection); } void ClientStream::bs_connectionClosed() { reset(); connectionClosed(); } void ClientStream::bs_delayedCloseFinished() { // we don't care about this (we track all important data ourself) } void ClientStream::bs_error(int) { // TODO } void ClientStream::ss_readyRead() { TQByteArray a = d->ss->read(); #ifdef XMPP_DEBUG TQCString cs(a.data(), a.size()+1); fprintf(stderr, "ClientStream: recv: %d [%s]\n", a.size(), cs.data()); #endif if(d->mode == Client) d->client.addIncomingData(a); else d->srv.addIncomingData(a); if(d->notify & CoreProtocol::NRecv) { #ifdef XMPP_DEBUG printf("We needed data, so let's process it\n"); #endif processNext(); } } void ClientStream::ss_bytesWritten(int bytes) { if(d->mode == Client) d->client.outgoingDataWritten(bytes); else d->srv.outgoingDataWritten(bytes); if(d->notify & CoreProtocol::NSend) { #ifdef XMPP_DEBUG printf("We were waiting for data to be written, so let's process\n"); #endif processNext(); } } void ClientStream::ss_tlsHandshaken() { TQGuardedPtr self = this; securityLayerActivated(LayerTLS); if(!self) return; processNext(); } void ClientStream::ss_tlsClosed() { reset(); connectionClosed(); } void ClientStream::ss_error(int x) { if(x == SecureStream::ErrTLS) { reset(); d->errCond = TLSFail; error(ErrTLS); } else { reset(); error(ErrSecurityLayer); } } void ClientStream::sasl_clientFirstStep(const TQString &mech, const TQByteArray *stepData) { d->client.setSASLFirst(mech, stepData ? *stepData : TQByteArray()); //d->client.sasl_mech = mech; //d->client.sasl_firstStep = stepData ? true : false; //d->client.sasl_step = stepData ? *stepData : TQByteArray(); processNext(); } void ClientStream::sasl_nextStep(const TQByteArray &stepData) { if(d->mode == Client) d->client.setSASLNext(stepData); //d->client.sasl_step = stepData; else d->srv.setSASLNext(stepData); //d->srv.sasl_step = stepData; processNext(); } void ClientStream::sasl_needParams(bool user, bool authzid, bool pass, bool realm) { #ifdef XMPP_DEBUG printf("need params: %d,%d,%d,%d\n", user, authzid, pass, realm); #endif if(authzid && !user) { d->sasl->setAuthzid(d->jid.bare()); //d->sasl->setAuthzid("infiniti.homelesshackers.org"); } if(user || pass || realm) { d->state = NeedParams; needAuthParams(user, pass, realm); } else d->sasl->continueAfterParams(); } void ClientStream::sasl_authCheck(const TQString &user, const TQString &) { //#ifdef XMPP_DEBUG // printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); //#endif TQString u = user; int n = u.find('@'); if(n != -1) u.truncate(n); d->srv.user = u; d->sasl->continueAfterAuthCheck(); } void ClientStream::sasl_authenticated() { #ifdef XMPP_DEBUG printf("sasl authed!!\n"); #endif d->sasl_ssf = d->sasl->ssf(); if(d->mode == Server) { d->srv.setSASLAuthed(); processNext(); } } void ClientStream::sasl_error(int) { //#ifdef XMPP_DEBUG // printf("sasl error: %d\n", c); //#endif // has to be auth error int x = convertedSASLCond(); reset(); d->errCond = x; error(ErrAuth); } void ClientStream::srvProcessNext() { while(1) { printf("Processing step...\n"); if(!d->srv.processStep()) { int need = d->srv.need; if(need == CoreProtocol::NNotify) { d->notify = d->srv.notify; if(d->notify & CoreProtocol::NSend) printf("More data needs to be written to process next step\n"); if(d->notify & CoreProtocol::NRecv) printf("More data is needed to process next step\n"); } else if(need == CoreProtocol::NSASLMechs) { if(!d->sasl) { d->sasl = new TQCA::SASL; connect(d->sasl, TQT_SIGNAL(authCheck(const TQString &, const TQString &)), TQT_SLOT(sasl_authCheck(const TQString &, const TQString &))); connect(d->sasl, TQT_SIGNAL(nextStep(const TQByteArray &)), TQT_SLOT(sasl_nextStep(const TQByteArray &))); connect(d->sasl, TQT_SIGNAL(authenticated()), TQT_SLOT(sasl_authenticated())); connect(d->sasl, TQT_SIGNAL(error(int)), TQT_SLOT(sasl_error(int))); //d->sasl->setAllowAnonymous(false); //d->sasl->setRequirePassCredentials(true); //d->sasl->setExternalAuthID("localhost"); d->sasl->setMinimumSSF(0); d->sasl->setMaximumSSF(256); TQStringList list; // TODO: d->server is probably wrong here if(!d->sasl->startServer("xmpp", d->server, d->defRealm, &list)) { printf("Error initializing SASL\n"); return; } d->sasl_mechlist = list; } d->srv.setSASLMechList(d->sasl_mechlist); continue; } else if(need == CoreProtocol::NStartTLS) { printf("Need StartTLS\n"); if(!d->tls->startServer()) { printf("unable to start server!\n"); // TODO return; } TQByteArray a = d->srv.spare; d->ss->startTLSServer(d->tls, a); } else if(need == CoreProtocol::NSASLFirst) { printf("Need SASL First Step\n"); TQByteArray a = d->srv.saslStep(); d->sasl->putServerFirstStep(d->srv.saslMech(), a); } else if(need == CoreProtocol::NSASLNext) { printf("Need SASL Next Step\n"); TQByteArray a = d->srv.saslStep(); TQCString cs(a.data(), a.size()+1); printf("[%s]\n", cs.data()); d->sasl->putStep(a); } else if(need == CoreProtocol::NSASLLayer) { } // now we can announce stanzas //if(!d->in.isEmpty()) // readyRead(); return; } d->notify = 0; int event = d->srv.event; printf("event: %d\n", event); switch(event) { case CoreProtocol::EError: { printf("Error! Code=%d\n", d->srv.errorCode); reset(); error(ErrProtocol); //handleError(); return; } case CoreProtocol::ESend: { TQByteArray a = d->srv.takeOutgoingData(); TQCString cs(a.size()+1); memcpy(cs.data(), a.data(), a.size()); printf("Need Send: {%s}\n", cs.data()); d->ss->write(a); break; } case CoreProtocol::ERecvOpen: { printf("Break (RecvOpen)\n"); // calculate key TQCString str = TQCA::SHA1::hashToString(TQCString("secret")).utf8(); str = TQCA::SHA1::hashToString(str + "im.pyxa.org").utf8(); str = TQCA::SHA1::hashToString(str + d->srv.id.utf8()).utf8(); d->srv.setDialbackKey(str); //d->srv.setDialbackKey("3c5d721ea2fcc45b163a11420e4e358f87e3142a"); if(d->srv.to != d->server) { // host-gone, host-unknown, see-other-host d->srv.shutdownWithError(CoreProtocol::HostUnknown); } else d->srv.setFrom(d->server); break; } case CoreProtocol::ESASLSuccess: { printf("Break SASL Success\n"); disconnect(d->sasl, TQT_SIGNAL(error(int)), this, TQT_SLOT(sasl_error(int))); TQByteArray a = d->srv.spare; d->ss->setLayerSASL(d->sasl, a); break; } case CoreProtocol::EPeerClosed: { // TODO: this isn' an error printf("peer closed\n"); reset(); error(ErrProtocol); return; } } } } void ClientStream::doReadyRead() { //TQGuardedPtr self = this; readyRead(); //if(!self) // return; //d->in_rrsig = false; } void ClientStream::processNext() { if(d->mode == Server) { srvProcessNext(); return; } TQGuardedPtr self = this; while(1) { #ifdef XMPP_DEBUG printf("Processing step...\n"); #endif bool ok = d->client.processStep(); // deal with send/received items for(TQValueList::ConstIterator it = d->client.transferItemList.begin(); it != d->client.transferItemList.end(); ++it) { const XmlProtocol::TransferItem &i = *it; if(i.isExternal) continue; TQString str; if(i.isString) { // skip whitespace pings if(i.str.stripWhiteSpace().isEmpty()) continue; str = i.str; } else str = d->client.elementToString(i.elem); if(i.isSent) outgoingXml(str); else incomingXml(str); } if(!ok) { bool cont = handleNeed(); // now we can announce stanzas //if(!d->in_rrsig && !d->in.isEmpty()) { if(!d->in.isEmpty()) { //d->in_rrsig = true; TQTimer::singleShot(0, this, TQT_SLOT(doReadyRead())); } if(cont) continue; return; } int event = d->client.event; d->notify = 0; switch(event) { case CoreProtocol::EError: { #ifdef XMPP_DEBUG printf("Error! Code=%d\n", d->client.errorCode); #endif handleError(); return; } case CoreProtocol::ESend: { TQByteArray a = d->client.takeOutgoingData(); #ifdef XMPP_DEBUG TQCString cs(a.size()+1); memcpy(cs.data(), a.data(), a.size()); printf("Need Send: {%s}\n", cs.data()); #endif d->ss->write(a); break; } case CoreProtocol::ERecvOpen: { #ifdef XMPP_DEBUG printf("Break (RecvOpen)\n"); #endif #ifdef XMPP_TEST TQString s = TQString("handshake success (lang=[%1]").arg(d->client.lang); if(!d->client.from.isEmpty()) s += TQString(", from=[%1]").arg(d->client.from); s += ')'; TD::msg(s); #endif if(d->client.old) { d->state = WaitVersion; warning(WarnOldVersion); return; } break; } case CoreProtocol::EFeatures: { #ifdef XMPP_DEBUG printf("Break (Features)\n"); #endif if(!d->tls_warned && !d->using_tls && !d->client.features.tls_supported) { d->tls_warned = true; d->state = WaitTLS; warning(WarnNoTLS); return; } break; } case CoreProtocol::ESASLSuccess: { #ifdef XMPP_DEBUG printf("Break SASL Success\n"); #endif break; } case CoreProtocol::EReady: { #ifdef XMPP_DEBUG printf("Done!\n"); #endif // grab the JID, in case it changed // TODO: d->jid = d->client.jid; d->state = Active; setNoopTime(d->noop_time); authenticated(); if(!self) return; break; } case CoreProtocol::EPeerClosed: { #ifdef XMPP_DEBUG printf("DocumentClosed\n"); #endif reset(); connectionClosed(); return; } case CoreProtocol::EStanzaReady: { #ifdef XMPP_DEBUG printf("StanzaReady\n"); #endif // store the stanza for now, announce after processing all events Stanza s = createStanza(d->client.recvStanza()); if(s.isNull()) break; d->in.append(new Stanza(s)); break; } case CoreProtocol::EStanzaSent: { #ifdef XMPP_DEBUG printf("StanzasSent\n"); #endif stanzaWritten(); if(!self) return; break; } case CoreProtocol::EClosed: { #ifdef XMPP_DEBUG printf("Closed\n"); #endif reset(); delayedCloseFinished(); return; } } } } bool ClientStream::handleNeed() { int need = d->client.need; if(need == CoreProtocol::NNotify) { d->notify = d->client.notify; #ifdef XMPP_DEBUG if(d->notify & CoreProtocol::NSend) printf("More data needs to be written to process next step\n"); if(d->notify & CoreProtocol::NRecv) printf("More data is needed to process next step\n"); #endif return false; } d->notify = 0; switch(need) { case CoreProtocol::NStartTLS: { #ifdef XMPP_DEBUG printf("Need StartTLS\n"); #endif d->using_tls = true; d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); return false; } case CoreProtocol::NSASLFirst: { #ifdef XMPP_DEBUG printf("Need SASL First Step\n"); #endif // no SASL plugin? fall back to Simple SASL if(!TQCA::isSupported(TQCA::CAP_SASL)) { // Simple SASL needs MD5. do we have that either? if(!TQCA::isSupported(TQCA::CAP_MD5)) TQCA::insertProvider(createProviderHash()); TQCA::insertProvider(createProviderSimpleSASL()); } d->sasl = new TQCA::SASL; connect(d->sasl, TQT_SIGNAL(clientFirstStep(const TQString &, const TQByteArray *)), TQT_SLOT(sasl_clientFirstStep(const TQString &, const TQByteArray *))); connect(d->sasl, TQT_SIGNAL(nextStep(const TQByteArray &)), TQT_SLOT(sasl_nextStep(const TQByteArray &))); connect(d->sasl, TQT_SIGNAL(needParams(bool, bool, bool, bool)), TQT_SLOT(sasl_needParams(bool, bool, bool, bool))); connect(d->sasl, TQT_SIGNAL(authenticated()), TQT_SLOT(sasl_authenticated())); connect(d->sasl, TQT_SIGNAL(error(int)), TQT_SLOT(sasl_error(int))); if(d->haveLocalAddr) d->sasl->setLocalAddr(d->localAddr, d->localPort); if(d->conn->havePeerAddress()) d->sasl->setRemoteAddr(d->conn->peerAddress(), d->conn->peerPort()); d->sasl->setAllowAnonymous(false); //d->sasl_mech = "ANONYMOUS"; //d->sasl->setRequirePassCredentials(true); //d->sasl->setExternalAuthID("localhost"); //d->sasl->setExternalSSF(64); //d->sasl_mech = "EXTERNAL"; d->sasl->setAllowPlain(d->allowPlain); d->sasl->setRequireMutualAuth(d->mutualAuth); d->sasl->setMinimumSSF(d->minimumSSF); d->sasl->setMaximumSSF(d->maximumSSF); TQStringList ml; if(!d->sasl_mech.isEmpty()) ml += d->sasl_mech; else ml = d->client.features.sasl_mechs; if(!d->sasl->startClient("xmpp", d->server, ml, true)) { int x = convertedSASLCond(); reset(); d->errCond = x; error(ErrAuth); return false; } return false; } case CoreProtocol::NSASLNext: { #ifdef XMPP_DEBUG printf("Need SASL Next Step\n"); #endif TQByteArray a = d->client.saslStep(); d->sasl->putStep(a); return false; } case CoreProtocol::NSASLLayer: { // SecureStream will handle the errors from this point disconnect(d->sasl, TQT_SIGNAL(error(int)), this, TQT_SLOT(sasl_error(int))); d->ss->setLayerSASL(d->sasl, d->client.spare); if(d->sasl_ssf > 0) { TQGuardedPtr self = this; securityLayerActivated(LayerSASL); if(!self) return false; } break; } case CoreProtocol::NPassword: { #ifdef XMPP_DEBUG printf("Need Password\n"); #endif d->state = NeedParams; needAuthParams(false, true, false); return false; } } return true; } int ClientStream::convertedSASLCond() const { int x = d->sasl->errorCondition(); if(x == TQCA::SASL::NoMech) return NoMech; else if(x == TQCA::SASL::BadProto) return BadProto; else if(x == TQCA::SASL::BadServ) return BadServ; else if(x == TQCA::SASL::TooWeak) return MechTooWeak; else return GenericAuthError; } void ClientStream::doNoop() { if(d->state == Active) { #ifdef XMPP_DEBUG printf("doPing\n"); #endif d->client.sendWhitespace(); processNext(); } } void ClientStream::writeDirect(const TQString &s) { if(d->state == Active) { #ifdef XMPP_DEBUG printf("writeDirect\n"); #endif d->client.sendDirect(s); processNext(); } } void ClientStream::handleError() { int c = d->client.errorCode; if(c == CoreProtocol::ErrParse) { reset(); error(ErrParse); } else if(c == CoreProtocol::ErrProtocol) { reset(); error(ErrProtocol); } else if(c == CoreProtocol::ErrStream) { int x = d->client.errCond; TQString text = d->client.errText; TQDomElement appSpec = d->client.errAppSpec; int connErr = -1; int strErr = -1; switch(x) { case CoreProtocol::BadFormat: { break; } // should NOT happen (we send the right format) case CoreProtocol::BadNamespacePrefix: { break; } // should NOT happen (we send prefixes) case CoreProtocol::Conflict: { strErr = Conflict; break; } case CoreProtocol::ConnectionTimeout: { strErr = ConnectionTimeout; break; } case CoreProtocol::HostGone: { connErr = HostGone; break; } case CoreProtocol::HostUnknown: { connErr = HostUnknown; break; } case CoreProtocol::ImproperAddressing: { break; } // should NOT happen (we aren't a server) case CoreProtocol::InternalServerError: { strErr = InternalServerError; break; } case CoreProtocol::InvalidFrom: { strErr = InvalidFrom; break; } case CoreProtocol::InvalidId: { break; } // should NOT happen (clients don't specify id) case CoreProtocol::InvalidNamespace: { break; } // should NOT happen (we set the right ns) case CoreProtocol::InvalidXml: { strErr = InvalidXml; break; } // shouldn't happen either, but just in case ... case CoreProtocol::StreamNotAuthorized: { break; } // should NOT happen (we're not stupid) case CoreProtocol::PolicyViolation: { strErr = PolicyViolation; break; } case CoreProtocol::RemoteConnectionFailed: { connErr = RemoteConnectionFailed; break; } case CoreProtocol::ResourceConstraint: { strErr = ResourceConstraint; break; } case CoreProtocol::RestrictedXml: { strErr = InvalidXml; break; } // group with this one case CoreProtocol::SeeOtherHost: { connErr = SeeOtherHost; break; } case CoreProtocol::SystemShutdown: { strErr = SystemShutdown; break; } case CoreProtocol::UndefinedCondition: { break; } // leave as null error case CoreProtocol::UnsupportedEncoding: { break; } // should NOT happen (we send good encoding) case CoreProtocol::UnsupportedStanzaType: { break; } // should NOT happen (we're not stupid) case CoreProtocol::UnsupportedVersion: { connErr = UnsupportedVersion; break; } case CoreProtocol::XmlNotWellFormed: { strErr = InvalidXml; break; } // group with this one default: { break; } } reset(); d->errText = text; d->errAppSpec = appSpec; if(connErr != -1) { d->errCond = connErr; error(ErrNeg); } else { if(strErr != -1) d->errCond = strErr; else d->errCond = GenericStreamError; error(ErrStream); } } else if(c == CoreProtocol::ErrStartTLS) { reset(); d->errCond = TLSStart; error(ErrTLS); } else if(c == CoreProtocol::ErrAuth) { int x = d->client.errCond; int r = GenericAuthError; if(d->client.old) { if(x == 401) // not authorized r = NotAuthorized; else if(x == 409) // conflict r = GenericAuthError; else if(x == 406) // not acceptable (this should NOT happen) r = GenericAuthError; } else { switch(x) { case CoreProtocol::Aborted: { r = GenericAuthError; break; } // should NOT happen (we never send ) case CoreProtocol::IncorrectEncoding: { r = GenericAuthError; break; } // should NOT happen case CoreProtocol::InvalidAuthzid: { r = InvalidAuthzid; break; } case CoreProtocol::InvalidMech: { r = InvalidMech; break; } case CoreProtocol::MechTooWeak: { r = MechTooWeak; break; } case CoreProtocol::NotAuthorized: { r = NotAuthorized; break; } case CoreProtocol::TemporaryAuthFailure: { r = TemporaryAuthFailure; break; } } } reset(); d->errCond = r; error(ErrAuth); } else if(c == CoreProtocol::ErrPlain) { reset(); d->errCond = NoMech; error(ErrAuth); } else if(c == CoreProtocol::ErrBind) { int r = -1; if(d->client.errCond == CoreProtocol::BindBadRequest) { // should NOT happen } else if(d->client.errCond == CoreProtocol::BindNotAllowed) { r = BindNotAllowed; } else if(d->client.errCond == CoreProtocol::BindConflict) { r = BindConflict; } if(r != -1) { reset(); d->errCond = r; error(ErrBind); } else { reset(); error(ErrProtocol); } } } //---------------------------------------------------------------------------- // Debug //---------------------------------------------------------------------------- Debug::~Debug() { } #ifdef XMPP_TEST TD::TD() { } TD::~TD() { } void TD::msg(const TQString &s) { if(debug_ptr) debug_ptr->msg(s); } void TD::outgoingTag(const TQString &s) { if(debug_ptr) debug_ptr->outgoingTag(s); } void TD::incomingTag(const TQString &s) { if(debug_ptr) debug_ptr->incomingTag(s); } void TD::outgoingXml(const TQDomElement &e) { if(debug_ptr) debug_ptr->outgoingXml(e); } void TD::incomingXml(const TQDomElement &e) { if(debug_ptr) debug_ptr->incomingXml(e); } #endif