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