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 | |
32 | INIT_SYNCABLE_OBJECT(CoreNetwork) |
33 | CoreNetwork::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 | |
91 | CoreNetwork::~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 | |
100 | QString 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 | |
111 | QString 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 | |
120 | QByteArray 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 | |
131 | QByteArray 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 | |
140 | void 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 | |
211 | void 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 | |
250 | void CoreNetwork::userInput(BufferInfo buf, QString msg) |
251 | { |
252 | userInputHandler()->handleUserInput(buf, msg); |
253 | } |
254 | |
255 | |
256 | void CoreNetwork::putRawLine(QByteArray s) |
257 | { |
258 | if (_tokenBucket > 0) |
259 | writeToSocket(s); |
260 | else |
261 | _msgQueue.append(s); |
262 | } |
263 | |
264 | |
265 | void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> ¶ms, 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 | |
286 | void 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 | |
295 | void 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 | |
305 | void 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 | |
316 | void CoreNetwork::removeChannelKey(const QString &channel) |
317 | { |
318 | _channelKeys.remove(channel.toLower()); |
319 | } |
320 | |
321 | |
322 | #ifdef HAVE_QCA2 |
323 | Cipher *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 | |
345 | QByteArray 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 | |
359 | void 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 | |
378 | bool 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 | |
391 | bool 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 | |
402 | void CoreNetwork::setMyNick(const QString &mynick) |
403 | { |
404 | Network::setMyNick(mynick); |
405 | if (connectionState() == Network::Initializing) |
406 | networkInitialized(); |
407 | } |
408 | |
409 | |
410 | void 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 | |
429 | void 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 | |
445 | void 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 | |
489 | void 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 | |
527 | void 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 | |
551 | void 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 | |
582 | void 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 | |
628 | void 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 | |
667 | void 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 | |
705 | void 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 | |
751 | void CoreNetwork::resetPersistentModes() |
752 | { |
753 | _requestedUserModes = QString('-'); |
754 | Core::setUserModes(userId(), networkId(), QString()); |
755 | } |
756 | |
757 | |
758 | void CoreNetwork::setUseAutoReconnect(bool use) |
759 | { |
760 | Network::setUseAutoReconnect(use); |
761 | if (!use) |
762 | _autoReconnectTimer.stop(); |
763 | } |
764 | |
765 | |
766 | void CoreNetwork::setAutoReconnectInterval(quint32 interval) |
767 | { |
768 | Network::setAutoReconnectInterval(interval); |
769 | _autoReconnectTimer.setInterval(interval * 1000); |
770 | } |
771 | |
772 | |
773 | void 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 | |
785 | void 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 | |
797 | void 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 | |
820 | void 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 | |
832 | void CoreNetwork::disablePingTimeout() |
833 | { |
834 | _pingTimer.stop(); |
835 | _sendPings = false; |
836 | resetPingTimeout(); |
837 | } |
838 | |
839 | |
840 | void CoreNetwork::setPingInterval(int interval) |
841 | { |
842 | _pingTimer.setInterval(interval * 1000); |
843 | } |
844 | |
845 | |
846 | /******** AutoWHO ********/ |
847 | |
848 | void CoreNetwork::startAutoWhoCycle() |
849 | { |
850 | if (!_autoWhoQueue.isEmpty()) { |
851 | _autoWhoCycleTimer.stop(); |
852 | return; |
853 | } |
854 | _autoWhoQueue = channels(); |
855 | } |
856 | |
857 | |
858 | void CoreNetwork::setAutoWhoDelay(int delay) |
859 | { |
860 | _autoWhoTimer.setInterval(delay * 1000); |
861 | } |
862 | |
863 | |
864 | void CoreNetwork::setAutoWhoInterval(int interval) |
865 | { |
866 | _autoWhoCycleTimer.setInterval(interval * 1000); |
867 | } |
868 | |
869 | |
870 | void 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 | |
881 | void 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 |
906 | void 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 | |
916 | void 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 | |
928 | void CoreNetwork::writeToSocket(const QByteArray &data) |
929 | { |
930 | socket.write(data); |
931 | socket.write("\r\n" ); |
932 | _tokenBucket--; |
933 | } |
934 | |
935 | |
936 | Network::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 | |
948 | void 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 | |
958 | void 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 | |
968 | void 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 | |