1/***************************************************************************
2 * Copyright (C) 2005-2014 by the Quassel Project *
3 * devel@quassel-irc.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
9 * *
10 * This program 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 *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21#include <QHostInfo>
22
23#include "corenetwork.h"
24
25#include "core.h"
26#include "coreidentity.h"
27#include "corenetworkconfig.h"
28#include "coresession.h"
29#include "coreuserinputhandler.h"
30#include "networkevent.h"
31
32INIT_SYNCABLE_OBJECT(CoreNetwork)
33CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
34 : Network(networkid, session),
35 _coreSession(session),
36 _userInputHandler(new CoreUserInputHandler(this)),
37 _autoReconnectCount(0),
38 _quitRequested(false),
39
40 _previousConnectionAttemptFailed(false),
41 _lastUsedServerIndex(0),
42
43 _lastPingTime(0),
44 _pingCount(0),
45 _sendPings(false),
46 _requestedUserModes('-')
47{
48 _autoReconnectTimer.setSingleShot(true);
49 connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
50
51 setPingInterval(networkConfig()->pingInterval());
52 connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
53
54 setAutoWhoDelay(networkConfig()->autoWhoDelay());
55 setAutoWhoInterval(networkConfig()->autoWhoInterval());
56
57 QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
58 foreach(QString chan, channels.keys()) {
59 _channelKeys[chan.toLower()] = channels[chan];
60 }
61
62 connect(networkConfig(), SIGNAL(pingTimeoutEnabledSet(bool)), SLOT(enablePingTimeout(bool)));
63 connect(networkConfig(), SIGNAL(pingIntervalSet(int)), SLOT(setPingInterval(int)));
64 connect(networkConfig(), SIGNAL(autoWhoEnabledSet(bool)), SLOT(setAutoWhoEnabled(bool)));
65 connect(networkConfig(), SIGNAL(autoWhoIntervalSet(int)), SLOT(setAutoWhoInterval(int)));
66 connect(networkConfig(), SIGNAL(autoWhoDelaySet(int)), SLOT(setAutoWhoDelay(int)));
67
68 connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
69 connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
70 connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
71 connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
72
73 connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
74 connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
75 connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
76 connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
77 connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
78#ifdef HAVE_SSL
79 connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
80 connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
81#endif
82 connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
83
84 if (Quassel::isOptionSet("oidentd")) {
85 connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
86 connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
87 }
88}
89
90
91CoreNetwork::~CoreNetwork()
92{
93 if (connectionState() != Disconnected && connectionState() != Network::Reconnecting)
94 disconnectFromIrc(false); // clean up, but this does not count as requested disconnect!
95 disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
96 delete _userInputHandler;
97}
98
99
100QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
101{
102 if (!bufferName.isEmpty()) {
103 IrcChannel *channel = ircChannel(bufferName);
104 if (channel)
105 return channel->decodeString(string);
106 }
107 return decodeString(string);
108}
109
110
111QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const
112{
113 IrcUser *user = ircUser(userNick);
114 if (user)
115 return user->decodeString(string);
116 return decodeString(string);
117}
118
119
120QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const
121{
122 if (!bufferName.isEmpty()) {
123 IrcChannel *channel = ircChannel(bufferName);
124 if (channel)
125 return channel->encodeString(string);
126 }
127 return encodeString(string);
128}
129
130
131QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const
132{
133 IrcUser *user = ircUser(userNick);
134 if (user)
135 return user->encodeString(string);
136 return encodeString(string);
137}
138
139
140void CoreNetwork::connectToIrc(bool reconnecting)
141{
142 if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
143 _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
144 if (unlimitedReconnectRetries())
145 _autoReconnectCount = -1;
146 else
147 _autoReconnectCount = autoReconnectRetries();
148 }
149 if (serverList().isEmpty()) {
150 qWarning() << "Server list empty, ignoring connect request!";
151 return;
152 }
153 CoreIdentity *identity = identityPtr();
154 if (!identity) {
155 qWarning() << "Invalid identity configures, ignoring connect request!";
156 return;
157 }
158
159 // cleaning up old quit reason
160 _quitReason.clear();
161
162 // use a random server?
163 if (useRandomServer()) {
164 _lastUsedServerIndex = qrand() % serverList().size();
165 }
166 else if (_previousConnectionAttemptFailed) {
167 // cycle to next server if previous connection attempt failed
168 displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
169 if (++_lastUsedServerIndex >= serverList().size()) {
170 _lastUsedServerIndex = 0;
171 }
172 }
173 _previousConnectionAttemptFailed = false;
174
175 Server server = usedServer();
176 displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
177 displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
178
179 if (server.useProxy) {
180 QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
181 socket.setProxy(proxy);
182 }
183 else {
184 socket.setProxy(QNetworkProxy::NoProxy);
185 }
186
187 enablePingTimeout();
188
189 // Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
190 // connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
191 QHostInfo::fromName(server.host);
192
193#ifdef HAVE_SSL
194 if (server.useSsl) {
195 CoreIdentity *identity = identityPtr();
196 if (identity) {
197 socket.setLocalCertificate(identity->sslCert());
198 socket.setPrivateKey(identity->sslKey());
199 }
200 socket.connectToHostEncrypted(server.host, server.port);
201 }
202 else {
203 socket.connectToHost(server.host, server.port);
204 }
205#else
206 socket.connectToHost(server.host, server.port);
207#endif
208}
209
210
211void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect)
212{
213 _quitRequested = requested; // see socketDisconnected();
214 if (!withReconnect) {
215 _autoReconnectTimer.stop();
216 _autoReconnectCount = 0; // prohibiting auto reconnect
217 }
218 disablePingTimeout();
219 _msgQueue.clear();
220
221 IrcUser *me_ = me();
222 if (me_) {
223 QString awayMsg;
224 if (me_->isAway())
225 awayMsg = me_->awayMessage();
226 Core::setAwayMessage(userId(), networkId(), awayMsg);
227 }
228
229 if (reason.isEmpty() && identityPtr())
230 _quitReason = identityPtr()->quitReason();
231 else
232 _quitReason = reason;
233
234 displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
235 switch (socket.state()) {
236 case QAbstractSocket::ConnectedState:
237 userInputHandler()->issueQuit(_quitReason);
238 if (requested || withReconnect) {
239 // the irc server has 10 seconds to close the socket
240 _socketCloseTimer.start(10000);
241 break;
242 }
243 default:
244 socket.close();
245 socketDisconnected();
246 }
247}
248
249
250void CoreNetwork::userInput(BufferInfo buf, QString msg)
251{
252 userInputHandler()->handleUserInput(buf, msg);
253}
254
255
256void CoreNetwork::putRawLine(QByteArray s)
257{
258 if (_tokenBucket > 0)
259 writeToSocket(s);
260 else
261 _msgQueue.append(s);
262}
263
264
265void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> &params, const QByteArray &prefix)
266{
267 QByteArray msg;
268
269 if (!prefix.isEmpty())
270 msg += ":" + prefix + " ";
271 msg += cmd.toUpper().toLatin1();
272
273 for (int i = 0; i < params.size(); i++) {
274 msg += " ";
275
276 if (i == params.size() - 1 && (params[i].contains(' ') || (!params[i].isEmpty() && params[i][0] == ':')))
277 msg += ":";
278
279 msg += params[i];
280 }
281
282 putRawLine(msg);
283}
284
285
286void CoreNetwork::setChannelJoined(const QString &channel)
287{
288 _autoWhoQueue.prepend(channel.toLower()); // prepend so this new chan is the first to be checked
289
290 Core::setChannelPersistent(userId(), networkId(), channel, true);
291 Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
292}
293
294
295void CoreNetwork::setChannelParted(const QString &channel)
296{
297 removeChannelKey(channel);
298 _autoWhoQueue.removeAll(channel.toLower());
299 _autoWhoPending.remove(channel.toLower());
300
301 Core::setChannelPersistent(userId(), networkId(), channel, false);
302}
303
304
305void CoreNetwork::addChannelKey(const QString &channel, const QString &key)
306{
307 if (key.isEmpty()) {
308 removeChannelKey(channel);
309 }
310 else {
311 _channelKeys[channel.toLower()] = key;
312 }
313}
314
315
316void CoreNetwork::removeChannelKey(const QString &channel)
317{
318 _channelKeys.remove(channel.toLower());
319}
320
321
322#ifdef HAVE_QCA2
323Cipher *CoreNetwork::cipher(const QString &target)
324{
325 if (target.isEmpty())
326 return 0;
327
328 if (!Cipher::neededFeaturesAvailable())
329 return 0;
330
331 CoreIrcChannel *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
332 if (channel) {
333 return channel->cipher();
334 }
335 CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
336 if (user) {
337 return user->cipher();
338 } else if (!isChannelName(target)) {
339 return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
340 }
341 return 0;
342}
343
344
345QByteArray CoreNetwork::cipherKey(const QString &target) const
346{
347 CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
348 if (c)
349 return c->cipher()->key();
350
351 CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
352 if (u)
353 return u->cipher()->key();
354
355 return QByteArray();
356}
357
358
359void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
360{
361 CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
362 if (c) {
363 c->setEncrypted(c->cipher()->setKey(key));
364 return;
365 }
366
367 CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
368 if (!u && !isChannelName(target))
369 u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
370
371 if (u) {
372 u->setEncrypted(u->cipher()->setKey(key));
373 return;
374 }
375}
376
377
378bool CoreNetwork::cipherUsesCBC(const QString &target)
379{
380 CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
381 if (c)
382 return c->cipher()->usesCBC();
383 CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
384 if (u)
385 return u->cipher()->usesCBC();
386
387 return false;
388}
389#endif /* HAVE_QCA2 */
390
391bool CoreNetwork::setAutoWhoDone(const QString &channel)
392{
393 QString chan = channel.toLower();
394 if (_autoWhoPending.value(chan, 0) <= 0)
395 return false;
396 if (--_autoWhoPending[chan] <= 0)
397 _autoWhoPending.remove(chan);
398 return true;
399}
400
401
402void CoreNetwork::setMyNick(const QString &mynick)
403{
404 Network::setMyNick(mynick);
405 if (connectionState() == Network::Initializing)
406 networkInitialized();
407}
408
409
410void CoreNetwork::socketHasData()
411{
412 while (socket.canReadLine()) {
413 QByteArray s = socket.readLine();
414 if (s.endsWith("\r\n"))
415 s.chop(2);
416 else if (s.endsWith("\n"))
417 s.chop(1);
418 NetworkDataEvent *event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
419#if QT_VERSION >= 0x040700
420 event->setTimestamp(QDateTime::currentDateTimeUtc());
421#else
422 event->setTimestamp(QDateTime::currentDateTime().toUTC());
423#endif
424 emit newEvent(event);
425 }
426}
427
428
429void CoreNetwork::socketError(QAbstractSocket::SocketError error)
430{
431 if (_quitRequested && error == QAbstractSocket::RemoteHostClosedError)
432 return;
433
434 _previousConnectionAttemptFailed = true;
435 qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
436 emit connectionError(socket.errorString());
437 displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
438 emitConnectionError(socket.errorString());
439 if (socket.state() < QAbstractSocket::ConnectedState) {
440 socketDisconnected();
441 }
442}
443
444
445void CoreNetwork::socketInitialized()
446{
447 Server server = usedServer();
448#ifdef HAVE_SSL
449 if (server.useSsl && !socket.isEncrypted())
450 return;
451#endif
452#if QT_VERSION >= 0x040600
453 socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
454#endif
455 CoreIdentity *identity = identityPtr();
456 if (!identity) {
457 qCritical() << "Identity invalid!";
458 disconnectFromIrc();
459 return;
460 }
461
462 emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
463
464 // TokenBucket to avoid sending too much at once
465 _messageDelay = 2200; // this seems to be a safe value (2.2 seconds delay)
466 _burstSize = 5;
467 _tokenBucket = _burstSize; // init with a full bucket
468 _tokenBucketTimer.start(_messageDelay);
469
470 if (networkInfo().useSasl) {
471 putRawLine(serverEncode(QString("CAP REQ :sasl")));
472 }
473 if (!server.password.isEmpty()) {
474 putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
475 }
476 QString nick;
477 if (identity->nicks().isEmpty()) {
478 nick = "quassel";
479 qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id();
480 }
481 else {
482 nick = identity->nicks()[0];
483 }
484 putRawLine(serverEncode(QString("NICK :%1").arg(nick)));
485 putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
486}
487
488
489void CoreNetwork::socketDisconnected()
490{
491 disablePingTimeout();
492 _msgQueue.clear();
493
494 _autoWhoCycleTimer.stop();
495 _autoWhoTimer.stop();
496 _autoWhoQueue.clear();
497 _autoWhoPending.clear();
498
499 _socketCloseTimer.stop();
500
501 _tokenBucketTimer.stop();
502
503 IrcUser *me_ = me();
504 if (me_) {
505 foreach(QString channel, me_->channels())
506 displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
507 }
508
509 setConnected(false);
510 emit disconnected(networkId());
511 emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
512 if (_quitRequested) {
513 _quitRequested = false;
514 setConnectionState(Network::Disconnected);
515 Core::setNetworkConnected(userId(), networkId(), false);
516 }
517 else if (_autoReconnectCount != 0) {
518 setConnectionState(Network::Reconnecting);
519 if (_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries())
520 doAutoReconnect(); // first try is immediate
521 else
522 _autoReconnectTimer.start();
523 }
524}
525
526
527void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState)
528{
529 Network::ConnectionState state;
530 switch (socketState) {
531 case QAbstractSocket::UnconnectedState:
532 state = Network::Disconnected;
533 break;
534 case QAbstractSocket::HostLookupState:
535 case QAbstractSocket::ConnectingState:
536 state = Network::Connecting;
537 break;
538 case QAbstractSocket::ConnectedState:
539 state = Network::Initializing;
540 break;
541 case QAbstractSocket::ClosingState:
542 state = Network::Disconnecting;
543 break;
544 default:
545 state = Network::Disconnected;
546 }
547 setConnectionState(state);
548}
549
550
551void CoreNetwork::networkInitialized()
552{
553 setConnectionState(Network::Initialized);
554 setConnected(true);
555 _quitRequested = false;
556
557 if (useAutoReconnect()) {
558 // reset counter
559 _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries();
560 }
561
562 // restore away state
563 QString awayMsg = Core::awayMessage(userId(), networkId());
564 if (!awayMsg.isEmpty())
565 userInputHandler()->handleAway(BufferInfo(), Core::awayMessage(userId(), networkId()));
566
567 sendPerform();
568
569 _sendPings = true;
570
571 if (networkConfig()->autoWhoEnabled()) {
572 _autoWhoCycleTimer.start();
573 _autoWhoTimer.start();
574 startAutoWhoCycle(); // FIXME wait for autojoin to be completed
575 }
576
577 Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer); // create status buffer
578 Core::setNetworkConnected(userId(), networkId(), true);
579}
580
581
582void CoreNetwork::sendPerform()
583{
584 BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
585
586 // do auto identify
587 if (useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
588 userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
589 }
590
591 // restore old user modes if server default mode is set.
592 IrcUser *me_ = me();
593 if (me_) {
594 if (!me_->userModes().isEmpty()) {
595 restoreUserModes();
596 }
597 else {
598 connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
599 connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
600 }
601 }
602
603 // send perform list
604 foreach(QString line, perform()) {
605 if (!line.isEmpty()) userInput(statusBuf, line);
606 }
607
608 // rejoin channels we've been in
609 if (rejoinChannels()) {
610 QStringList channels, keys;
611 foreach(QString chan, coreSession()->persistentChannels(networkId()).keys()) {
612 QString key = channelKey(chan);
613 if (!key.isEmpty()) {
614 channels.prepend(chan);
615 keys.prepend(key);
616 }
617 else {
618 channels.append(chan);
619 }
620 }
621 QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
622 if (!joinString.isEmpty())
623 userInputHandler()->handleJoin(statusBuf, joinString);
624 }
625}
626
627
628void CoreNetwork::restoreUserModes()
629{
630 IrcUser *me_ = me();
631 Q_ASSERT(me_);
632
633 disconnect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
634 disconnect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
635
636 QString modesDelta = Core::userModes(userId(), networkId());
637 QString currentModes = me_->userModes();
638
639 QString addModes, removeModes;
640 if (modesDelta.contains('-')) {
641 addModes = modesDelta.section('-', 0, 0);
642 removeModes = modesDelta.section('-', 1);
643 }
644 else {
645 addModes = modesDelta;
646 }
647
648 addModes.remove(QRegExp(QString("[%1]").arg(currentModes)));
649 if (currentModes.isEmpty())
650 removeModes = QString();
651 else
652 removeModes.remove(QRegExp(QString("[^%1]").arg(currentModes)));
653
654 if (addModes.isEmpty() && removeModes.isEmpty())
655 return;
656
657 if (!addModes.isEmpty())
658 addModes = '+' + addModes;
659 if (!removeModes.isEmpty())
660 removeModes = '-' + removeModes;
661
662 // don't use InputHandler::handleMode() as it keeps track of our persistent mode changes
663 putRawLine(serverEncode(QString("MODE %1 %2%3").arg(me_->nick()).arg(addModes).arg(removeModes)));
664}
665
666
667void CoreNetwork::updateIssuedModes(const QString &requestedModes)
668{
669 QString addModes;
670 QString removeModes;
671 bool addMode = true;
672
673 for (int i = 0; i < requestedModes.length(); i++) {
674 if (requestedModes[i] == '+') {
675 addMode = true;
676 continue;
677 }
678 if (requestedModes[i] == '-') {
679 addMode = false;
680 continue;
681 }
682 if (addMode) {
683 addModes += requestedModes[i];
684 }
685 else {
686 removeModes += requestedModes[i];
687 }
688 }
689
690 QString addModesOld = _requestedUserModes.section('-', 0, 0);
691 QString removeModesOld = _requestedUserModes.section('-', 1);
692
693 addModes.remove(QRegExp(QString("[%1]").arg(addModesOld))); // deduplicate
694 addModesOld.remove(QRegExp(QString("[%1]").arg(removeModes))); // update
695 addModes += addModesOld;
696
697 removeModes.remove(QRegExp(QString("[%1]").arg(removeModesOld))); // deduplicate
698 removeModesOld.remove(QRegExp(QString("[%1]").arg(addModes))); // update
699 removeModes += removeModesOld;
700
701 _requestedUserModes = QString("%1-%2").arg(addModes).arg(removeModes);
702}
703
704
705void CoreNetwork::updatePersistentModes(QString addModes, QString removeModes)
706{
707 QString persistentUserModes = Core::userModes(userId(), networkId());
708
709 QString requestedAdd = _requestedUserModes.section('-', 0, 0);
710 QString requestedRemove = _requestedUserModes.section('-', 1);
711
712 QString persistentAdd, persistentRemove;
713 if (persistentUserModes.contains('-')) {
714 persistentAdd = persistentUserModes.section('-', 0, 0);
715 persistentRemove = persistentUserModes.section('-', 1);
716 }
717 else {
718 persistentAdd = persistentUserModes;
719 }
720
721 // remove modes we didn't issue
722 if (requestedAdd.isEmpty())
723 addModes = QString();
724 else
725 addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd)));
726
727 if (requestedRemove.isEmpty())
728 removeModes = QString();
729 else
730 removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove)));
731
732 // deduplicate
733 persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
734 persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
735
736 // update
737 persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes)));
738 persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes)));
739
740 // update issued mode list
741 requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
742 requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
743 _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove);
744
745 persistentAdd += addModes;
746 persistentRemove += removeModes;
747 Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove));
748}
749
750
751void CoreNetwork::resetPersistentModes()
752{
753 _requestedUserModes = QString('-');
754 Core::setUserModes(userId(), networkId(), QString());
755}
756
757
758void CoreNetwork::setUseAutoReconnect(bool use)
759{
760 Network::setUseAutoReconnect(use);
761 if (!use)
762 _autoReconnectTimer.stop();
763}
764
765
766void CoreNetwork::setAutoReconnectInterval(quint32 interval)
767{
768 Network::setAutoReconnectInterval(interval);
769 _autoReconnectTimer.setInterval(interval * 1000);
770}
771
772
773void CoreNetwork::setAutoReconnectRetries(quint16 retries)
774{
775 Network::setAutoReconnectRetries(retries);
776 if (_autoReconnectCount != 0) {
777 if (unlimitedReconnectRetries())
778 _autoReconnectCount = -1;
779 else
780 _autoReconnectCount = autoReconnectRetries();
781 }
782}
783
784
785void CoreNetwork::doAutoReconnect()
786{
787 if (connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
788 qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
789 return;
790 }
791 if (_autoReconnectCount > 0 || _autoReconnectCount == -1)
792 _autoReconnectCount--; // -2 means we delay the next reconnect
793 connectToIrc(true);
794}
795
796
797void CoreNetwork::sendPing()
798{
799 uint now = QDateTime::currentDateTime().toTime_t();
800 if (_pingCount != 0) {
801 qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
802 << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
803 }
804 if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
805 // the second check compares the actual elapsed time since the last ping and the pingTimer interval
806 // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
807 // and unable to even handle a ping answer. So we ignore those misses.
808 disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000), true /* withReconnect */);
809 }
810 else {
811 _lastPingTime = now;
812 _pingCount++;
813 // Don't send pings until the network is initialized
814 if(_sendPings)
815 userInputHandler()->handlePing(BufferInfo(), QString());
816 }
817}
818
819
820void CoreNetwork::enablePingTimeout(bool enable)
821{
822 if (!enable)
823 disablePingTimeout();
824 else {
825 resetPingTimeout();
826 if (networkConfig()->pingTimeoutEnabled())
827 _pingTimer.start();
828 }
829}
830
831
832void CoreNetwork::disablePingTimeout()
833{
834 _pingTimer.stop();
835 _sendPings = false;
836 resetPingTimeout();
837}
838
839
840void CoreNetwork::setPingInterval(int interval)
841{
842 _pingTimer.setInterval(interval * 1000);
843}
844
845
846/******** AutoWHO ********/
847
848void CoreNetwork::startAutoWhoCycle()
849{
850 if (!_autoWhoQueue.isEmpty()) {
851 _autoWhoCycleTimer.stop();
852 return;
853 }
854 _autoWhoQueue = channels();
855}
856
857
858void CoreNetwork::setAutoWhoDelay(int delay)
859{
860 _autoWhoTimer.setInterval(delay * 1000);
861}
862
863
864void CoreNetwork::setAutoWhoInterval(int interval)
865{
866 _autoWhoCycleTimer.setInterval(interval * 1000);
867}
868
869
870void CoreNetwork::setAutoWhoEnabled(bool enabled)
871{
872 if (enabled && isConnected() && !_autoWhoTimer.isActive())
873 _autoWhoTimer.start();
874 else if (!enabled) {
875 _autoWhoTimer.stop();
876 _autoWhoCycleTimer.stop();
877 }
878}
879
880
881void CoreNetwork::sendAutoWho()
882{
883 // Don't send autowho if there are still some pending
884 if (_autoWhoPending.count())
885 return;
886
887 while (!_autoWhoQueue.isEmpty()) {
888 QString chan = _autoWhoQueue.takeFirst();
889 IrcChannel *ircchan = ircChannel(chan);
890 if (!ircchan) continue;
891 if (networkConfig()->autoWhoNickLimit() > 0 && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit())
892 continue;
893 _autoWhoPending[chan]++;
894 putRawLine("WHO " + serverEncode(chan));
895 break;
896 }
897 if (_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive()) {
898 // Timer was stopped, means a new cycle is due immediately
899 _autoWhoCycleTimer.start();
900 startAutoWhoCycle();
901 }
902}
903
904
905#ifdef HAVE_SSL
906void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors)
907{
908 Q_UNUSED(sslErrors)
909 socket.ignoreSslErrors();
910 // TODO errorhandling
911}
912
913
914#endif // HAVE_SSL
915
916void CoreNetwork::fillBucketAndProcessQueue()
917{
918 if (_tokenBucket < _burstSize) {
919 _tokenBucket++;
920 }
921
922 while (_msgQueue.size() > 0 && _tokenBucket > 0) {
923 writeToSocket(_msgQueue.takeFirst());
924 }
925}
926
927
928void CoreNetwork::writeToSocket(const QByteArray &data)
929{
930 socket.write(data);
931 socket.write("\r\n");
932 _tokenBucket--;
933}
934
935
936Network::Server CoreNetwork::usedServer() const
937{
938 if (_lastUsedServerIndex < serverList().count())
939 return serverList()[_lastUsedServerIndex];
940
941 if (!serverList().isEmpty())
942 return serverList()[0];
943
944 return Network::Server();
945}
946
947
948void CoreNetwork::requestConnect() const
949{
950 if (connectionState() != Disconnected) {
951 qWarning() << "Requesting connect while already being connected!";
952 return;
953 }
954 QMetaObject::invokeMethod(const_cast<CoreNetwork *>(this), "connectToIrc", Qt::QueuedConnection);
955}
956
957
958void CoreNetwork::requestDisconnect() const
959{
960 if (connectionState() == Disconnected) {
961 qWarning() << "Requesting disconnect while not being connected!";
962 return;
963 }
964 userInputHandler()->handleQuit(BufferInfo(), QString());
965}
966
967
968void CoreNetwork::requestSetNetworkInfo(const NetworkInfo &info)
969{
970 Network::Server currentServer = usedServer();
971 setNetworkInfo(info);
972 Core::updateNetwork(coreSession()->user(), info);
973
974 // the order of the servers might have changed,
975 // so we try to find the previously used server
976 _lastUsedServerIndex = 0;
977 for (int i = 0; i < serverList().count(); i++) {
978 Network::Server server = serverList()[i];
979 if (server.host == currentServer.host && server.port == currentServer.port) {
980 _lastUsedServerIndex = i;
981 break;
982 }
983 }
984}
985