virtual void SockError(int iErrno, const CString& sDescription);
virtual void Timeout();
virtual void ReachedMaxBuffer();
+
+ void Register();
void PutIRC(const CString& sLine);
void PutIRCQuick(const CString& sLine); //!< Should be used for PONG only
class CMD5 {
protected:
char m_szMD5[33];
+ unsigned char* m_pszMD5;
public:
CMD5();
}
char* MakeHash(const char* szText, uint32 nTextLen);
+ unsigned char* GetHash();
protected:
void md5_starts( md5_context *ctx ) const;
*/
#include <znc/znc.h>
+#include <znc/IRCNetwork.h>
#include <znc/User.h>
+#include <znc/MD5.h>
#include <sasl/sasl.h>
+#include <sstream>
class CSASLAuthMod : public CModule {
public:
MODCONSTRUCTOR(CSASLAuthMod) {
m_Cache.SetTTL(60000/*ms*/);
-
+ m_bWebIrcEnabled = false;
m_cbs[0].id = SASL_CB_GETOPT;
m_cbs[0].proc = reinterpret_cast<int(*)()>(CSASLAuthMod::getopt);
m_cbs[0].context = this;
AddCommand("CloneUser", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::CloneUserCommand),
"[username]");
AddCommand("DisableCloneUser", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::DisableCloneUserCommand));
+ AddCommand("SetImpersonateAccount", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::SetImpersonateAccount),
+ "username password", "Set the username and password for the SASL Impersonaton Account");
+ AddCommand("SetWebIrc", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::SetWebIrc),
+ "username password", "Set the username and password for WebIRC");
+ AddCommand("SetWebIrcHost", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::SetWebIrcHost),
+ "hostname", "Set the hostname used for WebIRC");
+ AddCommand("SetUserSalt", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::SetUserSalt),
+ "salt", "Set the salt used when hashing usernames");
+ AddCommand("SetNetworkName", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::SetNetworkName),
+ "network", "Set the network name used for newly created accounts");
+ AddCommand("SetServer", static_cast<CModCommand::ModCmdFunc>(&CSASLAuthMod::SetServer),
+ "server port ssl", "Set the server and port used for newly created accounts");
+ }
+
+ void SetServer(const CString& sLine) {
+ SetNV("server", sLine.Token(1));
+ SetNV("port", sLine.Token(2));
+ SetNV("ssl", sLine.Token(3));
+
+ PutModule("Server and port used for newly created accounts has been set to [" + GetNV("server") + ":" + (GetNV("ssl").ToBool() ? "+" + GetNV("port") : GetNV("port")) + "]");
+ }
+
+ void SetNetworkName(const CString& sLine) {
+ SetNV("networkname", sLine.Token(1));
+
+ PutModule("Network name used for newly created accounts has been set to [" + GetNV("networkname") + "]");
+ }
+
+ void SetImpersonateAccount(const CString& sLine) {
+ SetNV("impersonationusername", sLine.Token(1));
+ SetNV("impersonationpassword", sLine.Token(2));
+
+ PutModule("SASL Impersonaton Account Username has been set to [" + GetNV("impersonationusername") + "]");
+ PutModule("SASL Impersonaton Account Password has been set to [" + GetNV("impersonationpassword") + "]");
+ }
+
+ void SetWebIrc(const CString& sLine) {
+ SetNV("webircusername", sLine.Token(1));
+ SetNV("webircpassword", sLine.Token(2));
+
+ PutModule("WebIRC Username has been set to [" + GetNV("webircusername") + "]");
+ PutModule("WebIRC Password has been set to [" + GetNV("webircpassword") + "]");
+ }
+
+ void SetWebIrcHost(const CString& sLine) {
+ SetNV("webirchost", sLine);
+
+ PutModule("WebIRC hostname has been set to [" + GetNV("webirchost") + "]");
+ }
+
+ void SetUserSalt(const CString& sLine) {
+ SetNV("usersalt", sLine.Token(1));
+
+ PutModule("User salt has been set to [" + GetNV("usersalt") + "]");
+
}
virtual ~CSASLAuthMod() {
if (it->Equals("saslauthd") || it->Equals("auxprop")) {
m_sMethod += *it + " ";
} else {
+ if (it->Equals("webirc")) {
+ m_bWebIrcEnabled = true;
+ } else if (it->Equals("impersonation")) {
+ m_bSaslImpersonateEnabled = true;
+ } else {
CUtils::PrintError("Ignoring invalid SASL pwcheck method: " + *it);
sMessage = "Ignored invalid SASL pwcheck method";
}
}
+ }
m_sMethod.TrimRight();
virtual EModRet OnLoginAttempt(std::shared_ptr<CAuthBase> Auth) override {
const CString& sUsername = Auth->GetUsername();
const CString& sPassword = Auth->GetPassword();
- CUser *pUser(CZNC::Get().FindUser(sUsername));
+ CUser *pUser(CZNC::Get().FindUser(sUsername.AsLower()));
sasl_conn_t *sasl_conn(NULL);
bool bSuccess = false;
if (bSuccess) {
if (!pUser) {
CString sErr;
- pUser = new CUser(sUsername);
+ pUser = new CUser(sUsername.AsLower());
if (ShouldCloneUser()) {
CUser *pBaseUser = CZNC::Get().FindUser(CloneUser());
}
}
+ pUser->SetNick(sUsername);
+ pUser->SetAltNick(sUsername + "_");
+ pUser->SetIdent(sUsername);
+ pUser->SetRealName(sUsername);
+
+ CString sAddNetworkError;
+ CIRCNetwork* pNetwork = pUser->AddNetwork(GetNV("networkname"), sAddNetworkError);
+
+ if (pNetwork) {
+ pNetwork->AddServer(GetNV("server"), GetNV("port").ToUShort(), "", GetNV("ssl").ToBool());
+
if (pUser) {
// "::" is an invalid MD5 hash, so user won't be able to login by usual method
pUser->SetPass("::", CUser::HASH_MD5, "::");
delete pUser;
pUser = NULL;
}
+
+ if (m_bSaslImpersonateEnabled) {
+ CString sModRet;
+
+ if (pNetwork->GetModules().LoadModule("sasl", "", CModInfo::NetworkModule, pUser, pNetwork, sModRet))
+ {
+ CModule* pModule = pNetwork->GetModules().FindModule("sasl");
+ if (pModule) {
+ pModule->SetNV("saslimpersonation", "yes");
+ pModule->SetNV("impersonationuser", sUsername);
+ pModule->SetNV("username", GetNV("impersonationusername"));
+ pModule->SetNV("password", GetNV("impersonationpassword"));
+ pModule->SetNV("require_auth", "yes");
+ pModule->SetNV("mechanisms", "PLAIN");
+ }
+ }
+ else DEBUG("saslauth: Failure loading sasl module for created user [" << sUsername << "] ");
+ }
+ }
+ else DEBUG("saslauth: Failure adding network for created user [" << sUsername << "]: " << sAddNetworkError);
}
if (pUser) {
return HALT;
}
}
+ return CONTINUE;
+ }
+ virtual EModRet OnIRCRegistration(CString& sPass, CString& sNick,
+ CString& sIdent, CString& sRealName)
+ {
+ if (m_bWebIrcEnabled) {
+ CUser* pUser = CModule::GetUser();
+ CIRCNetwork* pNetwork = CModule::GetNetwork();
+
+ if (pUser != NULL && !(pNetwork->GetName().CaseCmp(GetNV("networkname")))) {
+ if (!(pUser->GetUserName().StrCmp("MrLenin")))
+ return CONTINUE;
+
+ std::ostringstream sWebIrcMsg;
+ CString sUsername = pUser->GetUserName();
+ CMD5 md5(sUsername + GetNV("usersalt"));
+ uint8* hashBytes = downsample(md5.GetHash());
+ unsigned int hashInts[3] = { hashBytes[0], hashBytes[1], hashBytes[2] };
+
+ sWebIrcMsg << "WEBIRC " << GetNV("webircpassword") << " " << GetNV("webircusername") << " " << sUsername <<
+ GetNV("webirchost") << " 255." << hashInts[0] << "." << hashInts[1] << "." << hashInts[2];
+
+ CModule::PutIRC(sWebIrcMsg.str());
+
+ delete[] hashBytes;
+ }
+ }
+
return CONTINUE;
}
sasl_callback_t m_cbs[2];
CString m_sMethod;
+ bool m_bWebIrcEnabled;
+ bool m_bSaslImpersonateEnabled;
static int getopt(void *context, const char *plugin_name,
const char *option, const char **result, unsigned *len) {
return SASL_CONTINUE;
}
+
+ /** Downsamples a 128bit result to 32bits (md5 -> unsigned int).
+ * @param[in] i 128bit result to downsample.
+ * @return downsampled result.
+
+ */
+ static inline uint8* downsample(unsigned char *i)
+ {
+ uint8* r = new uint8[3];
+ r[0] = i[0] ^ i[1] ^ i[2] ^ i[3] ^ i[4];
+ r[1] = i[5] ^ i[6] ^ i[7] ^ i[8] ^ i[9];
+ r[2] = i[10] ^ i[11] ^ i[12] ^ i[13] ^ i[14] ^ i[15];
+
+ return r;
+ }
};
template<> void TModInfo<CSASLAuthMod>(CModInfo& Info) {
Info.SetWikiPage("cyrusauth");
Info.SetHasArgs(true);
- Info.SetArgsHelpText("This global module takes up to two arguments - the methods of authentication - auxprop and saslauthd");
+ Info.SetArgsHelpText("This global module takes up to four arguments - the methods of authentication - auxprop and saslauthd - and optionally, if WebIRC host spoofing and/or SASL Impersonation is to be enabled - webirc and impersonation");
}
GLOBALMODULEDEFS(CSASLAuthMod, "Allow users to authenticate via SASL password verification method")
#include <znc/IRCNetwork.h>
#include <znc/IRCSock.h>
+#include <znc/User.h>
static const struct {
const char *szName;
}
void Set(const CString& sLine) {
+ if (!SaslImpersonation()) {
SetNV("username", sLine.Token(1));
SetNV("password", sLine.Token(2));
PutModule("Username has been set to [" + GetNV("username") + "]");
PutModule("Password has been set to [" + GetNV("password") + "]");
}
-
+ }
+
+ bool SaslImpersonation() const {
+ return GetNV("saslimpersonation").ToBool();
+ }
+
void SetMechanismCommand(const CString& sLine) {
+ if (!SaslImpersonation()) {
CString sMechanisms = sLine.Token(1, true).AsUpper();
if (!sMechanisms.empty()) {
PutModule("Current mechanisms set: " + GetMechanismsString());
}
+ }
void RequireAuthCommand(const CString& sLine) {
+ if (!SaslImpersonation()) {
if (!sLine.Token(1).empty()) {
SetNV(NV_REQUIRE_AUTH, sLine.Token(1));
}
PutModule("We will connect even if SASL fails");
}
}
+ }
bool SupportsMechanism(const CString& sMechanism) const {
for (size_t i = 0; SupportedMechanisms[i].szName != NULL; i++) {
void Authenticate(const CString& sLine) {
if (m_Mechanisms.GetCurrent().Equals("PLAIN") && sLine.Equals("+")) {
- CString sAuthLine = GetNV("username") + '\0' + GetNV("username") + '\0' + GetNV("password");
+ CString sAuthLine = (SaslImpersonation() ? GetNV("impersonationuser") : GetNV("username")) + '\0' + GetNV("username") + '\0' + GetNV("password");
sAuthLine.Base64Encode();
PutIRC("AUTHENTICATE " + sAuthLine);
} else {
return NetworkPage(WebSock, Tmpl, pNetwork->GetUser(), pNetwork);
} else if (sPageName == "delnetwork") {
+ // Admin||Self Check
+ if (!spSession->IsAdmin()) {
+ return false;
+ }
+
CString sUser = WebSock.GetParam("user");
if (sUser.empty() && !WebSock.IsPost()) {
sUser = WebSock.GetParam("user", false);
CUser* pUser = CZNC::Get().FindUser(sUser);
- // Admin||Self Check
- if (!spSession->IsAdmin() && (!spSession->GetUser() || spSession->GetUser() != pUser)) {
- return false;
- }
-
return DelNetwork(WebSock, pUser, Tmpl);
} else if (sPageName == "editchan") {
CIRCNetwork* pNetwork = SafeGetNetworkFromParam(WebSock);
breadNet["Text"] = "Edit Network [" + pNetwork->GetName() + "]";
+ if (spSession->IsAdmin()) {
const vector<CServer*>& vServers = pNetwork->GetServers();
for (unsigned int a = 0; a < vServers.size(); a++) {
CTemplate& l = Tmpl.AddRow("ServerLoop");
l["Server"] = vServers[a]->GetString();
}
+ }
const vector<CChan*>& Channels = pNetwork->GetChans();
for (unsigned int c = 0; c < Channels.size(); c++) {
VCString vsArgs;
+ if (spSession->IsAdmin()) {
+ WebSock.GetRawParam("servers").Split("\n", vsArgs);
+ if (vsArgs.size()) {
pNetwork->DelServers();
- WebSock.GetRawParam("servers").Split("\n", vsArgs);
for (unsigned int a = 0; a < vsArgs.size(); a++) {
pNetwork->AddServer(vsArgs[a].Trim_n());
}
+ }
+ }
WebSock.GetRawParam("fingerprints").Split("\n", vsArgs);
while (!pNetwork->GetTrustedFingerprints().empty()) {
}
}
+ CString sModUnloadError;
+ CModule* pModule;
+
for (set<CString>::iterator it2 = ssUnloadMods.begin(); it2 != ssUnloadMods.end(); ++it2) {
+ if (!it2->CaseCmp("sasl")) {
+ pModule = pNetwork->GetModules().FindModule("sasl");
+
+ if (pModule) {
+ if (pModule->GetNV("saslimpersonation").ToBool()) {
+ sModUnloadError = "Unable to unload module [sasl] SaslImpersonation enabled.";
+ continue;
+ }
+ }
+ }
pNetwork->GetModules().UnloadModule(*it2);
}
+
+ if (!sModUnloadError.empty()) {
+ DEBUG(sModUnloadError);
+ WebSock.GetSession()->AddError(sModUnloadError);
+ }
CTemplate TmplMod;
TmplMod["Username"] = pUser->GetUserName();
"You need to send your password. "
"Configure your client to send a server password.");
PutClient(":irc.znc.in NOTICE AUTH :*** "
- "To connect now, you can use /quote PASS <username>:<password>, "
- "or /quote PASS <username>/<network>:<password> to connect to a specific network.");
+ "To connect now, you can use /quote PASS /<username>/<password>, "
+ "or /quote PASS <network>/<username>/<password> to connect to a specific network.");
}
void CClient::ReadLine(const CString& sData) {
}
void CClient::ParsePass(const CString& sAuthLine) {
- // [user[@identifier][/network]:]password
- const size_t uColon = sAuthLine.find(":");
- if (uColon != CString::npos) {
- m_sPass = sAuthLine.substr(uColon + 1);
+ // [network[/user[@identifier]/]]password
- ParseUser(sAuthLine.substr(0, uColon));
+ if (sAuthLine.find("/") == CString::npos) {
+ m_sPass = sAuthLine;
} else {
+ VCString vsAuthLine;
+ sAuthLine.Split("/", vsAuthLine, false);
+
+ switch (vsAuthLine.size()) {
+ case 2:
+ ParseUser(vsAuthLine[0]);
+ m_sPass = vsAuthLine[1];
+ break;
+ case 3:
+ m_sNetwork = vsAuthLine[0];
+ ParseUser(vsAuthLine[1]);
+ m_sPass = vsAuthLine[2];
+ break;
+ default:
m_sPass = sAuthLine;
}
}
void CClient::ParseUser(const CString& sAuthLine) {
- // user[@identifier][/network]
-
- const size_t uSlash = sAuthLine.rfind("/");
- if (uSlash != CString::npos) {
- m_sNetwork = sAuthLine.substr(uSlash + 1);
+ // user[@identifier]
- ParseIdentifier(sAuthLine.substr(0, uSlash));
- } else {
ParseIdentifier(sAuthLine);
- }
}
void CClient::ParseIdentifier(const CString& sAuthLine) {
PutStatus(sNetworkAddError);
}
} else if (sCommand.Equals("DELNETWORK")) {
+ if (!m_pUser->IsAdmin()) {
+ PutStatus("Administrative access required to remove a Network. Ask an admin to remove the network for you.");
+ return;
+ }
+
CString sNetwork = sLine.Token(1);
if (sNetwork.empty()) {
PutStatus("You don't have a network named " + sNetwork);
}
} else if (sCommand.Equals("ADDSERVER")) {
+ if (!m_pUser->IsAdmin()) {
+ PutStatus("Administrative access required to add a Server. Ask an admin to add the server for you.");
+ return;
+ }
+
CString sServer = sLine.Token(1);
if (!m_pNetwork) {
PutStatus("Perhaps the server is already added or openssl is disabled?");
}
} else if (sCommand.Equals("REMSERVER") || sCommand.Equals("DELSERVER")) {
+ if (!m_pUser->IsAdmin()) {
+ PutStatus("Administrative access required to remove a Server. Ask an admin to remove the server for you.");
+ return;
+ }
+
if (!m_pNetwork) {
PutStatus("You must be connected with a network to use this command");
return;
}
CString sModRet;
+ CModule* pModule;
switch (eType) {
case CModInfo::GlobalModule:
m_pUser->GetModules().UnloadModule(sMod, sModRet);
break;
case CModInfo::NetworkModule:
+ if (!sMod.CaseCmp("sasl")) {
+ pModule = m_pNetwork->GetModules().FindModule("sasl");
+
+ if (pModule) {
+ if (pModule->GetNV("saslimpersonation").ToBool()) {
+ PutStatus("Unable to unload module [sasl] SaslImpersonation enabled.");
+ return;
+ }
+ }
+ }
m_pNetwork->GetModules().UnloadModule(sMod, sModRet);
break;
default:
}
CString sModRet;
+ CModule* pModule;
switch (eType) {
case CModInfo::GlobalModule:
m_pUser->GetModules().ReloadModule(sMod, sArgs, m_pUser, NULL, sModRet);
break;
case CModInfo::NetworkModule:
+ if (!sMod.CaseCmp("sasl")) {
+ pModule = m_pNetwork->GetModules().FindModule("sasl");
+
+ if (pModule) {
+ if (pModule->GetNV("saslimpersonation").ToBool()) {
+ PutStatus("Unable to reload module [sasl] SaslImpersonation enabled.");
+ return;
+ }
+ }
+ }
m_pNetwork->GetModules().ReloadModule(sMod, sArgs, m_pUser, m_pNetwork, sModRet);
break;
default:
if (!m_pUser->IsAdmin()) {
AddCommandHelp(Table, "ListClients", "", "List all clients connected to your ZNC user", sFilter);
}
+
+ if (m_pUser->IsAdmin()) {
AddCommandHelp(Table, "ListServers", "", "List all servers of current IRC network", sFilter);
AddCommandHelp(Table, "AddNetwork", "<name>", "Add a network to your user", sFilter);
AddCommandHelp(Table, "DelNetwork", "<name>", "Delete a network from your user", sFilter);
AddCommandHelp(Table, "ListNetworks", "", "List all networks", sFilter);
- if (m_pUser->IsAdmin()) {
AddCommandHelp(Table, "MoveNetwork", "<old user> <old network> <new user> [new network]", "Move an IRC network from one user to another", sFilter);
- }
AddCommandHelp(Table, "JumpNetwork", "<network>", "Jump to another network (Alternatively, you can connect to ZNC several times, using `user/network` as username)", sFilter);
AddCommandHelp(Table, "AddServer", "<host> [[+]port] [pass]", "Add a server to the list of alternate/backup servers of current IRC network.", sFilter);
AddCommandHelp(Table, "DelServer", "<host> [port] [pass]", "Remove a server from the list of alternate/backup servers of current IRC network", sFilter);
-
+ }
AddCommandHelp(Table, "AddTrustedServerFingerprint", "<fi:ng:er>", "Add a trusted server SSL certificate fingerprint (SHA-256) to current IRC network.", sFilter);
AddCommandHelp(Table, "DelTrustedServerFingerprint", "<fi:ng:er>", "Delete a trusted server SSL certificate from current IRC network.", sFilter);
AddCommandHelp(Table, "ListTrustedServerFingerprints", "", "List all trusted server SSL certificates of current IRC network.", sFilter);
if (m_ssPendingCaps.empty()) {
// We already got all needed ACK/NAK replies.
PutIRC("CAP END");
+ Register();
} else {
CString sCap = *m_ssPendingCaps.begin();
m_ssPendingCaps.erase(m_ssPendingCaps.begin());
void CIRCSock::Connected() {
DEBUG(GetSockName() << " == Connected()");
+ PutIRC("CAP LS");
+}
+void CIRCSock::Register() {
+ DEBUG(GetSockName() << " == Register()");
+
CString sPass = m_sPass;
CString sNick = m_pNetwork->GetNick();
CString sIdent = m_pNetwork->GetIdent();
IRCSOCKMODULECALL(OnIRCRegistration(sPass, sNick, sIdent, sRealName), &bReturn);
if (bReturn) return;
- PutIRC("CAP LS");
-
- if (!sPass.empty()) {
+ if (!sPass.empty())
PutIRC("PASS " + sPass);
- }
-
+
PutIRC("NICK " + sNick);
PutIRC("USER " + sIdent + " \"" + sIdent + "\" \"" + sIdent + "\" :" + sRealName);
MakeHash(szText, nTextLen);
}
-CMD5::~CMD5() {}
+CMD5::~CMD5()
+{
+ if (m_pszMD5 != NULL)
+ delete[] m_pszMD5;
+}
+
+unsigned char* CMD5::GetHash()
+{
+ return m_pszMD5;
+}
#define GET_UINT32(n,b,i) { \
(n) = ((uint32) (b)[(i) ] ) \
md5sum[0], md5sum[1], md5sum[2], md5sum[3], md5sum[4], md5sum[5],
md5sum[6], md5sum[7], md5sum[8], md5sum[9], md5sum[10], md5sum[11],
md5sum[12], md5sum[13], md5sum[14], md5sum[15]);
+
+ m_pszMD5 = new unsigned char[16];
+ m_pszMD5[0] = md5sum[0]; m_pszMD5[1] = md5sum[1]; m_pszMD5[2] = md5sum[2]; m_pszMD5[3] = md5sum[3];
+ m_pszMD5[4] = md5sum[4]; m_pszMD5[5] = md5sum[5]; m_pszMD5[6] = md5sum[6]; m_pszMD5[7] = md5sum[7];
+ m_pszMD5[8] = md5sum[8]; m_pszMD5[9] = md5sum[9]; m_pszMD5[10] = md5sum[10]; m_pszMD5[11] = md5sum[11];
+ m_pszMD5[12] = md5sum[12]; m_pszMD5[13] = md5sum[13]; m_pszMD5[14] = md5sum[14]; m_pszMD5[15] = md5sum[15];
return m_szMD5;
}