Add "AuthOnlyViaModule" global/user setting

Setting AuthOnlyViaModule on a user causes CheckPass to never return true,
causing all authentication attempts using the configured password to fail, both
on IRC connections and for webadmin. This is useful in situations where an
external module (cyrusauth, certauth, imapauth) handles authentication. Setting
the global AuthOnlyViaModule option causes similar behavior across every
user. If AuthOnlyViaModule is set to true globally, it cannot be overridden
per-user.

Close #1474
Close #331
This commit is contained in:
Fox Wilson
2017-12-18 23:00:40 -05:00
committed by Alexey Sokolov
parent 3d874f6fe4
commit 42939c998f
9 changed files with 89 additions and 0 deletions
+3
View File
@@ -150,6 +150,7 @@ class CUser {
void SetTimestampFormat(const CString& s) { m_sTimestampFormat = s; }
void SetTimestampAppend(bool b) { m_bAppendTimestamp = b; }
void SetTimestampPrepend(bool b) { m_bPrependTimestamp = b; }
void SetAuthOnlyViaModule(bool b) { m_bAuthOnlyViaModule = b; }
void SetTimezone(const CString& s) { m_sTimezone = s; }
void SetJoinTries(unsigned int i) { m_uMaxJoinTries = i; }
void SetMaxJoins(unsigned int i) { m_uMaxJoins = i; }
@@ -185,6 +186,7 @@ class CUser {
bool IsAdmin() const;
bool DenySetBindHost() const;
bool MultiClients() const;
bool AuthOnlyViaModule() const;
const CString& GetStatusPrefix() const;
const CString& GetDefaultChanModes() const;
/** How long must an IRC connection be idle before ZNC sends a ping */
@@ -250,6 +252,7 @@ class CUser {
bool m_bBeingDeleted;
bool m_bAppendTimestamp;
bool m_bPrependTimestamp;
bool m_bAuthOnlyViaModule;
CUserTimer* m_pUserTimer;
+3
View File
@@ -123,6 +123,7 @@ class CZNC {
}
void SetProtectWebSessions(bool b) { m_bProtectWebSessions = b; }
void SetHideVersion(bool b) { m_bHideVersion = b; }
void SetAuthOnlyViaModule(bool b) { m_bAuthOnlyViaModule = b; }
void SetConnectDelay(unsigned int i);
void SetSSLCiphers(const CString& sCiphers) { m_sSSLCiphers = sCiphers; }
bool SetSSLProtocols(const CString& sProtocols);
@@ -166,6 +167,7 @@ class CZNC {
unsigned int GetConnectDelay() const { return m_uiConnectDelay; }
bool GetProtectWebSessions() const { return m_bProtectWebSessions; }
bool GetHideVersion() const { return m_bHideVersion; }
bool GetAuthOnlyViaModule() const { return m_bAuthOnlyViaModule; }
CString GetSSLCiphers() const { return m_sSSLCiphers; }
CString GetSSLProtocols() const { return m_sSSLProtocols; }
Csock::EDisableProtocol GetDisabledSSLProtocols() const {
@@ -305,6 +307,7 @@ class CZNC {
TCacheMap<CString> m_sConnectThrottle;
bool m_bProtectWebSessions;
bool m_bHideVersion;
bool m_bAuthOnlyViaModule;
CTranslationDomainRefHolder m_Translation;
unsigned int m_uiConfigWriteDelay;
CConfigWriteTimer* m_pConfigTimer;
+12
View File
@@ -108,6 +108,7 @@ class CAdminMod : public CModule {
{"Admin", boolean},
{"AppendTimestamp", boolean},
{"PrependTimestamp", boolean},
{"AuthOnlyViaModule", boolean},
{"TimestampFormat", str},
{"DCCBindHost", str},
{"StatusPrefix", str},
@@ -273,6 +274,9 @@ class CAdminMod : public CModule {
else if (sVar == "prependtimestamp")
PutModule("PrependTimestamp = " +
CString(pUser->GetTimestampPrepend()));
else if (sVar == "authonlyviamodule")
PutModule("AuthOnlyViaModule = " +
CString(pUser->AuthOnlyViaModule()));
else if (sVar == "timestampformat")
PutModule("TimestampFormat = " + pUser->GetTimestampFormat());
else if (sVar == "dccbindhost")
@@ -442,6 +446,14 @@ class CAdminMod : public CModule {
bool b = sValue.ToBool();
pUser->SetTimestampAppend(b);
PutModule("AppendTimestamp = " + CString(b));
} else if (sVar == "authonlyviamodule") {
if (GetUser()->IsAdmin()) {
bool b = sValue.ToBool();
pUser->SetAuthOnlyViaModule(b);
PutModule("AuthOnlyViaModule = " + CString(b));
} else {
PutModule(t_s("Access denied!"));
}
} else if (sVar == "timestampformat") {
pUser->SetTimestampFormat(sValue);
PutModule("TimestampFormat = " + sValue);
@@ -40,6 +40,12 @@
<input id="password2" type="password" name="password2" class="half"
title="<? FORMAT "Please re-type the above password." ?>"/>
</div>
<div class="subsection">
<div class="inputlabel"><label for="authonlyviamodule"><? FORMAT "Auth Only Via Module:" ?></label></div>
<input id="authonlyviamodule" type="checkbox" name="authonlyviamodule"
title="<? FORMAT "Allow user authentication by external modules only, disabling built-in password authentication." ?>"
<? IF AuthOnlyViaModule ?>checked="checked" <? ENDIF ?><? IF !ImAdmin ?>disabled="disabled" <? ENDIF ?>/>
</div>
<div class="subsection half">
<div class="inputlabel"><label for="allowedips"><? FORMAT "Allowed IPs:" ?></label></div>
<textarea id="allowedips" name="allowedips" cols="70" rows="5"><? LOOP AllowedHostLoop ?><? VAR Host ?>
+6
View File
@@ -147,6 +147,12 @@
<label for="hideversion_checkbox"><? FORMAT "Hide version number from non-ZNC users" ?></label></div>
</div>
<div class="subsection">
<div class="inputlabel"><? FORMAT "Auth Only Via Module:" ?></div>
<div class="checkbox"><input type="checkbox" name="authonlyviamodule" id="authonlyviamodule_checkbox"<? IF AuthOnlyViaModule ?> checked="checked"<? ENDIF ?> />
<label for="authonlyviamodule_checkbox"><? FORMAT "Allow user authentication by external modules only" ?></label></div>
</div>
<div class="subsection twothird">
<div class="inputlabel"><label for="motd"><? FORMAT "MOTD:" ?></label></div>
+16
View File
@@ -210,6 +210,15 @@ class CWebAdminMod : public CModule {
pNewUser->SetPass(sHash, CUser::HASH_DEFAULT, sSalt);
}
sArg = WebSock.GetParam("authonlyviamodule");
if (spSession->IsAdmin()) {
if (!sArg.empty()) {
pNewUser->SetAuthOnlyViaModule(sArg.ToBool());
}
} else if (pUser) {
pNewUser->SetAuthOnlyViaModule(pUser->AuthOnlyViaModule());
}
VCString vsArgs;
WebSock.GetRawParam("allowedips").Split("\n", vsArgs);
@@ -344,11 +353,14 @@ class CWebAdminMod : public CModule {
pNewUser->SetDenyLoadMod(WebSock.GetParam("denyloadmod").ToBool());
pNewUser->SetDenySetBindHost(
WebSock.GetParam("denysetbindhost").ToBool());
pNewUser->SetAuthOnlyViaModule(
WebSock.GetParam("authonlyviamodule").ToBool());
sArg = WebSock.GetParam("maxnetworks");
if (!sArg.empty()) pNewUser->SetMaxNetworks(sArg.ToUInt());
} else if (pUser) {
pNewUser->SetDenyLoadMod(pUser->DenyLoadMod());
pNewUser->SetDenySetBindHost(pUser->DenySetBindHost());
pNewUser->SetAuthOnlyViaModule(pUser->AuthOnlyViaModule());
pNewUser->SetMaxNetworks(pUser->MaxNetworks());
}
@@ -1327,6 +1339,7 @@ class CWebAdminMod : public CModule {
Tmpl["ImAdmin"] = CString(spSession->IsAdmin());
Tmpl["Username"] = pUser->GetUserName();
Tmpl["AuthOnlyViaModule"] = CString(pUser->AuthOnlyViaModule());
Tmpl["Nick"] = pUser->GetNick();
Tmpl["AltNick"] = pUser->GetAltNick();
Tmpl["StatusPrefix"] = pUser->GetStatusPrefix();
@@ -1872,6 +1885,7 @@ class CWebAdminMod : public CModule {
Tmpl["ProtectWebSessions"] =
CString(CZNC::Get().GetProtectWebSessions());
Tmpl["HideVersion"] = CString(CZNC::Get().GetHideVersion());
Tmpl["AuthOnlyViaModule"] = CString(CZNC::Get().GetAuthOnlyViaModule());
const VCString& vsMotd = CZNC::Get().GetMotd();
for (const CString& sMotd : vsMotd) {
@@ -2018,6 +2032,8 @@ class CWebAdminMod : public CModule {
CZNC::Get().SetProtectWebSessions(sArg.ToBool());
sArg = WebSock.GetParam("hideversion");
CZNC::Get().SetHideVersion(sArg.ToBool());
sArg = WebSock.GetParam("authonlyviamodule");
CZNC::Get().SetAuthOnlyViaModule(sArg.ToBool());
VCString vsArgs;
WebSock.GetRawParam("motd").Split("\n", vsArgs);
+9
View File
@@ -88,6 +88,7 @@ CUser::CUser(const CString& sUserName)
m_bBeingDeleted(false),
m_bAppendTimestamp(false),
m_bPrependTimestamp(true),
m_bAuthOnlyViaModule(false),
m_pUserTimer(nullptr),
m_vIRCNetworks(),
m_vClients(),
@@ -170,6 +171,7 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
{"denysetvhost", &CUser::SetDenySetBindHost},
{"appendtimestamp", &CUser::SetTimestampAppend},
{"prependtimestamp", &CUser::SetTimestampPrepend},
{"authonlyviamodule", &CUser::SetAuthOnlyViaModule},
};
for (const auto& Option : StringOptions) {
@@ -801,6 +803,7 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
SetDenyLoadMod(User.DenyLoadMod());
SetAdmin(User.IsAdmin());
SetDenySetBindHost(User.DenySetBindHost());
SetAuthOnlyViaModule(User.AuthOnlyViaModule());
SetTimestampAppend(User.GetTimestampAppend());
SetTimestampPrepend(User.GetTimestampPrepend());
SetTimestampFormat(User.GetTimestampFormat());
@@ -963,6 +966,7 @@ CConfig CUser::ToConfig() const {
config.AddKeyValuePair("TimestampFormat", GetTimestampFormat());
config.AddKeyValuePair("AppendTimestamp", CString(GetTimestampAppend()));
config.AddKeyValuePair("PrependTimestamp", CString(GetTimestampPrepend()));
config.AddKeyValuePair("AuthOnlyViaModule", CString(AuthOnlyViaModule()));
config.AddKeyValuePair("Timezone", m_sTimezone);
config.AddKeyValuePair("JoinTries", CString(m_uMaxJoinTries));
config.AddKeyValuePair("MaxNetworks", CString(m_uMaxNetworks));
@@ -1012,6 +1016,10 @@ CConfig CUser::ToConfig() const {
}
bool CUser::CheckPass(const CString& sPass) const {
if(AuthOnlyViaModule() || CZNC::Get().GetAuthOnlyViaModule()) {
return false;
}
switch (m_eHashType) {
case HASH_MD5:
return m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
@@ -1370,6 +1378,7 @@ bool CUser::DenyLoadMod() const { return m_bDenyLoadMod; }
bool CUser::IsAdmin() const { return m_bAdmin; }
bool CUser::DenySetBindHost() const { return m_bDenySetBindHost; }
bool CUser::MultiClients() const { return m_bMultiClients; }
bool CUser::AuthOnlyViaModule() const { return m_bAuthOnlyViaModule; }
const CString& CUser::GetStatusPrefix() const { return m_sStatusPrefix; }
const CString& CUser::GetDefaultChanModes() const {
return m_sDefaultChanModes;
+4
View File
@@ -76,6 +76,7 @@ CZNC::CZNC()
m_sConnectThrottle(),
m_bProtectWebSessions(true),
m_bHideVersion(false),
m_bAuthOnlyViaModule(false),
m_Translation("znc"),
m_uiConfigWriteDelay(0),
m_pConfigTimer(nullptr) {
@@ -492,6 +493,7 @@ bool CZNC::WriteConfig() {
config.AddKeyValuePair("ProtectWebSessions",
CString(m_bProtectWebSessions));
config.AddKeyValuePair("HideVersion", CString(m_bHideVersion));
config.AddKeyValuePair("AuthOnlyViaModule", CString(m_bAuthOnlyViaModule));
config.AddKeyValuePair("Version", CString(VERSION_STR));
config.AddKeyValuePair("ConfigWriteDelay", CString(m_uiConfigWriteDelay));
@@ -1215,6 +1217,8 @@ bool CZNC::LoadGlobal(CConfig& config, CString& sError) {
m_bProtectWebSessions = sVal.ToBool();
if (config.FindStringEntry("hideversion", sVal))
m_bHideVersion = sVal.ToBool();
if (config.FindStringEntry("authonlyviamodule", sVal))
m_bAuthOnlyViaModule = sVal.ToBool();
if (config.FindStringEntry("sslprotocols", sVal)) {
if (!SetSSLProtocols(sVal)) {
VCString vsProtocols = GetAvailableSSLProtocols();
+30
View File
@@ -119,3 +119,33 @@ TEST_F(UserTest, IsHostAllowed) {
<< "Allow-host is " << h.sMask;
}
}
TEST_F(UserTest, TestAuthOnlyViaModule) {
CUser user("user");
user.SetPass("password", CUser::HASH_NONE);
bool bAuthOnlyViaModuleDefault = CZNC::Get().GetAuthOnlyViaModule();
CZNC::Get().SetAuthOnlyViaModule(false);
user.SetAuthOnlyViaModule(false);
EXPECT_TRUE(user.CheckPass("password"));
// user-level only
user.SetAuthOnlyViaModule(true);
EXPECT_FALSE(user.CheckPass("password"));
// re-enabling built-in authentication
user.SetAuthOnlyViaModule(false);
EXPECT_TRUE(user.CheckPass("password"));
// on at global level, off at user level
CZNC::Get().SetAuthOnlyViaModule(true);
EXPECT_FALSE(user.CheckPass("password"));
// on at both levels
user.SetAuthOnlyViaModule(true);
EXPECT_FALSE(user.CheckPass("password"));
CZNC::Get().SetAuthOnlyViaModule(bAuthOnlyViaModuleDefault);
}