mirror of
https://github.com/znc/znc.git
synced 2026-08-08 18:03:22 +02:00
Support SASL EXTERNAL in certauth
This commit is contained in:
+79
-8
@@ -53,15 +53,18 @@ class CSSLClientCertMod : public CModule {
|
||||
|
||||
for (MCString::const_iterator it = BeginNV(); it != EndNV(); ++it) {
|
||||
VCString vsKeys;
|
||||
const CString& sUser = it->first;
|
||||
|
||||
if (CZNC::Get().FindUser(it->first) == nullptr) {
|
||||
DEBUG("Unknown user in saved data [" + it->first + "]");
|
||||
if (CZNC::Get().FindUser(sUser) == nullptr) {
|
||||
DEBUG("Unknown user in saved data [" + sUser + "]");
|
||||
continue;
|
||||
}
|
||||
|
||||
it->second.Split(" ", vsKeys, false);
|
||||
for (const CString& sKey : vsKeys) {
|
||||
m_PubKeys[it->first].insert(sKey.AsLower());
|
||||
for (CString& sKey : vsKeys) {
|
||||
sKey.MakeLower();
|
||||
m_PubKeys[sUser].insert(sKey);
|
||||
m_KeyToUser[sKey].insert(sUser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,12 +93,14 @@ class CSSLClientCertMod : public CModule {
|
||||
return SaveRegistry();
|
||||
}
|
||||
|
||||
bool AddKey(CUser* pUser, const CString& sKey) {
|
||||
bool AddKey(CUser* pUser, CString sKey) {
|
||||
sKey.MakeLower();
|
||||
const pair<SCString::const_iterator, bool> pair =
|
||||
m_PubKeys[pUser->GetUsername()].insert(sKey.AsLower());
|
||||
m_PubKeys[pUser->GetUsername()].insert(sKey);
|
||||
|
||||
if (pair.second) {
|
||||
Save();
|
||||
m_KeyToUser[sKey].insert(pUser->GetUsername());
|
||||
}
|
||||
|
||||
return pair.second;
|
||||
@@ -118,7 +123,7 @@ class CSSLClientCertMod : public CModule {
|
||||
|
||||
MSCString::const_iterator it = m_PubKeys.find(sUser);
|
||||
if (it == m_PubKeys.end()) {
|
||||
DEBUG("No saved pubkeys for this client");
|
||||
DEBUG("No saved pubkeys for this user");
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
@@ -135,6 +140,56 @@ class CSSLClientCertMod : public CModule {
|
||||
return HALT;
|
||||
}
|
||||
|
||||
void OnClientGetSASLMechanisms(SCString& ssMechanisms) override {
|
||||
ssMechanisms.insert("EXTERNAL");
|
||||
}
|
||||
|
||||
EModRet OnClientSASLAuthenticate(const CString& sMechanism,
|
||||
const CString& sMessage) override {
|
||||
if (sMechanism != "EXTERNAL") {
|
||||
return CONTINUE;
|
||||
}
|
||||
CString sUser = GetClient()->ParseUser(sMessage);
|
||||
const CString sKey = GetKey(GetClient());
|
||||
DEBUG("Key: " << sKey);
|
||||
|
||||
if (sKey.empty()) {
|
||||
GetClient()->RefuseSASLLogin("No client cert presented");
|
||||
return HALT;
|
||||
}
|
||||
|
||||
auto it = m_KeyToUser.find(sKey);
|
||||
if (it == m_KeyToUser.end()) {
|
||||
GetClient()->RefuseSASLLogin("Client cert not recognized");
|
||||
return HALT;
|
||||
}
|
||||
|
||||
const SCString& ssUsers = it->second;
|
||||
|
||||
if (ssUsers.empty()) {
|
||||
GetClient()->RefuseSASLLogin("Key found, but list of users is empty, please report bug");
|
||||
return HALT;
|
||||
}
|
||||
|
||||
if (sUser.empty()) {
|
||||
sUser = *ssUsers.begin();
|
||||
} else if (ssUsers.count(sUser) == 0) {
|
||||
GetClient()->RefuseSASLLogin(
|
||||
"The specified user doesn't have this key");
|
||||
return HALT;
|
||||
}
|
||||
|
||||
CUser* pUser = CZNC::Get().FindUser(sUser);
|
||||
if (!pUser) {
|
||||
GetClient()->RefuseSASLLogin("User not found");
|
||||
return HALT;
|
||||
}
|
||||
|
||||
DEBUG("Accepted cert auth for " << sUser);
|
||||
GetClient()->AcceptSASLLogin(*pUser);
|
||||
return HALT;
|
||||
}
|
||||
|
||||
void HandleShowCommand(const CString& sLine) {
|
||||
const CString sPubKey = GetKey(GetClient());
|
||||
|
||||
@@ -211,8 +266,16 @@ class CSSLClientCertMod : public CModule {
|
||||
id--;
|
||||
}
|
||||
|
||||
CString sKey = *it2;
|
||||
it->second.erase(it2);
|
||||
if (it->second.size() == 0) m_PubKeys.erase(it);
|
||||
|
||||
it = m_KeyToUser.find(sKey);
|
||||
if (it != m_KeyToUser.end()) {
|
||||
it->second.erase(GetUser()->GetUsername());
|
||||
if (it->second.empty()) m_KeyToUser.erase(it);
|
||||
}
|
||||
|
||||
PutModule(t_s("Removed"));
|
||||
|
||||
Save();
|
||||
@@ -259,11 +322,18 @@ class CSSLClientCertMod : public CModule {
|
||||
} else if (sPageName == "delete") {
|
||||
MSCString::iterator it = m_PubKeys.find(pUser->GetUsername());
|
||||
if (it != m_PubKeys.end()) {
|
||||
if (it->second.erase(WebSock.GetParam("key", false))) {
|
||||
CString sKey = WebSock.GetParam("key", false);
|
||||
if (it->second.erase(sKey)) {
|
||||
if (it->second.size() == 0) {
|
||||
m_PubKeys.erase(it);
|
||||
}
|
||||
|
||||
it = m_KeyToUser.find(sKey);
|
||||
if (it != m_KeyToUser.end()) {
|
||||
it->second.erase(pUser->GetUsername());
|
||||
if (it->second.empty()) m_KeyToUser.erase(it);
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
}
|
||||
@@ -279,6 +349,7 @@ class CSSLClientCertMod : public CModule {
|
||||
// Maps user names to a list of allowed pubkeys
|
||||
typedef map<CString, set<CString>> MSCString;
|
||||
MSCString m_PubKeys;
|
||||
MSCString m_KeyToUser;
|
||||
};
|
||||
|
||||
template <>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
class CSASLMechanismPlain : public CModule {
|
||||
public:
|
||||
MODCONSTRUCTOR(CSASLMechanismPlain) { AddHelpCommand(); }
|
||||
MODCONSTRUCTOR(CSASLMechanismPlain) {}
|
||||
|
||||
void OnClientGetSASLMechanisms(SCString& ssMechanisms) override {
|
||||
ssMechanisms.insert("PLAIN");
|
||||
@@ -27,7 +27,7 @@ class CSASLMechanismPlain : public CModule {
|
||||
|
||||
EModRet OnClientSASLAuthenticate(const CString& sMechanism,
|
||||
const CString& sMessage) override {
|
||||
if (!sMechanism.Equals("PLAIN")) {
|
||||
if (sMechanism != "PLAIN") {
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "znctest.h"
|
||||
|
||||
#include <QSslSocket>
|
||||
|
||||
using testing::HasSubstr;
|
||||
using testing::Not;
|
||||
|
||||
@@ -362,5 +364,83 @@ TEST_F(ZNCTest, SaslAuthPlain) {
|
||||
client.ReadUntil(":irc.znc.in 903 foo :SASL authentication successful");
|
||||
}
|
||||
|
||||
TEST_F(ZNCTest, SaslAuthExternal) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
ircd.Write(":server 001 nick :Hello");
|
||||
auto client = LoginClient();
|
||||
client.Write("znc addport +12346 all all");
|
||||
client.ReadUntil(":Port added");
|
||||
client.Write("znc loadmod certauth");
|
||||
client.ReadUntil("Loaded");
|
||||
client.Close();
|
||||
|
||||
QSslSocket sock;
|
||||
// Could generate a new one for the test, but this one is good enough
|
||||
sock.setLocalCertificate(m_dir.path() + "/znc.pem");
|
||||
sock.setPrivateKey(m_dir.path() + "/znc.pem");
|
||||
sock.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
sock.connectToHostEncrypted("127.0.0.1", 12346);
|
||||
ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString();
|
||||
ASSERT_TRUE(sock.waitForEncrypted()) << sock.errorString().toStdString();
|
||||
auto client2 = WrapIO(&sock);
|
||||
client2.Write("PASS :hunter2");
|
||||
client2.Write("NICK nick");
|
||||
client2.Write("USER user/test x x :x");
|
||||
client2.Write("privmsg *certauth add");
|
||||
client2.ReadUntil("added");
|
||||
|
||||
auto Reconnect = [&] {
|
||||
client2.Close();
|
||||
ASSERT_TRUE(sock.state() == QAbstractSocket::UnconnectedState || sock.waitForDisconnected())
|
||||
<< sock.errorString().toStdString();
|
||||
sock.connectToHostEncrypted("127.0.0.1", 12346);
|
||||
ASSERT_TRUE(sock.waitForConnected())
|
||||
<< sock.errorString().toStdString();
|
||||
ASSERT_TRUE(sock.waitForEncrypted())
|
||||
<< sock.errorString().toStdString();
|
||||
client2.Write("CAP REQ sasl");
|
||||
client2.Write("NICK nick");
|
||||
client2.Write("USER u x x :x");
|
||||
client2.ReadUntil("ACK :sasl");
|
||||
client2.Write("AUTHENTICATE EXTERNAL");
|
||||
client2.ReadUntil("AUTHENTICATE +");
|
||||
};
|
||||
|
||||
Reconnect();
|
||||
ircd.Write(":friend PRIVMSG nick :hello");
|
||||
client2.Write("AUTHENTICATE +");
|
||||
client2.ReadUntil(
|
||||
":irc.znc.in 900 nick nick!user@127.0.0.1 user :You are now logged in "
|
||||
"as user");
|
||||
client2.ReadUntil(":irc.znc.in 903 nick :SASL authentication successful");
|
||||
client2.Write("CAP END");
|
||||
// '[' comes from lack of server-time
|
||||
client2.ReadUntil(":friend PRIVMSG nick :[");
|
||||
|
||||
Reconnect();
|
||||
client2.Write("AUTHENTICATE " + QString("user/te").toUtf8().toBase64());
|
||||
client2.ReadUntil(
|
||||
":irc.znc.in 900 nick nick!user@127.0.0.1 user :You are now logged in "
|
||||
"as user");
|
||||
client2.ReadUntil(":irc.znc.in 903 nick :SASL authentication successful");
|
||||
client2.Write("CAP END");
|
||||
client2.ReadUntil(
|
||||
":*status!status@znc.in PRIVMSG nick :Network te doesn't exist.");
|
||||
|
||||
Reconnect();
|
||||
client2.Write("AUTHENTICATE " + QString("moo").toUtf8().toBase64());
|
||||
client2.ReadUntil(
|
||||
":irc.znc.in 904 nick :The specified user doesn't have this key");
|
||||
|
||||
client = LoginClient();
|
||||
client.Write("privmsg *certauth :del 1");
|
||||
client.ReadUntil("Removed");
|
||||
Reconnect();
|
||||
client2.Write("AUTHENTICATE +");
|
||||
client2.ReadUntil(
|
||||
":irc.znc.in 904 nick :Client cert not recognized");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace znc_inttest
|
||||
|
||||
Reference in New Issue
Block a user