1 /*
2  * ibb.cpp - Inband bytestream
3  * Copyright (C) 2001, 2002  Justin Karneges
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  *
19  */
20 
21 #include "xmpp_ibb.h"
22 
23 #include <qtimer.h>
24 #include "xmpp_xmlcommon.h"
25 #include <QtCrypto>
26 
27 #include <stdlib.h>
28 
29 #define IBB_PACKET_SIZE   4096
30 #define IBB_PACKET_DELAY  0
31 
32 using namespace XMPP;
33 
34 static int num_conn = 0;
35 static int id_conn = 0;
36 static const char *IBB_NS = "http://jabber.org/protocol/ibb";
37 
38 //----------------------------------------------------------------------------
39 // IBBConnection
40 //----------------------------------------------------------------------------
41 class IBBConnection::Private
42 {
43 public:
Private()44 	Private() {}
45 
46 	int state;
47 	quint16 seq;
48 	Jid peer;
49 	QString sid;
50 	IBBManager *m;
51 	JT_IBB *j;
52 	QString iq_id;
53 	QString stanza;
54 
55 	int blockSize;
56 	//QByteArray recvBuf, sendBuf;
57 	bool closePending, closing;
58 
59 	int id; // connection id
60 };
61 
IBBConnection(IBBManager * m)62 IBBConnection::IBBConnection(IBBManager *m)
63 	: BSConnection(m)
64 {
65 	d = new Private;
66 	d->m = m;
67 	d->j = 0;
68 	d->blockSize = IBB_PACKET_SIZE;
69 	resetConnection();
70 
71 	++num_conn;
72 	d->id = id_conn++;
73 #ifdef IBB_DEBUG
74 	qDebug("IBBConnection[%d]: constructing, count=%d", d->id, num_conn);
75 #endif
76 }
77 
resetConnection(bool clear)78 void IBBConnection::resetConnection(bool clear)
79 {
80 	d->m->unlink(this);
81 	d->state = Idle;
82 	d->closePending = false;
83 	d->closing = false;
84 	d->seq = 0;
85 
86 	delete d->j;
87 	d->j = 0;
88 
89 	clearWriteBuffer();
90 	if(clear)
91 		clearReadBuffer();
92 	setOpenMode(clear || !bytesAvailable()? QIODevice::NotOpen : QIODevice::ReadOnly);
93 }
94 
~IBBConnection()95 IBBConnection::~IBBConnection()
96 {
97 	clearWriteBuffer(); // drop buffer to make closing procedure fast
98 	close();
99 
100 	--num_conn;
101 #ifdef IBB_DEBUG
102 	qDebug("IBBConnection[%d]: destructing, count=%d", d->id, num_conn);
103 #endif
104 
105 	delete d;
106 }
107 
connectToJid(const Jid & peer,const QString & sid)108 void IBBConnection::connectToJid(const Jid &peer, const QString &sid)
109 {
110 	close();
111 	resetConnection(true);
112 
113 	d->state = Requesting;
114 	d->peer = peer;
115 	d->sid = sid;
116 
117 #ifdef IBB_DEBUG
118 	qDebug("IBBConnection[%d]: initiating request to %s", d->id, qPrintable(peer.full()));
119 #endif
120 
121 	d->j = new JT_IBB(d->m->client()->rootTask());
122 	connect(d->j, SIGNAL(finished()), SLOT(ibb_finished()));
123 	d->j->request(d->peer, d->sid);
124 	d->j->go(true);
125 }
126 
accept()127 void IBBConnection::accept()
128 {
129 	if(d->state != WaitingForAccept)
130 		return;
131 
132 #ifdef IBB_DEBUG
133 	qDebug("IBBConnection[%d]: accepting %s [%s]", d->id,
134 		   qPrintable(d->peer.full()), qPrintable(d->sid));
135 #endif
136 
137 	d->m->doAccept(this, d->iq_id);
138 	d->state = Active;
139 	setOpenMode(QIODevice::ReadWrite);
140 	d->m->link(this);
141 
142 	emit connected(); // to be compatible with S5B
143 }
144 
close()145 void IBBConnection::close()
146 {
147 	if(d->state == Idle)
148 		return;
149 
150 	if(d->state == WaitingForAccept) {
151 		d->m->doReject(this, d->iq_id, Stanza::Error::Forbidden, "Rejected");
152 		resetConnection();
153 		return;
154 	}
155 
156 #ifdef IBB_DEBUG
157 	qDebug("IBBConnection[%d]: closing", d->id);
158 #endif
159 
160 	if(d->state == Active) {
161 		d->closePending = true;
162 		trySend();
163 
164 		// if there is data pending to be written, then pend the closing
165 		if(bytesToWrite() > 0) {
166 			return;
167 		}
168 	}
169 
170 	resetConnection();
171 }
172 
state() const173 int IBBConnection::state() const
174 {
175 	return d->state;
176 }
177 
peer() const178 Jid IBBConnection::peer() const
179 {
180 	return d->peer;
181 }
182 
sid() const183 QString IBBConnection::sid() const
184 {
185 	return d->sid;
186 }
187 
manager() const188 BytestreamManager* IBBConnection::manager() const
189 {
190 	return d->m;
191 }
192 
isOpen() const193 bool IBBConnection::isOpen() const
194 {
195 	if(d->state == Active)
196 		return true;
197 	else
198 		return false;
199 }
200 
writeData(const char * data,qint64 maxSize)201 qint64 IBBConnection::writeData(const char *data, qint64 maxSize)
202 {
203 	if(d->state != Active || d->closePending || d->closing) {
204 		setErrorString("read only");
205 		return 0;
206 	}
207 
208 	ByteStream::appendWrite(QByteArray::fromRawData(data, maxSize));
209 	trySend();
210 	return maxSize;
211 }
212 
waitForAccept(const Jid & peer,const QString & iq_id,const QString & sid,int blockSize,const QString & stanza)213 void IBBConnection::waitForAccept(const Jid &peer, const QString &iq_id,
214 								  const QString &sid, int blockSize,
215 								  const QString &stanza)
216 {
217 	close();
218 	resetConnection(true);
219 
220 	d->state = WaitingForAccept;
221 	d->peer = peer;
222 	d->iq_id = iq_id;
223 	d->sid = sid;
224 	d->blockSize = blockSize;
225 	d->stanza = stanza;
226 
227 }
228 
takeIncomingData(const IBBData & ibbData)229 void IBBConnection::takeIncomingData(const IBBData &ibbData)
230 {
231 	if (ibbData.seq != d->seq) {
232 		d->m->doReject(this, d->iq_id, Stanza::Error::UnexpectedRequest, "Invalid sequence");
233 		return;
234 	}
235 	if (ibbData.data.size() > d->blockSize) {
236 		d->m->doReject(this, d->iq_id, Stanza::Error::BadRequest, "Too much data");
237 		return;
238 	}
239 	d->seq++;
240 	appendRead(ibbData.data);
241 
242 	emit readyRead();
243 }
244 
setRemoteClosed()245 void IBBConnection::setRemoteClosed()
246 {
247 	resetConnection();
248 	emit connectionClosed();
249 }
250 
ibb_finished()251 void IBBConnection::ibb_finished()
252 {
253 	JT_IBB *j = d->j;
254 	d->j = 0;
255 
256 	if(j->success()) {
257 		if(j->mode() == JT_IBB::ModeRequest) {
258 
259 #ifdef IBB_DEBUG
260 			qDebug("IBBConnection[%d]: %s [%s] accepted.", d->id,
261 				   qPrintable(d->peer.full()), qPrintable(d->sid));
262 #endif
263 			d->state = Active;
264 			setOpenMode(QIODevice::ReadWrite);
265 			d->m->link(this);
266 			emit connected();
267 		}
268 		else {
269 			if(d->closing) {
270 				resetConnection();
271 				emit delayedCloseFinished();
272 			}
273 
274 			if(bytesToWrite() || d->closePending)
275 				QTimer::singleShot(IBB_PACKET_DELAY, this, SLOT(trySend()));
276 
277 			emit bytesWritten(j->bytesWritten()); // will delete this connection if no bytes left.
278 		}
279 	}
280 	else {
281 		if(j->mode() == JT_IBB::ModeRequest) {
282 #ifdef IBB_DEBUG
283 			qDebug("IBBConnection[%d]: %s refused.", d->id, qPrintable(d->peer.full()));
284 #endif
285 			resetConnection(true);
286 			setError(ErrRequest);
287 		}
288 		else {
289 			resetConnection(true);
290 			setError(ErrData);
291 		}
292 	}
293 }
294 
trySend()295 void IBBConnection::trySend()
296 {
297 	// if we already have an active task, then don't do anything
298 	if(d->j)
299 		return;
300 
301 	QByteArray a = takeWrite(d->blockSize);
302 
303 	if(a.isEmpty()) {
304 		if (!d->closePending)
305 			return; // null operation?
306 		d->closePending = false;
307 		d->closing = true;
308 #ifdef IBB_DEBUG
309 		qDebug("IBBConnection[%d]: closing", d->id);
310 #endif
311 	}
312 	else {
313 #ifdef IBB_DEBUG
314 		qDebug("IBBConnection[%d]: sending [%d] bytes (%d bytes left)",
315 			   d->id, a.size(), bytesToWrite());
316 #endif
317 	}
318 
319 	d->j = new JT_IBB(d->m->client()->rootTask());
320 	connect(d->j, SIGNAL(finished()), SLOT(ibb_finished()));
321 	if (d->closing) {
322 		d->j->close(d->peer, d->sid);
323 	}
324 	else {
325 		d->j->sendData(d->peer, IBBData(d->sid, d->seq++, a));
326 	}
327 	d->j->go(true);
328 }
329 
330 
331 
332 //----------------------------------------------------------------------------
333 // IBBData
334 //----------------------------------------------------------------------------
fromXml(const QDomElement & e)335 IBBData& IBBData::fromXml(const QDomElement &e)
336 {
337 	sid = e.attribute("sid");
338 	seq = e.attribute("seq").toInt();
339 	data = QByteArray::fromBase64(e.text().toUtf8());
340 	return *this;
341 }
342 
toXml(QDomDocument * doc) const343 QDomElement IBBData::toXml(QDomDocument *doc) const
344 {
345 	QDomElement query = textTag(doc, "data", QString::fromLatin1(data.toBase64())).toElement();
346 	query.setAttribute("xmlns", IBB_NS);
347 	query.setAttribute("seq", QString::number(seq));
348 	query.setAttribute("sid", sid);
349 	return query;
350 }
351 
352 //----------------------------------------------------------------------------
353 // IBBManager
354 //----------------------------------------------------------------------------
355 class IBBManager::Private
356 {
357 public:
Private()358 	Private() {}
359 
360 	Client *client;
361 	IBBConnectionList activeConns;
362 	IBBConnectionList incomingConns;
363 	JT_IBB *ibb;
364 };
365 
IBBManager(Client * parent)366 IBBManager::IBBManager(Client *parent)
367 	: BytestreamManager(parent)
368 {
369 	d = new Private;
370 	d->client = parent;
371 
372 	d->ibb = new JT_IBB(d->client->rootTask(), true);
373 	connect(d->ibb,
374 			SIGNAL(incomingRequest(Jid,QString,QString,int,QString)),
375 			SLOT(ibb_incomingRequest(Jid,QString,QString,int,QString)));
376 	connect(d->ibb,
377 			SIGNAL(incomingData(Jid,QString,IBBData,Stanza::Kind)),
378 			SLOT(takeIncomingData(Jid,QString,IBBData,Stanza::Kind)));
379 	connect(d->ibb,
380 			SIGNAL(closeRequest(Jid,QString,QString)),
381 			SLOT(ibb_closeRequest(Jid,QString,QString)));
382 }
383 
~IBBManager()384 IBBManager::~IBBManager()
385 {
386 	qDeleteAll(d->incomingConns);
387 	d->incomingConns.clear();
388 	delete d->ibb;
389 	delete d;
390 }
391 
ns()392 const char* IBBManager::ns()
393 {
394 	return IBB_NS;
395 }
396 
client() const397 Client *IBBManager::client() const
398 {
399 	return d->client;
400 }
401 
createConnection()402 BSConnection *IBBManager::createConnection()
403 {
404 	return new IBBConnection(this);
405 }
406 
takeIncoming()407 IBBConnection *IBBManager::takeIncoming()
408 {
409 	return d->incomingConns.isEmpty()? 0 : d->incomingConns.takeFirst();
410 }
411 
ibb_incomingRequest(const Jid & from,const QString & id,const QString & sid,int blockSize,const QString & stanza)412 void IBBManager::ibb_incomingRequest(const Jid &from, const QString &id,
413 									 const QString &sid, int blockSize,
414 									 const QString &stanza)
415 {
416 	// create a "waiting" connection
417 	IBBConnection *c = new IBBConnection(this);
418 	c->waitForAccept(from, id, sid, blockSize, stanza);
419 	d->incomingConns.append(c);
420 	emit incomingReady();
421 }
422 
takeIncomingData(const Jid & from,const QString & id,const IBBData & data,Stanza::Kind sKind)423 void IBBManager::takeIncomingData(const Jid &from, const QString &id,
424 								  const IBBData &data, Stanza::Kind sKind)
425 {
426 	IBBConnection *c = findConnection(data.sid, from);
427 	if(!c) {
428 		if (sKind == Stanza::IQ) {
429 			d->ibb->respondError(from, id, Stanza::Error::ItemNotFound, "No such stream");
430 		}
431 		// TODO imeplement xep-0079 error processing in case of Stanza::Message
432 	}
433 	else {
434 		if (sKind == Stanza::IQ) {
435 			d->ibb->respondAck(from, id);
436 		}
437 		c->takeIncomingData(data);
438 	}
439 }
440 
ibb_closeRequest(const Jid & from,const QString & id,const QString & sid)441 void IBBManager::ibb_closeRequest(const Jid &from, const QString &id,
442 								  const QString &sid)
443 {
444 	IBBConnection *c = findConnection(sid, from);
445 	if(!c) {
446 		d->ibb->respondError(from, id, Stanza::Error::ItemNotFound, "No such stream");
447 	}
448 	else {
449 		d->ibb->respondAck(from, id);
450 		c->setRemoteClosed();
451 	}
452 }
453 
isAcceptableSID(const XMPP::Jid & jid,const QString & sid) const454 bool IBBManager::isAcceptableSID(const XMPP::Jid &jid, const QString&sid) const
455 {
456 	return findConnection(sid, jid) == NULL;
457 }
458 
sidPrefix() const459 const char* IBBManager::sidPrefix() const
460 {
461 	return "ibb_";
462 }
463 
link(IBBConnection * c)464 void IBBManager::link(IBBConnection *c)
465 {
466 	d->activeConns.append(c);
467 }
468 
unlink(IBBConnection * c)469 void IBBManager::unlink(IBBConnection *c)
470 {
471 	d->activeConns.removeAll(c);
472 }
473 
findConnection(const QString & sid,const Jid & peer) const474 IBBConnection *IBBManager::findConnection(const QString &sid, const Jid &peer) const
475 {
476 	foreach(IBBConnection* c, d->activeConns) {
477 		if(c->sid() == sid && (peer.isEmpty() || c->peer().compare(peer)) )
478 			return c;
479 	}
480 	return 0;
481 }
482 
doAccept(IBBConnection * c,const QString & id)483 void IBBManager::doAccept(IBBConnection *c, const QString &id)
484 {
485 	d->ibb->respondAck(c->peer(), id);
486 }
487 
doReject(IBBConnection * c,const QString & id,Stanza::Error::ErrorCond cond,const QString & str)488 void IBBManager::doReject(IBBConnection *c, const QString &id,
489 						  Stanza::Error::ErrorCond cond, const QString &str)
490 {
491 	d->ibb->respondError(c->peer(), id, cond, str);
492 }
493 
494 
495 //----------------------------------------------------------------------------
496 // JT_IBB
497 //----------------------------------------------------------------------------
498 class JT_IBB::Private
499 {
500 public:
Private()501 	Private() {}
502 
503 	QDomElement iq;
504 	int mode;
505 	bool serve;
506 	Jid to;
507 	QString sid;
508 	int bytesWritten;
509 };
510 
JT_IBB(Task * parent,bool serve)511 JT_IBB::JT_IBB(Task *parent, bool serve)
512 :Task(parent)
513 {
514 	d = new Private;
515 	d->serve = serve;
516 }
517 
~JT_IBB()518 JT_IBB::~JT_IBB()
519 {
520 	delete d;
521 }
522 
request(const Jid & to,const QString & sid)523 void JT_IBB::request(const Jid &to, const QString &sid)
524 {
525 	d->mode = ModeRequest;
526 	QDomElement iq;
527 	d->to = to;
528 	iq = createIQ(doc(), "set", to.full(), id());
529 	QDomElement query = doc()->createElement("open");
530 	//genUniqueKey
531 	query.setAttribute("xmlns", IBB_NS);
532 	query.setAttribute("sid", sid);
533 	query.setAttribute("block-size", IBB_PACKET_SIZE);
534 	query.setAttribute("stanza", "iq");
535 	iq.appendChild(query);
536 	d->iq = iq;
537 }
538 
sendData(const Jid & to,const IBBData & ibbData)539 void JT_IBB::sendData(const Jid &to, const IBBData &ibbData)
540 {
541 	d->mode = ModeSendData;
542 	QDomElement iq;
543 	d->to = to;
544 	d->bytesWritten = ibbData.data.size();
545 	iq = createIQ(doc(), "set", to.full(), id());
546 	iq.appendChild(ibbData.toXml(doc()));
547 	d->iq = iq;
548 }
549 
close(const Jid & to,const QString & sid)550 void JT_IBB::close(const Jid &to, const QString &sid)
551 {
552 	d->mode = ModeSendData;
553 	QDomElement iq;
554 	d->to = to;
555 	iq = createIQ(doc(), "set", to.full(), id());
556 	QDomElement query = iq.appendChild(doc()->createElement("close")).toElement();
557 	query.setAttribute("xmlns", IBB_NS);
558 	query.setAttribute("sid", sid);
559 
560 	d->iq = iq;
561 }
562 
respondError(const Jid & to,const QString & id,Stanza::Error::ErrorCond cond,const QString & text)563 void JT_IBB::respondError(const Jid &to, const QString &id,
564 						  Stanza::Error::ErrorCond cond, const QString &text)
565 {
566 	QDomElement iq = createIQ(doc(), "error", to.full(), id);
567 	Stanza::Error error(Stanza::Error::Cancel, cond, text);
568 	iq.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS()));
569 	send(iq);
570 }
571 
respondAck(const Jid & to,const QString & id)572 void JT_IBB::respondAck(const Jid &to, const QString &id)
573 {
574 	send( createIQ(doc(), "result", to.full(), id) );
575 }
576 
onGo()577 void JT_IBB::onGo()
578 {
579 	send(d->iq);
580 }
581 
take(const QDomElement & e)582 bool JT_IBB::take(const QDomElement &e)
583 {
584 	if(d->serve) {
585 		// must be an iq-set tag
586 		if(e.tagName() != "iq" || e.attribute("type") != "set")
587 			return false;
588 
589 		QString id = e.attribute("id");
590 		QString from = e.attribute("from");
591 		QDomElement openEl = e.firstChildElement("open");
592 		if (!openEl.isNull() && openEl.attribute("xmlns") == IBB_NS) {
593 			emit incomingRequest(Jid(from), id,
594 							openEl.attribute("sid"),
595 							openEl.attribute("block-size").toInt(),
596 							openEl.attribute("stanza"));
597 			return true;
598 		}
599 		QDomElement dataEl = e.firstChildElement("data");
600 		if (!dataEl.isNull() && dataEl.attribute("xmlns") == IBB_NS) {
601 			IBBData data;
602 			emit incomingData(Jid(from), id, data.fromXml(dataEl), Stanza::IQ);
603 			return true;
604 		}
605 		QDomElement closeEl = e.firstChildElement("close");
606 		if (!closeEl.isNull() && closeEl.attribute("xmlns") == IBB_NS) {
607 			emit closeRequest(Jid(from), id, closeEl.attribute("sid"));
608 			return true;
609 		}
610 		return false;
611 	}
612 	else {
613 		Jid from(e.attribute("from"));
614 		if(e.attribute("id") != id() || !d->to.compare(from))
615 			return false;
616 
617 		if(e.attribute("type") == "result") {
618 			setSuccess();
619 		}
620 		else {
621 			setError(e);
622 		}
623 
624 		return true;
625 	}
626 }
627 
jid() const628 Jid JT_IBB::jid() const
629 {
630 	return d->to;
631 }
632 
mode() const633 int JT_IBB::mode() const
634 {
635 	return d->mode;
636 }
637 
bytesWritten() const638 int JT_IBB::bytesWritten() const
639 {
640 	return d->bytesWritten;
641 }
642 
643