Sync IrcUser account, add WHOX for account-notify
[quassel.git] / src / core / corenetwork.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2016 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
32 // IRCv3 capabilities
33 #include "irccap.h"
34
35 INIT_SYNCABLE_OBJECT(CoreNetwork)
36 CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
37     : Network(networkid, session),
38     _coreSession(session),
39     _userInputHandler(new CoreUserInputHandler(this)),
40     _autoReconnectCount(0),
41     _quitRequested(false),
42
43     _previousConnectionAttemptFailed(false),
44     _lastUsedServerIndex(0),
45
46     _lastPingTime(0),
47     _pingCount(0),
48     _sendPings(false),
49     _requestedUserModes('-')
50 {
51     _autoReconnectTimer.setSingleShot(true);
52     connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
53
54     setPingInterval(networkConfig()->pingInterval());
55     connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
56
57     setAutoWhoDelay(networkConfig()->autoWhoDelay());
58     setAutoWhoInterval(networkConfig()->autoWhoInterval());
59
60     QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
61     foreach(QString chan, channels.keys()) {
62         _channelKeys[chan.toLower()] = channels[chan];
63     }
64
65     connect(networkConfig(), SIGNAL(pingTimeoutEnabledSet(bool)), SLOT(enablePingTimeout(bool)));
66     connect(networkConfig(), SIGNAL(pingIntervalSet(int)), SLOT(setPingInterval(int)));
67     connect(networkConfig(), SIGNAL(autoWhoEnabledSet(bool)), SLOT(setAutoWhoEnabled(bool)));
68     connect(networkConfig(), SIGNAL(autoWhoIntervalSet(int)), SLOT(setAutoWhoInterval(int)));
69     connect(networkConfig(), SIGNAL(autoWhoDelaySet(int)), SLOT(setAutoWhoDelay(int)));
70
71     connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
72     connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
73     connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
74     connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
75
76     connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
77     connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
78     connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
79     connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
80 #ifdef HAVE_SSL
81     connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
82     connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
83 #endif
84     connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
85
86     // IRCv3 capability handling
87     // These react to CAP messages from the server
88     connect(this, SIGNAL(capAdded(QString)), this, SLOT(serverCapAdded(QString)));
89     connect(this, SIGNAL(capAcknowledged(QString)), this, SLOT(serverCapAcknowledged(QString)));
90     connect(this, SIGNAL(capRemoved(QString)), this, SLOT(serverCapRemoved(QString)));
91
92     if (Quassel::isOptionSet("oidentd")) {
93         connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
94         connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
95     }
96 }
97
98
99 CoreNetwork::~CoreNetwork()
100 {
101     if (connectionState() != Disconnected && connectionState() != Network::Reconnecting)
102         disconnectFromIrc(false);  // clean up, but this does not count as requested disconnect!
103     disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
104     delete _userInputHandler;
105 }
106
107
108 QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
109 {
110     if (!bufferName.isEmpty()) {
111         IrcChannel *channel = ircChannel(bufferName);
112         if (channel)
113             return channel->decodeString(string);
114     }
115     return decodeString(string);
116 }
117
118
119 QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const
120 {
121     IrcUser *user = ircUser(userNick);
122     if (user)
123         return user->decodeString(string);
124     return decodeString(string);
125 }
126
127
128 QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const
129 {
130     if (!bufferName.isEmpty()) {
131         IrcChannel *channel = ircChannel(bufferName);
132         if (channel)
133             return channel->encodeString(string);
134     }
135     return encodeString(string);
136 }
137
138
139 QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const
140 {
141     IrcUser *user = ircUser(userNick);
142     if (user)
143         return user->encodeString(string);
144     return encodeString(string);
145 }
146
147
148 void CoreNetwork::connectToIrc(bool reconnecting)
149 {
150     if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
151         _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
152         if (unlimitedReconnectRetries())
153             _autoReconnectCount = -1;
154         else
155             _autoReconnectCount = autoReconnectRetries();
156     }
157     if (serverList().isEmpty()) {
158         qWarning() << "Server list empty, ignoring connect request!";
159         return;
160     }
161     CoreIdentity *identity = identityPtr();
162     if (!identity) {
163         qWarning() << "Invalid identity configures, ignoring connect request!";
164         return;
165     }
166
167     // cleaning up old quit reason
168     _quitReason.clear();
169
170     // Reset capability negotiation tracking, also handling server changes during reconnect
171     _capsQueued.clear();
172     clearCaps();
173     _capNegotiationActive = false;
174     _capInitialNegotiationEnded = false;
175
176     // use a random server?
177     if (useRandomServer()) {
178         _lastUsedServerIndex = qrand() % serverList().size();
179     }
180     else if (_previousConnectionAttemptFailed) {
181         // cycle to next server if previous connection attempt failed
182         displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
183         if (++_lastUsedServerIndex >= serverList().size()) {
184             _lastUsedServerIndex = 0;
185         }
186     }
187     _previousConnectionAttemptFailed = false;
188
189     Server server = usedServer();
190     displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
191     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
192
193     if (server.useProxy) {
194         QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
195         socket.setProxy(proxy);
196     }
197     else {
198         socket.setProxy(QNetworkProxy::NoProxy);
199     }
200
201     enablePingTimeout();
202
203     // Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
204     // connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
205     QHostInfo::fromName(server.host);
206
207 #ifdef HAVE_SSL
208     if (server.useSsl) {
209         CoreIdentity *identity = identityPtr();
210         if (identity) {
211             socket.setLocalCertificate(identity->sslCert());
212             socket.setPrivateKey(identity->sslKey());
213         }
214         socket.connectToHostEncrypted(server.host, server.port);
215     }
216     else {
217         socket.connectToHost(server.host, server.port);
218     }
219 #else
220     socket.connectToHost(server.host, server.port);
221 #endif
222 }
223
224
225 void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect)
226 {
227     _quitRequested = requested; // see socketDisconnected();
228     if (!withReconnect) {
229         _autoReconnectTimer.stop();
230         _autoReconnectCount = 0; // prohibiting auto reconnect
231     }
232     disablePingTimeout();
233     _msgQueue.clear();
234
235     IrcUser *me_ = me();
236     if (me_) {
237         QString awayMsg;
238         if (me_->isAway())
239             awayMsg = me_->awayMessage();
240         Core::setAwayMessage(userId(), networkId(), awayMsg);
241     }
242
243     if (reason.isEmpty() && identityPtr())
244         _quitReason = identityPtr()->quitReason();
245     else
246         _quitReason = reason;
247
248     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
249     if (socket.state() == QAbstractSocket::UnconnectedState) {
250         socketDisconnected();
251     } else {
252         if (socket.state() == QAbstractSocket::ConnectedState) {
253             userInputHandler()->issueQuit(_quitReason);
254         } else {
255             socket.close();
256         }
257         if (requested || withReconnect) {
258             // the irc server has 10 seconds to close the socket
259             _socketCloseTimer.start(10000);
260         }
261     }
262 }
263
264
265 void CoreNetwork::userInput(BufferInfo buf, QString msg)
266 {
267     userInputHandler()->handleUserInput(buf, msg);
268 }
269
270
271 void CoreNetwork::putRawLine(QByteArray s)
272 {
273     if (_tokenBucket > 0)
274         writeToSocket(s);
275     else
276         _msgQueue.append(s);
277 }
278
279
280 void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> &params, const QByteArray &prefix)
281 {
282     QByteArray msg;
283
284     if (!prefix.isEmpty())
285         msg += ":" + prefix + " ";
286     msg += cmd.toUpper().toLatin1();
287
288     for (int i = 0; i < params.size(); i++) {
289         msg += " ";
290
291         if (i == params.size() - 1 && (params[i].contains(' ') || (!params[i].isEmpty() && params[i][0] == ':')))
292             msg += ":";
293
294         msg += params[i];
295     }
296
297     putRawLine(msg);
298 }
299
300
301 void CoreNetwork::putCmd(const QString &cmd, const QList<QList<QByteArray>> &params, const QByteArray &prefix)
302 {
303     QListIterator<QList<QByteArray>> i(params);
304     while (i.hasNext()) {
305         QList<QByteArray> msg = i.next();
306         putCmd(cmd, msg, prefix);
307     }
308 }
309
310
311 void CoreNetwork::setChannelJoined(const QString &channel)
312 {
313     queueAutoWhoOneshot(channel); // check this new channel first
314
315     Core::setChannelPersistent(userId(), networkId(), channel, true);
316     Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
317 }
318
319
320 void CoreNetwork::setChannelParted(const QString &channel)
321 {
322     removeChannelKey(channel);
323     _autoWhoQueue.removeAll(channel.toLower());
324     _autoWhoPending.remove(channel.toLower());
325
326     Core::setChannelPersistent(userId(), networkId(), channel, false);
327 }
328
329
330 void CoreNetwork::addChannelKey(const QString &channel, const QString &key)
331 {
332     if (key.isEmpty()) {
333         removeChannelKey(channel);
334     }
335     else {
336         _channelKeys[channel.toLower()] = key;
337     }
338 }
339
340
341 void CoreNetwork::removeChannelKey(const QString &channel)
342 {
343     _channelKeys.remove(channel.toLower());
344 }
345
346
347 #ifdef HAVE_QCA2
348 Cipher *CoreNetwork::cipher(const QString &target)
349 {
350     if (target.isEmpty())
351         return 0;
352
353     if (!Cipher::neededFeaturesAvailable())
354         return 0;
355
356     CoreIrcChannel *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
357     if (channel) {
358         return channel->cipher();
359     }
360     CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
361     if (user) {
362         return user->cipher();
363     } else if (!isChannelName(target)) {
364         return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
365     }
366     return 0;
367 }
368
369
370 QByteArray CoreNetwork::cipherKey(const QString &target) const
371 {
372     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
373     if (c)
374         return c->cipher()->key();
375
376     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
377     if (u)
378         return u->cipher()->key();
379
380     return QByteArray();
381 }
382
383
384 void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
385 {
386     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
387     if (c) {
388         c->setEncrypted(c->cipher()->setKey(key));
389         return;
390     }
391
392     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
393     if (!u && !isChannelName(target))
394         u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
395
396     if (u) {
397         u->setEncrypted(u->cipher()->setKey(key));
398         return;
399     }
400 }
401
402
403 bool CoreNetwork::cipherUsesCBC(const QString &target)
404 {
405     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
406     if (c)
407         return c->cipher()->usesCBC();
408     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
409     if (u)
410         return u->cipher()->usesCBC();
411
412     return false;
413 }
414 #endif /* HAVE_QCA2 */
415
416 bool CoreNetwork::setAutoWhoDone(const QString &channel)
417 {
418     QString chan = channel.toLower();
419     if (_autoWhoPending.value(chan, 0) <= 0)
420         return false;
421     if (--_autoWhoPending[chan] <= 0)
422         _autoWhoPending.remove(chan);
423     return true;
424 }
425
426
427 void CoreNetwork::setMyNick(const QString &mynick)
428 {
429     Network::setMyNick(mynick);
430     if (connectionState() == Network::Initializing)
431         networkInitialized();
432 }
433
434
435 void CoreNetwork::socketHasData()
436 {
437     while (socket.canReadLine()) {
438         QByteArray s = socket.readLine();
439         if (s.endsWith("\r\n"))
440             s.chop(2);
441         else if (s.endsWith("\n"))
442             s.chop(1);
443         NetworkDataEvent *event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
444         event->setTimestamp(QDateTime::currentDateTimeUtc());
445         emit newEvent(event);
446     }
447 }
448
449
450 void CoreNetwork::socketError(QAbstractSocket::SocketError error)
451 {
452     if (_quitRequested && error == QAbstractSocket::RemoteHostClosedError)
453         return;
454
455     _previousConnectionAttemptFailed = true;
456     qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
457     emit connectionError(socket.errorString());
458     displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
459     emitConnectionError(socket.errorString());
460     if (socket.state() < QAbstractSocket::ConnectedState) {
461         socketDisconnected();
462     }
463 }
464
465
466 void CoreNetwork::socketInitialized()
467 {
468     CoreIdentity *identity = identityPtr();
469     if (!identity) {
470         qCritical() << "Identity invalid!";
471         disconnectFromIrc();
472         return;
473     }
474
475     Server server = usedServer();
476
477 #ifdef HAVE_SSL
478     // Non-SSL connections enter here only once, always emit socketInitialized(...) in these cases
479     // SSL connections call socketInitialized() twice, only emit socketInitialized(...) on the first (not yet encrypted) run
480     if (!server.useSsl || !socket.isEncrypted()) {
481         emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
482     }
483
484     if (server.useSsl && !socket.isEncrypted()) {
485         // We'll finish setup once we're encrypted, and called again
486         return;
487     }
488 #else
489     emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
490 #endif
491
492     socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
493
494     // TokenBucket to avoid sending too much at once
495     _messageDelay = 2200;  // this seems to be a safe value (2.2 seconds delay)
496     _burstSize = 5;
497     _tokenBucket = _burstSize; // init with a full bucket
498     _tokenBucketTimer.start(_messageDelay);
499
500     // Request capabilities as per IRCv3.2 specifications
501     // Older servers should ignore this; newer servers won't downgrade to RFC1459
502     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Requesting capability list..."));
503     putRawLine(serverEncode(QString("CAP LS 302")));
504
505     if (!server.password.isEmpty()) {
506         putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
507     }
508     QString nick;
509     if (identity->nicks().isEmpty()) {
510         nick = "quassel";
511         qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id();
512     }
513     else {
514         nick = identity->nicks()[0];
515     }
516     putRawLine(serverEncode(QString("NICK %1").arg(nick)));
517     putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
518 }
519
520
521 void CoreNetwork::socketDisconnected()
522 {
523     disablePingTimeout();
524     _msgQueue.clear();
525
526     _autoWhoCycleTimer.stop();
527     _autoWhoTimer.stop();
528     _autoWhoQueue.clear();
529     _autoWhoPending.clear();
530
531     _socketCloseTimer.stop();
532
533     _tokenBucketTimer.stop();
534
535     IrcUser *me_ = me();
536     if (me_) {
537         foreach(QString channel, me_->channels())
538         displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
539     }
540
541     setConnected(false);
542     emit disconnected(networkId());
543     emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
544     if (_quitRequested) {
545         _quitRequested = false;
546         setConnectionState(Network::Disconnected);
547         Core::setNetworkConnected(userId(), networkId(), false);
548     }
549     else if (_autoReconnectCount != 0) {
550         setConnectionState(Network::Reconnecting);
551         if (_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries())
552             doAutoReconnect();  // first try is immediate
553         else
554             _autoReconnectTimer.start();
555     }
556 }
557
558
559 void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState)
560 {
561     Network::ConnectionState state;
562     switch (socketState) {
563     case QAbstractSocket::UnconnectedState:
564         state = Network::Disconnected;
565         socketDisconnected();
566         break;
567     case QAbstractSocket::HostLookupState:
568     case QAbstractSocket::ConnectingState:
569         state = Network::Connecting;
570         break;
571     case QAbstractSocket::ConnectedState:
572         state = Network::Initializing;
573         break;
574     case QAbstractSocket::ClosingState:
575         state = Network::Disconnecting;
576         break;
577     default:
578         state = Network::Disconnected;
579     }
580     setConnectionState(state);
581 }
582
583
584 void CoreNetwork::networkInitialized()
585 {
586     setConnectionState(Network::Initialized);
587     setConnected(true);
588     _quitRequested = false;
589
590     if (useAutoReconnect()) {
591         // reset counter
592         _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries();
593     }
594
595     // restore away state
596     QString awayMsg = Core::awayMessage(userId(), networkId());
597     if (!awayMsg.isEmpty())
598         userInputHandler()->handleAway(BufferInfo(), Core::awayMessage(userId(), networkId()));
599
600     sendPerform();
601
602     _sendPings = true;
603
604     if (networkConfig()->autoWhoEnabled()) {
605         _autoWhoCycleTimer.start();
606         _autoWhoTimer.start();
607         startAutoWhoCycle(); // FIXME wait for autojoin to be completed
608     }
609
610     Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer); // create status buffer
611     Core::setNetworkConnected(userId(), networkId(), true);
612 }
613
614
615 void CoreNetwork::sendPerform()
616 {
617     BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
618
619     // do auto identify
620     if (useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
621         userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
622     }
623
624     // restore old user modes if server default mode is set.
625     IrcUser *me_ = me();
626     if (me_) {
627         if (!me_->userModes().isEmpty()) {
628             restoreUserModes();
629         }
630         else {
631             connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
632             connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
633         }
634     }
635
636     // send perform list
637     foreach(QString line, perform()) {
638         if (!line.isEmpty()) userInput(statusBuf, line);
639     }
640
641     // rejoin channels we've been in
642     if (rejoinChannels()) {
643         QStringList channels, keys;
644         foreach(QString chan, coreSession()->persistentChannels(networkId()).keys()) {
645             QString key = channelKey(chan);
646             if (!key.isEmpty()) {
647                 channels.prepend(chan);
648                 keys.prepend(key);
649             }
650             else {
651                 channels.append(chan);
652             }
653         }
654         QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
655         if (!joinString.isEmpty())
656             userInputHandler()->handleJoin(statusBuf, joinString);
657     }
658 }
659
660
661 void CoreNetwork::restoreUserModes()
662 {
663     IrcUser *me_ = me();
664     Q_ASSERT(me_);
665
666     disconnect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
667     disconnect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
668
669     QString modesDelta = Core::userModes(userId(), networkId());
670     QString currentModes = me_->userModes();
671
672     QString addModes, removeModes;
673     if (modesDelta.contains('-')) {
674         addModes = modesDelta.section('-', 0, 0);
675         removeModes = modesDelta.section('-', 1);
676     }
677     else {
678         addModes = modesDelta;
679     }
680
681     addModes.remove(QRegExp(QString("[%1]").arg(currentModes)));
682     if (currentModes.isEmpty())
683         removeModes = QString();
684     else
685         removeModes.remove(QRegExp(QString("[^%1]").arg(currentModes)));
686
687     if (addModes.isEmpty() && removeModes.isEmpty())
688         return;
689
690     if (!addModes.isEmpty())
691         addModes = '+' + addModes;
692     if (!removeModes.isEmpty())
693         removeModes = '-' + removeModes;
694
695     // don't use InputHandler::handleMode() as it keeps track of our persistent mode changes
696     putRawLine(serverEncode(QString("MODE %1 %2%3").arg(me_->nick()).arg(addModes).arg(removeModes)));
697 }
698
699
700 void CoreNetwork::updateIssuedModes(const QString &requestedModes)
701 {
702     QString addModes;
703     QString removeModes;
704     bool addMode = true;
705
706     for (int i = 0; i < requestedModes.length(); i++) {
707         if (requestedModes[i] == '+') {
708             addMode = true;
709             continue;
710         }
711         if (requestedModes[i] == '-') {
712             addMode = false;
713             continue;
714         }
715         if (addMode) {
716             addModes += requestedModes[i];
717         }
718         else {
719             removeModes += requestedModes[i];
720         }
721     }
722
723     QString addModesOld = _requestedUserModes.section('-', 0, 0);
724     QString removeModesOld = _requestedUserModes.section('-', 1);
725
726     addModes.remove(QRegExp(QString("[%1]").arg(addModesOld))); // deduplicate
727     addModesOld.remove(QRegExp(QString("[%1]").arg(removeModes))); // update
728     addModes += addModesOld;
729
730     removeModes.remove(QRegExp(QString("[%1]").arg(removeModesOld))); // deduplicate
731     removeModesOld.remove(QRegExp(QString("[%1]").arg(addModes))); // update
732     removeModes += removeModesOld;
733
734     _requestedUserModes = QString("%1-%2").arg(addModes).arg(removeModes);
735 }
736
737
738 void CoreNetwork::updatePersistentModes(QString addModes, QString removeModes)
739 {
740     QString persistentUserModes = Core::userModes(userId(), networkId());
741
742     QString requestedAdd = _requestedUserModes.section('-', 0, 0);
743     QString requestedRemove = _requestedUserModes.section('-', 1);
744
745     QString persistentAdd, persistentRemove;
746     if (persistentUserModes.contains('-')) {
747         persistentAdd = persistentUserModes.section('-', 0, 0);
748         persistentRemove = persistentUserModes.section('-', 1);
749     }
750     else {
751         persistentAdd = persistentUserModes;
752     }
753
754     // remove modes we didn't issue
755     if (requestedAdd.isEmpty())
756         addModes = QString();
757     else
758         addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd)));
759
760     if (requestedRemove.isEmpty())
761         removeModes = QString();
762     else
763         removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove)));
764
765     // deduplicate
766     persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
767     persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
768
769     // update
770     persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes)));
771     persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes)));
772
773     // update issued mode list
774     requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
775     requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
776     _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove);
777
778     persistentAdd += addModes;
779     persistentRemove += removeModes;
780     Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove));
781 }
782
783
784 void CoreNetwork::resetPersistentModes()
785 {
786     _requestedUserModes = QString('-');
787     Core::setUserModes(userId(), networkId(), QString());
788 }
789
790
791 void CoreNetwork::setUseAutoReconnect(bool use)
792 {
793     Network::setUseAutoReconnect(use);
794     if (!use)
795         _autoReconnectTimer.stop();
796 }
797
798
799 void CoreNetwork::setAutoReconnectInterval(quint32 interval)
800 {
801     Network::setAutoReconnectInterval(interval);
802     _autoReconnectTimer.setInterval(interval * 1000);
803 }
804
805
806 void CoreNetwork::setAutoReconnectRetries(quint16 retries)
807 {
808     Network::setAutoReconnectRetries(retries);
809     if (_autoReconnectCount != 0) {
810         if (unlimitedReconnectRetries())
811             _autoReconnectCount = -1;
812         else
813             _autoReconnectCount = autoReconnectRetries();
814     }
815 }
816
817
818 void CoreNetwork::doAutoReconnect()
819 {
820     if (connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
821         qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
822         return;
823     }
824     if (_autoReconnectCount > 0 || _autoReconnectCount == -1)
825         _autoReconnectCount--;  // -2 means we delay the next reconnect
826     connectToIrc(true);
827 }
828
829
830 void CoreNetwork::sendPing()
831 {
832     uint now = QDateTime::currentDateTime().toTime_t();
833     if (_pingCount != 0) {
834         qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
835                  << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
836     }
837     if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
838         // the second check compares the actual elapsed time since the last ping and the pingTimer interval
839         // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
840         // and unable to even handle a ping answer. So we ignore those misses.
841         disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000), true /* withReconnect */);
842     }
843     else {
844         _lastPingTime = now;
845         _pingCount++;
846         // Don't send pings until the network is initialized
847         if(_sendPings)
848             userInputHandler()->handlePing(BufferInfo(), QString());
849     }
850 }
851
852
853 void CoreNetwork::enablePingTimeout(bool enable)
854 {
855     if (!enable)
856         disablePingTimeout();
857     else {
858         resetPingTimeout();
859         if (networkConfig()->pingTimeoutEnabled())
860             _pingTimer.start();
861     }
862 }
863
864
865 void CoreNetwork::disablePingTimeout()
866 {
867     _pingTimer.stop();
868     _sendPings = false;
869     resetPingTimeout();
870 }
871
872
873 void CoreNetwork::setPingInterval(int interval)
874 {
875     _pingTimer.setInterval(interval * 1000);
876 }
877
878 /******** IRCv3 Capability Negotiation ********/
879
880 void CoreNetwork::serverCapAdded(const QString &capability)
881 {
882     // Check if it's a known capability; if so, add it to the list
883     // Handle special cases first
884     if (capability == IrcCap::SASL) {
885         // Only request SASL if it's enabled
886         if (networkInfo().useSasl)
887             queueCap(capability);
888     } else if (IrcCap::knownCaps.contains(capability)) {
889         // Handling for general known capabilities
890         queueCap(capability);
891     }
892 }
893
894 void CoreNetwork::serverCapAcknowledged(const QString &capability)
895 {
896     // This may be called multiple times in certain situations.
897
898     // Handle core-side configuration
899     if (capability == IrcCap::AWAY_NOTIFY) {
900         // away-notify enabled, stop the autoWho timers, handle manually
901         setAutoWhoEnabled(false);
902     }
903
904     // Handle capabilities that require further messages sent to the IRC server
905     // If you change this list, ALSO change the list in CoreNetwork::capsRequiringServerMessages
906     if (capability == IrcCap::SASL) {
907         // If SASL mechanisms specified, limit to what's accepted for authentication
908         // if the current identity has a cert set, use SASL EXTERNAL
909         // FIXME use event
910 #ifdef HAVE_SSL
911         if (!identityPtr()->sslCert().isNull()) {
912             if (IrcCap::SaslMech::maybeSupported(capValue(IrcCap::SASL), IrcCap::SaslMech::EXTERNAL)) {
913                 // EXTERNAL authentication supported, send request
914                 putRawLine(serverEncode("AUTHENTICATE EXTERNAL"));
915             } else {
916                 displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
917                            tr("SASL EXTERNAL authentication not supported"));
918                 sendNextCap();
919             }
920         } else {
921 #endif
922             if (IrcCap::SaslMech::maybeSupported(capValue(IrcCap::SASL), IrcCap::SaslMech::PLAIN)) {
923                 // PLAIN authentication supported, send request
924                 // Only working with PLAIN atm, blowfish later
925                 putRawLine(serverEncode("AUTHENTICATE PLAIN"));
926             } else {
927                 displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
928                            tr("SASL PLAIN authentication not supported"));
929                 sendNextCap();
930             }
931 #ifdef HAVE_SSL
932         }
933 #endif
934     }
935 }
936
937 void CoreNetwork::serverCapRemoved(const QString &capability)
938 {
939     // This may be called multiple times in certain situations.
940
941     // Handle special cases here
942     if (capability == IrcCap::AWAY_NOTIFY) {
943         // away-notify disabled, enable autoWho according to configuration
944         setAutoWhoEnabled(networkConfig()->autoWhoEnabled());
945     }
946 }
947
948 void CoreNetwork::queueCap(const QString &capability)
949 {
950     // IRCv3 specs all use lowercase capability names
951     QString _capLowercase = capability.toLower();
952     if (!_capsQueued.contains(_capLowercase)) {
953         _capsQueued.append(_capLowercase);
954     }
955 }
956
957 QString CoreNetwork::takeQueuedCap()
958 {
959     if (!_capsQueued.empty()) {
960         return _capsQueued.takeFirst();
961     } else {
962         return QString();
963     }
964 }
965
966 void CoreNetwork::beginCapNegotiation()
967 {
968     // Don't begin negotiation if no capabilities are queued to request
969     if (!capNegotiationInProgress())
970         return;
971
972     _capNegotiationActive = true;
973     displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
974                tr("Ready to negotiate (found: %1)").arg(caps().join(", ")));
975     displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
976                tr("Negotiating capabilities (requesting: %1)...").arg(_capsQueued.join(", ")));
977     sendNextCap();
978 }
979
980 void CoreNetwork::sendNextCap()
981 {
982     if (capNegotiationInProgress()) {
983         // Request the next capability and remove it from the list
984         // Handle one at a time so one capability failing won't NAK all of 'em
985         putRawLine(serverEncode(QString("CAP REQ :%1").arg(takeQueuedCap())));
986     } else {
987         // No pending desired capabilities, capability negotiation finished
988         // If SASL requested but not available, print a warning
989         if (networkInfo().useSasl && !capEnabled(IrcCap::SASL))
990             displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
991                        tr("SASL authentication currently not supported by server"));
992
993         if (_capNegotiationActive) {
994             displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
995                    tr("Capability negotiation finished (enabled: %1)").arg(capsEnabled().join(", ")));
996             _capNegotiationActive = false;
997         }
998
999         // If nick registration is already complete, CAP END is not required
1000         if (!_capInitialNegotiationEnded) {
1001             putRawLine(serverEncode(QString("CAP END")));
1002             _capInitialNegotiationEnded = true;
1003         }
1004     }
1005 }
1006
1007 /******** AutoWHO ********/
1008
1009 void CoreNetwork::startAutoWhoCycle()
1010 {
1011     if (!_autoWhoQueue.isEmpty()) {
1012         _autoWhoCycleTimer.stop();
1013         return;
1014     }
1015     _autoWhoQueue = channels();
1016 }
1017
1018 void CoreNetwork::queueAutoWhoOneshot(const QString &channelOrNick)
1019 {
1020     // Prepend so these new channels/nicks are the first to be checked
1021     // Don't allow duplicates
1022     if (!_autoWhoQueue.contains(channelOrNick.toLower())) {
1023         _autoWhoQueue.prepend(channelOrNick.toLower());
1024     }
1025     if (capEnabled(IrcCap::AWAY_NOTIFY)) {
1026         // When away-notify is active, the timer's stopped.  Start a new cycle to who this channel.
1027         setAutoWhoEnabled(true);
1028     }
1029 }
1030
1031
1032 void CoreNetwork::setAutoWhoDelay(int delay)
1033 {
1034     _autoWhoTimer.setInterval(delay * 1000);
1035 }
1036
1037
1038 void CoreNetwork::setAutoWhoInterval(int interval)
1039 {
1040     _autoWhoCycleTimer.setInterval(interval * 1000);
1041 }
1042
1043
1044 void CoreNetwork::setAutoWhoEnabled(bool enabled)
1045 {
1046     if (enabled && isConnected() && !_autoWhoTimer.isActive())
1047         _autoWhoTimer.start();
1048     else if (!enabled) {
1049         _autoWhoTimer.stop();
1050         _autoWhoCycleTimer.stop();
1051     }
1052 }
1053
1054
1055 void CoreNetwork::sendAutoWho()
1056 {
1057     // Don't send autowho if there are still some pending
1058     if (_autoWhoPending.count())
1059         return;
1060
1061     while (!_autoWhoQueue.isEmpty()) {
1062         QString chanOrNick = _autoWhoQueue.takeFirst();
1063         // Check if it's a known channel or nick
1064         IrcChannel *ircchan = ircChannel(chanOrNick);
1065         IrcUser *ircuser = ircUser(chanOrNick);
1066         if (ircchan) {
1067             // Apply channel limiting rules
1068             // If using away-notify, don't impose channel size limits in order to capture away
1069             // state of everyone.  Auto-who won't run on a timer so network impact is minimal.
1070             if (networkConfig()->autoWhoNickLimit() > 0
1071                 && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit()
1072                 && !capEnabled(IrcCap::AWAY_NOTIFY))
1073                 continue;
1074             _autoWhoPending[chanOrNick.toLower()]++;
1075         } else if (ircuser) {
1076             // Checking a nick, add it to the pending list
1077             _autoWhoPending[ircuser->nick().toLower()]++;
1078         } else {
1079             // Not a channel or a nick, skip it
1080             qDebug() << "Skipping who polling of unknown channel or nick" << chanOrNick;
1081             continue;
1082         }
1083         if (supports("WHOX")) {
1084             // Use WHO extended to poll away users and/or user accounts
1085             // See http://faerion.sourceforge.net/doc/irc/whox.var
1086             // And https://github.com/hexchat/hexchat/blob/c874a9525c9b66f1d5ddcf6c4107d046eba7e2c5/src/common/proto-irc.c#L750
1087             putRawLine(serverEncode(QString("WHO %1 %%chtsunfra,%2")
1088                                     .arg(serverEncode(chanOrNick), QString::number(IrcCap::ACCOUNT_NOTIFY_WHOX_NUM))));
1089         } else {
1090             putRawLine(serverEncode(QString("WHO %1").arg(chanOrNick)));
1091         }
1092         break;
1093     }
1094
1095     if (_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive()
1096         && !capEnabled(IrcCap::AWAY_NOTIFY)) {
1097         // Timer was stopped, means a new cycle is due immediately
1098         // Don't run a new cycle if using away-notify; server will notify as appropriate
1099         _autoWhoCycleTimer.start();
1100         startAutoWhoCycle();
1101     } else if (capEnabled(IrcCap::AWAY_NOTIFY) && _autoWhoCycleTimer.isActive()) {
1102         // Don't run another who cycle if away-notify is enabled
1103         _autoWhoCycleTimer.stop();
1104     }
1105 }
1106
1107
1108 #ifdef HAVE_SSL
1109 void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors)
1110 {
1111     Q_UNUSED(sslErrors)
1112     socket.ignoreSslErrors();
1113     // TODO errorhandling
1114 }
1115
1116
1117 #endif  // HAVE_SSL
1118
1119 void CoreNetwork::fillBucketAndProcessQueue()
1120 {
1121     if (_tokenBucket < _burstSize) {
1122         _tokenBucket++;
1123     }
1124
1125     while (_msgQueue.size() > 0 && _tokenBucket > 0) {
1126         writeToSocket(_msgQueue.takeFirst());
1127     }
1128 }
1129
1130
1131 void CoreNetwork::writeToSocket(const QByteArray &data)
1132 {
1133     socket.write(data);
1134     socket.write("\r\n");
1135     _tokenBucket--;
1136 }
1137
1138
1139 Network::Server CoreNetwork::usedServer() const
1140 {
1141     if (_lastUsedServerIndex < serverList().count())
1142         return serverList()[_lastUsedServerIndex];
1143
1144     if (!serverList().isEmpty())
1145         return serverList()[0];
1146
1147     return Network::Server();
1148 }
1149
1150
1151 void CoreNetwork::requestConnect() const
1152 {
1153     if (connectionState() != Disconnected) {
1154         qWarning() << "Requesting connect while already being connected!";
1155         return;
1156     }
1157     QMetaObject::invokeMethod(const_cast<CoreNetwork *>(this), "connectToIrc", Qt::QueuedConnection);
1158 }
1159
1160
1161 void CoreNetwork::requestDisconnect() const
1162 {
1163     if (connectionState() == Disconnected) {
1164         qWarning() << "Requesting disconnect while not being connected!";
1165         return;
1166     }
1167     userInputHandler()->handleQuit(BufferInfo(), QString());
1168 }
1169
1170
1171 void CoreNetwork::requestSetNetworkInfo(const NetworkInfo &info)
1172 {
1173     Network::Server currentServer = usedServer();
1174     setNetworkInfo(info);
1175     Core::updateNetwork(coreSession()->user(), info);
1176
1177     // the order of the servers might have changed,
1178     // so we try to find the previously used server
1179     _lastUsedServerIndex = 0;
1180     for (int i = 0; i < serverList().count(); i++) {
1181         Network::Server server = serverList()[i];
1182         if (server.host == currentServer.host && server.port == currentServer.port) {
1183             _lastUsedServerIndex = i;
1184             break;
1185         }
1186     }
1187 }
1188
1189
1190 QList<QList<QByteArray>> CoreNetwork::splitMessage(const QString &cmd, const QString &message, std::function<QList<QByteArray>(QString &)> cmdGenerator)
1191 {
1192     QString wrkMsg(message);
1193     QList<QList<QByteArray>> msgsToSend;
1194
1195     // do while (wrkMsg.size() > 0)
1196     do {
1197         // First, check to see if the whole message can be sent at once.  The
1198         // cmdGenerator function is passed in by the caller and is used to encode
1199         // and encrypt (if applicable) the message, since different callers might
1200         // want to use different encoding or encode different values.
1201         int splitPos = wrkMsg.size();
1202         QList<QByteArray> initialSplitMsgEnc = cmdGenerator(wrkMsg);
1203         int initialOverrun = userInputHandler()->lastParamOverrun(cmd, initialSplitMsgEnc);
1204
1205         if (initialOverrun) {
1206             // If the message was too long to be sent, first try splitting it along
1207             // word boundaries with QTextBoundaryFinder.
1208             QString splitMsg(wrkMsg);
1209             QTextBoundaryFinder qtbf(QTextBoundaryFinder::Word, splitMsg);
1210             qtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1211             QList<QByteArray> splitMsgEnc;
1212             int overrun = initialOverrun;
1213
1214             while (overrun) {
1215                 splitPos = qtbf.toPreviousBoundary();
1216
1217                 // splitPos==-1 means the QTBF couldn't find a split point at all and
1218                 // splitPos==0 means the QTBF could only find a boundary at the beginning of
1219                 // the string.  Neither one of these works for us.
1220                 if (splitPos > 0) {
1221                     // If a split point could be found, split the message there, calculate the
1222                     // overrun, and continue with the loop.
1223                     splitMsg = splitMsg.left(splitPos);
1224                     splitMsgEnc = cmdGenerator(splitMsg);
1225                     overrun = userInputHandler()->lastParamOverrun(cmd, splitMsgEnc);
1226                 }
1227                 else {
1228                     // If a split point could not be found (the beginning of the message
1229                     // is reached without finding a split point short enough to send) and we
1230                     // are still in Word mode, switch to Grapheme mode.  We also need to restore
1231                     // the full wrkMsg to splitMsg, since splitMsg may have been cut down during
1232                     // the previous attempt to find a split point.
1233                     if (qtbf.type() == QTextBoundaryFinder::Word) {
1234                         splitMsg = wrkMsg;
1235                         splitPos = splitMsg.size();
1236                         QTextBoundaryFinder graphemeQtbf(QTextBoundaryFinder::Grapheme, splitMsg);
1237                         graphemeQtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1238                         qtbf = graphemeQtbf;
1239                     }
1240                     else {
1241                         // If the QTBF fails to find a split point in Grapheme mode, we give up.
1242                         // This should never happen, but it should be handled anyway.
1243                         qWarning() << "Unexpected failure to split message!";
1244                         return msgsToSend;
1245                     }
1246                 }
1247             }
1248
1249             // Once a message of sendable length has been found, remove it from the wrkMsg and
1250             // add it to the list of messages to be sent.
1251             wrkMsg.remove(0, splitPos);
1252             msgsToSend.append(splitMsgEnc);
1253         }
1254         else{
1255             // If the entire remaining message is short enough to be sent all at once, remove
1256             // it from the wrkMsg and add it to the list of messages to be sent.
1257             wrkMsg.remove(0, splitPos);
1258             msgsToSend.append(initialSplitMsgEnc);
1259         }
1260     } while (wrkMsg.size() > 0);
1261
1262     return msgsToSend;
1263 }