From 70c7745899c659c5b85ea592cb221208a0a6cad0 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 28 Mar 2011 15:57:46 +0200 Subject: [PATCH] Overhaul the config parsing This moves stuff to a two-step model. First, the new class CConfig reads the config file, parses it and creates a in-memory model of stuff. Only then do we actually go forward and apply the stuff. The upside of this is that some config errors are caught before we change anything on the running upside. Let's see how much stuff this broke... Signed-off-by: Uli Schlachter --- .gitignore | 2 + Chan.cpp | 20 +- Chan.h | 3 +- Config.cpp | 179 +++++++++ Config.h | 92 +++++ Makefile.in | 2 +- User.cpp | 212 ++++++++++ User.h | 3 + configure.ac | 1 + test/ConfigTest.cpp | 164 ++++++++ test/Makefile.in | 46 +++ znc.cpp | 922 +++++++++++++++----------------------------- znc.h | 4 + 13 files changed, 1037 insertions(+), 613 deletions(-) create mode 100644 Config.cpp create mode 100644 Config.h create mode 100644 test/ConfigTest.cpp create mode 100644 test/Makefile.in diff --git a/.gitignore b/.gitignore index aa2f4964..4b760b40 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ Makefile /modules/modpython/*.pyc /modules/*.pyc +/test/ConfigTest + # Compiled Object files *.o diff --git a/Chan.cpp b/Chan.cpp index 8d26391f..093e6fec 100644 --- a/Chan.cpp +++ b/Chan.cpp @@ -10,8 +10,9 @@ #include "IRCSock.h" #include "User.h" #include "znc.h" +#include "Config.h" -CChan::CChan(const CString& sName, CUser* pUser, bool bInConfig) { +CChan::CChan(const CString& sName, CUser* pUser, bool bInConfig, CConfig *pConfig) { m_sName = sName.Token(0); m_sKey = sName.Token(1); m_pUser = pUser; @@ -27,6 +28,23 @@ CChan::CChan(const CString& sName, CUser* pUser, bool bInConfig) { m_bKeepBuffer = m_pUser->KeepBuffer(); m_bDisabled = false; Reset(); + + if (pConfig) { + CString sValue; + if (pConfig->FindStringEntry("buffer", sValue)) + SetBufferCount(sValue.ToUInt(), true); + if (pConfig->FindStringEntry("keepbuffer", sValue)) + SetKeepBuffer(sValue.ToBool()); + if (pConfig->FindStringEntry("detached", sValue)) + SetDetached(sValue.ToBool()); + if (pConfig->FindStringEntry("autocycle", sValue)) + if (sValue.Equals("true")) + CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle " + sName); + if (pConfig->FindStringEntry("key", sValue)) + SetKey(sValue); + if (pConfig->FindStringEntry("modes", sValue)) + SetDefaultModes(sValue); + } } CChan::~CChan() { diff --git a/Chan.h b/Chan.h index e781ddba..0064210d 100644 --- a/Chan.h +++ b/Chan.h @@ -24,6 +24,7 @@ using std::set; // Forward Declarations class CUser; class CClient; +class CConfig; // !Forward Declarations class CChan { @@ -51,7 +52,7 @@ public: M_Except = 'e' } EModes; - CChan(const CString& sName, CUser* pUser, bool bInConfig); + CChan(const CString& sName, CUser* pUser, bool bInConfig, CConfig *pConfig = NULL); ~CChan(); void Reset(); diff --git a/Config.cpp b/Config.cpp new file mode 100644 index 00000000..c17e66b2 --- /dev/null +++ b/Config.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2004-2011 See the AUTHORS file for details. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "Config.h" +#include +#include + +struct ConfigStackEntry { + CString sTag; + CString sName; + CConfig Config; + + ConfigStackEntry(const CString& Tag, const CString Name) { + sTag = Tag; + sName = Name; + } +}; + +CConfig::CConfigEntry::CConfigEntry() + : m_pSubConfig(NULL) { +} + +CConfig::CConfigEntry::CConfigEntry(const CConfig& Config) + : m_pSubConfig(new CConfig(Config)) { +} + +CConfig::CConfigEntry::CConfigEntry(const CConfigEntry& other) + : m_pSubConfig(NULL) { + if (other.m_pSubConfig) + m_pSubConfig = new CConfig(*other.m_pSubConfig); +} + +CConfig::CConfigEntry::~CConfigEntry() +{ + delete m_pSubConfig; +} + +CConfig::CConfigEntry& CConfig::CConfigEntry::operator=(const CConfigEntry& other) { + delete m_pSubConfig; + if (other.m_pSubConfig) + m_pSubConfig = new CConfig(*other.m_pSubConfig); + else + m_pSubConfig = NULL; + return *this; +} + +bool CConfig::Parse(CFile& file, CString& sErrorMsg) +{ + CString sLine; + unsigned int uLineNum = 0; + CConfig *pActiveConfig = this; + std::stack ConfigStack; + bool bCommented = false; // support for /**/ style comments + + if (!file.Seek(0)) + return "Could not seek to the beginning of the config."; + + while (file.ReadLine(sLine)) { + uLineNum++; + +#define ERROR(arg) do { \ + std::stringstream stream; \ + stream << "Error on line " << uLineNum << ": " << arg; \ + sErrorMsg = stream.str(); \ + m_SubConfigs.clear(); \ + m_ConfigEntries.clear(); \ + return false; \ +} while (0) + + // Remove all leading spaces and trailing line endings + sLine.TrimLeft(); + sLine.TrimRight("\r\n"); + + if ((sLine.empty()) || (sLine[0] == '#') || (sLine.Left(2) == "//")) { + continue; + } + + if (sLine.Left(2) == "/*") { + if (sLine.Right(2) != "*/") { + bCommented = true; + } + + continue; + } + + if (bCommented) { + if (sLine.Right(2) == "*/") { + bCommented = false; + } + + continue; + } + + if ((sLine.Left(1) == "<") && (sLine.Right(1) == ">")) { + sLine.LeftChomp(); + sLine.RightChomp(); + sLine.Trim(); + + CString sTag = sLine.Token(0); + CString sValue = sLine.Token(1, true); + + sTag.Trim(); + sValue.Trim(); + + if (sTag.Left(1) == "/") { + sTag = sTag.substr(1); + + if (!sValue.empty()) + ERROR("Malformated closing tag. Expected \"\"."); + if (ConfigStack.empty()) + ERROR("Closing tag \"" << sTag << "\" which is not open."); + + const struct ConfigStackEntry& entry = ConfigStack.top(); + CConfig myConfig(entry.Config); + CString sName(entry.sName); + + if (!sTag.Equals(entry.sTag)) + ERROR("Closing tag \"" << sTag << "\" which is not open."); + + // This breaks entry + ConfigStack.pop(); + + if (ConfigStack.empty()) + pActiveConfig = this; + else + pActiveConfig = &ConfigStack.top().Config; + + SubConfig &conf = pActiveConfig->m_SubConfigs[sTag.AsLower()]; + SubConfig::const_iterator it = conf.find(sName); + + if (it != conf.end()) + ERROR("Duplicate entry for tag \"" << sTag << "\" name \"" << sName << "\"."); + + conf[sName] = CConfigEntry(myConfig); + } else { + if (sValue.empty()) + ERROR("Empty block name at begin of block."); + ConfigStack.push(ConfigStackEntry(sTag.AsLower(), sValue)); + pActiveConfig = &ConfigStack.top().Config; + } + + continue; + } + + // If we have a regular line, figure out where it goes + CString sName = sLine.Token(0, false, "="); + CString sValue = sLine.Token(1, true, "="); + + // Only remove the first space, people might want + // leading spaces (e.g. in the MOTD). + if (sValue.Left(1) == " ") + sValue.LeftChomp(); + + // We don't have any names with spaces, trim all + // leading/trailing spaces. + sName.Trim(); + + if (sName.empty() || sValue.empty()) + ERROR("Malformed line"); + + CString sNameLower = sName.AsLower(); + pActiveConfig->m_ConfigEntries[sNameLower].push_back(sValue); + } + + if (bCommented) + ERROR("Comment not closed at end of file."); + + if (!ConfigStack.empty()) { + const CString& sTag = ConfigStack.top().sTag; + ERROR("Not all tags are closed at the end of the file. Inner-most open tag is \"" << sTag << "\"."); + } + + return true; +} diff --git a/Config.h b/Config.h new file mode 100644 index 00000000..e8853bbe --- /dev/null +++ b/Config.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2004-2011 See the AUTHORS file for details. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include "ZNCString.h" +#include "FileUtils.h" + +class CConfig { +public: + struct CConfigEntry { + CConfigEntry(); + CConfigEntry(const CConfig& Config); + CConfigEntry(const CConfigEntry& other); + ~CConfigEntry(); + CConfigEntry& operator=(const CConfigEntry& other); + + CConfig* m_pSubConfig; + }; + + typedef map EntryMap; + typedef map SubConfig; + typedef map SubConfigMap; + + typedef EntryMap::const_iterator EntryMapIterator; + typedef SubConfigMap::const_iterator SubConfigMapIterator; + + EntryMapIterator BeginEntries() const { + return m_ConfigEntries.begin(); + } + EntryMapIterator EndEntries() const { + return m_ConfigEntries.end(); + } + + SubConfigMapIterator BeginSubConfigs() const { + return m_SubConfigs.begin(); + } + SubConfigMapIterator EndSubConfigs() const { + return m_SubConfigs.end(); + } + + bool FindStringVector(const CString& sName, VCString& vsList) { + EntryMap::iterator it = m_ConfigEntries.find(sName); + vsList.clear(); + if (it == m_ConfigEntries.end()) + return false; + vsList = it->second; + m_ConfigEntries.erase(it); + return true; + } + + bool FindStringEntry(const CString& sName, CString& sRes) { + EntryMap::iterator it = m_ConfigEntries.find(sName); + sRes.clear(); + if (it == m_ConfigEntries.end() || it->second.empty()) + return false; + sRes = it->second.front(); + it->second.erase(it->second.begin()); + if (it->second.empty()) + m_ConfigEntries.erase(it); + return true; + } + + bool FindSubConfig(const CString& sName, SubConfig& Config) { + SubConfigMap::iterator it = m_SubConfigs.find(sName); + if (it == m_SubConfigs.end()) { + Config.clear(); + return false; + } + Config = it->second; + m_SubConfigs.erase(it); + return true; + } + + bool empty() const { + return m_ConfigEntries.empty() && m_SubConfigs.empty(); + } + + bool Parse(CFile& file, CString& sErrorMsg); + +private: + EntryMap m_ConfigEntries; + SubConfigMap m_SubConfigs; +}; + +#endif // !CONFIG_H diff --git a/Makefile.in b/Makefile.in index 1873c2a7..6111cd64 100644 --- a/Makefile.in +++ b/Makefile.in @@ -31,7 +31,7 @@ INSTALL_DATA := @INSTALL_DATA@ LIB_SRCS := ZNCString.cpp Csocket.cpp znc.cpp User.cpp IRCSock.cpp Client.cpp DCCBounce.cpp \ DCCSock.cpp Chan.cpp Nick.cpp Server.cpp Modules.cpp MD5.cpp Buffer.cpp Utils.cpp \ FileUtils.cpp HTTPSock.cpp Template.cpp ClientCommand.cpp Socket.cpp SHA256.cpp \ - WebModules.cpp Listener.cpp + WebModules.cpp Listener.cpp Config.cpp BIN_SRCS := main.cpp LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS)) BIN_OBJS := $(patsubst %cpp,%o,$(BIN_SRCS)) diff --git a/User.cpp b/User.cpp index b3c28f51..30a1e8cd 100644 --- a/User.cpp +++ b/User.cpp @@ -8,6 +8,7 @@ #include "User.h" #include "Chan.h" +#include "Config.h" #include "DCCSock.h" #include "IRCSock.h" #include "Server.h" @@ -111,6 +112,217 @@ CUser::~CUser() { CZNC::Get().GetManager().DelCronByAddr(m_pUserTimer); } +template +struct TOption { + const char *name; + void (CUser::*pSetter)(T); +}; + +bool CUser::ParseConfig(CConfig* pConfig, CString& sError) { + TOption StringOptions[] = { + { "nick", &CUser::SetNick }, + { "quitmsg", &CUser::SetQuitMsg }, + { "altnick", &CUser::SetAltNick }, + { "ident", &CUser::SetIdent }, + { "realname", &CUser::SetRealName }, + { "chanmodes", &CUser::SetDefaultChanModes }, + { "bindhost", &CUser::SetBindHost }, + { "vhost", &CUser::SetBindHost }, + { "dccbindhost", &CUser::SetDCCBindHost }, + { "dccvhost", &CUser::SetDCCBindHost }, + { "timestampformat", &CUser::SetTimestampFormat }, + { "skin", &CUser::SetSkinName }, + }; + size_t numStringOptions = sizeof(StringOptions) / sizeof(StringOptions[0]); + TOption UIntOptions[] = { + { "jointries", &CUser::SetJoinTries }, + { "maxjoins", &CUser::SetMaxJoins }, + }; + size_t numUIntOptions = sizeof(UIntOptions) / sizeof(UIntOptions[0]); + TOption BoolOptions[] = { + { "keepbuffer", &CUser::SetKeepBuffer }, + { "multiclients", &CUser::SetMultiClients }, + { "bouncedccs", &CUser::SetBounceDCCs }, + { "denyloadmod", &CUser::SetDenyLoadMod }, + { "admin", &CUser::SetAdmin }, + { "denysetbindhost", &CUser::SetDenySetBindHost }, + { "denysetvhost", &CUser::SetDenySetBindHost }, + { "appendtimestamp", &CUser::SetTimestampAppend }, + { "prependtimestamp", &CUser::SetTimestampPrepend }, + { "ircconnectenabled", &CUser::SetIRCConnectEnabled }, + }; + size_t numBoolOptions = sizeof(BoolOptions) / sizeof(BoolOptions[0]); + + for (size_t i = 0; i < numStringOptions; i++) { + CString sValue; + if (pConfig->FindStringEntry(StringOptions[i].name, sValue)) + (this->*StringOptions[i].pSetter)(sValue); + } + for (size_t i = 0; i < numUIntOptions; i++) { + CString sValue; + if (pConfig->FindStringEntry(UIntOptions[i].name, sValue)) + (this->*UIntOptions[i].pSetter)(sValue.ToUInt()); + } + for (size_t i = 0; i < numBoolOptions; i++) { + CString sValue; + if (pConfig->FindStringEntry(BoolOptions[i].name, sValue)) + (this->*BoolOptions[i].pSetter)(sValue.ToBool()); + } + + VCString vsList; + VCString::const_iterator vit; + pConfig->FindStringVector("allow", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + AddAllowedHost(*vit); + } + pConfig->FindStringVector("ctcpreply", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + const CString& sValue = *vit; + AddCTCPReply(sValue.Token(0), sValue.Token(1, true)); + } + pConfig->FindStringVector("server", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + CUtils::PrintAction("Adding Server [" + *vit + "]"); + CUtils::PrintStatus(AddServer(*vit)); + } + pConfig->FindStringVector("chan", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + AddChan(*vit, true); + } + + CString sValue; + if (pConfig->FindStringEntry("buffer", sValue)) + SetBufferCount(sValue.ToUInt(), true); + if (pConfig->FindStringEntry("awaysuffix", sValue)) { + CUtils::PrintMessage("WARNING: AwaySuffix has been depricated, instead try -> LoadModule = awaynick %nick%_" + sValue); + } + if (pConfig->FindStringEntry("autocycle", sValue)) { + if (sValue.Equals("true")) + CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle"); + } + if (pConfig->FindStringEntry("keepnick", sValue)) { + if (sValue.Equals("true")) + CUtils::PrintError("WARNING: KeepNick has been deprecated, instead try -> LoadModule = keepnick"); + } + if (pConfig->FindStringEntry("statusprefix", sValue)) { + if (!SetStatusPrefix(sValue)) { + sError = "Invalid StatusPrefix [" + sValue + "] Must be 1-5 chars, no spaces."; + CUtils::PrintError(sError); + return false; + } + } + if (pConfig->FindStringEntry("timezoneoffset", sValue)) { + SetTimezoneOffset(sValue.ToDouble()); + } + if (pConfig->FindStringEntry("timestamp", sValue)) { + if (!sValue.Trim_n().Equals("true")) { + if (sValue.Trim_n().Equals("append")) { + SetTimestampAppend(true); + SetTimestampPrepend(false); + } else if (sValue.Trim_n().Equals("prepend")) { + SetTimestampAppend(false); + SetTimestampPrepend(true); + } else if (sValue.Trim_n().Equals("false")) { + SetTimestampAppend(false); + SetTimestampPrepend(false); + } else { + SetTimestampFormat(sValue); + } + } + } + if (pConfig->FindStringEntry("dcclookupmethod", sValue)) + SetUseClientIP(sValue.Equals("Client")); + pConfig->FindStringEntry("pass", sValue); + // There are different formats for this available: + // Pass = + // Pass = - + // Pass = plain# + // Pass = # + // Pass = ### + // 'Salted hash' means hash of 'password' + 'salt' + // Possible hashes are md5 and sha256 + if (sValue.Right(1) == "-") { + sValue.RightChomp(); + sValue.Trim(); + SetPass(sValue, CUser::HASH_MD5); + } else { + CString sMethod = sValue.Token(0, false, "#"); + CString sPass = sValue.Token(1, true, "#"); + if (sMethod == "md5" || sMethod == "sha256") { + CUser::eHashType type = CUser::HASH_MD5; + if (sMethod == "sha256") + type = CUser::HASH_SHA256; + + CString sSalt = sPass.Token(1, false, "#"); + sPass = sPass.Token(0, false, "#"); + SetPass(sPass, type, sSalt); + } else if (sMethod == "plain") { + SetPass(sPass, CUser::HASH_NONE); + } else { + SetPass(sValue, CUser::HASH_NONE); + } + } + + CConfig::SubConfig subConf; + CConfig::SubConfig::const_iterator subIt; + pConfig->FindSubConfig("chan", subConf); + for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) { + const CString& sChanName = subIt->first; + CConfig* pSubConf = subIt->second.m_pSubConfig; + CChan* pChan = new CChan(sChanName, this, true, pSubConf); + + if (!pSubConf->empty()) { + sError = "Unhandled lines in config for User [" + GetUserName() + "], Channel [" + sChanName + "]!"; + CUtils::PrintError(sError); + + CZNC::DumpConfig(pSubConf); + return false; + } + + // Save the channel name, because AddChan + // deletes the CChannel*, if adding fails + sError = pChan->GetName(); + if (!AddChan(pChan)) { + sError = "Channel [" + sError + "] defined more than once"; + CUtils::PrintError(sError); + return false; + } + sError.clear(); + } + + pConfig->FindStringVector("loadmodule", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + sValue = *vit; + CString sModName = sValue.Token(0); + + // XXX Legacy crap, added in znc 0.089 + if (sModName == "discon_kick") { + CUtils::PrintMessage("NOTICE: [discon_kick] was renamed, loading [disconkick] instead"); + sModName = "disconkick"; + } + + CUtils::PrintAction("Loading Module [" + sModName + "]"); + CString sModRet; + CString sArgs = sValue.Token(1, true); + + bool bModRet = GetModules().LoadModule(sModName, sArgs, this, sModRet); + + // If the module was loaded, sModRet contains + // "Loaded Module [name] ..." and we strip away this beginning. + if (bModRet) + sModRet = sModRet.Token(1, true, sModName + "] "); + + CUtils::PrintStatus(bModRet, sModRet); + if (!bModRet) { + sError = sModRet; + return false; + } + continue; + } + + return true; +} + void CUser::DelModules() { delete m_pModules; m_pModules = NULL; diff --git a/User.h b/User.h index 9d49d2ca..b9b5ae03 100644 --- a/User.h +++ b/User.h @@ -22,6 +22,7 @@ using std::vector; class CChan; class CClient; +class CConfig; class CIRCSock; class CUserTimer; class CServer; @@ -33,6 +34,8 @@ public: CUser(const CString& sUserName); ~CUser(); + bool ParseConfig(CConfig* Config, CString& sError); + enum eHashType { HASH_NONE, HASH_MD5, diff --git a/configure.ac b/configure.ac index dbd633ba..e63e6845 100644 --- a/configure.ac +++ b/configure.ac @@ -474,6 +474,7 @@ AC_CONFIG_FILES([man/Makefile]) AC_CONFIG_FILES([znc.pc]) AC_CONFIG_FILES([znc-uninstalled.pc]) AC_CONFIG_FILES([modules/Makefile]) +AC_CONFIG_FILES([test/Makefile]) AC_OUTPUT echo diff --git a/test/ConfigTest.cpp b/test/ConfigTest.cpp new file mode 100644 index 00000000..8c5bb9fd --- /dev/null +++ b/test/ConfigTest.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2004-2011 See the AUTHORS file for details. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "Config.h" +#include + +class CConfigTest { +public: + CConfigTest(const CString& sConfig) : m_sConfig(sConfig) { } + virtual ~CConfigTest() { m_File.Delete(); } + + virtual bool Test() = 0; + +protected: + CFile& WriteFile() { + char sName[] = "./temp-XXXXXX"; + int fd = mkstemp(sName); + m_File.Open(sName, O_RDWR); + close(fd); + + m_File.Write(m_sConfig); + + return m_File; + } + +private: + CFile m_File; + CString m_sConfig; +}; + +class CConfigErrorTest : public CConfigTest { +public: + CConfigErrorTest(const CString& sConfig, const CString& sError) + : CConfigTest(sConfig), m_sError(sError) { } + + bool Test() { + CFile &File = WriteFile(); + + CConfig conf; + CString sError; + bool res = conf.Parse(File, sError); + if (res) { + std::cout << "Didn't error out!\n"; + return false; + } + + if (sError != m_sError) { + std::cout << "Wrong error\n Expected: " << m_sError << "\n Got: " << sError << std::endl; + return false; + } + + return true; + } +private: + CString m_sError; +}; + +class CConfigSuccessTest : public CConfigTest { +public: + CConfigSuccessTest(const CString& sConfig, const CString& sExpectedOutput) + : CConfigTest(sConfig), m_sOutput(sExpectedOutput) { } + + bool Test() { + CFile &File = WriteFile(); + // Verify that Parse() rewinds the file + File.Seek(12); + + CConfig conf; + CString sError; + bool res = conf.Parse(File, sError); + if (!res) { + std::cout << "Error'd out! (" + sError + ")\n"; + return false; + } + if (!sError.empty()) { + std::cout << "Non-empty error string!\n"; + return false; + } + + CString sOutput; + ToString(sOutput, conf); + + if (sOutput != m_sOutput) { + std::cout << "Wrong output\n Expected: " << m_sOutput << "\n Got: " << sOutput << std::endl; + return false; + } + + return true; + } + + void ToString(CString& sRes, CConfig& conf) { + CConfig::EntryMapIterator it = conf.BeginEntries(); + while (it != conf.EndEntries()) { + const CString& sKey = it->first; + const VCString& vsEntries = it->second; + VCString::const_iterator i = vsEntries.begin(); + if (i == vsEntries.end()) + sRes += sKey + " <- Error, empty list!\n"; + else + while (i != vsEntries.end()) { + sRes += sKey + "=" + *i + "\n"; + i++; + } + ++it; + } + + CConfig::SubConfigMapIterator it2 = conf.BeginSubConfigs(); + while (it2 != conf.EndSubConfigs()) { + map::const_iterator it3 = it2->second.begin(); + + while (it3 != it2->second.end()) { + sRes += "->" + it2->first + "/" + it3->first + "\n"; + ToString(sRes, *it3->second.m_pSubConfig); + sRes += "<-\n"; + ++it3; + } + + ++it2; + } + } + +private: + CString m_sOutput; +}; + +int main() { +#define TEST_ERROR(a, b) new CConfigErrorTest(a, b) +#define TEST_SUCCESS(a, b) new CConfigSuccessTest(a, b) +#define ARRAY_SIZE(a) (sizeof(a)/(sizeof((a)[0]))) + CConfigTest *tests[] = { + TEST_SUCCESS("", ""), + /* duplicate entries */ + TEST_SUCCESS("Foo = bar\nFoo = baz\n", "foo=bar\nfoo=baz\n"), + TEST_SUCCESS("Foo = baz\nFoo = bar\n", "foo=baz\nfoo=bar\n"), + /* sub configs */ + TEST_ERROR("", "Error on line 1: Closing tag \"foo\" which is not open."), + TEST_ERROR("\n\n", "Error on line 2: Closing tag \"bar\" which is not open."), + TEST_ERROR("", "Error on line 1: Not all tags are closed at the end of the file. Inner-most open tag is \"foo\"."), + TEST_ERROR("\n", "Error on line 1: Empty block name at begin of block."), + TEST_ERROR("\n\n\n", "Error on line 4: Duplicate entry for tag \"foo\" name \"1\"."), + TEST_SUCCESS("\n", "->foo/a\n<-\n"), + TEST_SUCCESS("\n \n \n", "->a/b\n->c/d\n<-\n<-\n"), + TEST_SUCCESS(" \t \nfoo = bar\n\tFooO = bar\n", "->a/B\nfoo=bar\nfooo=bar\n<-\n"), + /* comments */ + TEST_SUCCESS("Foo = bar // baz\n// Bar = baz", "foo=bar // baz\n"), + TEST_SUCCESS("Foo = bar /* baz */\n/*** Foo = baz ***/\n /**** asdsdfdf \n Some quite invalid stuff ***/\n", "foo=bar /* baz */\n"), + TEST_ERROR("\n/* Just a comment\n", "Error on line 3: Comment not closed at end of file."), + }; + unsigned int i; + unsigned int failed = 0; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + if (!tests[i]->Test()) + failed++; + delete tests[i]; + } + + return failed; +} diff --git a/test/Makefile.in b/test/Makefile.in new file mode 100644 index 00000000..691d611b --- /dev/null +++ b/test/Makefile.in @@ -0,0 +1,46 @@ +SHELL := @SHELL@ + +# Support out-of-tree builds +srcdir := @srcdir@ +VPATH := @srcdir@ + +CXX := @CXX@ +CXXFLAGS := @DEFS@ @CPPFLAGS@ @CXXFLAGS@ -I.. +LDFLAGS := @LDFLAGS@ +LIBS := @LIBS@ + +TARGETS := ConfigTest +OBJS := $(addsuffix .o, $(TARGETS)) +ZNC_OBJS := Config.o FileUtils.o Utils.o ZNCString.o MD5.o SHA256.o +ZNC_OBJS := $(addprefix ../, $(ZNC_OBJS)) + +ifneq "$(V)" "" +VERBOSE=1 +endif +ifeq "$(VERBOSE)" "" +Q=@ +E=@echo +C=-s +else +Q= +E=@\# +C= +endif + +.PHONY: test + +all: $(TARGETS) + +ConfigTest: ConfigTest.o + $(E) Linking $@... + $(Q)$(CXX) $(LDFLAGS) -o $@ $< $(ZNC_OBJS) $(LIBS) + +%.o: %.cpp Makefile + @mkdir -p .depend + $(E) Building $@... + $(Q)$(CXX) $(CXXFLAGS) -c -o $@ $< -MMD -MF .depend/$@.dep + +test: $(TARGETS) + for test in $(TARGETS) ; do \ + $$test || exit 1 ; \ + done diff --git a/znc.cpp b/znc.cpp index 045bf03a..bf534735 100644 --- a/znc.cpp +++ b/znc.cpp @@ -12,6 +12,7 @@ #include "Server.h" #include "User.h" #include "Listener.h" +#include "Config.h" #include static inline CString FormatBindError() { @@ -1008,13 +1009,11 @@ bool CZNC::DoRehash(CString& sError) m_pLockFile = pFile; CFile &File = *pFile; - // This fd is re-used for rehashing, so we must seek back to the beginning! - if (!File.Seek(0)) { - sError = "Could not seek to the beginning of the config."; + CConfig config; + if (!config.Parse(File, sError)) { CUtils::PrintStatus(false, sError); return false; } - CUtils::PrintStatus(true); m_vsBindHosts.clear(); @@ -1026,610 +1025,189 @@ bool CZNC::DoRehash(CString& sError) m_vpListeners.erase(m_vpListeners.begin()); } - CString sLine; - bool bCommented = false; // support for /**/ style comments - CUser* pUser = NULL; // Used to keep track of which user block we are in - CUser* pRealUser = NULL; // If we rehash a user, this is the real one - CChan* pChan = NULL; // Used to keep track of which chan block we are in - unsigned int uLineNum = 0; MCString msModules; // Modules are queued for later loading - while (File.ReadLine(sLine)) { - uLineNum++; + const char *szListenerEntries[] = { + "listen", "listen6", "listen4", + "listener", "listener6", "listener4" + }; + const size_t numListenerEntries = sizeof(szListenerEntries) / sizeof(szListenerEntries[0]); + VCString vsList; - // Remove all leading spaces and trailing line endings - sLine.TrimLeft(); - sLine.TrimRight("\r\n"); + for (size_t i = 0; i < numListenerEntries; i++) { + config.FindStringVector(szListenerEntries[i], vsList); + VCString::const_iterator it = vsList.begin(); - if ((sLine.empty()) || (sLine[0] == '#') || (sLine.Left(2) == "//")) { - continue; + for (; it != vsList.end(); ++it) { + if (!AddListener(szListenerEntries[i] + CString(" ") + *it, sError)) + return false; } + } - if (sLine.Left(2) == "/*") { - if (sLine.Right(2) != "*/") { - bCommented = true; - } + VCString::const_iterator vit; + config.FindStringVector("loadmodule", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + CString sModName = vit->Token(0); + CString sArgs = vit->Token(1, true); - continue; - } - - if (bCommented) { - if (sLine.Right(2) == "*/") { - bCommented = false; - } - - continue; - } - - if ((sLine.Left(1) == "<") && (sLine.Right(1) == ">")) { - sLine.LeftChomp(); - sLine.RightChomp(); - sLine.Trim(); - - CString sTag = sLine.substr(0, sLine.find_first_of(" \t\r\n")); - CString sValue = (sTag.size() < sLine.size()) ? sLine.substr(sTag.size() +1) : ""; - - sTag.Trim(); - sValue.Trim(); - - if (sLine.Left(1) == "/") { - sTag = sTag.substr(1); - - if (pUser) { - if (pChan) { - if (sTag.Equals("Chan")) { - // Save the channel name, because AddChan - // deletes the CChannel*, if adding fails - sError = pChan->GetName(); - if (!pUser->AddChan(pChan)) { - sError = "Channel [" + sError + "] defined more than once"; - CUtils::PrintError(sError); - return false; - } - sError.clear(); - pChan = NULL; - continue; - } - } else if (sTag.Equals("User")) { - CString sErr; - - if (pRealUser) { - if (!pRealUser->Clone(*pUser, sErr) - || !AddUser(pRealUser, sErr)) { - sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr; - DEBUG("CUser::Clone() failed in rehash"); - } - pUser->SetBeingDeleted(true); - delete pUser; - pUser = NULL; - } else if (!AddUser(pUser, sErr)) { - sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr; - } - - if (!sError.empty()) { - CUtils::PrintError(sError); - if (pUser) { - pUser->SetBeingDeleted(true); - delete pUser; - pUser = NULL; - } - return false; - } - - pUser = NULL; - pRealUser = NULL; - continue; - } - } - } else if (sTag.Equals("User")) { - if (pUser) { - sError = "You may not nest tags inside of other tags."; - CUtils::PrintError(sError); - return false; - } - - if (sValue.empty()) { - sError = "You must supply a username in the tag."; - CUtils::PrintError(sError); - return false; - } - - if (m_msUsers.find(sValue) != m_msUsers.end()) { - sError = "User [" + sValue + "] defined more than once."; - CUtils::PrintError(sError); - return false; - } - - CUtils::PrintMessage("Loading user [" + sValue + "]"); - - // Either create a CUser* or use an existing one - map::iterator it = m_msDelUsers.find(sValue); - - if (it != m_msDelUsers.end()) { - pRealUser = it->second; - m_msDelUsers.erase(it); - } else - pRealUser = NULL; - - pUser = new CUser(sValue); - - if (!m_sStatusPrefix.empty()) { - if (!pUser->SetStatusPrefix(m_sStatusPrefix)) { - sError = "Invalid StatusPrefix [" + m_sStatusPrefix + "] Must be 1-5 chars, no spaces."; - CUtils::PrintError(sError); - return false; - } - } - - continue; - } else if (sTag.Equals("Chan")) { - if (!pUser) { - sError = " tags must be nested inside of a tag."; - CUtils::PrintError(sError); - return false; - } - - if (pChan) { - sError = "You may not nest tags inside of other tags."; - CUtils::PrintError(sError); - return false; - } - - pChan = new CChan(sValue, pUser, true); - continue; - } - } - - // If we have a regular line, figure out where it goes - CString sName = sLine.Token(0, false, "="); - CString sValue = sLine.Token(1, true, "="); - - // Only remove the first space, people might want - // leading spaces (e.g. in the MOTD). - if (sValue.Left(1) == " ") - sValue.LeftChomp(); - - // We don't have any names with spaces, trim all - // leading/trailing spaces. - sName.Trim(); - - if ((!sName.empty()) && (!sValue.empty())) { - if (pUser) { - if (pChan) { - if (sName.Equals("Buffer")) { - pChan->SetBufferCount(sValue.ToUInt(), true); - continue; - } else if (sName.Equals("KeepBuffer")) { - pChan->SetKeepBuffer(sValue.Equals("true")); - continue; - } else if (sName.Equals("Detached")) { - pChan->SetDetached(sValue.Equals("true")); - continue; - } else if (sName.Equals("AutoCycle")) { - if (sValue.Equals("true")) { - CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle " + pChan->GetName()); - } - continue; - } else if (sName.Equals("Key")) { - pChan->SetKey(sValue); - continue; - } else if (sName.Equals("Modes")) { - pChan->SetDefaultModes(sValue); - continue; - } - } else { - if (sName.Equals("Buffer")) { - pUser->SetBufferCount(sValue.ToUInt(), true); - continue; - } else if (sName.Equals("KeepBuffer")) { - pUser->SetKeepBuffer(sValue.Equals("true")); - continue; - } else if (sName.Equals("Nick")) { - pUser->SetNick(sValue); - continue; - } else if (sName.Equals("CTCPReply")) { - pUser->AddCTCPReply(sValue.Token(0), sValue.Token(1, true)); - continue; - } else if (sName.Equals("QuitMsg")) { - pUser->SetQuitMsg(sValue); - continue; - } else if (sName.Equals("AltNick")) { - pUser->SetAltNick(sValue); - continue; - } else if (sName.Equals("AwaySuffix")) { - CUtils::PrintMessage("WARNING: AwaySuffix has been depricated, instead try -> LoadModule = awaynick %nick%_" + sValue); - continue; - } else if (sName.Equals("AutoCycle")) { - if (sValue.Equals("true")) { - CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle"); - } - continue; - } else if (sName.Equals("Pass")) { - // There are different formats for this available: - // Pass = - // Pass = - - // Pass = plain# - // Pass = # - // Pass = ### - // 'Salted hash' means hash of 'password' + 'salt' - // Possible hashes are md5 and sha256 - if (sValue.Right(1) == "-") { - sValue.RightChomp(); - sValue.Trim(); - pUser->SetPass(sValue, CUser::HASH_MD5); - } else { - CString sMethod = sValue.Token(0, false, "#"); - CString sPass = sValue.Token(1, true, "#"); - if (sMethod == "md5" || sMethod == "sha256") { - CUser::eHashType type = CUser::HASH_MD5; - if (sMethod == "sha256") - type = CUser::HASH_SHA256; - - CString sSalt = sPass.Token(1, false, "#"); - sPass = sPass.Token(0, false, "#"); - pUser->SetPass(sPass, type, sSalt); - } else if (sMethod == "plain") { - pUser->SetPass(sPass, CUser::HASH_NONE); - } else { - pUser->SetPass(sValue, CUser::HASH_NONE); - } - } - - continue; - } else if (sName.Equals("MultiClients")) { - pUser->SetMultiClients(sValue.Equals("true")); - continue; - } else if (sName.Equals("BounceDCCs")) { - pUser->SetBounceDCCs(sValue.Equals("true")); - continue; - } else if (sName.Equals("Ident")) { - pUser->SetIdent(sValue); - continue; - } else if (sName.Equals("DenyLoadMod")) { - pUser->SetDenyLoadMod(sValue.Equals("true")); - continue; - } else if (sName.Equals("Admin")) { - pUser->SetAdmin(sValue.Equals("true")); - continue; - } else if (sName.Equals("DenySetBindHost") || sName.Equals("DenySetVHost")) { - pUser->SetDenySetBindHost(sValue.Equals("true")); - continue; - } else if (sName.Equals("StatusPrefix")) { - if (!pUser->SetStatusPrefix(sValue)) { - sError = "Invalid StatusPrefix [" + sValue + "] Must be 1-5 chars, no spaces."; - CUtils::PrintError(sError); - return false; - } - continue; - } else if (sName.Equals("DCCLookupMethod")) { - pUser->SetUseClientIP(sValue.Equals("Client")); - continue; - } else if (sName.Equals("RealName")) { - pUser->SetRealName(sValue); - continue; - } else if (sName.Equals("KeepNick")) { - if (sValue.Equals("true")) { - CUtils::PrintError("WARNING: KeepNick has been deprecated, instead try -> LoadModule = keepnick"); - } - continue; - } else if (sName.Equals("ChanModes")) { - pUser->SetDefaultChanModes(sValue); - continue; - } else if (sName.Equals("BindHost") || sName.Equals("VHost")) { - pUser->SetBindHost(sValue); - continue; - } else if (sName.Equals("DCCBindHost") || sName.Equals("DCCVHost")) { - pUser->SetDCCBindHost(sValue); - continue; - } else if (sName.Equals("Allow")) { - pUser->AddAllowedHost(sValue); - continue; - } else if (sName.Equals("Server")) { - CUtils::PrintAction("Adding Server [" + sValue + "]"); - CUtils::PrintStatus(pUser->AddServer(sValue)); - continue; - } else if (sName.Equals("Chan")) { - pUser->AddChan(sValue, true); - continue; - } else if (sName.Equals("TimestampFormat")) { - pUser->SetTimestampFormat(sValue); - continue; - } else if (sName.Equals("AppendTimestamp")) { - pUser->SetTimestampAppend(sValue.ToBool()); - continue; - } else if (sName.Equals("PrependTimestamp")) { - pUser->SetTimestampPrepend(sValue.ToBool()); - continue; - } else if (sName.Equals("IRCConnectEnabled")) { - pUser->SetIRCConnectEnabled(sValue.ToBool()); - continue; - } else if (sName.Equals("Timestamp")) { - if (!sValue.Trim_n().Equals("true")) { - if (sValue.Trim_n().Equals("append")) { - pUser->SetTimestampAppend(true); - pUser->SetTimestampPrepend(false); - } else if (sValue.Trim_n().Equals("prepend")) { - pUser->SetTimestampAppend(false); - pUser->SetTimestampPrepend(true); - } else if (sValue.Trim_n().Equals("false")) { - pUser->SetTimestampAppend(false); - pUser->SetTimestampPrepend(false); - } else { - pUser->SetTimestampFormat(sValue); - } - } - continue; - } else if (sName.Equals("TimezoneOffset")) { - pUser->SetTimezoneOffset(sValue.ToDouble()); // there is no ToFloat() - continue; - } else if (sName.Equals("JoinTries")) { - pUser->SetJoinTries(sValue.ToUInt()); - continue; - } else if (sName.Equals("MaxJoins")) { - pUser->SetMaxJoins(sValue.ToUInt()); - continue; - } else if (sName.Equals("Skin")) { - pUser->SetSkinName(sValue); - continue; - } else if (sName.Equals("LoadModule")) { - CString sModName = sValue.Token(0); - - // XXX Legacy crap, added in znc 0.089 - if (sModName == "discon_kick") { - CUtils::PrintMessage("NOTICE: [discon_kick] was renamed, loading [disconkick] instead"); - sModName = "disconkick"; - } - - CUtils::PrintAction("Loading Module [" + sModName + "]"); - CString sModRet; - CString sArgs = sValue.Token(1, true); - - bool bModRet = pUser->GetModules().LoadModule(sModName, sArgs, pUser, sModRet); - - // If the module was loaded, sModRet contains - // "Loaded Module [name] ..." and we strip away this beginning. - if (bModRet) - sModRet = sModRet.Token(1, true, sModName + "] "); - - CUtils::PrintStatus(bModRet, sModRet); - if (!bModRet) { - sError = sModRet; - return false; - } - continue; - } - } - } else { - if (sName.Equals("Listen") || sName.Equals("Listen6") || sName.Equals("Listen4") - || sName.Equals("Listener") || sName.Equals("Listener6") || sName.Equals("Listener4")) { - EAddrType eAddr = ADDR_ALL; - if (sName.Equals("Listen4") || sName.Equals("Listen") || sName.Equals("Listener4")) { - eAddr = ADDR_IPV4ONLY; - } - if (sName.Equals("Listener6")) { - eAddr = ADDR_IPV6ONLY; - } - - CListener::EAcceptType eAccept = CListener::ACCEPT_ALL; - if (sValue.TrimPrefix("irc_only ")) - eAccept = CListener::ACCEPT_IRC; - else if (sValue.TrimPrefix("web_only ")) - eAccept = CListener::ACCEPT_HTTP; - - bool bSSL = false; - CString sPort; - CString sBindHost; - - if (ADDR_IPV4ONLY == eAddr) { - sValue.Replace(":", " "); - } - - if (sValue.find(" ") != CString::npos) { - sBindHost = sValue.Token(0, false, " "); - sPort = sValue.Token(1, true, " "); - } else { - sPort = sValue; - } - - if (sPort.Left(1) == "+") { - sPort.LeftChomp(); - bSSL = true; - } - - CString sHostComment; - - if (!sBindHost.empty()) { - sHostComment = " on host [" + sBindHost + "]"; - } - - CString sIPV6Comment; - - switch (eAddr) { - case ADDR_ALL: - sIPV6Comment = ""; - break; - case ADDR_IPV4ONLY: - sIPV6Comment = " using ipv4"; - break; - case ADDR_IPV6ONLY: - sIPV6Comment = " using ipv6"; - } - - unsigned short uPort = sPort.ToUShort(); - CUtils::PrintAction("Binding to port [" + CString((bSSL) ? "+" : "") + CString(uPort) + "]" + sHostComment + sIPV6Comment); - -#ifndef HAVE_IPV6 - if (ADDR_IPV6ONLY == eAddr) { - sError = "IPV6 is not enabled"; - CUtils::PrintStatus(false, sError); - return false; - } -#endif - -#ifndef HAVE_LIBSSL - if (bSSL) { - sError = "SSL is not enabled"; - CUtils::PrintStatus(false, sError); - return false; - } -#else - CString sPemFile = GetPemLocation(); - - if (bSSL && !CFile::Exists(sPemFile)) { - sError = "Unable to locate pem file: [" + sPemFile + "]"; - CUtils::PrintStatus(false, sError); - - // If stdin is e.g. /dev/null and we call GetBoolInput(), - // we are stuck in an endless loop! - if (isatty(0) && CUtils::GetBoolInput("Would you like to create a new pem file?", true)) { - sError.clear(); - WritePemFile(); - } else { - return false; - } - - CUtils::PrintAction("Binding to port [+" + CString(uPort) + "]" + sHostComment + sIPV6Comment); - } -#endif - if (!uPort) { - sError = "Invalid port"; - CUtils::PrintStatus(false, sError); - return false; - } - - CListener* pListener = new CListener(uPort, sBindHost, bSSL, eAddr, eAccept); - - if (!pListener->Listen()) { - sError = FormatBindError(); - CUtils::PrintStatus(false, sError); - delete pListener; - return false; - } - - m_vpListeners.push_back(pListener); - CUtils::PrintStatus(true); - - continue; - } else if (sName.Equals("LoadModule")) { - CString sModName = sValue.Token(0); - CString sArgs = sValue.Token(1, true); - - if (msModules.find(sModName) != msModules.end()) { - sError = "Module [" + sModName + - "] already loaded"; - CUtils::PrintError(sError); - return false; - } - CString sModRet; - CModule *pOldMod; - - pOldMod = GetModules().FindModule(sModName); - if (!pOldMod) { - CUtils::PrintAction("Loading Global Module [" + sModName + "]"); - - bool bModRet = GetModules().LoadModule(sModName, sArgs, NULL, sModRet); - - // If the module was loaded, sModRet contains - // "Loaded Module [name] ..." and we strip away this beginning. - if (bModRet) - sModRet = sModRet.Token(1, true, sModName + "] "); - - CUtils::PrintStatus(bModRet, sModRet); - if (!bModRet) { - sError = sModRet; - return false; - } - } else if (pOldMod->GetArgs() != sArgs) { - CUtils::PrintAction("Reloading Global Module [" + sModName + "]"); - - bool bModRet = GetModules().ReloadModule(sModName, sArgs, NULL, sModRet); - - // If the module was loaded, sModRet contains - // "Loaded Module [name] ..." and we strip away this beginning. - if (bModRet) - sModRet = sModRet.Token(1, true, sModName + "] "); - - CUtils::PrintStatus(bModRet, sModRet); - if (!bModRet) { - sError = sModRet; - return false; - } - } else - CUtils::PrintMessage("Module [" + sModName + "] already loaded."); - - msModules[sModName] = sArgs; - continue; - // Convert old-style ISpoofFormat's and ISpoofFile to identfile module - } else if (sName.Equals("ISpoofFormat") || sName.Equals("ISpoofFile")) { - CModule *pIdentFileMod = GetModules().FindModule("identfile"); - if (!pIdentFileMod) { - CUtils::PrintAction("Loading Global Module [identfile]"); - - CString sModRet; - bool bModRet = GetModules().LoadModule("identfile", "", NULL, sModRet); - - if (bModRet) { - sModRet = sModRet.Token(1, true, "identfile] "); - } - - CUtils::PrintStatus(bModRet, sModRet); - if (!bModRet) { - sError = sModRet; - return false; - } - - pIdentFileMod = GetModules().FindModule("identfile"); - msModules["identfile"] = ""; - } - - if (!pIdentFileMod->SetNV(sName.TrimPrefix_n("ISpoof"), sValue)) { - sError = "Failed to convert " + sName + " to the identfile module"; - CUtils::PrintError(sError); - return false; - } - continue; - } else if (sName.Equals("MOTD")) { - AddMotd(sValue); - continue; - } else if (sName.Equals("BindHost") || sName.Equals("VHost")) { - AddBindHost(sValue); - continue; - } else if (sName.Equals("PidFile")) { - m_sPidFile = sValue; - continue; - } else if (sName.Equals("Skin")) { - SetSkinName(sValue); - continue; - } else if (sName.Equals("StatusPrefix")) { - m_sStatusPrefix = sValue; - continue; - } else if (sName.Equals("ConnectDelay")) { - m_uiConnectDelay = sValue.ToUInt(); - continue; - } else if (sName.Equals("ServerThrottle")) { - m_sConnectThrottle.SetTTL(sValue.ToUInt()*1000); - continue; - } else if (sName.Equals("AnonIPLimit")) { - m_uiAnonIPLimit = sValue.ToUInt(); - continue; - } else if (sName.Equals("MaxBufferSize")) { - m_uiMaxBufferSize = sValue.ToUInt(); - continue; - } else if (sName.Equals("SSLCertFile")) { - m_sSSLCertFile = sValue; - continue; - } - } - - } - - { - sError = "Unhandled line " + CString(uLineNum) + " in config: [" + sLine + "]"; + if (msModules.find(sModName) != msModules.end()) { + sError = "Module [" + sModName + + "] already loaded"; CUtils::PrintError(sError); return false; } + CString sModRet; + CModule *pOldMod; + + pOldMod = GetModules().FindModule(sModName); + if (!pOldMod) { + CUtils::PrintAction("Loading Global Module [" + sModName + "]"); + + bool bModRet = GetModules().LoadModule(sModName, sArgs, NULL, sModRet); + + // If the module was loaded, sModRet contains + // "Loaded Module [name] ..." and we strip away this beginning. + if (bModRet) + sModRet = sModRet.Token(1, true, sModName + "] "); + + CUtils::PrintStatus(bModRet, sModRet); + if (!bModRet) { + sError = sModRet; + return false; + } + } else if (pOldMod->GetArgs() != sArgs) { + CUtils::PrintAction("Reloading Global Module [" + sModName + "]"); + + bool bModRet = GetModules().ReloadModule(sModName, sArgs, NULL, sModRet); + + // If the module was loaded, sModRet contains + // "Loaded Module [name] ..." and we strip away this beginning. + if (bModRet) + sModRet = sModRet.Token(1, true, sModName + "] "); + + CUtils::PrintStatus(bModRet, sModRet); + if (!bModRet) { + sError = sModRet; + return false; + } + } else + CUtils::PrintMessage("Module [" + sModName + "] already loaded."); + + msModules[sModName] = sArgs; } + config.FindStringVector("motd", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + AddMotd(*vit); + } + + config.FindStringVector("bindhost", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + AddBindHost(*vit); + } + config.FindStringVector("vhost", vsList); + for (vit = vsList.begin(); vit != vsList.end(); ++vit) { + AddBindHost(*vit); + } + + CString sVal; + if (config.FindStringEntry("pidfile", sVal)) + m_sPidFile = sVal; + if (config.FindStringEntry("statusprefix", sVal)) + m_sStatusPrefix = sVal; + if (config.FindStringEntry("sslcertfile", sVal)) + m_sSSLCertFile = sVal; + if (config.FindStringEntry("skin", sVal)) + SetSkinName(sVal); + if (config.FindStringEntry("connectdelay", sVal)) + m_uiConnectDelay = sVal.ToUInt(); + if (config.FindStringEntry("serverthrottle", sVal)) + m_sConnectThrottle.SetTTL(sVal.ToUInt() * 1000); + if (config.FindStringEntry("anoniplimit", sVal)) + m_uiAnonIPLimit = sVal.ToUInt(); + if (config.FindStringEntry("maxbuffersize", sVal)) + m_uiMaxBufferSize = sVal.ToUInt(); + + CConfig::SubConfig subConf; + CConfig::SubConfig::const_iterator subIt; + config.FindSubConfig("user", subConf); + for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) { + const CString& sUserName = subIt->first; + CConfig* pSubConf = subIt->second.m_pSubConfig; + CUser* pRealUser = NULL; + + CUtils::PrintMessage("Loading user [" + sUserName + "]"); + + // Either create a CUser* or use an existing one + map::iterator it = m_msDelUsers.find(sUserName); + + if (it != m_msDelUsers.end()) { + pRealUser = it->second; + m_msDelUsers.erase(it); + } + + CUser* pUser = new CUser(sUserName); + + if (!m_sStatusPrefix.empty()) { + if (!pUser->SetStatusPrefix(m_sStatusPrefix)) { + sError = "Invalid StatusPrefix [" + m_sStatusPrefix + "] Must be 1-5 chars, no spaces."; + CUtils::PrintError(sError); + return false; + } + } + + if (!pUser->ParseConfig(pSubConf, sError)) { + CUtils::PrintError(sError); + delete pUser; + pUser = NULL; + return false; + } + + if (!pSubConf->empty()) { + sError = "Unhandled lines in config for User [" + sUserName + "]!"; + CUtils::PrintError(sError); + + DumpConfig(pSubConf); + return false; + } + + CString sErr; + if (pRealUser) { + if (!pRealUser->Clone(*pUser, sErr) + || !AddUser(pRealUser, sErr)) { + sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr; + DEBUG("CUser::Clone() failed in rehash"); + } + pUser->SetBeingDeleted(true); + delete pUser; + pUser = NULL; + } else if (!AddUser(pUser, sErr)) { + sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr; + } + + if (!sError.empty()) { + CUtils::PrintError(sError); + if (pUser) { + pUser->SetBeingDeleted(true); + delete pUser; + pUser = NULL; + } + return false; + } + + pUser = NULL; + pRealUser = NULL; + } + + if (!config.empty()) { + sError = "Unhandled lines in config!"; + CUtils::PrintError(sError); + + DumpConfig(&config); + return false; + } + + // Unload modules which are no longer in the config set ssUnload; for (size_t i = 0; i < GetModules().size(); i++) { @@ -1646,20 +1224,6 @@ bool CZNC::DoRehash(CString& sError) CUtils::PrintMessage("Could not unload [" + *it + "]"); } - if (pChan) { - sError = "Last section not properly closed. File truncated?"; - CUtils::PrintError(sError); - delete pChan; - return false; - } - - if (pUser) { - sError = "Last section not properly closed. File truncated?"; - CUtils::PrintError(sError); - delete pUser; - return false; - } - if (m_msUsers.empty()) { sError = "You must define at least one user in your config."; CUtils::PrintError(sError); @@ -1680,6 +1244,30 @@ bool CZNC::DoRehash(CString& sError) return true; } +void CZNC::DumpConfig(const CConfig* pConfig) { + CConfig::EntryMapIterator eit = pConfig->BeginEntries(); + for (; eit != pConfig->EndEntries(); ++eit) { + const CString& sKey = eit->first; + const VCString& vsList = eit->second; + VCString::const_iterator it = vsList.begin(); + for (; it != vsList.end(); ++it) { + CUtils::PrintError(sKey + " = " + *it); + } + } + + CConfig::SubConfigMapIterator sit = pConfig->BeginSubConfigs(); + for (; sit != pConfig->EndSubConfigs(); ++sit) { + const CString& sKey = sit->first; + const CConfig::SubConfig& sSub = sit->second; + CConfig::SubConfig::const_iterator it = sSub.begin(); + + for (; it != sSub.end(); ++it) { + CUtils::PrintError("SubConfig [" + sKey + " " + it->first + "]:"); + DumpConfig(it->second.m_pSubConfig); + } + } +} + void CZNC::ClearBindHosts() { m_vsBindHosts.clear(); } @@ -1800,6 +1388,120 @@ CListener* CZNC::FindListener(u_short uPort, const CString& sBindHost, EAddrType return NULL; } +bool CZNC::AddListener(const CString& sLine, CString& sError) { + CString sName = sLine.Token(0); + CString sValue = sLine.Token(1, true); + + EAddrType eAddr = ADDR_ALL; + if (sName.Equals("Listen4") || sName.Equals("Listen") || sName.Equals("Listener4")) { + eAddr = ADDR_IPV4ONLY; + } + if (sName.Equals("Listener6")) { + eAddr = ADDR_IPV6ONLY; + } + + CListener::EAcceptType eAccept = CListener::ACCEPT_ALL; + if (sValue.TrimPrefix("irc_only ")) + eAccept = CListener::ACCEPT_IRC; + else if (sValue.TrimPrefix("web_only ")) + eAccept = CListener::ACCEPT_HTTP; + + bool bSSL = false; + CString sPort; + CString sBindHost; + + if (ADDR_IPV4ONLY == eAddr) { + sValue.Replace(":", " "); + } + + if (sValue.find(" ") != CString::npos) { + sBindHost = sValue.Token(0, false, " "); + sPort = sValue.Token(1, true, " "); + } else { + sPort = sValue; + } + + if (sPort.Left(1) == "+") { + sPort.LeftChomp(); + bSSL = true; + } + + CString sHostComment; + + if (!sBindHost.empty()) { + sHostComment = " on host [" + sBindHost + "]"; + } + + CString sIPV6Comment; + + switch (eAddr) { + case ADDR_ALL: + sIPV6Comment = ""; + break; + case ADDR_IPV4ONLY: + sIPV6Comment = " using ipv4"; + break; + case ADDR_IPV6ONLY: + sIPV6Comment = " using ipv6"; + } + + unsigned short uPort = sPort.ToUShort(); + CUtils::PrintAction("Binding to port [" + CString((bSSL) ? "+" : "") + CString(uPort) + "]" + sHostComment + sIPV6Comment); + +#ifndef HAVE_IPV6 + if (ADDR_IPV6ONLY == eAddr) { + sError = "IPV6 is not enabled"; + CUtils::PrintStatus(false, sError); + return false; + } +#endif + +#ifndef HAVE_LIBSSL + if (bSSL) { + sError = "SSL is not enabled"; + CUtils::PrintStatus(false, sError); + return false; + } +#else + CString sPemFile = GetPemLocation(); + + if (bSSL && !CFile::Exists(sPemFile)) { + sError = "Unable to locate pem file: [" + sPemFile + "]"; + CUtils::PrintStatus(false, sError); + + // If stdin is e.g. /dev/null and we call GetBoolInput(), + // we are stuck in an endless loop! + if (isatty(0) && CUtils::GetBoolInput("Would you like to create a new pem file?", true)) { + sError.clear(); + WritePemFile(); + } else { + return false; + } + + CUtils::PrintAction("Binding to port [+" + CString(uPort) + "]" + sHostComment + sIPV6Comment); + } +#endif + if (!uPort) { + sError = "Invalid port"; + CUtils::PrintStatus(false, sError); + return false; + } + + CListener* pListener = new CListener(uPort, sBindHost, bSSL, eAddr, eAccept); + + if (!pListener->Listen()) { + sError = FormatBindError(); + CUtils::PrintStatus(false, sError); + delete pListener; + return false; + } + + m_vpListeners.push_back(pListener); + CUtils::PrintStatus(true); + + return true; +} + bool CZNC::AddListener(CListener* pListener) { if (!pListener->GetRealListener()) { // Listener doesnt actually listen diff --git a/znc.h b/znc.h index 17f3a2fb..1bf60f27 100644 --- a/znc.h +++ b/znc.h @@ -21,6 +21,7 @@ using std::map; class CListener; class CUser; class CConnectUserTimer; +class CConfig; class CZNC { public: @@ -142,12 +143,15 @@ public: // Never call this unless you are CConnectUserTimer::~CConnectUserTimer() void LeakConnectUser(CConnectUserTimer *pTimer); + static void DumpConfig(const CConfig* Config); + private: CFile* InitPidFile(); bool DoRehash(CString& sError); // Returns true if something was done bool HandleUserDeletion(); CString MakeConfigHeader(); + bool AddListener(const CString& sLine, CString& sError); protected: time_t m_TimeStarted;