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 <psychon@znc.in>
This commit is contained in:
Uli Schlachter
2011-03-28 15:57:46 +02:00
parent ee9686e422
commit 70c7745899
13 changed files with 1037 additions and 613 deletions

2
.gitignore vendored
View File

@@ -30,6 +30,8 @@ Makefile
/modules/modpython/*.pyc
/modules/*.pyc
/test/ConfigTest
# Compiled Object files
*.o

View File

@@ -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() {

3
Chan.h
View File

@@ -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();

179
Config.cpp Normal file
View File

@@ -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 <stack>
#include <sstream>
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<ConfigStackEntry> 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 \"</" << sTag << ">\".");
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;
}

92
Config.h Normal file
View File

@@ -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<CString, VCString> EntryMap;
typedef map<CString, CConfigEntry> SubConfig;
typedef map<CString, SubConfig> 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

View File

@@ -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))

212
User.cpp
View File

@@ -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<class T>
struct TOption {
const char *name;
void (CUser::*pSetter)(T);
};
bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
TOption<const CString&> 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<unsigned int> UIntOptions[] = {
{ "jointries", &CUser::SetJoinTries },
{ "maxjoins", &CUser::SetMaxJoins },
};
size_t numUIntOptions = sizeof(UIntOptions) / sizeof(UIntOptions[0]);
TOption<bool> 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 = <plain text>
// Pass = <md5 hash> -
// Pass = plain#<plain text>
// Pass = <hash name>#<hash>
// Pass = <hash name>#<salted hash>#<salt>#
// '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;

3
User.h
View File

@@ -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,

View File

@@ -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

164
test/ConfigTest.cpp Normal file
View File

@@ -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 <cstdlib>
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<CString, CConfig::CConfigEntry>::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("</foo>", "Error on line 1: Closing tag \"foo\" which is not open."),
TEST_ERROR("<foo a>\n</bar>\n", "Error on line 2: Closing tag \"bar\" which is not open."),
TEST_ERROR("<foo bar>", "Error on line 1: Not all tags are closed at the end of the file. Inner-most open tag is \"foo\"."),
TEST_ERROR("<foo>\n</foo>", "Error on line 1: Empty block name at begin of block."),
TEST_ERROR("<foo 1>\n</foo>\n<foo 1>\n</foo>", "Error on line 4: Duplicate entry for tag \"foo\" name \"1\"."),
TEST_SUCCESS("<foo a>\n</foo>", "->foo/a\n<-\n"),
TEST_SUCCESS("<a b>\n <c d>\n </c>\n</a>", "->a/b\n->c/d\n<-\n<-\n"),
TEST_SUCCESS(" \t <A B>\nfoo = bar\n\tFooO = bar\n</a>", "->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("<foo foo>\n/* Just a comment\n</foo>", "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;
}

46
test/Makefile.in Normal file
View File

@@ -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

922
znc.cpp
View File

@@ -12,6 +12,7 @@
#include "Server.h"
#include "User.h"
#include "Listener.h"
#include "Config.h"
#include <list>
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 <User> tags inside of other <User> tags.";
CUtils::PrintError(sError);
return false;
}
if (sValue.empty()) {
sError = "You must supply a username in the <User> 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<CString, CUser*>::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 = "<Chan> tags must be nested inside of a <User> tag.";
CUtils::PrintError(sError);
return false;
}
if (pChan) {
sError = "You may not nest <Chan> tags inside of other <Chan> 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 = <plain text>
// Pass = <md5 hash> -
// Pass = plain#<plain text>
// Pass = <hash name>#<hash>
// Pass = <hash name>#<salted hash>#<salt>#
// '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<CString, CUser*>::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<CString> 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 <Chan> section not properly closed. File truncated?";
CUtils::PrintError(sError);
delete pChan;
return false;
}
if (pUser) {
sError = "Last <User> 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

4
znc.h
View File

@@ -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;