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 "netsplit.h"
22#include "network.h"
23#include "util.h"
24
25#include <QRegExp>
26
27Netsplit::Netsplit(Network *network, QObject *parent)
28 : QObject(parent),
29 _network(network), _quitMsg(""), _sentQuit(false), _joinCounter(0), _quitCounter(0)
30{
31 _discardTimer.setSingleShot(true);
32 _joinTimer.setSingleShot(true);
33 _quitTimer.setSingleShot(true);
34
35 connect(&_discardTimer, SIGNAL(timeout()), this, SIGNAL(finished()));
36
37 connect(&_joinTimer, SIGNAL(timeout()), this, SLOT(joinTimeout()));
38 connect(&_quitTimer, SIGNAL(timeout()), this, SLOT(quitTimeout()));
39
40 // wait for a maximum of 1 hour until we discard the netsplit
41 _discardTimer.start(3600000);
42}
43
44
45void Netsplit::userQuit(const QString &sender, const QStringList &channels, const QString &msg)
46{
47 if (_quitMsg.isEmpty())
48 _quitMsg = msg;
49 foreach(QString channel, channels) {
50 _quits[channel].append(sender);
51 }
52 _quitCounter++;
53 // now let's wait 10s to finish the netsplit-quit
54 _quitTimer.start(10000);
55}
56
57
58bool Netsplit::userJoined(const QString &sender, const QString &channel)
59{
60 if (!_quits.contains(channel))
61 return false;
62
63 QStringList &users = _quits[channel];
64
65 QStringList::iterator userIter;
66 const QString senderNick = nickFromMask(sender);
67 for (userIter = users.begin(); userIter != users.end(); ++userIter) {
68 if (nickFromMask(*userIter) == senderNick)
69 break;
70 }
71 if (userIter == users.end())
72 return false;
73
74 _joins[channel].first.append(*userIter);
75 _joins[channel].second.append(QString());
76
77 users.erase(userIter);
78
79 if (users.empty())
80 _quits.remove(channel);
81
82 _joinCounter++;
83
84 if (_quits.empty()) // all users joined already - no need to wait
85 _joinTimer.start(0);
86 else // wait 30s to finish the netsplit-join
87 _joinTimer.start(30000);
88
89 return true;
90}
91
92
93bool Netsplit::userAlreadyJoined(const QString &sender, const QString &channel)
94{
95 if (_joins.value(channel).first.contains(sender))
96 return true;
97 return false;
98}
99
100
101void Netsplit::addMode(const QString &sender, const QString &channel, const QString &mode)
102{
103 if (!_joins.contains(channel))
104 return;
105 int idx = _joins.value(channel).first.indexOf(sender);
106 if (idx == -1)
107 return;
108 _joins[channel].second[idx].append(mode);
109}
110
111
112bool Netsplit::isNetsplit(const QString &quitMessage)
113{
114 // check if we find some common chars that disqualify the netsplit as such
115 if (quitMessage.contains(':') || quitMessage.contains('/'))
116 return false;
117
118 // now test if message consists only of two dns names as the RFC requests
119 // but also allow the commonly used "*.net *.split"
120 QRegExp hostRx("^(?:[\\w\\d-.]+|\\*)\\.[\\w\\d-]+\\s(?:[\\w\\d-.]+|\\*)\\.[\\w\\d-]+$");
121 if (hostRx.exactMatch(quitMessage))
122 return true;
123
124 return false;
125}
126
127
128void Netsplit::joinTimeout()
129{
130 if (!_sentQuit) {
131 _quitTimer.stop();
132 quitTimeout();
133 }
134
135 QHash<QString, QPair<QStringList, QStringList> >::iterator it;
136
137 /*
138 Try to catch server jumpers.
139 If we have too few joins for a netsplit-quit,
140 we assume that the users manually changed servers and join them
141 without ending the netsplit.
142 A netsplit is assumed over only if at least 1/3 of all quits had their corresponding
143 join again.
144 */
145 if (_joinCounter < _quitCounter/3) {
146 for (it = _joins.begin(); it != _joins.end(); ++it)
147 emit earlyJoin(network(), it.key(), it.value().first, it.value().second);
148
149 // we don't care about those anymore
150 _joins.clear();
151
152 // restart the timer with 5min timeout
153 // This might happen a few times if netsplit lasts longer.
154 // As soon as another user joins, the timer is set to a shorter timeout again.
155 _joinTimer.start(300000);
156 return;
157 }
158
159 // send netsplitJoin for every recorded channel
160 for (it = _joins.begin(); it != _joins.end(); ++it)
161 emit netsplitJoin(network(), it.key(), it.value().first, it.value().second, _quitMsg);
162 _joins.clear();
163 _discardTimer.stop();
164 emit finished();
165}
166
167
168void Netsplit::quitTimeout()
169{
170 // send netsplitQuit for every recorded channel
171 QHash<QString, QStringList>::iterator channelIter;
172 for (channelIter = _quits.begin(); channelIter != _quits.end(); ++channelIter) {
173 QStringList usersToSend;
174
175 foreach(QString user, channelIter.value()) {
176 if (!_quitsWithMessageSent.value(channelIter.key()).contains(user)) {
177 usersToSend << user;
178 _quitsWithMessageSent[channelIter.key()].append(user);
179 }
180 }
181 // not yet sure how that could happen, but never send empty netsplit-quits
182 // anyway.
183 if (!usersToSend.isEmpty())
184 emit netsplitQuit(network(), channelIter.key(), usersToSend, _quitMsg);
185 }
186 _sentQuit = true;
187}
188