<commit_before>/*
 * stream.cpp - handles a client stream
 * Copyright (C) 2003  <NAME>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 </stream:stream> even if we close prematurely?
    - ensure ClientStream and child classes are fully deletable after signals
    - xml:lang in root (<stream>) element
    - sasl external
    - sasl anonymous
*/

#include"xmpp.h"

#include<qtextstream.h>
#include<qguardedptr.h>
#include<qtimer.h>
#include<qca.h>
#include<stdlib.h>
#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 QByteArray randomArray(int size)
{
	QByteArray a(size);
	for(int n = 0; n < size; ++n)
		a[n] = (char)(256.0*rand()/(RAND_MAX+1.0));
	return a;
}

static QString genId()
{
	// need SHA1 here
	if(!QCA::isSupported(QCA::CAP_SHA1))
		QCA::insertProvider(createProviderHash());

	return QCA::SHA1::hashToString(randomArray(128));
}

//----------------------------------------------------------------------------
// Stanza
//----------------------------------------------------------------------------
Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_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 QString &s)
	{
		if(s == "message")
			return Message;
		else if(s == "presence")
			return Presence;
		else if(s == "iq")
			return IQ;
		else
			return -1;
	}

	static QString kindToString(Kind k)
	{
		if(k == Message)
			return "message";
		else if(k == Presence)
			return "presence";
		else
			return "iq";
	}

	static int stringToErrorType(const QString &s)
	{
		for(int n = 0; errorTypeTable[n].str; ++n) {
			if(s == errorTypeTable[n].str)
				return errorTypeTable[n].type;
		}
		return -1;
	}

	static QString errorTypeToString(int x)
	{
		for(int n = 0; errorTypeTable[n].str; ++n) {
			if(x == errorTypeTable[n].type)
				return errorTypeTable[n].str;
		}
		return QString();
	}

	static int stringToErrorCond(const QString &s)
	{
		for(int n = 0; errorCondTable[n].str; ++n) {
			if(s == errorCondTable[n].str)
				return errorCondTable[n].cond;
		}
		return -1;
	}

	static QString errorCondToString(int x)
	{
		for(int n = 0; errorCondTable[n].str; ++n) {
			if(x == errorCondTable[n].cond)
				return errorCondTable[n].str;
		}
		return QString();
	}

	Stream *s;
	QDomElement 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 QString &type, const QString &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 QDomElement &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);
}

QDomElement Stanza::element() const
{
	return d->e;
}

QString Stanza::toString() const
{
	return Stream::xmlToString(d->e);
}

QDomDocument & Stanza::doc() const
{
	return d->s->doc();
}

QString Stanza::baseNS() const
{
	return d->s->baseNS();
}

QDomElement Stanza::createElement(const QString &ns, const QString &tagName)
{
	return d->s->doc().createElementNS(ns, tagName);
}

QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text)
{
	QDomElement e = d->s->doc().createElementNS(ns, tagName);
	e.appendChild(d->s->doc().createTextNode(text));
	return e;
}

void Stanza::appendChild(const QDomElement &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"));
}

QString Stanza::id() const
{
	return d->e.attribute("id");
}

QString Stanza::type() const
{
	return d->e.attribute("type");
}

QString Stanza::lang() const
{
	return d->e.attributeNS(NS_XML, "lang", QString());
}

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 QString &id)
{
	d->e.setAttribute("id", id);
}

void Stanza::setType(const QString &type)
{
	d->e.setAttribute("type", type);
}

void Stanza::setLang(const QString &lang)
{
	d->e.setAttribute("xml:lang", lang);
}

Stanza::Error Stanza::error() const
{
	Error err;
	QDomElement 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
	QDomNodeList nl = e.childNodes();
	QDomElement t;
	uint n;
	for(n = 0; n < nl.count(); ++n) {
		QDomNode 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) {
		QDomNode 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
	QDomElement 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", QString::number(err.condition));
	}
	else {
		QString stype = Private::errorTypeToString(err.type);
		if(stype.isEmpty())
			return;
		QString 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 {
		QDomElement 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()
{
	QDomElement 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(QObject *parent)
:QObject(parent)
{
}

Stream::~Stream()
{
}

Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id)
{
	return Stanza(this, k, to, type, id);
}

Stanza Stream::createStanza(const QDomElement &e)
{
	return Stanza(this, e);
}

QString Stream::xmlToString(const QDomElement &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;

		reset();
	}

	void reset()
	{
		state = Idle;
		notify = 0;
		newStanzas = false;
		sasl_ssf = 0;
		tls_warned = false;
		using_tls = false;
	}

	Jid jid;
	QString server;
	bool oldOnly;
	bool allowPlain, mutualAuth;
	bool haveLocalAddr;
	QHostAddress localAddr;
	Q_UINT16 localPort;
	int minimumSSF, maximumSSF;
	QString sasl_mech;
	bool doBinding;

	Connector *conn;
	ByteStream *bs;
	TLSHandler *tlsHandler;
	QCA::TLS *tls;
	QCA::SASL *sasl;
	SecureStream *ss;
	CoreProtocol client;
	CoreProtocol srv;

	QString defRealm;

	int mode;
	int state;
	int notify;
	bool newStanzas;
	int sasl_ssf;
	bool tls_warned, using_tls;
	bool doAuth;

	QStringList sasl_mechlist;

	int errCond;
	QString errText;
	QDomElement errAppSpec;

	QPtrList<Stanza> in;

	QTimer noopTimer;
	int noop_time;
};

ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent)
:Stream(parent)
{
	d = new Private;
	d->mode = Client;
	d->conn = conn;
	connect(d->conn, SIGNAL(connected()), SLOT(cr_connected()));
	connect(d->conn, SIGNAL(error()), SLOT(cr_error()));

	d->noop_time = 0;
	connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop()));

	d->tlsHandler = tlsHandler;
}

ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent)
:Stream(parent)
{
	d = new Private;
	d->mode = Server;
	d->bs = bs;
	connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed()));
	connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished()));
	connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int)));

	QByteArray spare = d->bs->read();

	d->ss = new SecureStream(d->bs);
	connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead()));
	connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int)));
	connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken()));
	connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed()));
	connect(d->ss, SIGNAL(error(int)), 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 = QString();
}

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 QString &s)
{
	if(d->sasl)
		d->sasl->setUsername(s);
}

void ClientStream::setPassword(const QString &s)
{
	if(d->client.old) {
		d->client.setPassword(s);
	}
	else {
		if(d->sasl)
			d->sasl->setPassword(s);
	}
}

void ClientStream::setRealm(const QString &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);
}

QString ClientStream::saslMechanism() const
{
	return d->client.saslMech();
}

int ClientStream::saslSSF() const
{
	return d->sasl_ssf;
}

void ClientStream::setSASLMechanism(const QString &s)
{
	d->sasl_mech = s;
}

void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port)
{
	d->haveLocalAddr = true;
	d->localAddr = addr;
	d->localPort = port;
}

int ClientStream::errorCondition() const
{
	return d->errCond;
}

QString ClientStream::errorText() const
{
	return d->errText;
}

QDomElement 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();
	}
}

QDomDocument & ClientStream::doc() const
{
	return d->client.doc;
}

QString ClientStream::baseNS() const
{
	return NS_CLIENT;
}

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, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed()));
	connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished()));

	QByteArray spare = d->bs->read();

	d->ss = new SecureStream(d->bs);
	connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead()));
	connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int)));
	connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken()));
	connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed()));
	connect(d->ss, SIGNAL(error(int)), 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;*/

	connected();

	// 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()
{
	QByteArray a = d->ss->read();

#ifdef XMPP_DEBUG
	QCString 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()
{
	securityLayerActivated(LayerTLS);
	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 QString &mech, const QByteArray *stepData)
{
	d->client.setSASLFirst(mech, stepData ? *stepData : QByteArray());
	//d->client.sasl_mech = mech;
	//d->client.sasl_firstStep = stepData ? true : false;
	//d->client.sasl_step = stepData ? *stepData : QByteArray();

	processNext();
}

void ClientStream::sasl_nextStep(const QByteArray &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("<EMAIL>.<EMAIL>");
	}
	if(user || pass || realm) {
		d->state = NeedParams;
		needAuthParams(user, pass, realm);
	}
	else
		d->sasl->continueAfterParams();
}

void ClientStream::sasl_authCheck(const QString &user, const QString &)
{
//#ifdef XMPP_DEBUG
//	printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1());
//#endif
	QString 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 QCA::SASL;
					connect(d->sasl, SIGNAL(authCheck(const QString &, const QString &)), SLOT(sasl_authCheck(const QString &, const QString &)));
					connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &)));
					connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated()));
					connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int)));

					//d->sasl->setAllowAnonymous(false);
					//d->sasl->setRequirePassCredentials(true);
					//d->sasl->setExternalAuthID("localhost");

					d->sasl->setMinimumSSF(0);
					d->sasl->setMaximumSSF(256);

					QStringList 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;
				}
				QByteArray a = d->srv.spare;
				d->ss->startTLSServer(d->tls, a);
			}
			else if(need == CoreProtocol::NSASLFirst) {
				printf("Need SASL First Step\n");
				QByteArray a = d->srv.saslStep();
				d->sasl->putServerFirstStep(d->srv.saslMech(), a);
			}
			else if(need == CoreProtocol::NSASLNext) {
				printf("Need SASL Next Step\n");
				QByteArray a = d->srv.saslStep();
				QCString 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: {
				QByteArray a = d->srv.takeOutgoingData();
				QCString 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
				QCString str = QCA::SHA1::hashToString("secret").utf8();
				str = QCA::SHA1::hashToString(str + "im.pyxa.org").utf8();
				str = QCA::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, SIGNAL(error(int)), this, SLOT(sasl_error(int)));
				QByteArray 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::processNext()
{
	if(d->mode == Server) {
		srvProcessNext();
		return;
	}

	while(1) {
#ifdef XMPP_DEBUG
		printf("Processing step...\n");
#endif
		if(!d->client.processStep()) {
			bool cont = handleNeed();

			// now we can announce stanzas
			if(!d->in.isEmpty())
				readyRead();

			if(cont)
				continue;
			return;
		}

		int event = d->client.event;
		switch(event) {
			case CoreProtocol::EError: {
#ifdef XMPP_DEBUG
				printf("Error! Code=%d\n", d->client.errorCode);
#endif
				handleError();
				return;
			}
			case CoreProtocol::ESend: {
				QByteArray a = d->client.takeOutgoingData();
#ifdef XMPP_DEBUG
				QCString cs(a.size()+1);
				memcpy(cs.data(), a.data(), a.size());
				printf("Need Send: {%s}\n", cs.data());
#endif<commit_msg>Make Iris not continue with stream processing if there are unhandled warnings (patch by Justin)<commit_after>/*
 * stream.cpp - handles a client stream
 * Copyright (C) 2003  <NAME>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 </stream:stream> even if we close prematurely?
    - ensure ClientStream and child classes are fully deletable after signals
    - xml:lang in root (<stream>) element
    - sasl external
    - sasl anonymous
*/

#include"xmpp.h"

#include<qtextstream.h>
#include<qguardedptr.h>
#include<qtimer.h>
#include<qca.h>
#include<stdlib.h>
#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 QByteArray randomArray(int size)
{
	QByteArray a(size);
	for(int n = 0; n < size; ++n)
		a[n] = (char)(256.0*rand()/(RAND_MAX+1.0));
	return a;
}

static QString genId()
{
	// need SHA1 here
	if(!QCA::isSupported(QCA::CAP_SHA1))
		QCA::insertProvider(createProviderHash());

	return QCA::SHA1::hashToString(randomArray(128));
}

//----------------------------------------------------------------------------
// Stanza
//----------------------------------------------------------------------------
Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_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 QString &s)
	{
		if(s == "message")
			return Message;
		else if(s == "presence")
			return Presence;
		else if(s == "iq")
			return IQ;
		else
			return -1;
	}

	static QString kindToString(Kind k)
	{
		if(k == Message)
			return "message";
		else if(k == Presence)
			return "presence";
		else
			return "iq";
	}

	static int stringToErrorType(const QString &s)
	{
		for(int n = 0; errorTypeTable[n].str; ++n) {
			if(s == errorTypeTable[n].str)
				return errorTypeTable[n].type;
		}
		return -1;
	}

	static QString errorTypeToString(int x)
	{
		for(int n = 0; errorTypeTable[n].str; ++n) {
			if(x == errorTypeTable[n].type)
				return errorTypeTable[n].str;
		}
		return QString();
	}

	static int stringToErrorCond(const QString &s)
	{
		for(int n = 0; errorCondTable[n].str; ++n) {
			if(s == errorCondTable[n].str)
				return errorCondTable[n].cond;
		}
		return -1;
	}

	static QString errorCondToString(int x)
	{
		for(int n = 0; errorCondTable[n].str; ++n) {
			if(x == errorCondTable[n].cond)
				return errorCondTable[n].str;
		}
		return QString();
	}

	Stream *s;
	QDomElement 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 QString &type, const QString &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 QDomElement &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);
}

QDomElement Stanza::element() const
{
	return d->e;
}

QString Stanza::toString() const
{
	return Stream::xmlToString(d->e);
}

QDomDocument & Stanza::doc() const
{
	return d->s->doc();
}

QString Stanza::baseNS() const
{
	return d->s->baseNS();
}

QDomElement Stanza::createElement(const QString &ns, const QString &tagName)
{
	return d->s->doc().createElementNS(ns, tagName);
}

QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text)
{
	QDomElement e = d->s->doc().createElementNS(ns, tagName);
	e.appendChild(d->s->doc().createTextNode(text));
	return e;
}

void Stanza::appendChild(const QDomElement &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"));
}

QString Stanza::id() const
{
	return d->e.attribute("id");
}

QString Stanza::type() const
{
	return d->e.attribute("type");
}

QString Stanza::lang() const
{
	return d->e.attributeNS(NS_XML, "lang", QString());
}

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 QString &id)
{
	d->e.setAttribute("id", id);
}

void Stanza::setType(const QString &type)
{
	d->e.setAttribute("type", type);
}

void Stanza::setLang(const QString &lang)
{
	d->e.setAttribute("xml:lang", lang);
}

Stanza::Error Stanza::error() const
{
	Error err;
	QDomElement 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
	QDomNodeList nl = e.childNodes();
	QDomElement t;
	uint n;
	for(n = 0; n < nl.count(); ++n) {
		QDomNode 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) {
		QDomNode 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
	QDomElement 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", QString::number(err.condition));
	}
	else {
		QString stype = Private::errorTypeToString(err.type);
		if(stype.isEmpty())
			return;
		QString 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 {
		QDomElement 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()
{
	QDomElement 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(QObject *parent)
:QObject(parent)
{
}

Stream::~Stream()
{
}

Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id)
{
	return Stanza(this, k, to, type, id);
}

Stanza Stream::createStanza(const QDomElement &e)
{
	return Stanza(this, e);
}

QString Stream::xmlToString(const QDomElement &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;

		reset();
	}

	void reset()
	{
		state = Idle;
		notify = 0;
		newStanzas = false;
		sasl_ssf = 0;
		tls_warned = false;
		using_tls = false;
	}

	Jid jid;
	QString server;
	bool oldOnly;
	bool allowPlain, mutualAuth;
	bool haveLocalAddr;
	QHostAddress localAddr;
	Q_UINT16 localPort;
	int minimumSSF, maximumSSF;
	QString sasl_mech;
	bool doBinding;

	Connector *conn;
	ByteStream *bs;
	TLSHandler *tlsHandler;
	QCA::TLS *tls;
	QCA::SASL *sasl;
	SecureStream *ss;
	CoreProtocol client;
	CoreProtocol srv;

	QString defRealm;

	int mode;
	int state;
	int notify;
	bool newStanzas;
	int sasl_ssf;
	bool tls_warned, using_tls;
	bool doAuth;

	QStringList sasl_mechlist;

	int errCond;
	QString errText;
	QDomElement errAppSpec;

	QPtrList<Stanza> in;

	QTimer noopTimer;
	int noop_time;
};

ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent)
:Stream(parent)
{
	d = new Private;
	d->mode = Client;
	d->conn = conn;
	connect(d->conn, SIGNAL(connected()), SLOT(cr_connected()));
	connect(d->conn, SIGNAL(error()), SLOT(cr_error()));

	d->noop_time = 0;
	connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop()));

	d->tlsHandler = tlsHandler;
}

ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent)
:Stream(parent)
{
	d = new Private;
	d->mode = Server;
	d->bs = bs;
	connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed()));
	connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished()));
	connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int)));

	QByteArray spare = d->bs->read();

	d->ss = new SecureStream(d->bs);
	connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead()));
	connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int)));
	connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken()));
	connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed()));
	connect(d->ss, SIGNAL(error(int)), 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 = QString();
}

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 QString &s)
{
	if(d->sasl)
		d->sasl->setUsername(s);
}

void ClientStream::setPassword(const QString &s)
{
	if(d->client.old) {
		d->client.setPassword(s);
	}
	else {
		if(d->sasl)
			d->sasl->setPassword(s);
	}
}

void ClientStream::setRealm(const QString &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);
}

QString ClientStream::saslMechanism() const
{
	return d->client.saslMech();
}

int ClientStream::saslSSF() const
{
	return d->sasl_ssf;
}

void ClientStream::setSASLMechanism(const QString &s)
{
	d->sasl_mech = s;
}

void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port)
{
	d->haveLocalAddr = true;
	d->localAddr = addr;
	d->localPort = port;
}

int ClientStream::errorCondition() const
{
	return d->errCond;
}

QString ClientStream::errorText() const
{
	return d->errText;
}

QDomElement 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();
	}
}

QDomDocument & ClientStream::doc() const
{
	return d->client.doc;
}

QString ClientStream::baseNS() const
{
	return NS_CLIENT;
}

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, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed()));
	connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished()));

	QByteArray spare = d->bs->read();

	d->ss = new SecureStream(d->bs);
	connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead()));
	connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int)));
	connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken()));
	connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed()));
	connect(d->ss, SIGNAL(error(int)), 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;*/

	connected();

	// 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()
{
	QByteArray a = d->ss->read();

#ifdef XMPP_DEBUG
	QCString 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()
{
	securityLayerActivated(LayerTLS);
	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 QString &mech, const QByteArray *stepData)
{
	d->client.setSASLFirst(mech, stepData ? *stepData : QByteArray());
	//d->client.sasl_mech = mech;
	//d->client.sasl_firstStep = stepData ? true : false;
	//d->client.sasl_step = stepData ? *stepData : QByteArray();

	processNext();
}

void ClientStream::sasl_nextStep(const QByteArray &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("inf<EMAIL>.<EMAIL>.<EMAIL>");
	}
	if(user || pass || realm) {
		d->state = NeedParams;
		needAuthParams(user, pass, realm);
	}
	else
		d->sasl->continueAfterParams();
}

void ClientStream::sasl_authCheck(const QString &user, const QString &)
{
//#ifdef XMPP_DEBUG
//	printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1());
//#endif
	QString 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 QCA::SASL;
					connect(d->sasl, SIGNAL(authCheck(const QString &, const QString &)), SLOT(sasl_authCheck(const QString &, const QString &)));
					connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &)));
					connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated()));
					connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int)));

					//d->sasl->setAllowAnonymous(false);
					//d->sasl->setRequirePassCredentials(true);
					//d->sasl->setExternalAuthID("localhost");

					d->sasl->setMinimumSSF(0);
					d->sasl->setMaximumSSF(256);

					QStringList 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;
				}
				QByteArray a = d->srv.spare;
				d->ss->startTLSServer(d->tls, a);
			}
			else if(need == CoreProtocol::NSASLFirst) {
				printf("Need SASL First Step\n");
				QByteArray a = d->srv.saslStep();
				d->sasl->putServerFirstStep(d->srv.saslMech(), a);
			}
			else if(need == CoreProtocol::NSASLNext) {
				printf("Need SASL Next Step\n");
				QByteArray a = d->srv.saslStep();
				QCString 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: {
				QByteArray a = d->srv.takeOutgoingData();
				QCString 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
				QCString str = QCA::SHA1::hashToString("secret").utf8();
				str = QCA::SHA1::hashToString(str + "im.pyxa.org").utf8();
				str = QCA::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, SIGNAL(error(int)), this, SLOT(sasl_error(int)));
				QByteArray 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::processNext()
{
	if(d->mode == Server) {
		srvProcessNext();
		return;
	}

	while(1) {
#ifdef XMPP_DEBUG
		printf("Processing step...\n");
#endif
		if(!d->client.processStep()) {
			bool cont = handleNeed();

			// now we can announce stanzas
			if(!d->in.isEmpty())
				readyRead();

			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: {
				QByteArray a = d->client.takeOutgoingData();
#ifdef XMPP_DEBUG
				QCString cs(a.size()+1);
				memcpy(cs.data(), a.data(), a.size());
				printf("Need Send: {%s}\n", cs.data());
#endif
{'fraction_non_alphanumeric': 0.19031239427089208, 'fraction_numerical': 0.004981015751287545, 'mean_word_length': 3.655495274763738, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 715, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6031679', 'n_tokens_mistral': 21562, 'n_tokens_neox': 19900, 'n_words': 4027}