Merge branch 'notify'

This commit is contained in:
Alexey Sokolov
2022-11-24 20:03:06 +00:00
4 changed files with 135 additions and 19 deletions
+83 -16
View File
@@ -14,22 +14,28 @@
* limitations under the License.
*/
#include <znc/znc.h>
#include <znc/User.h>
#include <znc/znc.h>
using std::set;
class CClientNotifyMod : public CModule {
protected:
CString m_sMethod;
bool m_bNewOnly{};
bool m_bOnDisconnect{};
bool m_bNotifyOnNewIP{};
bool m_bNotifyOnNewClientID{};
set<CString> m_sClientsSeen;
set<CString> m_sClientsSeenIP;
set<CString> m_sClientsSeenID;
void SaveSettings() {
SetNV("method", m_sMethod);
SetNV("newonly", m_bNewOnly ? "1" : "0");
SetNV("notifyonnewip", m_bNotifyOnNewIP ? "1" : "0");
SetNV("notifyonnewclientid", m_bNotifyOnNewClientID ? "1" : "0");
SetNV("ondisconnect", m_bOnDisconnect ? "1" : "0");
}
@@ -48,8 +54,16 @@ class CClientNotifyMod : public CModule {
t_d("Sets the notify method"),
[=](const CString& sLine) { OnMethodCommand(sLine); });
AddCommand("NewOnly", t_d("<on|off>"),
t_d("Turns notifications for unseen IP addresses on or off"),
t_d("Turns notifications for unseen connections on or off"),
[=](const CString& sLine) { OnNewOnlyCommand(sLine); });
AddCommand("NotifyOnNewIP", t_d("<on|off>"),
t_d("Specifies whether you want to be notified about new "
"connections with new IPs"),
[=](const CString& sLine) { OnNotifyOnNewIP(sLine); });
AddCommand("NotifyOnNewID", t_d("<on|off>"),
t_d("Specifies whether you want to be notified about new "
"connections with new client IDs"),
[=](const CString& sLine) { OnNotifyOnNewID(sLine); });
AddCommand(
"OnDisconnect", t_d("<on|off>"),
t_d("Turns notifications for clients disconnecting on or off"),
@@ -68,6 +82,8 @@ class CClientNotifyMod : public CModule {
// default = off for these:
m_bNotifyOnNewIP = (GetNV("notifyonnewip") == "1");
m_bNotifyOnNewClientID = (GetNV("notifyonnewclientid") == "1");
m_bNewOnly = (GetNV("newonly") == "1");
m_bOnDisconnect = (GetNV("ondisconnect") == "1");
@@ -76,18 +92,41 @@ class CClientNotifyMod : public CModule {
void OnClientLogin() override {
CString sRemoteIP = GetClient()->GetRemoteIP();
if (!m_bNewOnly ||
m_sClientsSeen.find(sRemoteIP) == m_sClientsSeen.end()) {
SendNotification(t_p("<This message is impossible for 1 client>",
"Another client authenticated as your user. "
"Use the 'ListClients' command to see all {1} "
"clients.",
GetUser()->GetAllClients().size())(
GetUser()->GetAllClients().size()));
CString sRemoteClientID = GetClient()->GetIdentifier();
// the set<> will automatically disregard duplicates:
m_sClientsSeen.insert(sRemoteIP);
CString sClientNameMessage{sRemoteIP};
if (m_bNotifyOnNewClientID && sRemoteClientID != "") {
sClientNameMessage += " / " + sRemoteClientID;
}
auto sendLoginNotification = [&]() {
SendNotification(
t_p("<This message is impossible for 1 client>",
"Another client ({1}) authenticated as your user. "
"Use the 'ListClients' command to see all {2} "
"clients.",
GetUser()->GetAllClients().size())(
sClientNameMessage, GetUser()->GetAllClients().size()));
};
if (m_bNewOnly) {
// see if we actually got a new client
// TODO: replace setName.find(...) == setName.end() with
// !setName.contains() once ZNC uses C++20
if ((m_bNotifyOnNewIP && (m_sClientsSeenIP.find(sRemoteIP) ==
m_sClientsSeenIP.end())) ||
(m_bNotifyOnNewClientID &&
(m_sClientsSeenID.find(sRemoteClientID) ==
m_sClientsSeenID.end()))) {
sendLoginNotification();
}
} else {
sendLoginNotification();
}
// the set<> will automatically disregard duplicates:
m_sClientsSeenIP.insert(sRemoteIP);
m_sClientsSeenID.insert(sRemoteClientID);
}
void OnClientDisconnect() override {
@@ -127,6 +166,32 @@ class CClientNotifyMod : public CModule {
PutModule(t_s("Saved."));
}
void OnNotifyOnNewIP(const CString& sCommand) {
const CString sArg = sCommand.Token(1, true).AsLower();
if (sArg.empty()) {
PutModule(t_s("Usage: NotifyOnNewIP <on|off>"));
return;
}
m_bNotifyOnNewIP = sArg.ToBool();
SaveSettings();
PutModule(t_s("Saved."));
}
void OnNotifyOnNewID(const CString& sCommand) {
const CString sArg = sCommand.Token(1, true).AsLower();
if (sArg.empty()) {
PutModule(t_s("Usage: NotifyOnNewID <on|off>"));
return;
}
m_bNotifyOnNewClientID = sArg.ToBool();
SaveSettings();
PutModule(t_s("Saved."));
}
void OnDisconnectCommand(const CString& sCommand) {
const CString sArg = sCommand.Token(1, true).AsLower();
@@ -142,9 +207,11 @@ class CClientNotifyMod : public CModule {
void OnShowCommand(const CString& sLine) {
PutModule(
t_f("Current settings: Method: {1}, for unseen IP addresses only: "
"{2}, notify on disconnecting clients: {3}")(
m_sMethod, m_bNewOnly, m_bOnDisconnect));
t_f("Current settings: Method: {1}, for unseen only: {2}, notify"
"for unseen IPs: {3}, notify for unseen IDs: {4}, notify on"
"disconnecting clients: {5}")(
m_sMethod, m_bNewOnly, m_bNotifyOnNewIP, m_bNotifyOnNewClientID,
m_bOnDisconnect));
}
};
+6 -2
View File
@@ -72,11 +72,15 @@ Socket ZNCTest::ConnectClient() {
return WrapIO(&sock);
}
Socket ZNCTest::LoginClient() {
Socket ZNCTest::LoginClient(QString identifier) {
auto client = ConnectClient();
client.Write("PASS :hunter2");
client.Write("NICK nick");
client.Write("USER user/test x x :x");
if ( identifier.length() == 0 ) {
client.Write("USER user/test x x :x");
} else {
client.Write("USER user@" + identifier.toUtf8() + "/test x x :x");
}
return client;
}
+1 -1
View File
@@ -37,7 +37,7 @@ class ZNCTest : public testing::Test {
Socket ConnectIRCd();
Socket ConnectClient();
Socket LoginClient();
Socket LoginClient(QString identifier = "");
std::unique_ptr<Process> Run();
+45
View File
@@ -20,6 +20,7 @@
#include "znctest.h"
using testing::HasSubstr;
using testing::Not;
namespace znc_inttest {
namespace {
@@ -54,6 +55,50 @@ TEST_F(ZNCTest, NotifyConnectModule) {
"NOTICE nick :*** user@identifier detached from 127.0.0.1");
}
TEST_F(ZNCTest, ClientNotifyModule) {
auto znc = Run();
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod clientnotify");
client.ReadUntil("Loaded module");
auto check_not_sent = [](Socket& client, std::string wrongAnswer){
auto result = QString{client.ReadRemainder()}.toStdString();
EXPECT_THAT(result, Not(HasSubstr((wrongAnswer)))) << "Got an answer from the ClientNotifyModule even though we didnt want one with the given configuration";
};
auto client2 = LoginClient();
client.ReadUntil(":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 2 clients.");
auto client3 = LoginClient();
client.ReadUntil(":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 3 clients.");
// disable notifications for every message
client.Write("PRIVMSG *clientnotify :NewOnly on");
// check that we do not ge a notification after connecting from a know ip
auto client4 = LoginClient();
check_not_sent(client, ":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 4 clients.");
// choose to notify only on new client ids
client.Write("PRIVMSG *clientnotify :NotifyOnNewID on");
auto client5 = LoginClient("identifier123");
client.ReadUntil(":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 5 clients.");
auto client6 = LoginClient("identifier123");
check_not_sent(client, ":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 6 clients.");
auto client7 = LoginClient("not_identifier123");
client.ReadUntil(":Another client (127.0.0.1 / not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 7 clients.");
// choose to notify from both clientids and new IPs
client.Write("PRIVMSG *clientnotify :NotifyOnNewIP on");
auto client8 = LoginClient();
check_not_sent(client, ":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 8 clients.");
auto client9 = LoginClient("definitely_not_identifier123");
client.ReadUntil(":Another client (127.0.0.1 / definitely_not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 9 clients.");
}
TEST_F(ZNCTest, ShellModule) {
auto znc = Run();
auto ircd = ConnectIRCd();