2
* Copyright (C) 2004-2010 See the AUTHORS file for details.
4
* This program is free software; you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 2 as published
6
* by the Free Software Foundation.
19
{ // private namespace for local things
20
struct CGlobalModuleConfigLine
31
CUtils::PrintError("Could not initialize Csocket!");
35
m_pModules = new CGlobalModules();
36
m_pISpoofLockFile = NULL;
39
SetISpoofFormat(""); // Set ISpoofFormat to default
42
m_pConnectUserTimer = NULL;
43
m_eConfigState = ECONFIG_NOTHING;
44
m_TimeStarted = time(NULL);
45
m_sConnectThrottle.SetTTL(30000);
49
if (m_pISpoofLockFile)
52
m_pModules->UnloadAll();
54
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
55
a->second->GetModules().UnloadAll();
58
for (size_t b = 0; b < m_vpListeners.size(); b++) {
59
delete m_vpListeners[b];
62
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
63
a->second->SetBeingDeleted(true);
66
m_pConnectUserTimer = NULL;
67
// This deletes m_pConnectUserTimer
77
CString CZNC::GetVersion() {
80
snprintf(szBuf, sizeof(szBuf), "%1.3f"VERSION_EXTRA, VERSION);
81
// If snprintf overflows (which I doubt), we want to be on the safe side
82
szBuf[sizeof(szBuf) - 1] = '\0';
87
CString CZNC::GetTag(bool bIncludeVersion) {
88
if (!bIncludeVersion) {
89
return "ZNC - http://znc.sourceforge.net";
93
snprintf(szBuf, sizeof(szBuf), "ZNC %1.3f"VERSION_EXTRA" - http://znc.sourceforge.net", VERSION);
94
// If snprintf overflows (which I doubt), we want to be on the safe side
95
szBuf[sizeof(szBuf) - 1] = '\0';
100
CString CZNC::GetUptime() const {
101
time_t now = time(NULL);
102
return CString::ToTimeStr(now - TimeStarted());
105
bool CZNC::OnBoot() {
106
if (!GetModules().OnBoot()) {
110
for (map<CString,CUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); ++it) {
111
if (!it->second->GetModules().OnBoot()) {
119
bool CZNC::ConnectUser(CUser *pUser) {
120
CString sSockName = "IRC::" + pUser->GetUserName();
121
// Don't use pUser->GetIRCSock(), as that only returns something if the
122
// CIRCSock is already connected, not when it's still connecting!
123
CIRCSock* pIRCSock = (CIRCSock*) m_Manager.FindSockByName(sSockName);
125
if (m_pISpoofLockFile != NULL) {
129
if (!pUser->GetIRCConnectEnabled())
132
if (pIRCSock || !pUser->HasServers())
135
if (pUser->ConnectPaused())
138
CServer* pServer = pUser->GetNextServer();
143
if (m_sConnectThrottle.GetItem(pServer->GetName()))
146
if (!WriteISpoof(pUser)) {
147
DEBUG("ISpoof could not be written");
148
pUser->PutStatus("ISpoof could not be written, retrying...");
152
m_sConnectThrottle.AddItem(pServer->GetName());
154
DEBUG("User [" << pUser->GetUserName() << "] is connecting to [" << pServer->GetName() << " " << pServer->GetPort() << "] ...");
155
pUser->PutStatus("Attempting to connect to [" + pServer->GetName() + " " + CString(pServer->GetPort()) + "] ...");
157
pIRCSock = new CIRCSock(pUser);
158
pIRCSock->SetPass(pServer->GetPass());
162
if (pServer->IsSSL()) {
167
MODULECALL(OnIRCConnecting(pIRCSock), pUser, NULL,
168
DEBUG("Some module aborted the connection attempt");
169
pUser->PutStatus("Some module aborted the connection attempt");
175
if (!m_Manager.Connect(pServer->GetName(), pServer->GetPort(), sSockName, 120, bSSL, pUser->GetVHost(), pIRCSock)) {
177
pUser->PutStatus("Unable to connect. (Bad host?)");
183
bool CZNC::HandleUserDeletion()
185
map<CString, CUser*>::iterator it;
186
map<CString, CUser*>::iterator end;
188
if (m_msDelUsers.empty())
191
end = m_msDelUsers.end();
192
for (it = m_msDelUsers.begin(); it != end; ++it) {
193
CUser* pUser = it->second;
194
pUser->SetBeingDeleted(true);
196
if (GetModules().OnDeleteUser(*pUser)) {
197
pUser->SetBeingDeleted(false);
200
m_msUsers.erase(pUser->GetUserName());
202
// Don't use pUser->GetIRCSock(), as that only returns something if the
203
// CIRCSock is already connected, not when it's still connecting!
204
CIRCSock* pIRCSock = (CIRCSock*) m_Manager.FindSockByName("IRC::" + pUser->GetUserName());
207
m_Manager.DelSockByAddr(pIRCSock);
212
CWebSock::FinishUserSessions(*pUser);
213
AddBytesRead(pUser->BytesRead());
214
AddBytesWritten(pUser->BytesWritten());
218
m_msDelUsers.clear();
227
switch (GetConfigState()) {
228
case ECONFIG_NEED_REHASH:
229
SetConfigState(ECONFIG_NOTHING);
231
if (RehashConfig(sError)) {
232
Broadcast("Rehashing succeeded", true);
234
Broadcast("Rehashing failed: " + sError, true);
235
Broadcast("ZNC is in some possibly inconsistent state!", true);
238
case ECONFIG_NEED_WRITE:
239
SetConfigState(ECONFIG_NOTHING);
242
Broadcast("Writing the config suceeded", true);
244
Broadcast("Writing the config file failed", true);
247
case ECONFIG_NOTHING:
251
// Check for users that need to be deleted
252
if (HandleUserDeletion()) {
253
// Also remove those user(s) from the config file
257
// Csocket wants micro seconds
258
// 500 msec to 600 sec
259
m_Manager.DynamicSelectLoop(500 * 1000, 600 * 1000 * 1000);
263
bool CZNC::WriteISpoof(CUser* pUser) {
264
if (m_pISpoofLockFile != NULL)
267
if (!m_sISpoofFile.empty()) {
268
m_pISpoofLockFile = new CFile;
269
if (!m_pISpoofLockFile->TryExLock(m_sISpoofFile, O_RDWR | O_CREAT)) {
270
delete m_pISpoofLockFile;
271
m_pISpoofLockFile = NULL;
276
memset((char*) buf, 0, 1024);
277
m_pISpoofLockFile->Read(buf, 1023);
280
if (!m_pISpoofLockFile->Seek(0) || !m_pISpoofLockFile->Truncate()) {
281
delete m_pISpoofLockFile;
282
m_pISpoofLockFile = NULL;
286
CString sData = pUser->ExpandString(m_sISpoofFormat);
288
// If the format doesn't contain anything expandable, we'll
289
// assume this is an "old"-style format string.
290
if (sData == m_sISpoofFormat) {
291
sData.Replace("%", pUser->GetIdent());
293
m_pISpoofLockFile->Write(sData + "\n");
298
void CZNC::ReleaseISpoof() {
299
if (m_pISpoofLockFile == NULL)
302
if (!m_sISpoofFile.empty()) {
303
if (m_pISpoofLockFile->Seek(0) && m_pISpoofLockFile->Truncate()) {
304
m_pISpoofLockFile->Write(m_sOrigISpoof);
310
delete m_pISpoofLockFile;
311
m_pISpoofLockFile = NULL;
314
CFile* CZNC::InitPidFile() {
315
if (!m_sPidFile.empty()) {
318
// absolute path or relative to the data dir?
319
if (m_sPidFile[0] != '/')
320
sFile = GetZNCPath() + "/" + m_sPidFile;
324
return new CFile(sFile);
330
bool CZNC::WritePidFile(int iPid) {
331
CFile* File = InitPidFile();
335
CUtils::PrintAction("Writing pid file [" + File->GetLongName() + "]");
338
if (File->Open(O_WRONLY | O_TRUNC | O_CREAT)) {
339
File->Write(CString(iPid) + "\n");
345
CUtils::PrintStatus(bRet);
349
bool CZNC::DeletePidFile() {
350
CFile* File = InitPidFile();
354
CUtils::PrintAction("Deleting pid file [" + File->GetLongName() + "]");
361
CUtils::PrintStatus(bRet);
365
bool CZNC::WritePemFile() {
367
CUtils::PrintError("ZNC was not compiled with ssl support.");
370
CString sPemFile = GetPemLocation();
371
const char* pHostName = getenv("HOSTNAME");
378
if (CFile::Exists(sPemFile)) {
379
CUtils::PrintError("Pem file [" + sPemFile + "] already exists");
383
while (!CUtils::GetInput("hostname of your shell", sHost, sHost, "including the '.com' portion")) ;
385
CUtils::PrintAction("Writing Pem file [" + sPemFile + "]");
386
FILE *f = fopen(sPemFile.c_str(), "w");
389
CUtils::PrintStatus(false, "Unable to open");
393
CUtils::GenerateCert(f, sHost);
396
CUtils::PrintStatus(true);
401
void CZNC::DeleteUsers() {
402
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
403
a->second->SetBeingDeleted(true);
408
DisableConnectUser();
411
CUser* CZNC::GetUser(const CString& sUser) {
412
// Todo: make this case insensitive
413
map<CString,CUser*>::iterator it = m_msUsers.find(sUser);
414
return (it == m_msUsers.end()) ? NULL : it->second;
417
Csock* CZNC::FindSockByName(const CString& sSockName) {
418
return m_Manager.FindSockByName(sSockName);
421
bool CZNC::IsHostAllowed(const CString& sHostMask) const {
422
for (map<CString,CUser*>::const_iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
423
if (a->second->IsHostAllowed(sHostMask)) {
431
bool CZNC::AllowConnectionFrom(const CString& sIP) const {
432
if (m_uiAnonIPLimit == 0)
434
if (GetManager().GetAnonConnectionCount(sIP) >= m_uiAnonIPLimit)
439
void CZNC::InitDirs(const CString& sArgvPath, const CString& sDataDir) {
442
// If the bin was not ran from the current directory, we need to add that dir onto our cwd
443
CString::size_type uPos = sArgvPath.rfind('/');
444
if (uPos == CString::npos)
447
m_sCurPath = CDir::ChangeDir("./", sArgvPath.Left(uPos), "");
449
// Try to set the user's home dir, default to binpath on failure
450
home = getenv("HOME");
457
if (m_sHomePath.empty()) {
458
struct passwd* pUserInfo = getpwuid(getuid());
461
m_sHomePath = pUserInfo->pw_dir;
465
if (m_sHomePath.empty()) {
466
m_sHomePath = m_sCurPath;
469
if (sDataDir.empty()) {
470
m_sZNCPath = m_sHomePath + "/.znc";
472
m_sZNCPath = sDataDir;
476
CString CZNC::GetConfPath(bool bAllowMkDir) const {
477
CString sConfPath = m_sZNCPath + "/configs";
478
if (bAllowMkDir && !CFile::Exists(sConfPath)) {
479
CDir::MakeDir(sConfPath);
485
CString CZNC::GetUserPath() const {
486
CString sUserPath = m_sZNCPath + "/users";
487
if (!CFile::Exists(sUserPath)) {
488
CDir::MakeDir(sUserPath);
494
CString CZNC::GetModPath() const {
495
CString sModPath = m_sZNCPath + "/modules";
497
if (!CFile::Exists(sModPath)) {
498
CDir::MakeDir(sModPath);
505
CString CZNC::ExpandConfigPath(const CString& sConfigFile, bool bAllowMkDir) {
508
if (sConfigFile.empty()) {
509
sRetPath = GetConfPath(bAllowMkDir) + "/znc.conf";
511
if (sConfigFile.Left(2) == "./" || sConfigFile.Left(3) == "../") {
512
sRetPath = GetCurPath() + "/" + sConfigFile;
513
} else if (sConfigFile.Left(1) != "/") {
514
sRetPath = GetConfPath(bAllowMkDir) + "/" + sConfigFile;
516
sRetPath = sConfigFile;
523
bool CZNC::WriteConfig() {
524
if (GetConfigFile().empty()) {
528
// Close the old handle to the config file, we are replacing that file.
531
// We first write to a temporary file and then move it to the right place
532
m_LockFile.SetFileName(GetConfigFile() + "~");
534
if (!m_LockFile.Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) {
538
// We have to "transfer" our lock on the config to the new file.
539
// The old file (= inode) is going away and thus a lock on it would be
540
// useless. These lock should always succeed (races, anyone?).
541
if (!m_LockFile.TryExLock()) {
545
if (GetModules().OnWriteConfig(m_LockFile)) {
549
m_LockFile.Write("AnonIPLimit = " + CString(m_uiAnonIPLimit) + "\n");
551
for (size_t l = 0; l < m_vpListeners.size(); l++) {
552
CListener* pListener = m_vpListeners[l];
553
CString sHostPortion = pListener->GetBindHost();
555
if (!sHostPortion.empty()) {
556
sHostPortion = sHostPortion.FirstLine() + " ";
559
CString sAcceptProtocol;
560
if(pListener->GetAcceptType() == CListener::ACCEPT_IRC)
561
sAcceptProtocol = "irc_only ";
562
else if(pListener->GetAcceptType() == CListener::ACCEPT_HTTP)
563
sAcceptProtocol = "web_only ";
566
switch (pListener->GetAddrType()) {
578
m_LockFile.Write("Listener" + s6 + " = " + sAcceptProtocol + sHostPortion +
579
CString((pListener->IsSSL()) ? "+" : "") + CString(pListener->GetPort()) + "\n");
582
m_LockFile.Write("ConnectDelay = " + CString(m_uiConnectDelay) + "\n");
583
m_LockFile.Write("ServerThrottle = " + CString(m_sConnectThrottle.GetTTL()/1000) + "\n");
585
if (!m_sISpoofFile.empty()) {
586
m_LockFile.Write("ISpoofFile = " + m_sISpoofFile.FirstLine() + "\n");
587
if (!m_sISpoofFormat.empty()) {
588
m_LockFile.Write("ISpoofFormat = " + m_sISpoofFormat.FirstLine() + "\n");
592
if (!m_sPidFile.empty()) {
593
m_LockFile.Write("PidFile = " + m_sPidFile.FirstLine() + "\n");
596
if (!m_sSkinName.empty()) {
597
m_LockFile.Write("Skin = " + m_sSkinName.FirstLine() + "\n");
600
if (!m_sStatusPrefix.empty()) {
601
m_LockFile.Write("StatusPrefix = " + m_sStatusPrefix.FirstLine() + "\n");
604
for (unsigned int m = 0; m < m_vsMotd.size(); m++) {
605
m_LockFile.Write("Motd = " + m_vsMotd[m].FirstLine() + "\n");
608
for (unsigned int v = 0; v < m_vsVHosts.size(); v++) {
609
m_LockFile.Write("VHost = " + m_vsVHosts[v].FirstLine() + "\n");
612
CGlobalModules& Mods = GetModules();
614
for (unsigned int a = 0; a < Mods.size(); a++) {
615
CString sName = Mods[a]->GetModName();
616
CString sArgs = Mods[a]->GetArgs();
618
if (!sArgs.empty()) {
619
sArgs = " " + sArgs.FirstLine();
622
m_LockFile.Write("LoadModule = " + sName.FirstLine() + sArgs + "\n");
625
for (map<CString,CUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); ++it) {
628
if (!it->second->IsValid(sErr)) {
629
DEBUG("** Error writing config for user [" << it->first << "] [" << sErr << "]");
633
m_LockFile.Write("\n");
635
if (!it->second->WriteConfig(m_LockFile)) {
636
DEBUG("** Error writing config for user [" << it->first << "]");
640
// If Sync() fails... well, let's hope nothing important breaks..
643
// We wrote to a temporary name, move it to the right place
644
if (!m_LockFile.Move(GetConfigFile(), true))
647
// Everything went fine, just need to update the saved path.
648
m_LockFile.SetFileName(GetConfigFile());
653
bool CZNC::WriteNewConfig(const CString& sConfigFile) {
654
CString sAnswer, sUser;
657
m_sConfigFile = ExpandConfigPath(sConfigFile);
658
CUtils::PrintMessage("Building new config");
660
CUtils::PrintMessage("");
661
CUtils::PrintMessage("First lets start with some global settings...");
662
CUtils::PrintMessage("");
665
unsigned int uListenPort = 0;
666
while (!CUtils::GetNumInput("What port would you like ZNC to listen on?", uListenPort, 1, 65535)) ;
670
if (CUtils::GetBoolInput("Would you like ZNC to listen using SSL?", false)) {
673
CString sPemFile = GetPemLocation();
674
if (!CFile::Exists(sPemFile)) {
675
CUtils::PrintError("Unable to locate pem file: [" + sPemFile + "]");
676
if (CUtils::GetBoolInput("Would you like to create a new pem file now?",
686
if (CUtils::GetBoolInput("Would you like ZNC to listen using ipv6?", false)) {
692
CUtils::GetInput("Listen Host", sListenHost, "", "Blank for all ips");
694
if (!sListenHost.empty()) {
698
vsLines.push_back("Listener" + s6 + " = " + sListenHost + sSSL + CString(uListenPort));
701
set<CModInfo> ssGlobalMods;
702
GetModules().GetAvailableMods(ssGlobalMods, true);
703
size_t uNrOtherGlobalMods = FilterUncommonModules(ssGlobalMods);
705
if (!ssGlobalMods.empty()) {
706
CUtils::PrintMessage("");
707
CUtils::PrintMessage("-- Global Modules --");
708
CUtils::PrintMessage("");
710
if (CUtils::GetBoolInput("Do you want to load any global modules?")) {
712
Table.AddColumn("Name");
713
Table.AddColumn("Description");
714
set<CModInfo>::iterator it;
716
for (it = ssGlobalMods.begin(); it != ssGlobalMods.end(); ++it) {
717
const CModInfo& Info = *it;
719
Table.SetCell("Name", Info.GetName());
720
Table.SetCell("Description", Info.GetDescription().Ellipsize(128));
723
unsigned int uTableIdx = 0; CString sLine;
724
while (Table.GetLine(uTableIdx++, sLine)) {
725
CUtils::PrintMessage(sLine);
728
if (uNrOtherGlobalMods > 0) {
729
CUtils::PrintMessage("And " + CString(uNrOtherGlobalMods) + " other (uncommon) modules. You can enable those later.");
732
CUtils::PrintMessage("");
734
for (it = ssGlobalMods.begin(); it != ssGlobalMods.end(); ++it) {
735
const CModInfo& Info = *it;
736
CString sName = Info.GetName();
738
if (CUtils::StdoutIsTTY()) {
739
if (CUtils::GetBoolInput("Load global module <\033[1m" + sName + "\033[22m>?", false))
740
vsLines.push_back("LoadModule = " + sName);
742
if (CUtils::GetBoolInput("Load global module <" + sName + ">?", false))
743
vsLines.push_back("LoadModule = " + sName);
750
CUtils::PrintMessage("");
751
CUtils::PrintMessage("Now we need to setup a user...");
752
CUtils::PrintMessage("");
754
bool bFirstUser = true;
757
vsLines.push_back("");
760
CUtils::GetInput("Username", sUser, "", "AlphaNumeric");
761
} while (!CUser::IsValidUserName(sUser));
763
vsLines.push_back("<User " + sUser + ">");
765
sAnswer = CUtils::GetSaltedHashPass(sSalt);
766
vsLines.push_back("\tPass = " + CUtils::sDefaultHash + "#" + sAnswer + "#" + sSalt + "#");
768
if (CUtils::GetBoolInput("Would you like this user to be an admin?", bFirstUser)) {
769
vsLines.push_back("\tAdmin = true");
771
vsLines.push_back("\tAdmin = false");
774
CUtils::GetInput("Nick", sNick, CUser::MakeCleanUserName(sUser));
775
vsLines.push_back("\tNick = " + sNick);
776
CUtils::GetInput("Alt Nick", sAnswer, sNick + "_");
777
if (!sAnswer.empty()) {
778
vsLines.push_back("\tAltNick = " + sAnswer);
780
CUtils::GetInput("Ident", sAnswer, sNick);
781
vsLines.push_back("\tIdent = " + sAnswer);
782
CUtils::GetInput("Real Name", sAnswer, "Got ZNC?");
783
vsLines.push_back("\tRealName = " + sAnswer);
784
CUtils::GetInput("VHost", sAnswer, "", "optional");
785
if (!sAnswer.empty()) {
786
vsLines.push_back("\tVHost = " + sAnswer);
788
// todo: Possibly add motd
790
unsigned int uBufferCount = 0;
792
CUtils::GetNumInput("Number of lines to buffer per channel", uBufferCount, 0, ~0, 50);
794
vsLines.push_back("\tBuffer = " + CString(uBufferCount));
796
if (CUtils::GetBoolInput("Would you like to keep buffers after replay?", false)) {
797
vsLines.push_back("\tKeepBuffer = true");
799
vsLines.push_back("\tKeepBuffer = false");
802
CUtils::GetInput("Default channel modes", sAnswer, "+stn");
803
if (!sAnswer.empty()) {
804
vsLines.push_back("\tChanModes = " + sAnswer);
807
set<CModInfo> ssUserMods;
808
GetModules().GetAvailableMods(ssUserMods);
809
size_t uNrOtherUserMods = FilterUncommonModules(ssUserMods);
811
if (ssUserMods.size()) {
812
vsLines.push_back("");
813
CUtils::PrintMessage("");
814
CUtils::PrintMessage("-- User Modules --");
815
CUtils::PrintMessage("");
817
if (CUtils::GetBoolInput("Do you want to automatically load any user modules for this user?")) {
819
Table.AddColumn("Name");
820
Table.AddColumn("Description");
821
set<CModInfo>::iterator it;
823
for (it = ssUserMods.begin(); it != ssUserMods.end(); ++it) {
824
const CModInfo& Info = *it;
826
Table.SetCell("Name", Info.GetName());
827
Table.SetCell("Description", Info.GetDescription().Ellipsize(128));
830
unsigned int uTableIdx = 0; CString sLine;
831
while (Table.GetLine(uTableIdx++, sLine)) {
832
CUtils::PrintMessage(sLine);
835
if (uNrOtherUserMods > 0) {
836
CUtils::PrintMessage("And " + CString(uNrOtherUserMods) + " other (uncommon) modules. You can enable those later.");
839
CUtils::PrintMessage("");
841
for (it = ssUserMods.begin(); it != ssUserMods.end(); ++it) {
842
const CModInfo& Info = *it;
843
CString sName = Info.GetName();
845
if (CUtils::StdoutIsTTY()) {
846
if (CUtils::GetBoolInput("Load module <\033[1m" + sName + "\033[22m>?", false))
847
vsLines.push_back("\tLoadModule = " + sName);
849
if (CUtils::GetBoolInput("Load module <" + sName + ">?", false))
850
vsLines.push_back("\tLoadModule = " + sName);
856
vsLines.push_back("");
857
CUtils::PrintMessage("");
858
CUtils::PrintMessage("-- IRC Servers --");
859
CUtils::PrintMessage("");
862
CString sHost, sPass;
864
unsigned int uServerPort = 0;
866
while (!CUtils::GetInput("IRC server", sHost, "", "host only") || !CServer::IsValidHostName(sHost)) ;
867
while (!CUtils::GetNumInput("[" + sHost + "] Port", uServerPort, 1, 65535, 6667)) ;
868
CUtils::GetInput("[" + sHost + "] Password (probably empty)", sPass);
871
bSSL = CUtils::GetBoolInput("Does this server use SSL? (probably no)", false);
874
vsLines.push_back("\tServer = " + sHost + ((bSSL) ? " +" : " ") + CString(uServerPort) + " " + sPass);
876
CUtils::PrintMessage("");
877
} while (CUtils::GetBoolInput("Would you like to add another server?", false));
879
vsLines.push_back("");
880
CUtils::PrintMessage("");
881
CUtils::PrintMessage("-- Channels --");
882
CUtils::PrintMessage("");
885
CString sPost = " for ZNC to automatically join?";
886
bool bDefault = true;
888
while (CUtils::GetBoolInput("Would you like to add " + sArg + " channel" + sPost, bDefault)) {
889
while (!CUtils::GetInput("Channel name", sAnswer)) ;
890
vsLines.push_back("\t<Chan " + sAnswer + ">");
891
vsLines.push_back("\t</Chan>");
897
vsLines.push_back("</User>");
899
CUtils::PrintMessage("");
901
} while (CUtils::GetBoolInput("Would you like to setup another user?", false));
905
bool bFileOK, bFileOpen = false;
907
CUtils::PrintAction("Writing config [" + m_sConfigFile + "]");
910
if (CFile::Exists(m_sConfigFile)) {
911
if (!m_LockFile.TryExLock(m_sConfigFile)) {
912
CUtils::PrintStatus(false, "ZNC is currently running on this config.");
916
CUtils::PrintStatus(false, "This config already exists.");
917
if (CUtils::GetBoolInput("Would you like to overwrite it?", false))
918
CUtils::PrintAction("Overwriting config [" + m_sConfigFile + "]");
925
File.SetFileName(m_sConfigFile);
926
if (File.Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) {
929
CUtils::PrintStatus(false, "Unable to open file");
934
CUtils::GetInput("Please specify an alternate location (or \"stdout\" for displaying the config)", m_sConfigFile, m_sConfigFile);
935
if (m_sConfigFile.Equals("stdout"))
938
m_sConfigFile = ExpandConfigPath(m_sConfigFile);
943
CUtils::PrintMessage("");
944
CUtils::PrintMessage("Printing the new config to stdout:");
945
CUtils::PrintMessage("");
946
cout << endl << "----------------------------------------------------------------------------" << endl << endl;
949
for (unsigned int a = 0; a < vsLines.size(); a++) {
951
File.Write(vsLines[a] + "\n");
953
cout << vsLines[a] << endl;
959
CUtils::PrintStatus(true);
961
cout << endl << "----------------------------------------------------------------------------" << endl << endl;
964
CUtils::PrintMessage("");
965
CUtils::PrintMessage("To connect to this znc you need to connect to it as your irc server", true);
966
CUtils::PrintMessage("using the port that you supplied. You have to supply your login info", true);
967
CUtils::PrintMessage("as the irc server password like so... user:pass.", true);
968
CUtils::PrintMessage("");
969
CUtils::PrintMessage("Try something like this in your IRC client...", true);
970
CUtils::PrintMessage("/server <znc_server_ip> " + CString(uListenPort) + " " + sUser + ":<pass>", true);
971
CUtils::PrintMessage("");
974
return bFileOpen && CUtils::GetBoolInput("Launch znc now?", true);
977
size_t CZNC::FilterUncommonModules(set<CModInfo>& ssModules) {
978
const char* ns[] = { "webadmin", "admin",
979
"chansaver", "keepnick", "simple_away", "partyline",
980
"kickrejoin", "nickserv", "perform" };
981
const set<CString> ssNames(ns, ns + sizeof(ns) / sizeof(ns[0]));
983
size_t uNrRemoved = 0;
984
for(set<CModInfo>::iterator it = ssModules.begin(); it != ssModules.end(); ) {
985
if(ssNames.count(it->GetName()) > 0) {
988
set<CModInfo>::iterator it2 = it++;
989
ssModules.erase(it2);
997
bool CZNC::ParseConfig(const CString& sConfig)
1001
m_sConfigFile = ExpandConfigPath(sConfig, false);
1006
bool CZNC::RehashConfig(CString& sError)
1008
GetModules().OnPreRehash();
1009
for (map<CString, CUser*>::iterator itb = m_msUsers.begin();
1010
itb != m_msUsers.end(); ++itb) {
1011
itb->second->GetModules().OnPreRehash();
1014
// This clears m_msDelUsers
1015
HandleUserDeletion();
1017
// Mark all users as going-to-be deleted
1018
m_msDelUsers = m_msUsers;
1021
if (DoRehash(sError)) {
1022
GetModules().OnPostRehash();
1023
for (map<CString, CUser*>::iterator it = m_msUsers.begin();
1024
it != m_msUsers.end(); ++it) {
1025
it->second->GetModules().OnPostRehash();
1031
// Rehashing failed, try to recover
1033
while (!m_msDelUsers.empty()) {
1034
AddUser(m_msDelUsers.begin()->second, s);
1035
m_msDelUsers.erase(m_msDelUsers.begin());
1041
bool CZNC::DoRehash(CString& sError)
1045
CUtils::PrintAction("Opening Config [" + m_sConfigFile + "]");
1047
if (!CFile::Exists(m_sConfigFile)) {
1048
sError = "No such file";
1049
CUtils::PrintStatus(false, sError);
1050
CUtils::PrintMessage("Restart znc with the --makeconf option if you wish to create this config.");
1054
if (!CFile::IsReg(m_sConfigFile)) {
1055
sError = "Not a file";
1056
CUtils::PrintStatus(false, sError);
1060
// (re)open the config file
1061
if (m_LockFile.IsOpen())
1064
if (!m_LockFile.Open(m_sConfigFile)) {
1065
sError = "Can not open config file";
1066
CUtils::PrintStatus(false, sError);
1070
if (!m_LockFile.TryExLock()) {
1071
sError = "ZNC is already running on this config.";
1072
CUtils::PrintStatus(false, sError);
1076
CFile &File = m_LockFile;
1078
// This fd is re-used for rehashing, so we must seek back to the beginning!
1079
if (!File.Seek(0)) {
1080
sError = "Could not seek to the beginning of the config.";
1081
CUtils::PrintStatus(false, sError);
1085
CUtils::PrintStatus(true);
1090
// Delete all listeners
1091
while (!m_vpListeners.empty()) {
1092
delete m_vpListeners[0];
1093
m_vpListeners.erase(m_vpListeners.begin());
1097
bool bCommented = false; // support for /**/ style comments
1098
CUser* pUser = NULL; // Used to keep track of which user block we are in
1099
CUser* pRealUser = NULL; // If we rehash a user, this is the real one
1100
CChan* pChan = NULL; // Used to keep track of which chan block we are in
1101
unsigned int uLineNum = 0;
1102
MCString msModules; // Modules are queued for later loading
1104
std::list<CGlobalModuleConfigLine> lGlobalModuleConfigLine;
1106
while (File.ReadLine(sLine)) {
1109
// Remove all leading / trailing spaces and line endings
1112
if ((sLine.empty()) || (sLine[0] == '#') || (sLine.Left(2) == "//")) {
1116
if (sLine.Left(2) == "/*") {
1117
if (sLine.Right(2) != "*/") {
1125
if (sLine.Right(2) == "*/") {
1132
if ((sLine.Left(1) == "<") && (sLine.Right(1) == ">")) {
1137
CString sTag = sLine.substr(0, sLine.find_first_of(" \t\r\n"));
1138
CString sValue = (sTag.size() < sLine.size()) ? sLine.substr(sTag.size() +1) : "";
1143
if (sLine.Left(1) == "/") {
1144
sTag = sTag.substr(1);
1148
if (sTag.Equals("Chan")) {
1149
// Save the channel name, because AddChan
1150
// deletes the CChannel*, if adding fails
1151
sError = pChan->GetName();
1152
if (!pUser->AddChan(pChan)) {
1153
sError = "Channel [" + sError + "] defined more than once";
1154
CUtils::PrintError(sError);
1161
} else if (sTag.Equals("User")) {
1165
if (!pRealUser->Clone(*pUser, sErr)
1166
|| !AddUser(pRealUser, sErr)) {
1167
sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr;
1168
DEBUG("CUser::Clone() failed in rehash");
1170
pUser->SetBeingDeleted(true);
1173
} else if (!AddUser(pUser, sErr)) {
1174
sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr;
1177
if (!sError.empty()) {
1178
CUtils::PrintError(sError);
1180
pUser->SetBeingDeleted(true);
1192
} else if (sTag.Equals("User")) {
1194
sError = "You may not nest <User> tags inside of other <User> tags.";
1195
CUtils::PrintError(sError);
1199
if (sValue.empty()) {
1200
sError = "You must supply a username in the <User> tag.";
1201
CUtils::PrintError(sError);
1205
if (m_msUsers.find(sValue) != m_msUsers.end()) {
1206
sError = "User [" + sValue + "] defined more than once.";
1207
CUtils::PrintError(sError);
1211
CUtils::PrintMessage("Loading user [" + sValue + "]");
1213
// Either create a CUser* or use an existing one
1214
map<CString, CUser*>::iterator it = m_msDelUsers.find(sValue);
1216
if (it != m_msDelUsers.end()) {
1217
pRealUser = it->second;
1218
m_msDelUsers.erase(it);
1222
pUser = new CUser(sValue);
1224
if (!m_sStatusPrefix.empty()) {
1225
if (!pUser->SetStatusPrefix(m_sStatusPrefix)) {
1226
sError = "Invalid StatusPrefix [" + m_sStatusPrefix + "] Must be 1-5 chars, no spaces.";
1227
CUtils::PrintError(sError);
1233
} else if (sTag.Equals("Chan")) {
1235
sError = "<Chan> tags must be nested inside of a <User> tag.";
1236
CUtils::PrintError(sError);
1241
sError = "You may not nest <Chan> tags inside of other <Chan> tags.";
1242
CUtils::PrintError(sError);
1246
pChan = new CChan(sValue, pUser, true);
1251
// If we have a regular line, figure out where it goes
1252
CString sName = sLine.Token(0, false, "=");
1253
CString sValue = sLine.Token(1, true, "=");
1257
if ((!sName.empty()) && (!sValue.empty())) {
1260
if (sName.Equals("Buffer")) {
1261
pChan->SetBufferCount(sValue.ToUInt());
1263
} else if (sName.Equals("KeepBuffer")) {
1264
pChan->SetKeepBuffer(sValue.Equals("true"));
1266
} else if (sName.Equals("Detached")) {
1267
pChan->SetDetached(sValue.Equals("true"));
1269
} else if (sName.Equals("AutoCycle")) {
1270
if (sValue.Equals("true")) {
1271
CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle " + pChan->GetName());
1274
} else if (sName.Equals("Key")) {
1275
pChan->SetKey(sValue);
1277
} else if (sName.Equals("Modes")) {
1278
pChan->SetDefaultModes(sValue);
1282
if (sName.Equals("Buffer")) {
1283
pUser->SetBufferCount(sValue.ToUInt());
1285
} else if (sName.Equals("KeepBuffer")) {
1286
pUser->SetKeepBuffer(sValue.Equals("true"));
1288
} else if (sName.Equals("Nick")) {
1289
pUser->SetNick(sValue);
1291
} else if (sName.Equals("CTCPReply")) {
1292
pUser->AddCTCPReply(sValue.Token(0), sValue.Token(1, true));
1294
} else if (sName.Equals("QuitMsg")) {
1295
pUser->SetQuitMsg(sValue);
1297
} else if (sName.Equals("AltNick")) {
1298
pUser->SetAltNick(sValue);
1300
} else if (sName.Equals("AwaySuffix")) {
1301
CUtils::PrintMessage("WARNING: AwaySuffix has been depricated, instead try -> LoadModule = awaynick %nick%_" + sValue);
1303
} else if (sName.Equals("AutoCycle")) {
1304
if (sValue.Equals("true")) {
1305
CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle");
1308
} else if (sName.Equals("Pass")) {
1309
// There are different formats for this available:
1310
// Pass = <plain text>
1311
// Pass = <md5 hash> -
1312
// Pass = plain#<plain text>
1313
// Pass = <hash name>#<hash>
1314
// Pass = <hash name>#<salted hash>#<salt>#
1315
// 'Salted hash' means hash of 'password' + 'salt'
1316
// Possible hashes are md5 and sha256
1317
if (sValue.Right(1) == "-") {
1318
sValue.RightChomp();
1320
pUser->SetPass(sValue, CUser::HASH_MD5);
1322
CString sMethod = sValue.Token(0, false, "#");
1323
CString sPass = sValue.Token(1, true, "#");
1324
if (sMethod == "md5" || sMethod == "sha256") {
1325
CUser::eHashType type = CUser::HASH_MD5;
1326
if (sMethod == "sha256")
1327
type = CUser::HASH_SHA256;
1329
CString sSalt = sPass.Token(1, false, "#");
1330
sPass = sPass.Token(0, false, "#");
1331
pUser->SetPass(sPass, type, sSalt);
1332
} else if (sMethod == "plain") {
1333
pUser->SetPass(sPass, CUser::HASH_NONE);
1335
pUser->SetPass(sValue, CUser::HASH_NONE);
1340
} else if (sName.Equals("MultiClients")) {
1341
pUser->SetMultiClients(sValue.Equals("true"));
1343
} else if (sName.Equals("BounceDCCs")) {
1344
pUser->SetBounceDCCs(sValue.Equals("true"));
1346
} else if (sName.Equals("Ident")) {
1347
pUser->SetIdent(sValue);
1349
} else if (sName.Equals("DenyLoadMod")) {
1350
pUser->SetDenyLoadMod(sValue.Equals("true"));
1352
} else if (sName.Equals("Admin")) {
1353
pUser->SetAdmin(sValue.Equals("true"));
1355
} else if (sName.Equals("DenySetVHost")) {
1356
pUser->SetDenySetVHost(sValue.Equals("true"));
1358
} else if (sName.Equals("StatusPrefix")) {
1359
if (!pUser->SetStatusPrefix(sValue)) {
1360
sError = "Invalid StatusPrefix [" + sValue + "] Must be 1-5 chars, no spaces.";
1361
CUtils::PrintError(sError);
1365
} else if (sName.Equals("DCCLookupMethod")) {
1366
pUser->SetUseClientIP(sValue.Equals("Client"));
1368
} else if (sName.Equals("RealName")) {
1369
pUser->SetRealName(sValue);
1371
} else if (sName.Equals("KeepNick")) {
1372
if (sValue.Equals("true")) {
1373
CUtils::PrintError("WARNING: KeepNick has been deprecated, instead try -> LoadModule = keepnick");
1376
} else if (sName.Equals("ChanModes")) {
1377
pUser->SetDefaultChanModes(sValue);
1379
} else if (sName.Equals("VHost")) {
1380
pUser->SetVHost(sValue);
1382
} else if (sName.Equals("DCCVHost")) {
1383
pUser->SetDCCVHost(sValue);
1385
} else if (sName.Equals("Allow")) {
1386
pUser->AddAllowedHost(sValue);
1388
} else if (sName.Equals("Server")) {
1389
CUtils::PrintAction("Adding Server [" + sValue + "]");
1390
CUtils::PrintStatus(pUser->AddServer(sValue));
1392
} else if (sName.Equals("Chan")) {
1393
pUser->AddChan(sValue, true);
1395
} else if (sName.Equals("TimestampFormat")) {
1396
pUser->SetTimestampFormat(sValue);
1398
} else if (sName.Equals("AppendTimestamp")) {
1399
pUser->SetTimestampAppend(sValue.ToBool());
1401
} else if (sName.Equals("PrependTimestamp")) {
1402
pUser->SetTimestampPrepend(sValue.ToBool());
1404
} else if (sName.Equals("IRCConnectEnabled")) {
1405
pUser->SetIRCConnectEnabled(sValue.ToBool());
1407
} else if (sName.Equals("Timestamp")) {
1408
if (!sValue.Trim_n().Equals("true")) {
1409
if (sValue.Trim_n().Equals("append")) {
1410
pUser->SetTimestampAppend(true);
1411
pUser->SetTimestampPrepend(false);
1412
} else if (sValue.Trim_n().Equals("prepend")) {
1413
pUser->SetTimestampAppend(false);
1414
pUser->SetTimestampPrepend(true);
1415
} else if (sValue.Trim_n().Equals("false")) {
1416
pUser->SetTimestampAppend(false);
1417
pUser->SetTimestampPrepend(false);
1419
pUser->SetTimestampFormat(sValue);
1423
} else if (sName.Equals("TimezoneOffset")) {
1424
pUser->SetTimezoneOffset(sValue.ToDouble()); // there is no ToFloat()
1426
} else if (sName.Equals("JoinTries")) {
1427
pUser->SetJoinTries(sValue.ToUInt());
1429
} else if (sName.Equals("MaxJoins")) {
1430
pUser->SetMaxJoins(sValue.ToUInt());
1432
} else if (sName.Equals("Skin")) {
1433
pUser->SetSkinName(sValue);
1435
} else if (sName.Equals("LoadModule")) {
1436
CString sModName = sValue.Token(0);
1438
// XXX Legacy crap, added in znc 0.089
1439
if (sModName == "discon_kick") {
1440
CUtils::PrintMessage("NOTICE: [discon_kick] was renamed, loading [disconkick] instead");
1441
sModName = "disconkick";
1444
CUtils::PrintAction("Loading Module [" + sModName + "]");
1446
CString sArgs = sValue.Token(1, true);
1448
bool bModRet = pUser->GetModules().LoadModule(sModName, sArgs, pUser, sModRet);
1450
// If the module was loaded, sModRet contains
1451
// "Loaded Module [name] ..." and we strip away this beginning.
1453
sModRet = sModRet.Token(1, true, sModName + "] ");
1455
CUtils::PrintStatus(bModRet, sModRet);
1464
if (sName.Equals("Listen") || sName.Equals("Listen6") || sName.Equals("Listen4")
1465
|| sName.Equals("Listener") || sName.Equals("Listener6") || sName.Equals("Listener4")) {
1466
EAddrType eAddr = ADDR_ALL;
1467
if (sName.Equals("Listen4") || sName.Equals("Listen") || sName.Equals("Listener4")) {
1468
eAddr = ADDR_IPV4ONLY;
1470
if (sName.Equals("Listener6")) {
1471
eAddr = ADDR_IPV6ONLY;
1474
CListener::EAcceptType eAccept = CListener::ACCEPT_ALL;
1475
if (sValue.TrimPrefix("irc_only "))
1476
eAccept = CListener::ACCEPT_IRC;
1477
else if (sValue.TrimPrefix("web_only "))
1478
eAccept = CListener::ACCEPT_HTTP;
1484
if (ADDR_IPV4ONLY == eAddr) {
1485
sValue.Replace(":", " ");
1488
if (sValue.find(" ") != CString::npos) {
1489
sBindHost = sValue.Token(0, false, " ");
1490
sPort = sValue.Token(1, true, " ");
1495
if (sPort.Left(1) == "+") {
1500
CString sHostComment;
1502
if (!sBindHost.empty()) {
1503
sHostComment = " on host [" + sBindHost + "]";
1506
CString sIPV6Comment;
1513
sIPV6Comment = " using ipv4";
1516
sIPV6Comment = " using ipv6";
1519
unsigned short uPort = sPort.ToUShort();
1520
CUtils::PrintAction("Binding to port [" + CString((bSSL) ? "+" : "") + CString(uPort) + "]" + sHostComment + sIPV6Comment);
1523
if (ADDR_IPV6ONLY == eAddr) {
1524
sError = "IPV6 is not enabled";
1525
CUtils::PrintStatus(false, sError);
1532
sError = "SSL is not enabled";
1533
CUtils::PrintStatus(false, sError);
1537
CString sPemFile = GetPemLocation();
1539
if (bSSL && !CFile::Exists(sPemFile)) {
1540
sError = "Unable to locate pem file: [" + sPemFile + "]";
1541
CUtils::PrintStatus(false, sError);
1543
// If stdin is e.g. /dev/null and we call GetBoolInput(),
1544
// we are stuck in an endless loop!
1545
if (isatty(0) && CUtils::GetBoolInput("Would you like to create a new pem file?", true)) {
1552
CUtils::PrintAction("Binding to port [+" + CString(uPort) + "]" + sHostComment + sIPV6Comment);
1556
sError = "Invalid port";
1557
CUtils::PrintStatus(false, sError);
1561
CListener* pListener = new CListener(uPort, sBindHost, bSSL, eAddr, eAccept);
1563
if (!pListener->Listen()) {
1564
sError = (errno == 0 ? CString("unknown error, check the host name") : CString(strerror(errno)));
1565
sError = "Unable to bind [" + sError + "]";
1566
CUtils::PrintStatus(false, sError);
1571
m_vpListeners.push_back(pListener);
1572
CUtils::PrintStatus(true);
1575
} else if (sName.Equals("LoadModule")) {
1576
CString sModName = sValue.Token(0);
1577
CString sArgs = sValue.Token(1, true);
1579
if (msModules.find(sModName) != msModules.end()) {
1580
sError = "Module [" + sModName +
1582
CUtils::PrintError(sError);
1585
msModules[sModName] = sArgs;
1587
} else if (sName.Equals("ISpoofFormat")) {
1588
m_sISpoofFormat = sValue;
1590
} else if (sName.Equals("ISpoofFile")) {
1591
if (sValue.Left(2) == "~/") {
1592
sValue.LeftChomp(2);
1593
sValue = GetHomePath() + "/" + sValue;
1595
m_sISpoofFile = sValue;
1597
} else if (sName.Equals("MOTD")) {
1600
} else if (sName.Equals("VHost")) {
1603
} else if (sName.Equals("PidFile")) {
1604
m_sPidFile = sValue;
1606
} else if (sName.Equals("Skin")) {
1607
SetSkinName(sValue);
1609
} else if (sName.Equals("StatusPrefix")) {
1610
m_sStatusPrefix = sValue;
1612
} else if (sName.Equals("ConnectDelay")) {
1613
m_uiConnectDelay = sValue.ToUInt();
1615
} else if (sName.Equals("ServerThrottle")) {
1616
m_sConnectThrottle.SetTTL(sValue.ToUInt()*1000);
1618
} else if (sName.Equals("AnonIPLimit")) {
1619
m_uiAnonIPLimit = sValue.ToUInt();
1626
if (sName.Equals("GM:", false, 3))
1627
{ // GM: prefix is a pass through to config lines for global modules
1628
CGlobalModuleConfigLine cTmp;
1629
cTmp.m_sName = sName.substr(3, CString::npos);
1630
cTmp.m_sValue = sValue;
1631
cTmp.m_pChan = pChan;
1632
cTmp.m_pUser = pUser;
1633
lGlobalModuleConfigLine.push_back(cTmp);
1637
sError = "Unhandled line " + CString(uLineNum) + " in config: [" + sLine + "]";
1638
CUtils::PrintError(sError);
1643
// First step: Load and reload new modules or modules with new arguments
1644
for (MCString::iterator it = msModules.begin(); it != msModules.end(); ++it) {
1645
CString sModName = it->first;
1646
CString sArgs = it->second;
1650
pOldMod = GetModules().FindModule(sModName);
1652
CUtils::PrintAction("Loading Global Module [" + sModName + "]");
1654
bool bModRet = GetModules().LoadModule(sModName, sArgs, NULL, sModRet);
1656
// If the module was loaded, sModRet contains
1657
// "Loaded Module [name] ..." and we strip away this beginning.
1659
sModRet = sModRet.Token(1, true, sModName + "] ");
1661
CUtils::PrintStatus(bModRet, sModRet);
1666
} else if (pOldMod->GetArgs() != sArgs) {
1667
CUtils::PrintAction("Reloading Global Module [" + sModName + "]");
1669
bool bModRet = GetModules().ReloadModule(sModName, sArgs, NULL, sModRet);
1671
// If the module was loaded, sModRet contains
1672
// "Loaded Module [name] ..." and we strip away this beginning.
1674
sModRet = sModRet.Token(1, true, sModName + "] ");
1676
CUtils::PrintStatus(bModRet, sModRet);
1682
CUtils::PrintMessage("Module [" + sModName + "] already loaded.");
1685
// Second step: Unload modules which are no longer in the config
1686
set<CString> ssUnload;
1687
for (size_t i = 0; i < GetModules().size(); i++) {
1688
CModule *pCurMod = GetModules()[i];
1690
if (msModules.find(pCurMod->GetModName()) == msModules.end())
1691
ssUnload.insert(pCurMod->GetModName());
1694
for (set<CString>::iterator it = ssUnload.begin(); it != ssUnload.end(); ++it) {
1695
if (GetModules().UnloadModule(*it))
1696
CUtils::PrintMessage("Unloaded Global Module [" + *it + "]");
1698
CUtils::PrintMessage("Could not unload [" + *it + "]");
1701
// last step, throw unhandled config items at global config
1702
for (std::list<CGlobalModuleConfigLine>::iterator it = lGlobalModuleConfigLine.begin(); it != lGlobalModuleConfigLine.end(); ++it)
1704
if ((pChan && pChan == it->m_pChan) || (pUser && pUser == it->m_pUser))
1705
continue; // skip unclosed user or chan
1706
bool bHandled = false;
1708
MODULECALL(OnConfigLine(it->m_sName, it->m_sValue, it->m_pUser, it->m_pChan), it->m_pUser, NULL, bHandled = true);
1710
bHandled = GetModules().OnConfigLine(it->m_sName, it->m_sValue, it->m_pUser, it->m_pChan);
1713
CUtils::PrintMessage("unhandled global module config line [GM:" + it->m_sName + "] = [" + it->m_sValue + "]");
1718
// TODO last <Chan> not closed
1723
// TODO last <User> not closed
1727
if (m_msUsers.empty()) {
1728
sError = "You must define at least one user in your config.";
1729
CUtils::PrintError(sError);
1733
if (m_vpListeners.empty()) {
1734
sError = "You must supply at least one Listen port in your config.";
1735
CUtils::PrintError(sError);
1739
// Make sure that users that want to connect do so and also make sure a
1740
// new ConnectDelay setting is applied.
1741
DisableConnectUser();
1742
EnableConnectUser();
1747
void CZNC::ClearVHosts() {
1751
bool CZNC::AddVHost(const CString& sHost) {
1752
if (sHost.empty()) {
1756
for (unsigned int a = 0; a < m_vsVHosts.size(); a++) {
1757
if (m_vsVHosts[a].Equals(sHost)) {
1762
m_vsVHosts.push_back(sHost);
1766
bool CZNC::RemVHost(const CString& sHost) {
1767
VCString::iterator it;
1768
for (it = m_vsVHosts.begin(); it != m_vsVHosts.end(); ++it) {
1769
if (sHost.Equals(*it)) {
1770
m_vsVHosts.erase(it);
1778
void CZNC::Broadcast(const CString& sMessage, bool bAdminOnly,
1779
CUser* pSkipUser, CClient *pSkipClient) {
1780
for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
1781
if (bAdminOnly && !a->second->IsAdmin())
1784
if (a->second != pSkipUser) {
1785
CString sMsg = sMessage;
1787
MODULECALL(OnBroadcast(sMsg), a->second, NULL, continue);
1788
a->second->PutStatusNotice("*** " + sMsg, NULL, pSkipClient);
1793
CModule* CZNC::FindModule(const CString& sModName, const CString& sUsername) {
1794
if (sUsername.empty()) {
1795
return CZNC::Get().GetModules().FindModule(sModName);
1798
CUser* pUser = FindUser(sUsername);
1800
return (!pUser) ? NULL : pUser->GetModules().FindModule(sModName);
1803
CModule* CZNC::FindModule(const CString& sModName, CUser* pUser) {
1805
return pUser->GetModules().FindModule(sModName);
1808
return CZNC::Get().GetModules().FindModule(sModName);
1811
CUser* CZNC::FindUser(const CString& sUsername) {
1812
map<CString,CUser*>::iterator it = m_msUsers.find(sUsername);
1814
if (it != m_msUsers.end()) {
1821
bool CZNC::DeleteUser(const CString& sUsername) {
1822
CUser* pUser = FindUser(sUsername);
1828
m_msDelUsers[pUser->GetUserName()] = pUser;
1832
bool CZNC::AddUser(CUser* pUser, CString& sErrorRet) {
1833
if (FindUser(pUser->GetUserName()) != NULL) {
1834
sErrorRet = "User already exists";
1835
DEBUG("User [" << pUser->GetUserName() << "] - already exists");
1838
if (!pUser->IsValid(sErrorRet)) {
1839
DEBUG("Invalid user [" << pUser->GetUserName() << "] - ["
1840
<< sErrorRet << "]");
1843
if (GetModules().OnAddUser(*pUser, sErrorRet)) {
1844
DEBUG("AddUser [" << pUser->GetUserName() << "] aborted by a module ["
1845
<< sErrorRet << "]");
1848
m_msUsers[pUser->GetUserName()] = pUser;
1852
CListener* CZNC::FindListener(u_short uPort, const CString& sBindHost, EAddrType eAddr) {
1853
vector<CListener*>::iterator it;
1855
for (it = m_vpListeners.begin(); it < m_vpListeners.end(); ++it) {
1856
if ((*it)->GetPort() != uPort)
1858
if ((*it)->GetBindHost() != sBindHost)
1860
if ((*it)->GetAddrType() != eAddr)
1867
bool CZNC::AddListener(CListener* pListener) {
1868
if (!pListener->GetRealListener()) {
1869
// Listener doesnt actually listen
1874
// We don't check if there is an identical listener already listening
1875
// since one can't listen on e.g. the same port multiple times
1877
m_vpListeners.push_back(pListener);
1881
bool CZNC::DelListener(CListener* pListener) {
1882
vector<CListener*>::iterator it;
1884
for (it = m_vpListeners.begin(); it < m_vpListeners.end(); ++it) {
1885
if (*it == pListener) {
1886
m_vpListeners.erase(it);
1896
static CZNC* pZNC = new CZNC;
1900
CZNC::TrafficStatsMap CZNC::GetTrafficStats(TrafficStatsPair &Users,
1901
TrafficStatsPair &ZNC, TrafficStatsPair &Total) {
1902
TrafficStatsMap ret;
1903
unsigned long long uiUsers_in, uiUsers_out, uiZNC_in, uiZNC_out;
1904
const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
1906
uiUsers_in = uiUsers_out = 0;
1907
uiZNC_in = BytesRead();
1908
uiZNC_out = BytesWritten();
1910
for (map<CString, CUser*>::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it) {
1911
ret[it->first] = TrafficStatsPair(it->second->BytesRead(), it->second->BytesWritten());
1912
uiUsers_in += it->second->BytesRead();
1913
uiUsers_out += it->second->BytesWritten();
1916
for (CSockManager::const_iterator it = m_Manager.begin(); it != m_Manager.end(); ++it) {
1917
if ((*it)->GetSockName().Left(5) == "IRC::") {
1918
CIRCSock *p = (CIRCSock *) *it;
1919
ret[p->GetUser()->GetUserName()].first += p->GetBytesRead();
1920
ret[p->GetUser()->GetUserName()].second += p->GetBytesWritten();
1921
uiUsers_in += p->GetBytesRead();
1922
uiUsers_out += p->GetBytesWritten();
1923
} else if ((*it)->GetSockName().Left(5) == "USR::") {
1924
CClient *p = (CClient *) *it;
1925
ret[p->GetUser()->GetUserName()].first += p->GetBytesRead();
1926
ret[p->GetUser()->GetUserName()].second += p->GetBytesWritten();
1927
uiUsers_in += p->GetBytesRead();
1928
uiUsers_out += p->GetBytesWritten();
1930
uiZNC_in += (*it)->GetBytesRead();
1931
uiZNC_out += (*it)->GetBytesWritten();
1935
Users = TrafficStatsPair(uiUsers_in, uiUsers_out);
1936
ZNC = TrafficStatsPair(uiZNC_in, uiZNC_out);
1937
Total = TrafficStatsPair(uiUsers_in + uiZNC_in, uiUsers_out + uiZNC_out);
1942
void CZNC::AuthUser(CSmartPtr<CAuthBase> AuthClass) {
1943
// TODO unless the auth module calls it, CUser::IsHostAllowed() is not honoured
1944
if (GetModules().OnLoginAttempt(AuthClass)) {
1948
CUser* pUser = GetUser(AuthClass->GetUsername());
1950
if (!pUser || !pUser->CheckPass(AuthClass->GetPassword())) {
1951
AuthClass->RefuseLogin("Invalid Password");
1955
CString sHost = AuthClass->GetRemoteIP();
1957
if (!pUser->IsHostAllowed(sHost)) {
1958
AuthClass->RefuseLogin("Your host [" + sHost + "] is not allowed");
1962
AuthClass->AcceptLogin(*pUser);
1965
class CConnectUserTimer : public CCron {
1967
CConnectUserTimer(int iSecs) : CCron() {
1968
SetName("Connect users");
1970
m_uiPosNextUser = 0;
1971
// Don't wait iSecs seconds for first timer run
1972
m_bRunOnNextCall = true;
1974
virtual ~CConnectUserTimer() {
1975
// This is only needed when ZNC shuts down:
1976
// CZNC::~CZNC() sets its CConnectUserTimer pointer to NULL and
1977
// calls the manager's Cleanup() which destroys all sockets and
1978
// timers. If something calls CZNC::EnableConnectUser() here
1979
// (e.g. because a CIRCSock is destroyed), the socket manager
1980
// deletes that timer almost immediately, but CZNC now got a
1981
// dangling pointer to this timer which can crash later on.
1983
// Unlikely but possible ;)
1984
CZNC::Get().LeakConnectUser(this);
1988
virtual void RunJob() {
1989
unsigned int uiUserCount;
1990
bool bUsersLeft = false;
1991
const map<CString,CUser*>& mUsers = CZNC::Get().GetUserMap();
1992
map<CString,CUser*>::const_iterator it = mUsers.begin();
1994
uiUserCount = CZNC::Get().GetUserMap().size();
1996
if (m_uiPosNextUser >= uiUserCount) {
1997
m_uiPosNextUser = 0;
2000
for (unsigned int i = 0; i < m_uiPosNextUser; i++) {
2004
// Try to connect each user, if this doesnt work, abort
2005
for (unsigned int i = 0; i < uiUserCount; i++) {
2006
if (it == mUsers.end())
2007
it = mUsers.begin();
2009
CUser* pUser = it->second;
2011
m_uiPosNextUser = (m_uiPosNextUser + 1) % uiUserCount;
2013
// Is this user disconnected?
2014
if (pUser->GetIRCSock() != NULL)
2017
// Does this user want to connect?
2018
if (!pUser->GetIRCConnectEnabled())
2021
// Does this user have any servers?
2022
if (!pUser->HasServers())
2025
// The timer runs until it once didn't find any users to connect
2028
DEBUG("Connecting user [" << pUser->GetUserName() << "]");
2030
if (CZNC::Get().ConnectUser(pUser))
2031
// User connecting, wait until next time timer fires
2035
if (bUsersLeft == false) {
2036
DEBUG("ConnectUserTimer done");
2037
CZNC::Get().DisableConnectUser();
2042
size_t m_uiPosNextUser;
2045
void CZNC::EnableConnectUser() {
2046
if (m_pConnectUserTimer != NULL)
2049
m_pConnectUserTimer = new CConnectUserTimer(m_uiConnectDelay);
2050
GetManager().AddCron(m_pConnectUserTimer);
2053
void CZNC::DisableConnectUser() {
2054
if (m_pConnectUserTimer == NULL)
2057
// This will kill the cron
2058
m_pConnectUserTimer->Stop();
2059
m_pConnectUserTimer = NULL;
2062
void CZNC::LeakConnectUser(CConnectUserTimer *pTimer) {
2063
if (m_pConnectUserTimer == pTimer)
2064
m_pConnectUserTimer = NULL;