mirror of
https://github.com/znc/znc.git
synced 2026-08-07 01:13:25 +02:00
Various SASL changes
This commit is contained in:
+37
-7
@@ -41,6 +41,12 @@ class CAuthBase : private CCoreTranslationMixin {
|
||||
CZNCSock* pSock)
|
||||
: m_sUsername(sUsername), m_sPassword(sPassword), m_pSock(pSock) {}
|
||||
|
||||
// If a module tries to do std::make_shared, the vtable of the mutex inside
|
||||
// shared_ptr will point to the code in the module, and will crash when the
|
||||
// module is unloaded, e.g. shutdown. This function forces the creation of
|
||||
// shared_ptr in the 'znc' binary instead of in the module.
|
||||
static std::shared_ptr<CAuthBase> WrapPointer(CAuthBase*);
|
||||
|
||||
virtual ~CAuthBase() {}
|
||||
|
||||
CAuthBase(const CAuthBase&) = delete;
|
||||
@@ -96,6 +102,17 @@ class CClientAuth : public CAuthBase {
|
||||
CClient* m_pClient;
|
||||
};
|
||||
|
||||
// Workaround SWIG bug, TODO report it
|
||||
#ifndef SWIG
|
||||
/** Username+password auth, which reports success/failure to client via SASL. */
|
||||
class CClientSASLAuth : public CClientAuth {
|
||||
public:
|
||||
using CClientAuth::CClientAuth;
|
||||
void AcceptedLogin(CUser& User) override;
|
||||
void RefusedLogin(const CString& sReason) override;
|
||||
};
|
||||
#endif
|
||||
|
||||
class CClient : public CIRCSocket {
|
||||
public:
|
||||
CClient();
|
||||
@@ -250,6 +267,16 @@ class CClient : public CIRCSocket {
|
||||
CIRCSock* GetIRCSock();
|
||||
CString GetFullName() const;
|
||||
|
||||
/** Sends AUTHENTIATE message to client.
|
||||
* It encodes it to Base64 and splits to multiple IRC messages if necessary.
|
||||
*/
|
||||
void SendSASLChallenge(CString sMessage);
|
||||
void RefuseSASLLogin(const CString& sReason);
|
||||
void AcceptSASLLogin(CUser& User);
|
||||
// Like CZNC::AuthUser() but also stores the pointer, and calls Invalidate()
|
||||
// if the client is destroyed.
|
||||
void StartPasswordCheck(std::shared_ptr<CAuthBase> spAuth);
|
||||
|
||||
private:
|
||||
void HandleCap(const CMessage& Message);
|
||||
void RespondCap(const CString& sResponse);
|
||||
@@ -266,14 +293,14 @@ class CClient : public CIRCSocket {
|
||||
unsigned int DetachChans(const std::set<CChan*>& sChans);
|
||||
|
||||
bool OnActionMessage(CActionMessage& Message);
|
||||
void OnAuthenticateMessage(CAuthenticateMessage& Message);
|
||||
void OnAuthenticateMessage(const CAuthenticateMessage& Message);
|
||||
void AbortSASL(const CString& sFullIRCLine);
|
||||
bool IsDuringSASL() const { return !m_sSASLMechanism.empty(); }
|
||||
|
||||
/**
|
||||
* Fills all available SASL mechanisms in the passed set, and returns a comma-joined string of those mechanisms.
|
||||
* @param ssMechanisms Set of supported mechanisms, filled by this method.
|
||||
* @return A comma-joined string of supported mechanisms.
|
||||
* Returns set of all available SASL mechanisms.
|
||||
*/
|
||||
CString EnumerateSASLMechanisms(SCString& ssMechanisms);
|
||||
SCString EnumerateSASLMechanisms() const;
|
||||
|
||||
bool OnCTCPMessage(CCTCPMessage& Message);
|
||||
bool OnJoinMessage(CJoinMessage& Message);
|
||||
@@ -305,8 +332,7 @@ class CClient : public CIRCSocket {
|
||||
bool m_bBatch;
|
||||
bool m_bEchoMessage;
|
||||
bool m_bSelfMessage;
|
||||
bool m_bSASL;
|
||||
bool m_bSASLAuthenticating;
|
||||
bool m_bSASLCap;
|
||||
bool m_bPlaybackActive;
|
||||
CUser* m_pUser;
|
||||
CIRCNetwork* m_pNetwork;
|
||||
@@ -316,11 +342,15 @@ class CClient : public CIRCSocket {
|
||||
CString m_sNetwork;
|
||||
CString m_sIdentifier;
|
||||
CString m_sSASLBuffer;
|
||||
// Set while the exchange is in progress
|
||||
CString m_sSASLMechanism;
|
||||
// Username who successfully logged in using SASL. This is not a CUser*
|
||||
// because between the 903 and CAP END the user could have been deleted.
|
||||
CString m_sSASLUser;
|
||||
std::shared_ptr<CAuthBase> m_spAuth;
|
||||
SCString m_ssAcceptedCaps;
|
||||
SCString m_ssSupportedTags;
|
||||
SCString m_ssPreviouslyFailedSASLMechanisms;
|
||||
// The capabilities supported by the ZNC core - capability names mapped to
|
||||
// change handler. Note: this lists caps which don't require support on IRC
|
||||
// server.
|
||||
|
||||
+20
-21
@@ -1363,40 +1363,39 @@ class CModule {
|
||||
*/
|
||||
virtual void OnClientCapRequest(CClient* pClient, const CString& sCap,
|
||||
bool bState);
|
||||
|
||||
/** Called when a client requests SASL authentication. Use ssMechanisms.insert("MECHANISM")
|
||||
* for announcing SASL mechanisms which your module supports.
|
||||
* @param ssMechanisms The set of supported SASL mechanisms to append to.
|
||||
*/
|
||||
virtual void OnClientGetSASLMechanisms(SCString& ssMechanisms);
|
||||
/** Called when a client has selected a SASL mechanism for SASL authentication.
|
||||
* If implementing a SASL authentication mechanism, set sResponse to specify an initial challenge
|
||||
* message to send to the client. Otherwise, an empty response will be sent.
|
||||
* If implementing a SASL authentication mechanism, set sResponse to
|
||||
* specify an initial challenge message to send to the client. Otherwise, an
|
||||
* empty response will be sent. To avoid sending any immediate response,
|
||||
* return HALT; in that case the module should schedule calling
|
||||
* GetClient()->SendSASLChallenge() with the initial response: in IRC SASL,
|
||||
* server always responds first.
|
||||
* @param sMechanism The SASL mechanism selected by the client.
|
||||
* @param sResponse The optional value of an initial SASL challenge message to send to the client.
|
||||
* @param sResponse The optional value of an initial SASL challenge message
|
||||
* to send to the client.
|
||||
*/
|
||||
virtual EModRet OnClientSASLServerInitialChallenge(
|
||||
const CString& sMechanism, CString& sResponse);
|
||||
/** Called when a client is sending us a SASL message after the mechanism was selected.
|
||||
* If implementing a SASL authentication mechanism, check the passed
|
||||
* credentials, then either request more data by sending a challenge in
|
||||
* sMechanismResponse, reject authentication by setting
|
||||
* bAuthenticationSuccess to false, or accept authentication by setting
|
||||
* bAuthenticationSuccess to true and setting sUser to the authenticated
|
||||
* user name.
|
||||
* GetClient()->SendSASLChallenge(), or reject authentication by calling
|
||||
* GetClient()->RefuseSASLLogin(), or accept it by calling
|
||||
* GetClient()->AcceptSASLLogin().
|
||||
* @param sMechanism The SASL mechanism selected by the client.
|
||||
* @param sBuffer The SASL opaque value/credentials sent by the client.
|
||||
* @param sUser The optional name of the authenticated user to log in the
|
||||
* user as, if authentication is accepted.
|
||||
* @param sMechanismResponse The optional value of a SASL challenge message
|
||||
* to reply to the client to ask for more data.
|
||||
* @param bAuthenticationSuccess If sMechanismResponse is not set, whether
|
||||
* to accept or reject the authentication request.
|
||||
* @param sMessage The SASL opaque value/credentials sent by the client,
|
||||
* after debase64ing and concatenating if it was split.
|
||||
*/
|
||||
virtual EModRet OnClientSASLAuthenticate(const CString& sMechanism,
|
||||
const CString& sBuffer,
|
||||
CString& sUser,
|
||||
CString& sMechanismResponse,
|
||||
bool& bAuthenticationSuccess);
|
||||
const CString& sMessage);
|
||||
/** Called when a client sent '*' to abort SASL, or aborted it for another reason. */
|
||||
virtual void OnClientSASLAborted();
|
||||
|
||||
/** Called when a module is going to be loaded.
|
||||
* @param sModName name of the module.
|
||||
@@ -1699,13 +1698,13 @@ class CModules : public std::vector<CModule*>, private CCoreTranslationMixin {
|
||||
bool IsClientCapSupported(CClient* pClient, const CString& sCap,
|
||||
bool bState);
|
||||
bool OnClientCapRequest(CClient* pClient, const CString& sCap, bool bState);
|
||||
|
||||
bool OnClientGetSASLMechanisms(SCString& ssMechanisms);
|
||||
bool OnClientSASLAborted();
|
||||
bool OnClientSASLServerInitialChallenge(const CString& sMechanism,
|
||||
CString& sResponse);
|
||||
bool OnClientSASLAuthenticate(const CString& sMechanism,
|
||||
const CString& sBuffer, CString& sUser,
|
||||
CString& sResponse,
|
||||
bool& bAuthenticationSuccess);
|
||||
const CString& sBuffer);
|
||||
|
||||
bool OnModuleLoading(const CString& sModName, const CString& sArgs,
|
||||
CModInfo::EModuleType eType, bool& bSuccess,
|
||||
|
||||
@@ -114,7 +114,7 @@ bool IsClientCapSupported(CClient* pClient, const CString& sCap, bool bState)
|
||||
void OnClientCapRequest(CClient* pClient, const CString& sCap, bool bState)
|
||||
void OnClientGetSASLMechanisms(SCString& ssMechanisms)
|
||||
EModRet OnClientSASLServerInitialChallenge(const CString& sMechanism, CString& sResponse)
|
||||
EModRet OnClientSASLAuthenticate(const CString& sMechanism, const CString& sBuffer, CString& sUser, CString& sMechanismResponse, bool& bAuthenticationSuccess)
|
||||
EModRet OnClientSASLAuthenticate(const CString& sMechanism, const CString& sMessage)
|
||||
EModRet OnModuleLoading(const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, bool& bSuccess, CString& sRetMsg)
|
||||
EModRet OnModuleUnloading(CModule* pModule, bool& bSuccess, CString& sRetMsg)
|
||||
EModRet OnGetModInfo(CModInfo& ModInfo, const CString& sModule, bool& bSuccess, CString& sRetMsg)
|
||||
|
||||
@@ -198,9 +198,7 @@ class ZNC_EXPORT_LIB_EXPORT CPyModule : public CModule {
|
||||
EModRet OnClientSASLServerInitialChallenge(const CString& sMechanism,
|
||||
CString& sResponse) override;
|
||||
EModRet OnClientSASLAuthenticate(const CString& sMechanism,
|
||||
const CString& sBuffer, CString& sUser,
|
||||
CString& sMechanismResponse,
|
||||
bool& bAuthenticationSuccess) override;
|
||||
const CString& sMessage) override;
|
||||
virtual EModRet OnModuleLoading(const CString& sModName,
|
||||
const CString& sArgs,
|
||||
CModInfo::EModuleType eType, bool& bSuccess,
|
||||
|
||||
@@ -484,7 +484,7 @@ class Module:
|
||||
def OnClientSASLServerInitialChallenge(self, sMechanism, sResponse):
|
||||
pass
|
||||
|
||||
def OnClientSASLAuthenticate(self, sMechanism, sBuffer, sUser, sResponse, bAuthenticationSuccess):
|
||||
def OnClientSASLAuthenticate(self, sMechanism, sMessage):
|
||||
pass
|
||||
|
||||
def OnModuleLoading(self, sModName, sArgs, eType, bSuccess, sRetMsg):
|
||||
|
||||
+11
-14
@@ -21,34 +21,31 @@ class CSASLMechanismPlain : public CModule {
|
||||
public:
|
||||
MODCONSTRUCTOR(CSASLMechanismPlain) { AddHelpCommand(); }
|
||||
|
||||
void OnClientGetSASLMechanisms(SCString& ssMechanisms) override {
|
||||
ssMechanisms.insert("PLAIN");
|
||||
}
|
||||
|
||||
EModRet OnClientSASLAuthenticate(const CString& sMechanism,
|
||||
const CString& sBuffer, CString& sUser,
|
||||
CString& sMechanismResponse,
|
||||
bool& bAuthenticationSuccess) override {
|
||||
const CString& sMessage) override {
|
||||
if (!sMechanism.Equals("PLAIN")) {
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
bAuthenticationSuccess = false;
|
||||
|
||||
CString sNullSeparator = std::string("\0", 1);
|
||||
auto sAuthzId = sBuffer.Token(0, false, sNullSeparator, true);
|
||||
auto sAuthcId = sBuffer.Token(1, false, sNullSeparator, true);
|
||||
auto sPassword = sBuffer.Token(2, false, sNullSeparator, true);
|
||||
CString sAuthzId = sMessage.Token(0, false, sNullSeparator, true);
|
||||
CString sAuthcId = sMessage.Token(1, false, sNullSeparator, true);
|
||||
CString sPassword = sMessage.Token(2, false, sNullSeparator, true);
|
||||
|
||||
if (!sAuthzId.empty() && sAuthzId != sAuthcId) {
|
||||
// Reject custom SASL plain authorization identifiers
|
||||
GetClient()->RefuseSASLLogin("No support for custom AuthzId");
|
||||
return HALTMODS;
|
||||
}
|
||||
|
||||
auto spAuth = std::make_shared<CClientAuth>(GetClient(), sAuthcId, sPassword);
|
||||
CZNC::Get().AuthUser(spAuth);
|
||||
auto spAuth = CAuthBase::WrapPointer(new CClientSASLAuth(GetClient(), sAuthcId, sPassword));
|
||||
GetClient()->StartPasswordCheck(spAuth);
|
||||
return HALTMODS;
|
||||
}
|
||||
|
||||
void OnClientGetSASLMechanisms(SCString& ssMechanisms) override {
|
||||
ssMechanisms.insert("PLAIN");
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
|
||||
+203
-143
@@ -94,8 +94,7 @@ CClient::CClient()
|
||||
m_bBatch(false),
|
||||
m_bEchoMessage(false),
|
||||
m_bSelfMessage(false),
|
||||
m_bSASL(false),
|
||||
m_bSASLAuthenticating(false),
|
||||
m_bSASLCap(false),
|
||||
m_bPlaybackActive(false),
|
||||
m_pUser(nullptr),
|
||||
m_pNetwork(nullptr),
|
||||
@@ -366,14 +365,22 @@ void CClient::AuthUser() {
|
||||
(m_sSASLUser.empty() && !m_bGotPass) || IsAttached())
|
||||
return;
|
||||
|
||||
if (m_bSASL && !m_sSASLUser.empty()) {
|
||||
m_sUser = m_sSASLUser;
|
||||
auto pUser = CZNC::Get().FindUser(m_sUser);
|
||||
AcceptLogin(*pUser);
|
||||
return;
|
||||
if (m_sSASLUser.empty()) {
|
||||
StartPasswordCheck(
|
||||
std::make_shared<CClientAuth>(this, m_sUser, m_sPass));
|
||||
} else {
|
||||
// Already logged in, but the user could have been deleted meanwhile.
|
||||
CUser* pUser = CZNC::Get().FindUser(m_sSASLUser);
|
||||
if (pUser) {
|
||||
AcceptLogin(*pUser);
|
||||
} else {
|
||||
RefuseLogin("SASL login was valid, but user no longer exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_spAuth = std::make_shared<CClientAuth>(this, m_sUser, m_sPass);
|
||||
void CClient::StartPasswordCheck(std::shared_ptr<CAuthBase> spAuth) {
|
||||
m_spAuth = spAuth;
|
||||
|
||||
CZNC::Get().AuthUser(m_spAuth);
|
||||
}
|
||||
@@ -382,6 +389,12 @@ CClientAuth::CClientAuth(CClient* pClient, const CString& sUsername,
|
||||
const CString& sPassword)
|
||||
: CAuthBase(sUsername, sPassword, pClient), m_pClient(pClient) {}
|
||||
|
||||
void CClientSASLAuth::RefusedLogin(const CString& sReason) {
|
||||
if (m_pClient) {
|
||||
m_pClient->RefuseSASLLogin(sReason);
|
||||
}
|
||||
}
|
||||
|
||||
void CClientAuth::RefusedLogin(const CString& sReason) {
|
||||
if (m_pClient) {
|
||||
m_pClient->RefuseLogin(sReason);
|
||||
@@ -398,8 +411,12 @@ void CAuthBase::Invalidate() { m_pSock = nullptr; }
|
||||
void CAuthBase::AcceptLogin(CUser& User) {
|
||||
if (m_pSock) {
|
||||
AcceptedLogin(User);
|
||||
Invalidate();
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
std::shared_ptr<CAuthBase> CAuthBase::WrapPointer(CAuthBase* p) {
|
||||
return std::shared_ptr<CAuthBase>(p);
|
||||
}
|
||||
|
||||
void CAuthBase::RefuseLogin(const CString& sReason) {
|
||||
@@ -427,6 +444,12 @@ void CClient::RefuseLogin(const CString& sReason) {
|
||||
Close(Csock::CLT_AFTERWRITE);
|
||||
}
|
||||
|
||||
void CClientSASLAuth::AcceptedLogin(CUser& User) {
|
||||
if (m_pClient) {
|
||||
m_pClient->AcceptSASLLogin(User);
|
||||
}
|
||||
}
|
||||
|
||||
void CClientAuth::AcceptedLogin(CUser& User) {
|
||||
if (m_pClient) {
|
||||
m_pClient->AcceptLogin(User);
|
||||
@@ -436,7 +459,9 @@ void CClientAuth::AcceptedLogin(CUser& User) {
|
||||
void CClient::AcceptLogin(CUser& User) {
|
||||
m_sPass = "";
|
||||
m_pUser = &User;
|
||||
m_bSASLAuthenticating = m_bSASL;
|
||||
m_sSASLMechanism = "";
|
||||
m_sSASLBuffer = "";
|
||||
m_sSASLUser = "";
|
||||
|
||||
// Set our proper timeout and set back our proper timeout mode
|
||||
// (constructor set a different timeout and mode)
|
||||
@@ -765,37 +790,58 @@ static VCString MultiLine(const SCString& ssCaps) {
|
||||
|
||||
const std::map<CString, std::function<void(CClient*, bool bVal)>>&
|
||||
CClient::CoreCaps() {
|
||||
static const std::map<CString, std::function<void(CClient*, bool bVal)>> mCoreCaps = []{
|
||||
std::map<CString, std::function<void(CClient*, bool bVal)>> mCoreCaps = {
|
||||
{"multi-prefix",
|
||||
[](CClient* pClient, bool bVal) { pClient->m_bNamesx = bVal; }},
|
||||
{"userhost-in-names",
|
||||
[](CClient* pClient, bool bVal) { pClient->m_bUHNames = bVal; }},
|
||||
{"echo-message",
|
||||
[](CClient* pClient, bool bVal) { pClient->m_bEchoMessage = bVal; }},
|
||||
{"server-time",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
pClient->m_bServerTime = bVal;
|
||||
pClient->SetTagSupport("time", bVal);
|
||||
}},
|
||||
{"batch", [](CClient* pClient, bool bVal) {
|
||||
pClient->m_bBatch = bVal;
|
||||
pClient->SetTagSupport("batch", bVal);
|
||||
}},
|
||||
{"cap-notify",
|
||||
[](CClient* pClient, bool bVal) { pClient->m_bCapNotify = bVal; }},
|
||||
{"chghost", [](CClient* pClient, bool bVal) { pClient->m_bChgHost = bVal; }},
|
||||
};
|
||||
static const std::map<CString, std::function<void(CClient*, bool bVal)>>
|
||||
mCoreCaps = [] {
|
||||
std::map<CString, std::function<void(CClient*, bool bVal)>>
|
||||
mCoreCaps = {
|
||||
{"multi-prefix",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
pClient->m_bNamesx = bVal;
|
||||
}},
|
||||
{"userhost-in-names",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
pClient->m_bUHNames = bVal;
|
||||
}},
|
||||
{"echo-message",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
pClient->m_bEchoMessage = bVal;
|
||||
}},
|
||||
{"server-time",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
pClient->m_bServerTime = bVal;
|
||||
pClient->SetTagSupport("time", bVal);
|
||||
}},
|
||||
{"batch",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
pClient->m_bBatch = bVal;
|
||||
pClient->SetTagSupport("batch", bVal);
|
||||
}},
|
||||
{"cap-notify",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
pClient->m_bCapNotify = bVal;
|
||||
}},
|
||||
{"chghost", [](CClient* pClient,
|
||||
bool bVal) { pClient->m_bChgHost = bVal; }},
|
||||
{"sasl",
|
||||
[](CClient* pClient, bool bVal) {
|
||||
if (pClient->IsDuringSASL() && !bVal) {
|
||||
pClient->AbortSASL(
|
||||
":irc.znc.in 904 " + pClient->GetNick() +
|
||||
" :SASL authentication aborted");
|
||||
}
|
||||
pClient->m_bSASLCap = bVal;
|
||||
}},
|
||||
};
|
||||
|
||||
// For compatibility with older clients
|
||||
mCoreCaps["znc.in/server-time-iso"] = mCoreCaps["server-time"];
|
||||
mCoreCaps["znc.in/batch"] = mCoreCaps["batch"];
|
||||
mCoreCaps["znc.in/self-message"] = [](CClient* pClient, bool bVal) {
|
||||
pClient->m_bSelfMessage = bVal;
|
||||
};
|
||||
// For compatibility with older clients
|
||||
mCoreCaps["znc.in/server-time-iso"] = mCoreCaps["server-time"];
|
||||
mCoreCaps["znc.in/batch"] = mCoreCaps["batch"];
|
||||
mCoreCaps["znc.in/self-message"] = [](CClient* pClient, bool bVal) {
|
||||
pClient->m_bSelfMessage = bVal;
|
||||
};
|
||||
|
||||
return mCoreCaps;
|
||||
}();
|
||||
return mCoreCaps;
|
||||
}();
|
||||
return mCoreCaps;
|
||||
}
|
||||
|
||||
@@ -806,8 +852,19 @@ void CClient::HandleCap(const CMessage& Message) {
|
||||
m_uCapVersion = std::max(m_uCapVersion, Message.GetParam(1).ToUShort());
|
||||
SCString ssOfferCaps;
|
||||
for (const auto& it : CoreCaps()) {
|
||||
// TODO sasl value enumerating mechanisms
|
||||
ssOfferCaps.insert(it.first);
|
||||
// TODO figure out a better API for this, including for modules
|
||||
if (HasCap302() && it.first == "sasl") {
|
||||
SCString ssMechanisms = EnumerateSASLMechanisms();
|
||||
if (ssMechanisms.empty()) {
|
||||
// See the comment near 908. Here "sasl=" would also have wrong meaning.
|
||||
ssMechanisms.insert("*");
|
||||
}
|
||||
ssOfferCaps.insert(it.first + "=" +
|
||||
CString(",").Join(ssMechanisms.begin(),
|
||||
ssMechanisms.end()));
|
||||
} else {
|
||||
ssOfferCaps.insert(it.first);
|
||||
}
|
||||
}
|
||||
NETWORKMODULECALL(OnClientCapLs(this, ssOfferCaps), GetUser(), GetNetwork(), this, NOTHING);
|
||||
VCString vsCaps = MultiLine(ssOfferCaps);
|
||||
@@ -825,15 +882,12 @@ void CClient::HandleCap(const CMessage& Message) {
|
||||
} else if (sSubCmd.Equals("END")) {
|
||||
m_bInCap = false;
|
||||
if (!IsAttached()) {
|
||||
if (m_bSASL && m_sSASLUser.empty() && m_bSASLAuthenticating) {
|
||||
PutClient(":irc.znc.in 906 " + GetNick() +
|
||||
if (IsDuringSASL()) {
|
||||
AbortSASL(":irc.znc.in 904 " + GetNick() +
|
||||
" :SASL authentication aborted");
|
||||
m_sSASLMechanism = "";
|
||||
m_bSASLAuthenticating = false;
|
||||
}
|
||||
|
||||
if (!m_pUser && m_bGotUser &&
|
||||
(m_sSASLUser.empty() && !m_bGotPass)) {
|
||||
if (m_bGotUser && m_sSASLUser.empty() && !m_bGotPass) {
|
||||
SendRequiredPasswordNotice();
|
||||
} else {
|
||||
AuthUser();
|
||||
@@ -1076,35 +1130,25 @@ bool CClient::OnActionMessage(CActionMessage& Message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void CClient::OnAuthenticateMessage(CAuthenticateMessage& Message) {
|
||||
const auto uiMaxSASLMsgLength = 400u;
|
||||
auto bAuthenticationSuccess = false;
|
||||
auto sMessage = Message.GetText();
|
||||
const auto iBufferSize = sMessage.length();
|
||||
void CClient::SendSASLChallenge(CString sMessage) {
|
||||
constexpr size_t uMaxSASLMsgLength = 400u;
|
||||
sMessage.Base64Encode();
|
||||
size_t uChallengeSize = sMessage.length();
|
||||
|
||||
auto SASLReset = [this]() {
|
||||
m_sSASLMechanism = "";
|
||||
m_sSASLBuffer = "";
|
||||
};
|
||||
for (int i = 0; i < uChallengeSize; i += uMaxSASLMsgLength) {
|
||||
CString sMsgPart = sMessage.substr(i, uMaxSASLMsgLength);
|
||||
PutClient("AUTHENTICATE " + sMsgPart);
|
||||
}
|
||||
if (uChallengeSize % uMaxSASLMsgLength == 0) {
|
||||
PutClient("AUTHENTICATE +");
|
||||
}
|
||||
}
|
||||
|
||||
auto SASLChallenge = [this](CString sChallenge) {
|
||||
sChallenge.Base64Encode();
|
||||
auto sChallengeSize = sChallenge.length();
|
||||
|
||||
if (sChallengeSize > uiMaxSASLMsgLength) {
|
||||
for (int i = 0; i < sChallengeSize; i += uiMaxSASLMsgLength) {
|
||||
CString sMsgPart = sChallenge.substr(i, uiMaxSASLMsgLength);
|
||||
PutClient("AUTHENTICATE " + sMsgPart);
|
||||
}
|
||||
} else if (sChallengeSize > 0) {
|
||||
PutClient("AUTHENTICATE " + sChallenge);
|
||||
}
|
||||
if (sChallengeSize % uiMaxSASLMsgLength == 0) {
|
||||
PutClient("AUTHENTICATE +");
|
||||
}
|
||||
};
|
||||
|
||||
if (!m_bSASL) return;
|
||||
void CClient::OnAuthenticateMessage(const CAuthenticateMessage& Message) {
|
||||
if (!m_bSASLCap) {
|
||||
PutClient(":irc.znc.in 904 " + GetNick() + " :SASL not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_sSASLUser.empty() || IsAttached()) {
|
||||
PutClient(":irc.znc.in 907 " + GetNick() +
|
||||
@@ -1112,29 +1156,53 @@ void CClient::OnAuthenticateMessage(CAuthenticateMessage& Message) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_bSASLAuthenticating || sMessage.Equals("*")) {
|
||||
PutClient(":irc.znc.in 906 " + GetNick() +
|
||||
auto SASLReset = [this]() {
|
||||
m_sSASLMechanism = "";
|
||||
m_sSASLBuffer = "";
|
||||
};
|
||||
CString sMessage = Message.GetText();
|
||||
|
||||
if (sMessage.Equals("*")) {
|
||||
AbortSASL(":irc.znc.in 906 " + GetNick() +
|
||||
" :SASL authentication aborted");
|
||||
if (!IsAttached()) {
|
||||
m_bSASLAuthenticating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr size_t uMaxSASLMsgLength = 400u;
|
||||
if (sMessage.length() > uMaxSASLMsgLength) {
|
||||
AbortSASL(":irc.znc.in 905 " + GetNick() + " :SASL message too long");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsDuringSASL()) {
|
||||
if (m_ssPreviouslyFailedSASLMechanisms.find(sMessage) !=
|
||||
m_ssPreviouslyFailedSASLMechanisms.end()) {
|
||||
// This prevents the client from brute forcing multiple passwords
|
||||
// on the same connection.
|
||||
PutClient(":irc.znc.in 904 " + GetNick() +
|
||||
" :SASL authentication failed");
|
||||
SASLReset();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (iBufferSize > uiMaxSASLMsgLength) {
|
||||
PutClient(":irc.znc.in 905 " + GetNick() + " :SASL message too long");
|
||||
SASLReset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sSASLMechanism.empty()) {
|
||||
SCString ssMechanisms;
|
||||
auto sMechanisms = EnumerateSASLMechanisms(ssMechanisms);
|
||||
|
||||
SCString ssMechanisms = EnumerateSASLMechanisms();
|
||||
if (ssMechanisms.find(sMessage) == ssMechanisms.end()) {
|
||||
PutClient(":irc.znc.in 908 " + GetNick() + " " + sMechanisms +
|
||||
" :are available SASL mechanisms");
|
||||
if (ssMechanisms.empty()) {
|
||||
// If it happens that no mechanisms are available, an empty
|
||||
// string will cause issues with IRC frames. Probably we should
|
||||
// disable the whole 'sasl' cap, but that becomes complicated
|
||||
// because need to track changes to the list of available caps
|
||||
// (modules adding new mechanisms) and send cap-notify. This
|
||||
// hack is simpler to do. And if a client decides to use
|
||||
// actually use this fake '*' mechanism, they probably won't
|
||||
// succeed anyway.
|
||||
PutClient(":irc.znc.in 908 " + GetNick() +
|
||||
" * :No SASL mechanisms are available");
|
||||
} else {
|
||||
PutClient(":irc.znc.in 908 " + GetNick() + " " +
|
||||
CString(",").Join(ssMechanisms.begin(),
|
||||
ssMechanisms.end()) +
|
||||
" :are available SASL mechanisms");
|
||||
}
|
||||
PutClient(":irc.znc.in 904 " + GetNick() +
|
||||
" :SASL authentication failed");
|
||||
SASLReset();
|
||||
@@ -1144,27 +1212,24 @@ void CClient::OnAuthenticateMessage(CAuthenticateMessage& Message) {
|
||||
|
||||
m_sSASLMechanism = sMessage;
|
||||
|
||||
auto bResult = false;
|
||||
bool bResult = false;
|
||||
CString sChallenge;
|
||||
GLOBALMODULECALL(
|
||||
_GLOBALMODULECALL(
|
||||
OnClientSASLServerInitialChallenge(m_sSASLMechanism, sChallenge),
|
||||
&bResult);
|
||||
if (bResult) {
|
||||
SASLChallenge(sChallenge);
|
||||
} else {
|
||||
PutClient("AUTHENTICATE +");
|
||||
nullptr, nullptr, this, &bResult);
|
||||
if (!bResult) {
|
||||
SendSASLChallenge(std::move(sChallenge));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sSASLBuffer.length() + sMessage.length() > 10 * 1024) {
|
||||
PutClient(":irc.znc.in 904 " + GetNick() + " :SASL response too long");
|
||||
SASLReset();
|
||||
AbortSASL(":irc.znc.in 904 " + GetNick() + " :SASL response too long");
|
||||
return;
|
||||
}
|
||||
|
||||
if (iBufferSize == uiMaxSASLMsgLength) {
|
||||
m_sSASLBuffer.append(sMessage);
|
||||
if (sMessage.length() == uMaxSASLMsgLength) {
|
||||
m_sSASLBuffer += sMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1174,51 +1239,46 @@ void CClient::OnAuthenticateMessage(CAuthenticateMessage& Message) {
|
||||
|
||||
m_sSASLBuffer.Base64Decode();
|
||||
|
||||
CString sResponse;
|
||||
bool bResult;
|
||||
bool bResult = false;
|
||||
|
||||
CString sSASLUser;
|
||||
GLOBALMODULECALL(
|
||||
OnClientSASLAuthenticate(m_sSASLMechanism, m_sSASLBuffer, sSASLUser,
|
||||
sResponse, bAuthenticationSuccess),
|
||||
&bResult);
|
||||
_GLOBALMODULECALL(
|
||||
OnClientSASLAuthenticate(m_sSASLMechanism, m_sSASLBuffer),
|
||||
nullptr, nullptr, this, &bResult);
|
||||
m_sSASLBuffer.clear();
|
||||
|
||||
if (bResult && !sResponse.empty()) {
|
||||
SASLChallenge(sResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
auto pUser = CZNC::Get().FindUser(sSASLUser);
|
||||
|
||||
if (pUser && bAuthenticationSuccess) {
|
||||
PutClient(":irc.znc.in 900 " + GetNick() + " " + GetNick() + "!" +
|
||||
pUser->GetIdent() + "@" + GetHostName() + " " + sSASLUser +
|
||||
" :You are now logged in as " + sSASLUser);
|
||||
PutClient(":irc.znc.in 903 " + GetNick() +
|
||||
" :SASL authentication successful");
|
||||
m_sSASLUser = sSASLUser;
|
||||
m_bSASLAuthenticating = false;
|
||||
} else {
|
||||
PutClient(":irc.znc.in 904 " + GetNick() +
|
||||
" :SASL authentication failed");
|
||||
SASLReset();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CString CClient::EnumerateSASLMechanisms(SCString& ssMechanisms) {
|
||||
CString sMechanisms;
|
||||
void CClient::AbortSASL(const CString& sFullIRCLine) {
|
||||
PutClient(sFullIRCLine);
|
||||
_GLOBALMODULECALL(OnClientSASLAborted(), nullptr, nullptr, this, NOTHING);
|
||||
m_sSASLMechanism = "";
|
||||
m_sSASLBuffer = "";
|
||||
}
|
||||
|
||||
void CClient::RefuseSASLLogin(const CString& sReason) {
|
||||
PutClient(":irc.znc.in 904 " + GetNick() + " :" + sReason);
|
||||
m_ssPreviouslyFailedSASLMechanisms.insert(m_sSASLMechanism);
|
||||
m_sSASLMechanism = "";
|
||||
m_sSASLBuffer = "";
|
||||
_GLOBALMODULECALL(OnFailedLogin("", GetRemoteIP()), nullptr, nullptr, this,
|
||||
NOTHING);
|
||||
}
|
||||
|
||||
void CClient::AcceptSASLLogin(CUser& User) {
|
||||
PutClient(":irc.znc.in 900 " + GetNick() + " " + GetNick() + "!" +
|
||||
User.GetIdent() + "@" + GetHostName() + " " + User.GetUsername() +
|
||||
" :You are now logged in as " + User.GetUsername());
|
||||
PutClient(":irc.znc.in 903 " + GetNick() +
|
||||
" :SASL authentication successful");
|
||||
m_sSASLMechanism = "";
|
||||
m_sSASLBuffer = "";
|
||||
m_sSASLUser = User.GetUsername();
|
||||
}
|
||||
|
||||
SCString CClient::EnumerateSASLMechanisms() const {
|
||||
SCString ssMechanisms;
|
||||
// FIXME Currently GetClient()==nullptr due to const
|
||||
GLOBALMODULECALL(OnClientGetSASLMechanisms(ssMechanisms), NOTHING);
|
||||
|
||||
if (ssMechanisms.size()) {
|
||||
sMechanisms =
|
||||
CString(",").Join(ssMechanisms.begin(), ssMechanisms.end());
|
||||
}
|
||||
|
||||
return sMechanisms;
|
||||
return ssMechanisms;
|
||||
}
|
||||
|
||||
bool CClient::OnCTCPMessage(CCTCPMessage& Message) {
|
||||
|
||||
+11
-8
@@ -1203,8 +1203,7 @@ void CModule::InternalServerDependentCapsOnClientCapRequest(CClient* pClient,
|
||||
}
|
||||
|
||||
CModule::EModRet CModule::OnClientSASLAuthenticate(
|
||||
const CString& sMechanism, const CString& sBuffer, CString& sUser,
|
||||
CString& sMechanismResponse, bool& bAuthenticationSuccess) {
|
||||
const CString& sMechanism, const CString& sBuffer) {
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
@@ -1215,6 +1214,8 @@ CModule::EModRet CModule::OnClientSASLServerInitialChallenge(
|
||||
|
||||
void CModule::OnClientGetSASLMechanisms(SCString& ssMechanisms) {}
|
||||
|
||||
void CModule::OnClientSASLAborted() {}
|
||||
|
||||
CModule::EModRet CModule::OnModuleLoading(const CString& sModName,
|
||||
const CString& sArgs,
|
||||
CModInfo::EModuleType eType,
|
||||
@@ -1761,12 +1762,8 @@ bool CModules::OnClientCapRequest(CClient* pClient, const CString& sCap,
|
||||
}
|
||||
|
||||
bool CModules::OnClientSASLAuthenticate(const CString& sMechanism,
|
||||
const CString& sBuffer,
|
||||
CString& sUser,
|
||||
CString& sResponse,
|
||||
bool& bAuthenticationSuccess) {
|
||||
MODHALTCHK(OnClientSASLAuthenticate(sMechanism, sBuffer, sUser,
|
||||
sResponse, bAuthenticationSuccess));
|
||||
const CString& sBuffer) {
|
||||
MODHALTCHK(OnClientSASLAuthenticate(sMechanism, sBuffer));
|
||||
}
|
||||
|
||||
bool CModules::OnClientSASLServerInitialChallenge(const CString& sMechanism,
|
||||
@@ -1779,6 +1776,11 @@ bool CModules::OnClientGetSASLMechanisms(SCString& ssMechanisms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CModules::OnClientSASLAborted() {
|
||||
MODUNLOADCHK(OnClientSASLAborted());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CModules::OnModuleLoading(const CString& sModName, const CString& sArgs,
|
||||
CModInfo::EModuleType eType, bool& bSuccess,
|
||||
CString& sRetMsg) {
|
||||
@@ -2064,6 +2066,7 @@ void CModules::GetDefaultMods(set<CModInfo>& ssMods,
|
||||
{"chansaver", CModInfo::UserModule},
|
||||
{"controlpanel", CModInfo::UserModule},
|
||||
{"corecaps", CModInfo::GlobalModule},
|
||||
{"saslplain", CModInfo::GlobalModule},
|
||||
{"simple_away", CModInfo::NetworkModule},
|
||||
{"webadmin", CModInfo::GlobalModule}};
|
||||
|
||||
|
||||
@@ -1109,6 +1109,9 @@ bool CZNC::LoadGlobal(CConfig& config, CString& sError) {
|
||||
if (tSavedVersion < make_tuple(1, 9)) {
|
||||
vsList.push_back("corecaps");
|
||||
}
|
||||
if (tSavedVersion < make_tuple(1, 10)) {
|
||||
vsList.push_back("saslplain");
|
||||
}
|
||||
|
||||
for (const CString& sModLine : vsList) {
|
||||
CString sModName = sModLine.Token(0);
|
||||
|
||||
@@ -330,26 +330,6 @@ TEST_F(ZNCTest, SaslMechsNotInit) {
|
||||
ircd.ReadUntil("PONG foo");
|
||||
}
|
||||
|
||||
TEST_F(ZNCTest, SaslPlainModule) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
auto client = LoginClient();
|
||||
client.Write("znc loadmod saslplain");
|
||||
client.ReadUntil("Loaded module");
|
||||
client.Close();
|
||||
|
||||
auto client2 = ConnectClient();
|
||||
client2.Write("NICK foo");
|
||||
client2.Write("CAP LS");
|
||||
client2.Write("CAP REQ :sasl");
|
||||
client2.ReadUntil(":irc.znc.in CAP foo ACK :sasl");
|
||||
client2.Write("USER bar");
|
||||
client2.Write("AUTHENTICATE PLAIN");
|
||||
client2.ReadUntil("AUTHENTICATE +");
|
||||
client2.Write("AUTHENTICATE AHVzZXIAaHVudGVyMg=="); // \0user\0hunter2
|
||||
client2.ReadUntil(":irc.znc.in 903 foo :SASL authentication successful");
|
||||
}
|
||||
|
||||
TEST_F(ZNCTest, SaslRequire) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
@@ -366,5 +346,21 @@ TEST_F(ZNCTest, SaslRequire) {
|
||||
auto ircd2 = ConnectIRCd();
|
||||
}
|
||||
|
||||
TEST_F(ZNCTest, SaslAuthPlain) {
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
auto client = ConnectClient();
|
||||
client.Write("NICK foo");
|
||||
client.Write("CAP LS");
|
||||
client.ReadUntil(" sasl ");
|
||||
client.Write("CAP REQ :sasl");
|
||||
client.ReadUntil(":irc.znc.in CAP foo ACK :sasl");
|
||||
client.Write("USER bar");
|
||||
client.Write("AUTHENTICATE PLAIN");
|
||||
client.ReadUntil("AUTHENTICATE +");
|
||||
client.Write("AUTHENTICATE AHVzZXIAaHVudGVyMg=="); // \0user\0hunter2
|
||||
client.ReadUntil(":irc.znc.in 903 foo :SASL authentication successful");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace znc_inttest
|
||||
|
||||
Reference in New Issue
Block a user