Files
znc/src/znc.cpp
Alexey Sokolov 4596d97502 Remove "-" between "ZNC" and version number.
ZNC 1.6.0 looks better than ZNC - 1.6.0, IMHO
2015-04-06 20:41:57 +01:00

1976 lines
52 KiB
C++

/*
* Copyright (C) 2004-2015 ZNC, see the NOTICE file for details.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <znc/FileUtils.h>
#include <znc/IRCSock.h>
#include <znc/Server.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <znc/Config.h>
#include <tuple>
using std::endl;
using std::cout;
using std::map;
using std::set;
using std::vector;
using std::list;
using std::tuple;
using std::make_tuple;
static inline CString FormatBindError() {
CString sError = (errno == 0 ? CString("unknown error, check the host name") : CString(strerror(errno)));
return "Unable to bind [" + sError + "]";
}
CZNC::CZNC() {
if (!InitCsocket()) {
CUtils::PrintError("Could not initialize Csocket!");
exit(-1);
}
m_pModules = new CModules();
m_uiConnectDelay = 5;
m_uiAnonIPLimit = 10;
m_uBytesRead = 0;
m_uBytesWritten = 0;
m_uiMaxBufferSize = 500;
m_pConnectQueueTimer = NULL;
m_uiConnectPaused = 0;
m_eConfigState = ECONFIG_NOTHING;
m_TimeStarted = time(NULL);
m_sConnectThrottle.SetTTL(30000);
m_pLockFile = NULL;
m_bProtectWebSessions = true;
m_bHideVersion = false;
m_uDisabledSSLProtocols = Csock::EDP_SSL;
m_sSSLProtocols = "";
}
CZNC::~CZNC() {
m_pModules->UnloadAll();
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
a->second->GetModules().UnloadAll();
const vector<CIRCNetwork*>& networks = a->second->GetNetworks();
for (vector<CIRCNetwork*>::const_iterator b = networks.begin(); b != networks.end(); ++b) {
(*b)->GetModules().UnloadAll();
}
}
for (size_t b = 0; b < m_vpListeners.size(); b++) {
delete m_vpListeners[b];
}
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
a->second->SetBeingDeleted(true);
}
m_pConnectQueueTimer = NULL;
// This deletes m_pConnectQueueTimer
m_Manager.Cleanup();
DeleteUsers();
delete m_pModules;
delete m_pLockFile;
ShutdownCsocket();
DeletePidFile();
}
CString CZNC::GetVersion() {
return CString(VERSION_STR) + CString(ZNC_VERSION_EXTRA);
}
CString CZNC::GetTag(bool bIncludeVersion, bool bHTML) {
if (!Get().m_bHideVersion) {
bIncludeVersion = true;
}
CString sAddress = bHTML ? "<a href=\"http://znc.in\">http://znc.in</a>" : "http://znc.in";
if (!bIncludeVersion) {
return "ZNC - " + sAddress;
}
CString sVersion = GetVersion();
return "ZNC " + sVersion + " - " + sAddress;
}
CString CZNC::GetCompileOptionsString() {
return
"IPv6: "
#ifdef HAVE_IPV6
"yes"
#else
"no"
#endif
", SSL: "
#ifdef HAVE_LIBSSL
"yes"
#else
"no"
#endif
", DNS: "
#ifdef HAVE_THREADED_DNS
"threads"
#else
"blocking"
#endif
", charset: "
#ifdef HAVE_ICU
"yes"
#else
"no"
#endif
;
}
CString CZNC::GetUptime() const {
time_t now = time(NULL);
return CString::ToTimeStr(now - TimeStarted());
}
bool CZNC::OnBoot() {
bool bFail = false;
ALLMODULECALL(OnBoot(), &bFail);
if (bFail) return false;
return true;
}
bool CZNC::HandleUserDeletion()
{
map<CString, CUser*>::iterator it;
map<CString, CUser*>::iterator end;
if (m_msDelUsers.empty())
return false;
end = m_msDelUsers.end();
for (it = m_msDelUsers.begin(); it != end; ++it) {
CUser* pUser = it->second;
pUser->SetBeingDeleted(true);
if (GetModules().OnDeleteUser(*pUser)) {
pUser->SetBeingDeleted(false);
continue;
}
m_msUsers.erase(pUser->GetUserName());
CWebSock::FinishUserSessions(*pUser);
delete pUser;
}
m_msDelUsers.clear();
return true;
}
void CZNC::Loop() {
while (true) {
CString sError;
ConfigState eState = GetConfigState();
switch (eState) {
case ECONFIG_NEED_REHASH:
SetConfigState(ECONFIG_NOTHING);
if (RehashConfig(sError)) {
Broadcast("Rehashing succeeded", true);
} else {
Broadcast("Rehashing failed: " + sError, true);
Broadcast("ZNC is in some possibly inconsistent state!", true);
}
break;
case ECONFIG_NEED_WRITE:
case ECONFIG_NEED_VERBOSE_WRITE:
SetConfigState(ECONFIG_NOTHING);
if (!WriteConfig()) {
Broadcast("Writing the config file failed", true);
} else if (eState == ECONFIG_NEED_VERBOSE_WRITE) {
Broadcast("Writing the config succeeded", true);
}
break;
case ECONFIG_NOTHING:
break;
}
// Check for users that need to be deleted
if (HandleUserDeletion()) {
// Also remove those user(s) from the config file
WriteConfig();
}
// Csocket wants micro seconds
// 100 msec to 600 sec
m_Manager.DynamicSelectLoop(100 * 1000, 600 * 1000 * 1000);
}
}
CFile* CZNC::InitPidFile() {
if (!m_sPidFile.empty()) {
CString sFile;
// absolute path or relative to the data dir?
if (m_sPidFile[0] != '/')
sFile = GetZNCPath() + "/" + m_sPidFile;
else
sFile = m_sPidFile;
return new CFile(sFile);
}
return NULL;
}
bool CZNC::WritePidFile(int iPid) {
CFile* File = InitPidFile();
if (File == NULL)
return false;
CUtils::PrintAction("Writing pid file [" + File->GetLongName() + "]");
bool bRet = false;
if (File->Open(O_WRONLY | O_TRUNC | O_CREAT)) {
File->Write(CString(iPid) + "\n");
File->Close();
bRet = true;
}
delete File;
CUtils::PrintStatus(bRet);
return bRet;
}
bool CZNC::DeletePidFile() {
CFile* File = InitPidFile();
if (File == NULL)
return false;
CUtils::PrintAction("Deleting pid file [" + File->GetLongName() + "]");
bool bRet = File->Delete();
delete File;
CUtils::PrintStatus(bRet);
return bRet;
}
bool CZNC::WritePemFile() {
#ifndef HAVE_LIBSSL
CUtils::PrintError("ZNC was not compiled with ssl support.");
return false;
#else
CString sPemFile = GetPemLocation();
CUtils::PrintAction("Writing Pem file [" + sPemFile + "]");
#ifndef _WIN32
int fd = creat(sPemFile.c_str(), 0600);
if (fd == -1) {
CUtils::PrintStatus(false, "Unable to open");
return false;
}
FILE *f = fdopen(fd, "w");
#else
FILE *f = fopen(sPemFile.c_str(), "w");
#endif
if (!f) {
CUtils::PrintStatus(false, "Unable to open");
return false;
}
CUtils::GenerateCert(f, "");
fclose(f);
CUtils::PrintStatus(true);
return true;
#endif
}
void CZNC::DeleteUsers() {
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
a->second->SetBeingDeleted(true);
delete a->second;
}
m_msUsers.clear();
DisableConnectQueue();
}
bool CZNC::IsHostAllowed(const CString& sHostMask) const {
for (map<CString,CUser*>::const_iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
if (a->second->IsHostAllowed(sHostMask)) {
return true;
}
}
return false;
}
bool CZNC::AllowConnectionFrom(const CString& sIP) const {
if (m_uiAnonIPLimit == 0)
return true;
return (GetManager().GetAnonConnectionCount(sIP) < m_uiAnonIPLimit);
}
void CZNC::InitDirs(const CString& sArgvPath, const CString& sDataDir) {
// If the bin was not ran from the current directory, we need to add that dir onto our cwd
CString::size_type uPos = sArgvPath.rfind('/');
if (uPos == CString::npos)
m_sCurPath = "./";
else
m_sCurPath = CDir::ChangeDir("./", sArgvPath.Left(uPos), "");
// Try to set the user's home dir, default to binpath on failure
CFile::InitHomePath(m_sCurPath);
if (sDataDir.empty()) {
m_sZNCPath = CFile::GetHomePath() + "/.znc";
} else {
m_sZNCPath = sDataDir;
}
m_sSSLCertFile = m_sZNCPath + "/znc.pem";
}
CString CZNC::GetConfPath(bool bAllowMkDir) const {
CString sConfPath = m_sZNCPath + "/configs";
if (bAllowMkDir && !CFile::Exists(sConfPath)) {
CDir::MakeDir(sConfPath);
}
return sConfPath;
}
CString CZNC::GetUserPath() const {
CString sUserPath = m_sZNCPath + "/users";
if (!CFile::Exists(sUserPath)) {
CDir::MakeDir(sUserPath);
}
return sUserPath;
}
CString CZNC::GetModPath() const {
CString sModPath = m_sZNCPath + "/modules";
return sModPath;
}
const CString& CZNC::GetCurPath() const {
if (!CFile::Exists(m_sCurPath)) {
CDir::MakeDir(m_sCurPath);
}
return m_sCurPath;
}
const CString& CZNC::GetHomePath() const {
return CFile::GetHomePath();
}
const CString& CZNC::GetZNCPath() const {
if (!CFile::Exists(m_sZNCPath)) {
CDir::MakeDir(m_sZNCPath);
}
return m_sZNCPath;
}
CString CZNC::GetPemLocation() const {
return CDir::ChangeDir("", m_sSSLCertFile);
}
CString CZNC::ExpandConfigPath(const CString& sConfigFile, bool bAllowMkDir) {
CString sRetPath;
if (sConfigFile.empty()) {
sRetPath = GetConfPath(bAllowMkDir) + "/znc.conf";
} else {
if (sConfigFile.Left(2) == "./" || sConfigFile.Left(3) == "../") {
sRetPath = GetCurPath() + "/" + sConfigFile;
} else if (sConfigFile.Left(1) != "/") {
sRetPath = GetConfPath(bAllowMkDir) + "/" + sConfigFile;
} else {
sRetPath = sConfigFile;
}
}
return sRetPath;
}
bool CZNC::WriteConfig() {
if (GetConfigFile().empty()) {
DEBUG("Config file name is empty?!");
return false;
}
// We first write to a temporary file and then move it to the right place
CFile *pFile = new CFile(GetConfigFile() + "~");
if (!pFile->Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) {
DEBUG("Could not write config to " + GetConfigFile() + "~: " + CString(strerror(errno)));
delete pFile;
return false;
}
// We have to "transfer" our lock on the config to the new file.
// The old file (= inode) is going away and thus a lock on it would be
// useless. These lock should always succeed (races, anyone?).
if (!pFile->TryExLock()) {
DEBUG("Error while locking the new config file, errno says: " + CString(strerror(errno)));
pFile->Delete();
delete pFile;
return false;
}
pFile->Write(MakeConfigHeader() + "\n");
CConfig config;
config.AddKeyValuePair("AnonIPLimit", CString(m_uiAnonIPLimit));
config.AddKeyValuePair("MaxBufferSize", CString(m_uiMaxBufferSize));
config.AddKeyValuePair("SSLCertFile", CString(m_sSSLCertFile));
config.AddKeyValuePair("ProtectWebSessions", CString(m_bProtectWebSessions));
config.AddKeyValuePair("HideVersion", CString(m_bHideVersion));
config.AddKeyValuePair("Version", CString(VERSION_STR));
for (size_t l = 0; l < m_vpListeners.size(); l++) {
CListener* pListener = m_vpListeners[l];
CConfig listenerConfig;
listenerConfig.AddKeyValuePair("Host", pListener->GetBindHost());
listenerConfig.AddKeyValuePair("URIPrefix", pListener->GetURIPrefix() + "/");
listenerConfig.AddKeyValuePair("Port", CString(pListener->GetPort()));
listenerConfig.AddKeyValuePair("IPv4", CString(pListener->GetAddrType() != ADDR_IPV6ONLY));
listenerConfig.AddKeyValuePair("IPv6", CString(pListener->GetAddrType() != ADDR_IPV4ONLY));
listenerConfig.AddKeyValuePair("SSL", CString(pListener->IsSSL()));
listenerConfig.AddKeyValuePair("AllowIRC", CString(pListener->GetAcceptType() != CListener::ACCEPT_HTTP));
listenerConfig.AddKeyValuePair("AllowWeb", CString(pListener->GetAcceptType() != CListener::ACCEPT_IRC));
config.AddSubConfig("Listener", "listener" + CString(l), listenerConfig);
}
config.AddKeyValuePair("ConnectDelay", CString(m_uiConnectDelay));
config.AddKeyValuePair("ServerThrottle", CString(m_sConnectThrottle.GetTTL()/1000));
if (!m_sPidFile.empty()) {
config.AddKeyValuePair("PidFile", m_sPidFile.FirstLine());
}
if (!m_sSkinName.empty()) {
config.AddKeyValuePair("Skin", m_sSkinName.FirstLine());
}
if (!m_sStatusPrefix.empty()) {
config.AddKeyValuePair("StatusPrefix", m_sStatusPrefix.FirstLine());
}
if (!m_sSSLCiphers.empty()) {
config.AddKeyValuePair("SSLCiphers", CString(m_sSSLCiphers));
}
if (!m_sSSLProtocols.empty()) {
config.AddKeyValuePair("SSLProtocols", m_sSSLProtocols);
}
for (unsigned int m = 0; m < m_vsMotd.size(); m++) {
config.AddKeyValuePair("Motd", m_vsMotd[m].FirstLine());
}
for (unsigned int v = 0; v < m_vsBindHosts.size(); v++) {
config.AddKeyValuePair("BindHost", m_vsBindHosts[v].FirstLine());
}
for (unsigned int v = 0; v < m_vsTrustedProxies.size(); v++) {
config.AddKeyValuePair("TrustedProxy", m_vsTrustedProxies[v].FirstLine());
}
CModules& Mods = GetModules();
for (unsigned int a = 0; a < Mods.size(); a++) {
CString sName = Mods[a]->GetModName();
CString sArgs = Mods[a]->GetArgs();
if (!sArgs.empty()) {
sArgs = " " + sArgs.FirstLine();
}
config.AddKeyValuePair("LoadModule", sName.FirstLine() + sArgs);
}
for (map<CString,CUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); ++it) {
CString sErr;
if (!it->second->IsValid(sErr)) {
DEBUG("** Error writing config for user [" << it->first << "] [" << sErr << "]");
continue;
}
config.AddSubConfig("User", it->second->GetUserName(), it->second->ToConfig());
}
config.Write(*pFile);
// If Sync() fails... well, let's hope nothing important breaks..
pFile->Sync();
if (pFile->HadError()) {
DEBUG("Error while writing the config, errno says: " + CString(strerror(errno)));
pFile->Delete();
delete pFile;
return false;
}
// We wrote to a temporary name, move it to the right place
if (!pFile->Move(GetConfigFile(), true)) {
DEBUG("Error while replacing the config file with a new version, errno says " << strerror(errno));
pFile->Delete();
delete pFile;
return false;
}
// Everything went fine, just need to update the saved path.
pFile->SetFileName(GetConfigFile());
// Make sure the lock is kept alive as long as we need it.
delete m_pLockFile;
m_pLockFile = pFile;
return true;
}
CString CZNC::MakeConfigHeader() {
return
"// WARNING\n"
"//\n"
"// Do NOT edit this file while ZNC is running!\n"
"// Use webadmin or *controlpanel instead.\n"
"//\n"
"// Altering this file by hand will forfeit all support.\n"
"//\n"
"// But if you feel risky, you might want to read help on /znc saveconfig and /znc rehash.\n"
"// Also check http://en.znc.in/wiki/Configuration\n";
}
bool CZNC::WriteNewConfig(const CString& sConfigFile) {
CString sAnswer, sUser, sNetwork;
VCString vsLines;
vsLines.push_back(MakeConfigHeader());
vsLines.push_back("Version = " + CString(VERSION_STR));
m_sConfigFile = ExpandConfigPath(sConfigFile);
if (CFile::Exists(m_sConfigFile)) {
CUtils::PrintStatus(false, "WARNING: config [" + m_sConfigFile + "] already exists.");
}
CUtils::PrintMessage("");
CUtils::PrintMessage("-- Global settings --");
CUtils::PrintMessage("");
// Listen
#ifdef HAVE_IPV6
bool b6 = true;
#else
bool b6 = false;
#endif
CString sListenHost;
CString sURIPrefix;
bool bListenSSL = false;
unsigned int uListenPort = 0;
bool bSuccess;
do {
bSuccess = true;
while (true) {
if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025, 65534)) {
continue;
}
if (uListenPort == 6667) {
CUtils::PrintStatus(false, "WARNING: Some web browsers reject port 6667. If you intend to");
CUtils::PrintStatus(false, "use ZNC's web interface, you might want to use another port.");
if (!CUtils::GetBoolInput("Proceed with port 6667 anyway?", true)) {
continue;
}
}
break;
}
#ifdef HAVE_LIBSSL
bListenSSL = CUtils::GetBoolInput("Listen using SSL", bListenSSL);
#endif
#ifdef HAVE_IPV6
b6 = CUtils::GetBoolInput("Listen using both IPv4 and IPv6", b6);
#endif
// Don't ask for listen host, it may be configured later if needed.
CUtils::PrintAction("Verifying the listener");
CListener* pListener = new CListener((unsigned short int)uListenPort, sListenHost, sURIPrefix, bListenSSL,
b6 ? ADDR_ALL : ADDR_IPV4ONLY, CListener::ACCEPT_ALL);
if (!pListener->Listen()) {
CUtils::PrintStatus(false, FormatBindError());
bSuccess = false;
} else
CUtils::PrintStatus(true);
delete pListener;
} while (!bSuccess);
#ifdef HAVE_LIBSSL
CString sPemFile = GetPemLocation();
if (!CFile::Exists(sPemFile)) {
CUtils::PrintMessage("Unable to locate pem file: [" + sPemFile + "], creating it");
WritePemFile();
}
#endif
vsLines.push_back("<Listener l>");
vsLines.push_back("\tPort = " + CString(uListenPort));
vsLines.push_back("\tIPv4 = true");
vsLines.push_back("\tIPv6 = " + CString(b6));
vsLines.push_back("\tSSL = " + CString(bListenSSL));
if (!sListenHost.empty()) {
vsLines.push_back("\tHost = " + sListenHost);
}
vsLines.push_back("</Listener>");
// !Listen
set<CModInfo> ssGlobalMods;
GetModules().GetDefaultMods(ssGlobalMods, CModInfo::GlobalModule);
vector<CString> vsGlobalModNames;
for (set<CModInfo>::const_iterator it = ssGlobalMods.begin(); it != ssGlobalMods.end(); ++it) {
vsGlobalModNames.push_back(it->GetName());
vsLines.push_back("LoadModule = " + it->GetName());
}
CUtils::PrintMessage("Enabled global modules [" + CString(", ").Join(vsGlobalModNames.begin(), vsGlobalModNames.end()) + "]");
// User
CUtils::PrintMessage("");
CUtils::PrintMessage("-- Admin user settings --");
CUtils::PrintMessage("");
vsLines.push_back("");
CString sNick;
do {
CUtils::GetInput("Username", sUser, "", "alphanumeric");
} while (!CUser::IsValidUserName(sUser));
vsLines.push_back("<User " + sUser + ">");
CString sSalt;
sAnswer = CUtils::GetSaltedHashPass(sSalt);
vsLines.push_back("\tPass = " + CUtils::sDefaultHash + "#" + sAnswer + "#" + sSalt + "#");
vsLines.push_back("\tAdmin = true");
CUtils::GetInput("Nick", sNick, CUser::MakeCleanUserName(sUser));
vsLines.push_back("\tNick = " + sNick);
CUtils::GetInput("Alternate nick", sAnswer, sNick + "_");
if (!sAnswer.empty()) {
vsLines.push_back("\tAltNick = " + sAnswer);
}
CUtils::GetInput("Ident", sAnswer, sUser);
vsLines.push_back("\tIdent = " + sAnswer);
CUtils::GetInput("Real name", sAnswer, "Got ZNC?");
vsLines.push_back("\tRealName = " + sAnswer);
CUtils::GetInput("Bind host", sAnswer, "", "optional");
if (!sAnswer.empty()) {
vsLines.push_back("\tBindHost = " + sAnswer);
}
set<CModInfo> ssUserMods;
GetModules().GetDefaultMods(ssUserMods, CModInfo::UserModule);
vector<CString> vsUserModNames;
for (set<CModInfo>::const_iterator it = ssUserMods.begin(); it != ssUserMods.end(); ++it) {
vsUserModNames.push_back(it->GetName());
vsLines.push_back("\tLoadModule = " + it->GetName());
}
CUtils::PrintMessage("Enabled user modules [" + CString(", ").Join(vsUserModNames.begin(), vsUserModNames.end()) + "]");
CUtils::PrintMessage("");
if (CUtils::GetBoolInput("Set up a network?", true)) {
vsLines.push_back("");
CUtils::PrintMessage("");
CUtils::PrintMessage("-- Network settings --");
CUtils::PrintMessage("");
do {
CUtils::GetInput("Name", sNetwork, "freenode");
} while (!CIRCNetwork::IsValidNetwork(sNetwork));
vsLines.push_back("\t<Network " + sNetwork + ">");
set<CModInfo> ssNetworkMods;
GetModules().GetDefaultMods(ssNetworkMods, CModInfo::NetworkModule);
vector<CString> vsNetworkModNames;
for (set<CModInfo>::const_iterator it = ssNetworkMods.begin(); it != ssNetworkMods.end(); ++it) {
vsNetworkModNames.push_back(it->GetName());
vsLines.push_back("\t\tLoadModule = " + it->GetName());
}
CString sHost, sPass, sHint;
bool bSSL = false;
unsigned int uServerPort = 0;
if (sNetwork.Equals("freenode")) {
sHost = "chat.freenode.net";
#ifdef HAVE_LIBSSL
bSSL = true;
#endif
} else {
sHint = "host only";
}
while (!CUtils::GetInput("Server host", sHost, sHost, sHint) || !CServer::IsValidHostName(sHost));
#ifdef HAVE_LIBSSL
bSSL = CUtils::GetBoolInput("Server uses SSL?", bSSL);
#endif
while (!CUtils::GetNumInput("Server port", uServerPort, 1, 65535, bSSL ? 6697 : 6667));
CUtils::GetInput("Server password (probably empty)", sPass);
vsLines.push_back("\t\tServer = " + sHost + ((bSSL) ? " +" : " ") + CString(uServerPort) + " " + sPass);
CString sChans;
if (CUtils::GetInput("Initial channels", sChans)) {
vsLines.push_back("");
VCString vsChans;
sChans.Replace(",", " ");
sChans.Replace(";", " ");
sChans.Split(" ", vsChans, false, "", "", true, true);
for (const CString& sChan : vsChans) {
vsLines.push_back("\t\t<Chan " + sChan + ">");
vsLines.push_back("\t\t</Chan>");
}
}
CUtils::PrintMessage("Enabled network modules [" + CString(", ").Join(vsNetworkModNames.begin(), vsNetworkModNames.end()) + "]");
vsLines.push_back("\t</Network>");
}
vsLines.push_back("</User>");
CUtils::PrintMessage("");
// !User
CFile File;
bool bFileOK, bFileOpen = false;
do {
CUtils::PrintAction("Writing config [" + m_sConfigFile + "]");
bFileOK = true;
if (CFile::Exists(m_sConfigFile)) {
if (!File.TryExLock(m_sConfigFile)) {
CUtils::PrintStatus(false, "ZNC is currently running on this config.");
bFileOK = false;
} else {
File.Close();
CUtils::PrintStatus(false, "This config already exists.");
if (CUtils::GetBoolInput("Are you sure you want to overwrite it?", false))
CUtils::PrintAction("Overwriting config [" + m_sConfigFile + "]");
else
bFileOK = false;
}
}
if (bFileOK) {
File.SetFileName(m_sConfigFile);
if (File.Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) {
bFileOpen = true;
} else {
CUtils::PrintStatus(false, "Unable to open file");
bFileOK = false;
}
}
if (!bFileOK) {
while (!CUtils::GetInput("Please specify an alternate location", m_sConfigFile, "", "or \"stdout\" for displaying the config"));
if (m_sConfigFile.Equals("stdout"))
bFileOK = true;
else
m_sConfigFile = ExpandConfigPath(m_sConfigFile);
}
} while (!bFileOK);
if (!bFileOpen) {
CUtils::PrintMessage("");
CUtils::PrintMessage("Printing the new config to stdout:");
CUtils::PrintMessage("");
cout << endl << "----------------------------------------------------------------------------" << endl << endl;
}
for (unsigned int a = 0; a < vsLines.size(); a++) {
if (bFileOpen) {
File.Write(vsLines[a] + "\n");
} else {
cout << vsLines[a] << endl;
}
}
if (bFileOpen) {
File.Close();
if (File.HadError())
CUtils::PrintStatus(false, "There was an error while writing the config");
else
CUtils::PrintStatus(true);
} else {
cout << endl << "----------------------------------------------------------------------------" << endl << endl;
}
if (File.HadError()) {
bFileOpen = false;
CUtils::PrintMessage("Printing the new config to stdout instead:");
cout << endl << "----------------------------------------------------------------------------" << endl << endl;
for (unsigned int a = 0; a < vsLines.size(); a++) {
cout << vsLines[a] << endl;
}
cout << endl << "----------------------------------------------------------------------------" << endl << endl;
}
const CString sProtocol(bListenSSL ? "https" : "http");
const CString sSSL(bListenSSL ? "+" : "");
CUtils::PrintMessage("");
CUtils::PrintMessage("To connect to this ZNC you need to connect to it as your IRC server", true);
CUtils::PrintMessage("using the port that you supplied. You have to supply your login info", true);
CUtils::PrintMessage("as the IRC server password like this: user/network:pass.", true);
CUtils::PrintMessage("");
CUtils::PrintMessage("Try something like this in your IRC client...", true);
CUtils::PrintMessage("/server <znc_server_ip> " + sSSL + CString(uListenPort) + " " + sUser + ":<pass>", true);
CUtils::PrintMessage("");
CUtils::PrintMessage("To manage settings, users and networks, point your web browser to", true);
CUtils::PrintMessage(sProtocol + "://<znc_server_ip>:" + CString(uListenPort) + "/", true);
CUtils::PrintMessage("");
File.UnLock();
return bFileOpen && CUtils::GetBoolInput("Launch ZNC now?", true);
}
void CZNC::BackupConfigOnce(const CString& sSuffix) {
static bool didBackup = false;
if (didBackup)
return;
didBackup = true;
CUtils::PrintAction("Creating a config backup");
CString sBackup = CDir::ChangeDir(m_sConfigFile, "../znc.conf." + sSuffix);
if (CFile::Copy(m_sConfigFile, sBackup))
CUtils::PrintStatus(true, sBackup);
else
CUtils::PrintStatus(false, strerror(errno));
}
bool CZNC::ParseConfig(const CString& sConfig, CString& sError)
{
m_sConfigFile = ExpandConfigPath(sConfig, false);
return DoRehash(sError);
}
bool CZNC::RehashConfig(CString& sError)
{
ALLMODULECALL(OnPreRehash(), NOTHING);
// This clears m_msDelUsers
HandleUserDeletion();
// Mark all users as going-to-be deleted
m_msDelUsers = m_msUsers;
m_msUsers.clear();
if (DoRehash(sError)) {
ALLMODULECALL(OnPostRehash(), NOTHING);
return true;
}
// Rehashing failed, try to recover
CString s;
while (!m_msDelUsers.empty()) {
AddUser(m_msDelUsers.begin()->second, s);
m_msDelUsers.erase(m_msDelUsers.begin());
}
return false;
}
bool CZNC::DoRehash(CString& sError)
{
sError.clear();
CUtils::PrintAction("Opening config [" + m_sConfigFile + "]");
if (!CFile::Exists(m_sConfigFile)) {
sError = "No such file";
CUtils::PrintStatus(false, sError);
CUtils::PrintMessage("Restart ZNC with the --makeconf option if you wish to create this config.");
return false;
}
if (!CFile::IsReg(m_sConfigFile)) {
sError = "Not a file";
CUtils::PrintStatus(false, sError);
return false;
}
CFile *pFile = new CFile(m_sConfigFile);
// need to open the config file Read/Write for fcntl()
// exclusive locking to work properly!
if (!pFile->Open(m_sConfigFile, O_RDWR)) {
sError = "Can not open config file";
CUtils::PrintStatus(false, sError);
delete pFile;
return false;
}
if (!pFile->TryExLock()) {
sError = "ZNC is already running on this config.";
CUtils::PrintStatus(false, sError);
delete pFile;
return false;
}
// (re)open the config file
delete m_pLockFile;
m_pLockFile = pFile;
CFile &File = *pFile;
CConfig config;
if (!config.Parse(File, sError)) {
CUtils::PrintStatus(false, sError);
return false;
}
CUtils::PrintStatus(true);
CString sSavedVersion;
config.FindStringEntry("version", sSavedVersion);
tuple<unsigned int, unsigned int> tSavedVersion = make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(),
sSavedVersion.Token(1, false, ".").ToUInt());
tuple<unsigned int, unsigned int> tCurrentVersion = make_tuple(VERSION_MAJOR, VERSION_MINOR);
if (tSavedVersion < tCurrentVersion) {
if (sSavedVersion.empty()) {
sSavedVersion = "< 0.203";
}
CUtils::PrintMessage("Found old config from ZNC " + sSavedVersion + ". Saving a backup of it.");
BackupConfigOnce("pre-" + CString(VERSION_STR));
} else if (tSavedVersion > tCurrentVersion) {
CUtils::PrintError("Config was saved from ZNC " + sSavedVersion + ". It may or may not work with current ZNC " + GetVersion());
}
m_vsBindHosts.clear();
m_vsTrustedProxies.clear();
m_vsMotd.clear();
// Delete all listeners
while (!m_vpListeners.empty()) {
delete m_vpListeners[0];
m_vpListeners.erase(m_vpListeners.begin());
}
MCString msModules; // Modules are queued for later loading
VCString vsList;
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);
if (sModName == "saslauth" && tSavedVersion < make_tuple(0, 207)) {
// XXX compatibility crap, added in 0.207
CUtils::PrintMessage("saslauth module was renamed to cyrusauth. Loading cyrusauth instead.");
sModName = "cyrusauth";
}
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, CModInfo::GlobalModule, NULL, NULL, sModRet);
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, NULL, sModRet);
CUtils::PrintStatus(bModRet, sModRet);
if (!bModRet) {
sError = sModRet;
return false;
}
} else
CUtils::PrintMessage("Module [" + sModName + "] already loaded.");
msModules[sModName] = sArgs;
}
CString sISpoofFormat, sISpoofFile;
config.FindStringEntry("ispoofformat", sISpoofFormat);
config.FindStringEntry("ispooffile", sISpoofFile);
if (!sISpoofFormat.empty() || !sISpoofFile.empty()) {
CModule *pIdentFileMod = GetModules().FindModule("identfile");
if (!pIdentFileMod) {
CUtils::PrintAction("Loading global Module [identfile]");
CString sModRet;
bool bModRet = GetModules().LoadModule("identfile", "", CModInfo::GlobalModule, NULL, NULL, sModRet);
CUtils::PrintStatus(bModRet, sModRet);
if (!bModRet) {
sError = sModRet;
return false;
}
pIdentFileMod = GetModules().FindModule("identfile");
msModules["identfile"] = "";
}
pIdentFileMod->SetNV("File", sISpoofFile);
pIdentFileMod->SetNV("Format", sISpoofFormat);
}
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("trustedproxy", vsList);
for (vit = vsList.begin(); vit != vsList.end(); ++vit) {
AddTrustedProxy(*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("sslciphers", sVal))
m_sSSLCiphers = sVal;
if (config.FindStringEntry("skin", sVal))
SetSkinName(sVal);
if (config.FindStringEntry("connectdelay", sVal))
SetConnectDelay(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();
if (config.FindStringEntry("protectwebsessions", sVal))
m_bProtectWebSessions = sVal.ToBool();
if (config.FindStringEntry("hideversion", sVal))
m_bHideVersion = sVal.ToBool();
if (config.FindStringEntry("sslprotocols", m_sSSLProtocols)) {
VCString vsProtocols;
m_sSSLProtocols.Split(" ", vsProtocols, false, "", "", true, true);
for (CString& sProtocol : vsProtocols) {
unsigned int uFlag = 0;
bool bEnable = sProtocol.TrimPrefix("+");
bool bDisable = sProtocol.TrimPrefix("-");
if (sProtocol.Equals("All")) {
uFlag = ~0;
} else if (sProtocol.Equals("SSLv2")) {
uFlag = Csock::EDP_SSLv2;
} else if (sProtocol.Equals("SSLv3")) {
uFlag = Csock::EDP_SSLv3;
} else if (sProtocol.Equals("TLSv1")) {
uFlag = Csock::EDP_TLSv1;
} else if (sProtocol.Equals("TLSv1.1")) {
uFlag = Csock::EDP_TLSv1_1;
} else if (sProtocol.Equals("TLSv1.2")) {
uFlag = Csock::EDP_TLSv1_2;
} else {
CUtils::PrintError("Invalid SSLProtocols value [" + sProtocol + "]");
CUtils::PrintError("The syntax is [SSLProtocols = [+|-]<protocol> ...]");
CUtils::PrintError("Available protocols are [SSLv2, SSLv3, TLSv1, TLSv1.1, TLSv1.2]");
return false;
}
if (bEnable) {
m_uDisabledSSLProtocols &= ~uFlag;
} else if (bDisable) {
m_uDisabledSSLProtocols |= uFlag;
} else {
m_uDisabledSSLProtocols = ~uFlag;
}
}
}
// This has to be after SSLCertFile is handled since it uses that value
const char *szListenerEntries[] = {
"listen", "listen6", "listen4",
"listener", "listener6", "listener4"
};
const size_t numListenerEntries = sizeof(szListenerEntries) / sizeof(szListenerEntries[0]);
for (size_t i = 0; i < numListenerEntries; i++) {
config.FindStringVector(szListenerEntries[i], vsList);
vit = vsList.begin();
for (; vit != vsList.end(); ++vit) {
if (!AddListener(szListenerEntries[i] + CString(" ") + *vit, sError))
return false;
}
}
CConfig::SubConfig subConf;
CConfig::SubConfig::const_iterator subIt;
config.FindSubConfig("listener", subConf);
for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) {
CConfig* pSubConf = subIt->second.m_pSubConfig;
if (!AddListener(pSubConf, sError))
return false;
if (!pSubConf->empty()) {
sError = "Unhandled lines in Listener config!";
CUtils::PrintError(sError);
CZNC::DumpConfig(pSubConf);
return false;
}
}
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++) {
CModule *pCurMod = GetModules()[i];
if (msModules.find(pCurMod->GetModName()) == msModules.end())
ssUnload.insert(pCurMod->GetModName());
}
for (set<CString>::iterator it = ssUnload.begin(); it != ssUnload.end(); ++it) {
if (GetModules().UnloadModule(*it))
CUtils::PrintMessage("Unloaded global module [" + *it + "]");
else
CUtils::PrintMessage("Could not unload [" + *it + "]");
}
if (m_msUsers.empty()) {
sError = "You must define at least one user in your config.";
CUtils::PrintError(sError);
return false;
}
if (m_vpListeners.empty()) {
sError = "You must supply at least one Listen port in your config.";
CUtils::PrintError(sError);
return false;
}
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();
}
bool CZNC::AddBindHost(const CString& sHost) {
if (sHost.empty()) {
return false;
}
for (unsigned int a = 0; a < m_vsBindHosts.size(); a++) {
if (m_vsBindHosts[a].Equals(sHost)) {
return false;
}
}
m_vsBindHosts.push_back(sHost);
return true;
}
bool CZNC::RemBindHost(const CString& sHost) {
VCString::iterator it;
for (it = m_vsBindHosts.begin(); it != m_vsBindHosts.end(); ++it) {
if (sHost.Equals(*it)) {
m_vsBindHosts.erase(it);
return true;
}
}
return false;
}
void CZNC::ClearTrustedProxies() {
m_vsTrustedProxies.clear();
}
bool CZNC::AddTrustedProxy(const CString& sHost) {
if (sHost.empty()) {
return false;
}
for (unsigned int a = 0; a < m_vsTrustedProxies.size(); a++) {
if (m_vsTrustedProxies[a].Equals(sHost)) {
return false;
}
}
m_vsTrustedProxies.push_back(sHost);
return true;
}
bool CZNC::RemTrustedProxy(const CString& sHost) {
VCString::iterator it;
for (it = m_vsTrustedProxies.begin(); it != m_vsTrustedProxies.end(); ++it) {
if (sHost.Equals(*it)) {
m_vsTrustedProxies.erase(it);
return true;
}
}
return false;
}
void CZNC::Broadcast(const CString& sMessage, bool bAdminOnly,
CUser* pSkipUser, CClient *pSkipClient) {
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
if (bAdminOnly && !a->second->IsAdmin())
continue;
if (a->second != pSkipUser) {
CString sMsg = sMessage;
bool bContinue = false;
USERMODULECALL(OnBroadcast(sMsg), a->second, NULL, &bContinue);
if (bContinue) continue;
a->second->PutStatusNotice("*** " + sMsg, NULL, pSkipClient);
}
}
}
CModule* CZNC::FindModule(const CString& sModName, const CString& sUsername) {
if (sUsername.empty()) {
return CZNC::Get().GetModules().FindModule(sModName);
}
CUser* pUser = FindUser(sUsername);
return (!pUser) ? NULL : pUser->GetModules().FindModule(sModName);
}
CModule* CZNC::FindModule(const CString& sModName, CUser* pUser) {
if (pUser) {
return pUser->GetModules().FindModule(sModName);
}
return CZNC::Get().GetModules().FindModule(sModName);
}
bool CZNC::UpdateModule(const CString &sModule) {
CModule *pModule;
map<CString,CUser*>::const_iterator it;
map<CUser*, CString> musLoaded;
map<CUser*, CString>::iterator musIt;
map<CIRCNetwork*, CString> mnsLoaded;
map<CIRCNetwork*, CString>::iterator mnsIt;
// Unload the module for every user and network
for (it = m_msUsers.begin(); it != m_msUsers.end(); ++it) {
CUser *pUser = it->second;
pModule = pUser->GetModules().FindModule(sModule);
if (pModule) {
musLoaded[pUser] = pModule->GetArgs();
pUser->GetModules().UnloadModule(sModule);
}
// See if the user has this module loaded to a network
vector<CIRCNetwork*> vNetworks = pUser->GetNetworks();
vector<CIRCNetwork*>::iterator it2;
for (it2 = vNetworks.begin(); it2 != vNetworks.end(); ++it2) {
CIRCNetwork *pNetwork = *it2;
pModule = pNetwork->GetModules().FindModule(sModule);
if (pModule) {
mnsLoaded[pNetwork] = pModule->GetArgs();
pNetwork->GetModules().UnloadModule(sModule);
}
}
}
// Unload the global module
bool bGlobal = false;
CString sGlobalArgs;
pModule = GetModules().FindModule(sModule);
if (pModule) {
bGlobal = true;
sGlobalArgs = pModule->GetArgs();
GetModules().UnloadModule(sModule);
}
// Lets reload everything
bool bError = false;
CString sErr;
// Reload the global module
if (bGlobal) {
if (!GetModules().LoadModule(sModule, sGlobalArgs, CModInfo::GlobalModule, NULL, NULL, sErr)) {
DEBUG("Failed to reload [" << sModule << "] globally [" << sErr << "]");
bError = true;
}
}
// Reload the module for all users
for (musIt = musLoaded.begin(); musIt != musLoaded.end(); ++musIt) {
CUser *pUser = musIt->first;
CString& sArgs = musIt->second;
if (!pUser->GetModules().LoadModule(sModule, sArgs, CModInfo::UserModule, pUser, NULL, sErr)) {
DEBUG("Failed to reload [" << sModule << "] for ["
<< pUser->GetUserName() << "] [" << sErr << "]");
bError = true;
}
}
// Reload the module for all networks
for (mnsIt = mnsLoaded.begin(); mnsIt != mnsLoaded.end(); ++mnsIt) {
CIRCNetwork *pNetwork = mnsIt->first;
CString& sArgs = mnsIt->second;
if (!pNetwork->GetModules().LoadModule(sModule, sArgs, CModInfo::NetworkModule, pNetwork->GetUser(), pNetwork, sErr)) {
DEBUG("Failed to reload [" << sModule << "] for ["
<< pNetwork->GetUser()->GetUserName() << "/"
<< pNetwork->GetName() << "] [" << sErr << "]");
bError = true;
}
}
return !bError;
}
CUser* CZNC::FindUser(const CString& sUsername) {
map<CString,CUser*>::iterator it = m_msUsers.find(sUsername);
if (it != m_msUsers.end()) {
return it->second;
}
return NULL;
}
bool CZNC::DeleteUser(const CString& sUsername) {
CUser* pUser = FindUser(sUsername);
if (!pUser) {
return false;
}
m_msDelUsers[pUser->GetUserName()] = pUser;
return true;
}
bool CZNC::AddUser(CUser* pUser, CString& sErrorRet) {
if (FindUser(pUser->GetUserName()) != NULL) {
sErrorRet = "User already exists";
DEBUG("User [" << pUser->GetUserName() << "] - already exists");
return false;
}
if (!pUser->IsValid(sErrorRet)) {
DEBUG("Invalid user [" << pUser->GetUserName() << "] - ["
<< sErrorRet << "]");
return false;
}
bool bFailed = false;
GLOBALMODULECALL(OnAddUser(*pUser, sErrorRet), &bFailed);
if (bFailed) {
DEBUG("AddUser [" << pUser->GetUserName() << "] aborted by a module ["
<< sErrorRet << "]");
return false;
}
m_msUsers[pUser->GetUserName()] = pUser;
return true;
}
CListener* CZNC::FindListener(u_short uPort, const CString& sBindHost, EAddrType eAddr) {
vector<CListener*>::iterator it;
for (it = m_vpListeners.begin(); it < m_vpListeners.end(); ++it) {
if ((*it)->GetPort() != uPort)
continue;
if ((*it)->GetBindHost() != sBindHost)
continue;
if ((*it)->GetAddrType() != eAddr)
continue;
return *it;
}
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;
}
// No support for URIPrefix for old-style configs.
CString sURIPrefix;
unsigned short uPort = sPort.ToUShort();
return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, sError);
}
bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost,
const CString& sURIPrefixRaw, bool bSSL,
EAddrType eAddr, CListener::EAcceptType eAccept, CString& sError) {
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";
}
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;
}
// URIPrefix must start with a slash and end without one.
CString sURIPrefix = CString(sURIPrefixRaw);
if(!sURIPrefix.empty()) {
if (!sURIPrefix.StartsWith("/")) {
sURIPrefix = "/" + sURIPrefix;
}
if (sURIPrefix.EndsWith("/")) {
sURIPrefix.TrimRight("/");
}
}
CListener* pListener = new CListener(uPort, sBindHost, sURIPrefix, 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(CConfig* pConfig, CString& sError) {
CString sBindHost;
CString sURIPrefix;
bool bSSL;
bool b4;
#ifdef HAVE_IPV6
bool b6 = true;
#else
bool b6 = false;
#endif
bool bIRC;
bool bWeb;
unsigned short uPort;
if (!pConfig->FindUShortEntry("port", uPort)) {
sError = "No port given";
CUtils::PrintError(sError);
return false;
}
pConfig->FindStringEntry("host", sBindHost);
pConfig->FindBoolEntry("ssl", bSSL, false);
pConfig->FindBoolEntry("ipv4", b4, true);
pConfig->FindBoolEntry("ipv6", b6, b6);
pConfig->FindBoolEntry("allowirc", bIRC, true);
pConfig->FindBoolEntry("allowweb", bWeb, true);
pConfig->FindStringEntry("uriprefix", sURIPrefix);
EAddrType eAddr;
if (b4 && b6) {
eAddr = ADDR_ALL;
} else if (b4 && !b6) {
eAddr = ADDR_IPV4ONLY;
} else if (!b4 && b6) {
eAddr = ADDR_IPV6ONLY;
} else {
sError = "No address family given";
CUtils::PrintError(sError);
return false;
}
CListener::EAcceptType eAccept;
if (bIRC && bWeb) {
eAccept = CListener::ACCEPT_ALL;
} else if (bIRC && !bWeb) {
eAccept = CListener::ACCEPT_IRC;
} else if (!bIRC && bWeb) {
eAccept = CListener::ACCEPT_HTTP;
} else {
sError = "Either Web or IRC or both should be selected";
CUtils::PrintError(sError);
return false;
}
return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, sError);
}
bool CZNC::AddListener(CListener* pListener) {
if (!pListener->GetRealListener()) {
// Listener doesnt actually listen
delete pListener;
return false;
}
// We don't check if there is an identical listener already listening
// since one can't listen on e.g. the same port multiple times
m_vpListeners.push_back(pListener);
return true;
}
bool CZNC::DelListener(CListener* pListener) {
vector<CListener*>::iterator it;
for (it = m_vpListeners.begin(); it < m_vpListeners.end(); ++it) {
if (*it == pListener) {
m_vpListeners.erase(it);
delete pListener;
return true;
}
}
return false;
}
static CZNC* s_pZNC = NULL;
void CZNC::CreateInstance() {
if (s_pZNC)
abort();
s_pZNC = new CZNC();
}
CZNC& CZNC::Get() {
return *s_pZNC;
}
void CZNC::DestroyInstance() {
delete s_pZNC;
s_pZNC = NULL;
}
CZNC::TrafficStatsMap CZNC::GetTrafficStats(TrafficStatsPair &Users,
TrafficStatsPair &ZNC, TrafficStatsPair &Total) {
TrafficStatsMap ret;
unsigned long long uiUsers_in, uiUsers_out, uiZNC_in, uiZNC_out;
const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
uiUsers_in = uiUsers_out = 0;
uiZNC_in = BytesRead();
uiZNC_out = BytesWritten();
for (map<CString, CUser*>::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it) {
ret[it->first] = TrafficStatsPair(it->second->BytesRead(), it->second->BytesWritten());
uiUsers_in += it->second->BytesRead();
uiUsers_out += it->second->BytesWritten();
}
for (CSockManager::const_iterator it = m_Manager.begin(); it != m_Manager.end(); ++it) {
CUser *pUser = NULL;
if ((*it)->GetSockName().Left(5) == "IRC::") {
pUser = ((CIRCSock *) *it)->GetNetwork()->GetUser();
} else if ((*it)->GetSockName().Left(5) == "USR::") {
pUser = ((CClient*) *it)->GetUser();
}
if (pUser) {
ret[pUser->GetUserName()].first += (*it)->GetBytesRead();
ret[pUser->GetUserName()].second += (*it)->GetBytesWritten();
uiUsers_in += (*it)->GetBytesRead();
uiUsers_out += (*it)->GetBytesWritten();
} else {
uiZNC_in += (*it)->GetBytesRead();
uiZNC_out += (*it)->GetBytesWritten();
}
}
Users = TrafficStatsPair(uiUsers_in, uiUsers_out);
ZNC = TrafficStatsPair(uiZNC_in, uiZNC_out);
Total = TrafficStatsPair(uiUsers_in + uiZNC_in, uiUsers_out + uiZNC_out);
return ret;
}
void CZNC::AuthUser(std::shared_ptr<CAuthBase> AuthClass) {
// TODO unless the auth module calls it, CUser::IsHostAllowed() is not honoured
bool bReturn = false;
GLOBALMODULECALL(OnLoginAttempt(AuthClass), &bReturn);
if (bReturn) return;
CUser* pUser = FindUser(AuthClass->GetUsername());
if (!pUser || !pUser->CheckPass(AuthClass->GetPassword())) {
AuthClass->RefuseLogin("Invalid Password");
return;
}
CString sHost = AuthClass->GetRemoteIP();
if (!pUser->IsHostAllowed(sHost)) {
AuthClass->RefuseLogin("Your host [" + sHost + "] is not allowed");
return;
}
AuthClass->AcceptLogin(*pUser);
}
class CConnectQueueTimer : public CCron {
public:
CConnectQueueTimer(int iSecs) : CCron() {
SetName("Connect users");
Start(iSecs);
// Don't wait iSecs seconds for first timer run
m_bRunOnNextCall = true;
}
virtual ~CConnectQueueTimer() {
// This is only needed when ZNC shuts down:
// CZNC::~CZNC() sets its CConnectQueueTimer pointer to NULL and
// calls the manager's Cleanup() which destroys all sockets and
// timers. If something calls CZNC::EnableConnectQueue() here
// (e.g. because a CIRCSock is destroyed), the socket manager
// deletes that timer almost immediately, but CZNC now got a
// dangling pointer to this timer which can crash later on.
//
// Unlikely but possible ;)
CZNC::Get().LeakConnectQueueTimer(this);
}
protected:
virtual void RunJob() {
list<CIRCNetwork*> ConnectionQueue;
list<CIRCNetwork*>& RealConnectionQueue = CZNC::Get().GetConnectionQueue();
// Problem: If a network can't connect right now because e.g. it
// is throttled, it will re-insert itself into the connection
// queue. However, we must only give each network a single
// chance during this timer run.
//
// Solution: We move the connection queue to our local list at
// the beginning and work from that.
ConnectionQueue.swap(RealConnectionQueue);
while (!ConnectionQueue.empty()) {
CIRCNetwork *pNetwork = ConnectionQueue.front();
ConnectionQueue.pop_front();
if (pNetwork->Connect()) {
break;
}
}
/* Now re-insert anything that is left in our local list into
* the real connection queue.
*/
RealConnectionQueue.splice(RealConnectionQueue.begin(), ConnectionQueue);
if (RealConnectionQueue.empty()) {
DEBUG("ConnectQueueTimer done");
CZNC::Get().DisableConnectQueue();
}
}
};
void CZNC::SetConnectDelay(unsigned int i) {
if (i < 1) {
// Don't hammer server with our failed connects
i = 1;
}
if (m_uiConnectDelay != i && m_pConnectQueueTimer != NULL) {
m_pConnectQueueTimer->Start(i);
}
m_uiConnectDelay = i;
}
void CZNC::EnableConnectQueue() {
if (!m_pConnectQueueTimer && !m_uiConnectPaused && !m_lpConnectQueue.empty()) {
m_pConnectQueueTimer = new CConnectQueueTimer(m_uiConnectDelay);
GetManager().AddCron(m_pConnectQueueTimer);
}
}
void CZNC::DisableConnectQueue() {
if (m_pConnectQueueTimer) {
// This will kill the cron
m_pConnectQueueTimer->Stop();
m_pConnectQueueTimer = NULL;
}
}
void CZNC::PauseConnectQueue() {
DEBUG("Connection queue paused");
m_uiConnectPaused++;
if (m_pConnectQueueTimer) {
m_pConnectQueueTimer->Pause();
}
}
void CZNC::ResumeConnectQueue() {
DEBUG("Connection queue resumed");
m_uiConnectPaused--;
EnableConnectQueue();
if (m_pConnectQueueTimer) {
m_pConnectQueueTimer->UnPause();
}
}
void CZNC::AddNetworkToQueue(CIRCNetwork *pNetwork) {
// Make sure we are not already in the queue
for (list<CIRCNetwork*>::const_iterator it = m_lpConnectQueue.begin(); it != m_lpConnectQueue.end(); ++it) {
if (*it == pNetwork) {
return;
}
}
m_lpConnectQueue.push_back(pNetwork);
EnableConnectQueue();
}
void CZNC::LeakConnectQueueTimer(CConnectQueueTimer *pTimer) {
if (m_pConnectQueueTimer == pTimer)
m_pConnectQueueTimer = NULL;
}
bool CZNC::WaitForChildLock() {
return m_pLockFile && m_pLockFile->ExLock();
}