Merge branch 'master' into unix

This commit is contained in:
Alexey Sokolov
2025-04-18 23:46:16 +01:00
1234 changed files with 149930 additions and 10349 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -52,7 +52,7 @@ CMessage CBufLine::ToMessage(const CClient& Client,
mssThisParams["text"] = m_sText;
} else {
mssThisParams["text"] =
Client.GetUser()->AddTimestamp(Line.GetTime().tv_sec, m_sText);
Client.GetUser()->AddTimestamp(Line.GetTime(), m_sText);
}
// make a copy of params, because the following loop modifies the original
+25 -24
View File
@@ -1,5 +1,5 @@
#
# Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
# Copyright (C) 2004-2025 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.
@@ -14,18 +14,14 @@
# limitations under the License.
#
if(CMAKE_VERSION VERSION_LESS 3.2)
# Since 3.2 it does this automatically from BYPRODUCTS
set_source_files_properties("versionc.cpp" PROPERTIES GENERATED true)
endif()
set(znc_cpp "ZNCString.cpp" "znc.cpp" "IRCNetwork.cpp" "Translation.cpp"
"IRCSock.cpp" "Client.cpp" "Chan.cpp" "Nick.cpp" "Server.cpp"
"Modules.cpp" "MD5.cpp" "Buffer.cpp" "Utils.cpp" "FileUtils.cpp"
"HTTPSock.cpp" "Template.cpp" "ClientCommand.cpp" "Socket.cpp"
"SHA256.cpp" "WebModules.cpp" "Listener.cpp" "Config.cpp" "ZNCDebug.cpp"
"Threads.cpp" "Query.cpp" "SSLVerifyHost.cpp" "Message.cpp" "User.cpp")
znc_add_library(znclib ${lib_type} ${znc_cpp} "Csocket.cpp" "versionc.cpp")
znc_add_library(znclib ${lib_type} ${znc_cpp} "Csocket.cpp" "versionc.cpp"
${cctz_cc})
znc_add_executable(znc "main.cpp")
target_link_libraries(znc PRIVATE znclib)
@@ -63,23 +59,18 @@ set(znc_include_dirs
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_FULL_INCLUDEDIR}>")
target_link_libraries(znclib ${CMAKE_DL_LIBS} Threads::Threads)
if(OPENSSL_FOUND)
target_link_libraries(znclib ${OPENSSL_LIBRARIES})
list(APPEND znc_include_dirs "${OPENSSL_INCLUDE_DIR}")
endif()
target_link_libraries(znclib PRIVATE ${CMAKE_DL_LIBS} Threads::Threads)
if(ZLIB_FOUND)
target_link_libraries(znclib ${ZLIB_LIBRARIES})
list(APPEND znc_include_dirs ${ZLIB_INCLUDE_DIRS})
endif()
if(ICU_FOUND)
target_link_libraries(znclib ${ICU_LDFLAGS})
list(APPEND znc_include_dirs ${ICU_INCLUDE_DIRS})
target_link_libraries(znclib PRIVATE ZLIB::ZLIB)
endif()
if(Boost_FOUND)
target_link_libraries(znclib ${Boost_LIBRARIES})
list(APPEND znc_include_dirs ${Boost_INCLUDE_DIRS})
target_link_libraries(znclib PRIVATE Boost::locale)
endif()
if(ZNC_HAVE_ARGON)
target_link_libraries(znclib PRIVATE PkgConfig::ARGON)
endif()
target_link_libraries(znclib PUBLIC ${zncpubdeps})
target_link_libraries(znclib PRIVATE cctz::cctz)
target_include_directories(znc PUBLIC ${znc_include_dirs})
target_include_directories(znclib PUBLIC ${znc_include_dirs})
@@ -93,7 +84,13 @@ set_target_properties(znclib ${znc_link} PROPERTIES
# The following options are relevant only on cygwin
set_target_properties(znclib PROPERTIES
OUTPUT_NAME "znc"
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
SOVERSION "${ZNC_VERSION}")
add_library(ZNC INTERFACE)
target_link_libraries(ZNC INTERFACE ${znc_link} ${zncpubdeps})
target_compile_definitions(ZNC INTERFACE "znc_export_lib_EXPORTS")
target_compile_features(ZNC INTERFACE cxx_std_17)
target_compile_features(znclib PUBLIC cxx_std_17)
if(HAVE_I18N)
add_subdirectory(po)
@@ -101,11 +98,15 @@ endif()
install(TARGETS znc ${install_lib}
EXPORT znc
EXPORT znc_internal
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
# this one is libznc.dll.a for cygwin
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(EXPORT znc
install(TARGETS ZNC EXPORT znc_public)
install(EXPORT znc_internal
DESTINATION "${CMAKE_INSTALL_DATADIR}/znc/cmake"
NAMESPACE znc_internal_)
NAMESPACE ZNC::internal::)
install(EXPORT znc_public
DESTINATION "${CMAKE_INSTALL_DATADIR}/znc/cmake"
NAMESPACE ZNC::)
+98 -57
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -48,7 +48,7 @@ CChan::CChan(const CString& sName, CIRCNetwork* pNetwork, bool bInConfig,
m_msNicks(),
m_Buffer(),
m_bModeKnown(false),
m_musModes() {
m_mcsModes() {
if (!m_pNetwork->IsChan(m_sName)) {
m_sName = "#" + m_sName;
}
@@ -85,7 +85,7 @@ CChan::~CChan() { ClearNicks(); }
void CChan::Reset() {
m_bIsOn = false;
m_bModeKnown = false;
m_musModes.clear();
m_mcsModes.clear();
m_sTopic = "";
m_sTopicOwner = "";
m_ulTopicDate = 0;
@@ -156,11 +156,13 @@ void CChan::AttachUser(CClient* pClient) {
m_pNetwork->GetIRCNick().GetNick() + " " +
GetName() + " :" + GetTopic(),
pClient);
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 333 " +
m_pNetwork->GetIRCNick().GetNick() + " " +
GetName() + " " + GetTopicOwner() + " " +
CString(GetTopicDate()),
pClient);
if (!GetTopicOwner().empty()) {
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 333 " +
m_pNetwork->GetIRCNick().GetNick() + " " +
GetName() + " " + GetTopicOwner() + " " +
CString(GetTopicDate()),
pClient);
}
}
CString sPre = ":" + m_pNetwork->GetIRCServer() + " 353 " +
@@ -231,7 +233,7 @@ void CChan::DetachUser() {
CString CChan::GetModeString() const {
CString sModes, sArgs;
for (const auto& it : m_musModes) {
for (const auto& it : m_mcsModes) {
sModes += it.first;
if (it.second.size()) {
sArgs += " " + it.second;
@@ -244,7 +246,7 @@ CString CChan::GetModeString() const {
CString CChan::GetModeForNames() const {
CString sMode;
for (const auto& it : m_musModes) {
for (const auto& it : m_mcsModes) {
if (it.first == 's') {
sMode = "@";
} else if ((it.first == 'p') && sMode.empty()) {
@@ -256,10 +258,15 @@ CString CChan::GetModeForNames() const {
}
void CChan::SetModes(const CString& sModes) {
m_musModes.clear();
m_mcsModes.clear();
ModeChange(sModes);
}
void CChan::SetModes(const CString& modes, const VCString& vsModeParams) {
m_mcsModes.clear();
ModeChange(modes, vsModeParams);
}
void CChan::SetAutoClearChanBuffer(bool b) {
m_bHasAutoClearChanBufferSet = true;
m_bAutoClearChanBuffer = b;
@@ -295,9 +302,8 @@ void CChan::OnWho(const CString& sNick, const CString& sIdent,
}
}
void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) {
CString sModeArg = sModes.Token(0);
CString sArgs = sModes.Token(1, true);
void CChan::ModeChange(const CString& sModes, const VCString& vsModes,
const CNick* pOpNick) {
bool bAdd = true;
/* Try to find a CNick* from this channel so that pOpNick->HasPerm()
@@ -309,46 +315,56 @@ void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) {
if (OpNick) pOpNick = OpNick;
}
NETWORKMODULECALL(OnRawMode2(pOpNick, *this, sModeArg, sArgs),
m_pNetwork->GetUser(), m_pNetwork, nullptr, NOTHING);
{
CString sArgs = CString(" ").Join(vsModes.begin(), vsModes.end());
NETWORKMODULECALL(OnRawMode2(pOpNick, *this, sModes, sArgs),
m_pNetwork->GetUser(), m_pNetwork, nullptr, NOTHING);
}
for (unsigned int a = 0; a < sModeArg.size(); a++) {
const unsigned char& uMode = sModeArg[a];
VCString::const_iterator argIter = vsModes.begin();
const CString sEmpty;
auto nextArg = [&]() -> const CString& {
if (argIter == vsModes.end()) {
return sEmpty;
}
return *argIter++;
};
for (unsigned int a = 0; a < sModes.size(); a++) {
const char& cMode = sModes[a];
if (uMode == '+') {
if (cMode == '+') {
bAdd = true;
} else if (uMode == '-') {
} else if (cMode == '-') {
bAdd = false;
} else if (m_pNetwork->GetIRCSock()->IsPermMode(uMode)) {
CString sArg = GetModeArg(sArgs);
} else if (m_pNetwork->GetIRCSock()->IsPermMode(cMode)) {
const CString& sArg = nextArg();
CNick* pNick = FindNick(sArg);
if (pNick) {
unsigned char uPerm =
m_pNetwork->GetIRCSock()->GetPermFromMode(uMode);
char cPerm = m_pNetwork->GetIRCSock()->GetPermFromMode(cMode);
if (uPerm) {
bool bNoChange = (pNick->HasPerm(uPerm) == bAdd);
if (cPerm) {
bool bNoChange = (pNick->HasPerm(cPerm) == bAdd);
if (bAdd) {
pNick->AddPerm(uPerm);
pNick->AddPerm(cPerm);
if (pNick->NickEquals(m_pNetwork->GetCurNick())) {
AddPerm(uPerm);
AddPerm(cPerm);
}
} else {
pNick->RemPerm(uPerm);
pNick->RemPerm(cPerm);
if (pNick->NickEquals(m_pNetwork->GetCurNick())) {
RemPerm(uPerm);
RemPerm(cPerm);
}
}
NETWORKMODULECALL(OnChanPermission2(pOpNick, *pNick, *this,
uMode, bAdd, bNoChange),
NETWORKMODULECALL(OnChanPermission3(pOpNick, *pNick, *this,
cMode, bAdd, bNoChange),
m_pNetwork->GetUser(), m_pNetwork,
nullptr, NOTHING);
if (uMode == CChan::M_Op) {
if (cMode == CChan::M_Op) {
if (bAdd) {
NETWORKMODULECALL(
OnOp2(pOpNick, *pNick, *this, bNoChange),
@@ -360,7 +376,7 @@ void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) {
m_pNetwork->GetUser(), m_pNetwork, nullptr,
NOTHING);
}
} else if (uMode == CChan::M_Voice) {
} else if (cMode == CChan::M_Voice) {
if (bAdd) {
NETWORKMODULECALL(
OnVoice2(pOpNick, *pNick, *this, bNoChange),
@@ -379,19 +395,19 @@ void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) {
bool bList = false;
CString sArg;
switch (m_pNetwork->GetIRCSock()->GetModeType(uMode)) {
switch (m_pNetwork->GetIRCSock()->GetModeType(cMode)) {
case CIRCSock::ListArg:
bList = true;
sArg = GetModeArg(sArgs);
sArg = nextArg();
break;
case CIRCSock::HasArg:
sArg = GetModeArg(sArgs);
sArg = nextArg();
break;
case CIRCSock::NoArg:
break;
case CIRCSock::ArgWhenSet:
if (bAdd) {
sArg = GetModeArg(sArgs);
sArg = nextArg();
}
break;
@@ -401,28 +417,54 @@ void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) {
if (bList) {
bNoChange = false;
} else if (bAdd) {
bNoChange = HasMode(uMode) && GetModeArg(uMode) == sArg;
bNoChange = HasMode(cMode) && GetModeArg(cMode) == sArg;
} else {
bNoChange = !HasMode(uMode);
bNoChange = !HasMode(cMode);
}
NETWORKMODULECALL(
OnMode2(pOpNick, *this, uMode, sArg, bAdd, bNoChange),
OnMode2(pOpNick, *this, cMode, sArg, bAdd, bNoChange),
m_pNetwork->GetUser(), m_pNetwork, nullptr, NOTHING);
if (!bList) {
(bAdd) ? AddMode(uMode, sArg) : RemMode(uMode);
(bAdd) ? AddMode(cMode, sArg) : RemMode(cMode);
}
// This is called when we join (ZNC requests the channel modes
// on join) *and* when someone changes the channel keys.
// We ignore channel key "*" because of some broken nets.
if (uMode == M_Key && !bNoChange && bAdd && sArg != "*") {
if (cMode == M_Key && !bNoChange && bAdd && sArg != "*") {
SetKey(sArg);
}
}
}
}
void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) {
VCString vsModes;
CString sModeArg = sModes.Token(0);
bool colon = sModeArg.TrimPrefix(":");
// Only handle parameters if sModes doesn't start with a colon
// because if it does, we only have the mode string with no parameters
if (!colon) {
CString sArgs = sModes.Token(1, true);
while (!sArgs.empty()) {
// Check if this parameter is a trailing parameter
// If so, treat the rest of sArgs as one parameter
if (sArgs.TrimPrefix(":")) {
vsModes.push_back(sArgs);
sArgs.clear();
} else {
vsModes.push_back(sArgs.Token(0));
sArgs = sArgs.Token(1, true);
}
}
}
ModeChange(sModeArg, vsModes, pOpNick);
}
CString CChan::GetOptions() const {
VCString vsRet;
@@ -441,11 +483,11 @@ CString CChan::GetOptions() const {
return CString(", ").Join(vsRet.begin(), vsRet.end());
}
CString CChan::GetModeArg(unsigned char uMode) const {
if (uMode) {
map<unsigned char, CString>::const_iterator it = m_musModes.find(uMode);
CString CChan::GetModeArg(char cMode) const {
if (cMode) {
map<char, CString>::const_iterator it = m_mcsModes.find(cMode);
if (it != m_musModes.end()) {
if (it != m_mcsModes.end()) {
return it->second;
}
}
@@ -453,21 +495,21 @@ CString CChan::GetModeArg(unsigned char uMode) const {
return "";
}
bool CChan::HasMode(unsigned char uMode) const {
return (uMode && m_musModes.find(uMode) != m_musModes.end());
bool CChan::HasMode(char cMode) const {
return (cMode && m_mcsModes.find(cMode) != m_mcsModes.end());
}
bool CChan::AddMode(unsigned char uMode, const CString& sArg) {
m_musModes[uMode] = sArg;
bool CChan::AddMode(char cMode, const CString& sArg) {
m_mcsModes[cMode] = sArg;
return true;
}
bool CChan::RemMode(unsigned char uMode) {
if (!HasMode(uMode)) {
bool CChan::RemMode(char cMode) {
if (!HasMode(cMode)) {
return false;
}
m_musModes.erase(uMode);
m_mcsModes.erase(cMode);
return true;
}
@@ -556,7 +598,6 @@ map<char, unsigned int> CChan::GetPermCounts() const {
bool CChan::RemNick(const CString& sNick) {
map<CString, CNick>::iterator it;
set<unsigned char>::iterator it2;
it = m_msNicks.find(sNick);
if (it == m_msNicks.end()) {
@@ -636,7 +677,7 @@ void CChan::SendBuffer(CClient* pClient, const CBuffer& Buffer) {
if (!bSkipStatusMsg) {
m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + GetName() +
" :Buffer Playback...",
" :" + t_s("Buffer Playback..."),
pUseClient);
}
@@ -674,7 +715,7 @@ void CChan::SendBuffer(CClient* pClient, const CBuffer& Buffer) {
&bSkipStatusMsg);
if (!bSkipStatusMsg) {
m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + GetName() +
" :Playback Complete.",
" :" + t_s("Playback Complete."),
pUseClient);
}
+605 -159
View File
File diff suppressed because it is too large Load Diff
+782 -614
View File
File diff suppressed because it is too large Load Diff
+18 -8
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -68,6 +68,7 @@ bool CConfig::Parse(CFile& file, CString& sErrorMsg) {
std::stringstream stream; \
stream << "Error on line " << uLineNum << ": " << arg; \
sErrorMsg = stream.str(); \
m_SubConfigNameSets.clear(); \
m_SubConfigs.clear(); \
m_ConfigEntries.clear(); \
return false; \
@@ -122,14 +123,16 @@ bool CConfig::Parse(CFile& file, CString& sErrorMsg) {
else
pActiveConfig = &ConfigStack.top().Config;
SubConfig& conf = pActiveConfig->m_SubConfigs[sTag.AsLower()];
SubConfig::const_iterator it = conf.find(sName);
const auto sTagLower = sTag.AsLower();
auto& nameset = pActiveConfig->m_SubConfigNameSets[sTagLower];
if (it != conf.end())
if (nameset.find(sName) != nameset.end())
ERROR("Duplicate entry for tag \"" << sTag << "\" name \""
<< sName << "\".");
conf[sName] = CConfigEntry(myConfig);
nameset.insert(sName);
pActiveConfig->m_SubConfigs[sTagLower].emplace_back(sName,
myConfig);
} else {
if (sValue.empty())
ERROR("Empty block name at begin of block.");
@@ -174,9 +177,14 @@ bool CConfig::Parse(CFile& file, CString& sErrorMsg) {
void CConfig::Write(CFile& File, unsigned int iIndentation) {
CString sIndentation = CString(iIndentation, '\t');
auto SingleLine = [](const CString& s) {
return s.Replace_n("\r", "").Replace_n("\n", "");
};
for (const auto& it : m_ConfigEntries) {
for (const CString& sValue : it.second) {
File.Write(sIndentation + it.first + " = " + sValue + "\n");
File.Write(SingleLine(sIndentation + it.first + " = " + sValue) +
"\n");
}
}
@@ -184,9 +192,11 @@ void CConfig::Write(CFile& File, unsigned int iIndentation) {
for (const auto& it2 : it.second) {
File.Write("\n");
File.Write(sIndentation + "<" + it.first + " " + it2.first + ">\n");
File.Write(SingleLine(sIndentation + "<" + it.first + " " +
it2.first + ">") +
"\n");
it2.second.m_pSubConfig->Write(File, iIndentation + 1);
File.Write(sIndentation + "</" + it.first + ">\n");
File.Write(SingleLine(sIndentation + "</" + it.first + ">") + "\n");
}
}
}
+6 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -352,11 +352,11 @@ bool CFile::Open(int iFlags, mode_t iMode) {
}
// We never want to get a controlling TTY through this -> O_NOCTTY
iMode |= O_NOCTTY;
iFlags |= O_NOCTTY;
// Some weird OS from MS needs O_BINARY or else it generates fake EOFs
// when reading ^Z from a file.
iMode |= O_BINARY;
iFlags |= O_BINARY;
m_iFD = open(m_sLongName.c_str(), iFlags, iMode);
if (m_iFD < 0) {
@@ -645,6 +645,9 @@ int CExecSock::popen2(int& iReadFD, int& iWriteFD, const CString& sCommand) {
}
if (iPid == 0) {
sigset_t signals;
sigemptyset(&signals);
sigprocmask(SIG_SETMASK, &signals, nullptr);
close(wpipes[1]);
close(rpipes[0]);
dup2(wpipes[0], 0);
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
+123 -40
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -23,7 +23,6 @@
#include <znc/Chan.h>
#include <znc/Query.h>
#include <znc/Message.h>
#include <znc/ZNCDebug.h>
#include <algorithm>
#include <memory>
@@ -35,7 +34,7 @@ class CIRCNetworkPingTimer : public CCron {
CIRCNetworkPingTimer(CIRCNetwork* pNetwork)
: CCron(), m_pNetwork(pNetwork) {
SetName("CIRCNetworkPingTimer::" +
m_pNetwork->GetUser()->GetUserName() + "::" +
m_pNetwork->GetUser()->GetUsername() + "::" +
m_pNetwork->GetName());
Start(m_pNetwork->GetUser()->GetPingSlack());
}
@@ -78,7 +77,7 @@ class CIRCNetworkJoinTimer : public CCron {
CIRCNetworkJoinTimer(CIRCNetwork* pNetwork)
: CCron(), m_bDelayed(false), m_pNetwork(pNetwork) {
SetName("CIRCNetworkJoinTimer::" +
m_pNetwork->GetUser()->GetUserName() + "::" +
m_pNetwork->GetUser()->GetUsername() + "::" +
m_pNetwork->GetName());
Start(JOIN_FREQUENCY);
}
@@ -233,7 +232,8 @@ void CIRCNetwork::Clone(const CIRCNetwork& Network, bool bCloneName) {
if (pSock) {
PutStatus(
"Jumping servers because this server is no longer in the list");
t_s("Jumping servers because this server is no longer in the "
"list"));
pSock->Quit();
}
}
@@ -363,11 +363,13 @@ CString CIRCNetwork::GetNetworkPath() const {
return sNetworkPath;
}
namespace {
template <class T>
struct TOption {
const char* name;
void (CIRCNetwork::*pSetter)(T);
};
}
bool CIRCNetwork::ParseConfig(CConfig* pConfig, CString& sError,
bool bUpgrade) {
@@ -456,6 +458,35 @@ bool CIRCNetwork::ParseConfig(CConfig* pConfig, CString& sError,
bool bModRet = LoadModule(sModName, sArgs, sNotice, sModRet);
if (!bModRet) {
// Q is removed in znc 1.8
if (sModName == "q") {
CUtils::PrintError(
"NOTICE: [q] is unavailable, cannot load.");
CUtils::PrintError(
"NOTICE: [q] is removed in this release of ZNC. Please "
"either remove it from your config or install it as a "
"third party module with the same name.");
CUtils::PrintError(
"NOTICE: More info can be found on "
"https://wiki.znc.in/Q");
return false;
}
// Partyline is removed in znc 1.8
if (sModName == "partyline") {
CUtils::PrintError(
"NOTICE: [partyline] is unavailable, cannot load.");
CUtils::PrintError(
"NOTICE: [partyline] is removed in this release"
" of ZNC. Please either remove it from your config or "
"install it as a third party module with the same "
"name.");
CUtils::PrintError(
"NOTICE: More info can be found on "
"https://wiki.znc.in/Partyline");
return false;
}
// XXX The awaynick module was retired in 1.6 (still available
// as external module)
if (sModName == "awaynick") {
@@ -468,7 +499,9 @@ bool CIRCNetwork::ParseConfig(CConfig* pConfig, CString& sError,
}
if (!bFound) {
sNotice =
"Loading network module [simple_away] instead";
"NOTICE: awaynick was retired, loading network "
"module [simple_away] instead; if you still need "
"awaynick, install it as an external module";
sModName = "simple_away";
// not a fatal error if simple_away is not available
LoadModule(sModName, sArgs, sNotice, sModRet);
@@ -482,8 +515,8 @@ bool CIRCNetwork::ParseConfig(CConfig* pConfig, CString& sError,
}
pConfig->FindStringVector("server", vsList);
CUtils::PrintAction("Adding " + CString(vsList.size()) + " servers");
for (const CString& sServer : vsList) {
CUtils::PrintAction("Adding server [" + sServer + "]");
CUtils::PrintStatus(AddServer(sServer));
}
@@ -508,7 +541,7 @@ bool CIRCNetwork::ParseConfig(CConfig* pConfig, CString& sError,
if (!pSubConf->empty()) {
sError = "Unhandled lines in config for User [" +
m_pUser->GetUserName() + "], Network [" + GetName() +
m_pUser->GetUsername() + "], Network [" + GetName() +
"], Channel [" + sChanName + "]!";
CUtils::PrintError(sError);
@@ -627,15 +660,11 @@ void CIRCNetwork::ClientConnected(CClient* pClient) {
size_t uIdx, uSize;
if (m_pIRCSock) {
pClient->NotifyServerDependentCaps(m_pIRCSock->GetAcceptedCaps());
}
pClient->SetPlaybackActive(true);
if (m_RawBuffer.IsEmpty()) {
pClient->PutClient(":irc.znc.in 001 " + pClient->GetNick() +
" :- Welcome to ZNC -");
" :" + t_s("Welcome to ZNC"));
} else {
const CString& sClientNick = pClient->GetNick(false);
MCString msParams;
@@ -667,8 +696,8 @@ void CIRCNetwork::ClientConnected(CClient* pClient) {
if (GetIRCSock() != nullptr) {
CString sUserMode("");
const set<unsigned char>& scUserModes = GetIRCSock()->GetUserModes();
for (unsigned char cMode : scUserModes) {
const set<char>& scUserModes = GetIRCSock()->GetUserModes();
for (char cMode : scUserModes) {
sUserMode += cMode;
}
if (!sUserMode.empty()) {
@@ -723,13 +752,8 @@ void CIRCNetwork::ClientConnected(CClient* pClient) {
// Tell them why they won't connect
if (!GetIRCConnectEnabled())
pClient->PutStatus(
"You are currently disconnected from IRC. "
"Use 'connect' to reconnect.");
if (CDebug::Debug()) {
pClient->PutStatus("ZNC is presently running in DEBUG mode. Sensitive"
" data during your current session may be exposed to the host.");
}
t_s("You are currently disconnected from IRC. Use 'connect' to "
"reconnect."));
}
void CIRCNetwork::ClientDisconnected(CClient* pClient) {
@@ -758,7 +782,7 @@ std::vector<CClient*> CIRCNetwork::FindClients(
void CIRCNetwork::SetUser(CUser* pUser) {
for (CClient* pClient : m_vClients) {
pClient->PutStatus(
"This network is being deleted or moved to another user.");
t_s("This network is being deleted or moved to another user."));
pClient->SetNetwork(nullptr);
}
@@ -916,6 +940,49 @@ bool CIRCNetwork::DelChan(const CString& sName) {
return false;
}
bool CIRCNetwork::MoveChan(const CString& sChan, unsigned int uIndex,
CString& sError) {
if (uIndex >= m_vChans.size()) {
sError = t_s("Invalid index");
return false;
}
auto it = m_vChans.begin();
for (; it != m_vChans.end(); ++it)
if ((*it)->GetName().Equals(sChan)) break;
if (it == m_vChans.end()) {
sError = t_f("You are not on {1}")(sChan);
return false;
}
const auto pChan = *it;
m_vChans.erase(it);
m_vChans.insert(m_vChans.begin() + uIndex, pChan);
return true;
}
bool CIRCNetwork::SwapChans(const CString& sChan1, const CString& sChan2,
CString& sError) {
auto it1 = m_vChans.begin();
for (; it1 != m_vChans.end(); ++it1)
if ((*it1)->GetName().Equals(sChan1)) break;
if (it1 == m_vChans.end()) {
sError = t_f("You are not on {1}")(sChan1);
return false;
}
auto it2 = m_vChans.begin();
for (; it2 != m_vChans.end(); ++it2)
if ((*it2)->GetName().Equals(sChan2)) break;
if (it2 == m_vChans.end()) {
sError = t_f("You are not on {1}")(sChan2);
return false;
}
std::swap(*it1, *it2);
return true;
}
void CIRCNetwork::JoinChans() {
// Avoid divsion by zero, it's bad!
if (m_vChans.empty()) return;
@@ -987,8 +1054,8 @@ bool CIRCNetwork::JoinChan(CChan* pChan) {
if (m_pUser->JoinTries() != 0 &&
pChan->GetJoinTries() >= m_pUser->JoinTries()) {
PutStatus("The channel " + pChan->GetName() +
" could not be joined, disabling it.");
PutStatus(t_f("The channel {1} could not be joined, disabling it.")(
pChan->GetName()));
pChan->Disable();
} else {
pChan->IncJoinTries();
@@ -1116,7 +1183,7 @@ bool CIRCNetwork::DelServer(const CString& sName, unsigned short uPort,
if (pIRCSock) {
pIRCSock->Quit();
PutStatus("Your current server was removed, jumping...");
PutStatus(t_s("Your current server was removed, jumping..."));
}
} else if (!bSawCurrentServer) {
// Our current server comes after the server which we
@@ -1278,8 +1345,9 @@ bool CIRCNetwork::Connect() {
bool bSSL = pServer->IsSSL();
#ifndef HAVE_LIBSSL
if (bSSL) {
PutStatus("Cannot connect to [" + pServer->GetString(false) +
"], ZNC is not compiled with SSL.");
PutStatus(
t_f("Cannot connect to {1}, because ZNC is not compiled with SSL "
"support.")(pServer->GetString(false)));
CZNC::Get().AddNetworkToQueue(this);
return false;
}
@@ -1291,7 +1359,7 @@ bool CIRCNetwork::Connect() {
pIRCSock->SetTrustAllCerts(GetTrustAllCerts());
pIRCSock->SetTrustPKI(GetTrustPKI());
DEBUG("Connecting user/network [" << m_pUser->GetUserName() << "/"
DEBUG("Connecting user/network [" << m_pUser->GetUsername() << "/"
<< m_sName << "]");
bool bAbort = false;
@@ -1299,13 +1367,13 @@ bool CIRCNetwork::Connect() {
&bAbort);
if (bAbort) {
DEBUG("Some module aborted the connection attempt");
PutStatus("Some module aborted the connection attempt");
PutStatus(t_s("Some module aborted the connection attempt"));
delete pIRCSock;
CZNC::Get().AddNetworkToQueue(this);
return false;
}
CString sSockName = "IRC::" + m_pUser->GetUserName() + "::" + m_sName;
CString sSockName = "IRC::" + m_pUser->GetUsername() + "::" + m_sName;
CZNC::Get().GetManager().Connect(pServer->GetName(), pServer->GetPort(),
sSockName, 120, bSSL, GetBindHost(),
pIRCSock);
@@ -1321,10 +1389,6 @@ bool CIRCNetwork::IsIRCConnected() const {
void CIRCNetwork::SetIRCSocket(CIRCSock* pIRCSock) { m_pIRCSock = pIRCSock; }
void CIRCNetwork::IRCConnected() {
const SCString& ssCaps = m_pIRCSock->GetAcceptedCaps();
for (CClient* pClient : m_vClients) {
pClient->NotifyServerDependentCaps(ssCaps);
}
if (m_uJoinDelay > 0) {
m_pJoinTimer->Delay(m_uJoinDelay);
} else {
@@ -1333,9 +1397,6 @@ void CIRCNetwork::IRCConnected() {
}
void CIRCNetwork::IRCDisconnected() {
for (CClient* pClient : m_vClients) {
pClient->ClearServerDependentCaps();
}
m_pIRCSock = nullptr;
SetIRCServer("");
@@ -1345,6 +1406,17 @@ void CIRCNetwork::IRCDisconnected() {
CheckIRCConnect();
}
void CIRCNetwork::NotifyClientsAboutServerDependentCap(const CString& sCap, bool bValue) {
CString sValue = GetIRCSock() ? GetIRCSock()->GetCapLsValue(sCap) : "";
for (CClient* pClient : m_vClients) {
pClient->NotifyServerDependentCap(sCap, bValue, sValue);
}
}
bool CIRCNetwork::IsServerCapAccepted(const CString& sCap) const {
return m_pIRCSock && m_pIRCSock->IsCapAccepted(sCap);
}
void CIRCNetwork::SetIRCConnectEnabled(bool b) {
m_bIRCConnectEnabled = b;
@@ -1376,6 +1448,17 @@ bool CIRCNetwork::PutIRC(const CString& sLine) {
return true;
}
bool CIRCNetwork::PutIRC(const CMessage& Message) {
CIRCSock* pIRCSock = GetIRCSock();
if (!pIRCSock) {
return false;
}
pIRCSock->PutIRC(Message);
return true;
}
void CIRCNetwork::ClearQueryBuffer() {
std::for_each(m_vQueries.begin(), m_vQueries.end(),
std::default_delete<CQuery>());
@@ -1473,9 +1556,9 @@ void CIRCNetwork::SetBindHost(const CString& s) {
}
void CIRCNetwork::SetEncoding(const CString& s) {
m_sEncoding = s;
m_sEncoding = CZNC::Get().FixupEncoding(s);
if (GetIRCSock()) {
GetIRCSock()->SetEncoding(s);
GetIRCSock()->SetEncoding(m_sEncoding);
}
}
+374 -167
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -69,10 +69,11 @@ CIRCSock::CIRCSock(CIRCNetwork* pNetwork)
m_bAccountNotify(false),
m_bExtendedJoin(false),
m_bServerTime(false),
m_bMessageTagCap(false),
m_sPerms("*!@%+"),
m_sPermModes("qaohv"),
m_scUserModes(),
m_mueChanModes(),
m_mceChanModes(),
m_pNetwork(pNetwork),
m_Nick(),
m_sPass(""),
@@ -84,7 +85,7 @@ CIRCSock::CIRCSock(CIRCNetwork* pNetwork)
m_lastCTCP(0),
m_uNumCTCP(0),
m_mISupport(),
m_vsSendQueue(),
m_vSendQueue(),
m_iSendsAllowed(pNetwork->GetFloodBurst()),
m_uFloodBurst(pNetwork->GetFloodBurst()),
m_fFloodRate(pNetwork->GetFloodRate()),
@@ -94,16 +95,16 @@ CIRCSock::CIRCSock(CIRCNetwork* pNetwork)
m_Nick.SetHost(m_pNetwork->GetBindHost());
SetEncoding(m_pNetwork->GetEncoding());
m_mueChanModes['b'] = ListArg;
m_mueChanModes['e'] = ListArg;
m_mueChanModes['I'] = ListArg;
m_mueChanModes['k'] = HasArg;
m_mueChanModes['l'] = ArgWhenSet;
m_mueChanModes['p'] = NoArg;
m_mueChanModes['s'] = NoArg;
m_mueChanModes['t'] = NoArg;
m_mueChanModes['i'] = NoArg;
m_mueChanModes['n'] = NoArg;
m_mceChanModes['b'] = ListArg;
m_mceChanModes['e'] = ListArg;
m_mceChanModes['I'] = ListArg;
m_mceChanModes['k'] = HasArg;
m_mceChanModes['l'] = ArgWhenSet;
m_mceChanModes['p'] = NoArg;
m_mceChanModes['s'] = NoArg;
m_mceChanModes['t'] = NoArg;
m_mceChanModes['i'] = NoArg;
m_mceChanModes['n'] = NoArg;
pNetwork->SetIRCSocket(this);
@@ -156,9 +157,10 @@ void CIRCSock::Quit(const CString& sQuitMsg) {
void CIRCSock::ReadLine(const CString& sData) {
CString sLine = sData;
sLine.TrimRight("\n\r");
sLine.Replace("\n", "");
sLine.Replace("\r", "");
DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/"
DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/"
<< m_pNetwork->GetName() << ") IRC -> ZNC [" << sLine << "]");
bool bReturn = false;
@@ -184,6 +186,9 @@ void CIRCSock::ReadLine(const CString& sData) {
case CMessage::Type::Capability:
bReturn = OnCapabilityMessage(Message);
break;
case CMessage::Type::ChgHost:
bReturn = OnChgHostMessage(Message);
break;
case CMessage::Type::CTCP:
bReturn = OnCTCPMessage(Message);
break;
@@ -223,6 +228,9 @@ void CIRCSock::ReadLine(const CString& sData) {
case CMessage::Type::Quit:
bReturn = OnQuitMessage(Message);
break;
case CMessage::Type::TagMsg:
bReturn = OnTagMessage(Message);
break;
case CMessage::Type::Text:
bReturn = OnTextMessage(Message);
break;
@@ -244,7 +252,9 @@ void CIRCSock::SendNextCap() {
if (!m_uCapPaused) {
if (m_ssPendingCaps.empty()) {
// We already got all needed ACK/NAK replies.
PutIRC("CAP END");
if (!m_bAuthed) {
PutIRC("CAP END");
}
} else {
CString sCap = *m_ssPendingCaps.begin();
m_ssPendingCaps.erase(m_ssPendingCaps.begin());
@@ -260,9 +270,9 @@ void CIRCSock::ResumeCap() {
SendNextCap();
}
bool CIRCSock::OnServerCapAvailable(const CString& sCap) {
bool CIRCSock::OnServerCapAvailable(const CString& sCap, const CString& sValue) {
bool bResult = false;
IRCSOCKMODULECALL(OnServerCapAvailable(sCap), &bResult);
IRCSOCKMODULECALL(OnServerCapAvailable(sCap, sValue), &bResult);
return bResult;
}
@@ -345,66 +355,95 @@ bool CIRCSock::OnAwayMessage(CMessage& Message) {
}
bool CIRCSock::OnCapabilityMessage(CMessage& Message) {
// CAPs are supported only before authorization.
if (!m_bAuthed) {
// The first parameter is most likely "*". No idea why, the
// CAP spec don't mention this, but all implementations
// I've seen add this extra asterisk
CString sSubCmd = Message.GetParam(1);
// The first parameter is most likely "*". No idea why, the
// CAP spec don't mention this, but all implementations
// I've seen add this extra asterisk
CString sSubCmd = Message.GetParam(1);
// If the caplist of a reply is too long, it's split
// into multiple replies. A "*" is prepended to show
// that the list was split into multiple replies.
// This is useful mainly for LS. For ACK and NAK
// replies, there's no real need for this, because
// we request only 1 capability per line.
// If we will need to support broken servers or will
// send several requests per line, need to delay ACK
// actions until all ACK lines are received and
// to recognize past request of NAK by 100 chars
// of this reply.
CString sArgs;
if (Message.GetParam(2) == "*") {
sArgs = Message.GetParam(3);
} else {
sArgs = Message.GetParam(2);
// If the caplist of a reply is too long, it's split
// into multiple replies. A "*" is prepended to show
// that the list was split into multiple replies.
// This is useful mainly for LS. For ACK and NAK
// replies, there's no real need for this, because
// we request only 1 capability per line.
// If we will need to support broken servers or will
// send several requests per line, need to delay ACK
// actions until all ACK lines are received and
// to recognize past request of NAK by 100 chars
// of this reply.
// As for LS, we shouldn't don't send END after receiving first line,
// because interesting caps can be on next line.
CString sArgs;
bool bSendNext = true;
if (Message.GetParam(2) == "*") {
bSendNext = false;
sArgs = Message.GetParam(3);
} else {
sArgs = Message.GetParam(2);
}
static std::map<CString, std::function<void(bool bVal)>> mSupportedCaps = {
{"multi-prefix", [this](bool bVal) { m_bNamesx = bVal; }},
{"userhost-in-names", [this](bool bVal) { m_bUHNames = bVal; }},
{"cap-notify", [](bool bVal) {}},
{"server-time", [this](bool bVal) { m_bServerTime = bVal; }},
{"znc.in/server-time-iso", [this](bool bVal) { m_bServerTime = bVal; }},
{"chghost", [](bool) {}},
{"message-tags", [this](bool bVal) { m_bMessageTagCap = bVal; }},
};
auto RemoveCap = [&](const CString& sCap) {
IRCSOCKMODULECALL(OnServerCapResult(sCap, false), NOTHING);
auto it = mSupportedCaps.find(sCap);
if (it != mSupportedCaps.end()) {
it->second(false);
}
m_ssAcceptedCaps.erase(sCap);
m_ssPendingCaps.erase(sCap);
};
std::map<CString, std::function<void(bool bVal)>> mSupportedCaps = {
{"multi-prefix", [this](bool bVal) { m_bNamesx = bVal; }},
{"userhost-in-names", [this](bool bVal) { m_bUHNames = bVal; }},
{"away-notify", [this](bool bVal) { m_bAwayNotify = bVal; }},
{"account-notify", [this](bool bVal) { m_bAccountNotify = bVal; }},
{"extended-join", [this](bool bVal) { m_bExtendedJoin = bVal; }},
{"server-time", [this](bool bVal) { m_bServerTime = bVal; }},
{"znc.in/server-time-iso",
[this](bool bVal) { m_bServerTime = bVal; }},
};
if (sSubCmd == "LS" || sSubCmd == "NEW") {
VCString vsTokens;
sArgs.Split(" ", vsTokens, false);
if (sSubCmd == "LS") {
VCString vsTokens;
sArgs.Split(" ", vsTokens, false);
for (const CString& sCap : vsTokens) {
if (OnServerCapAvailable(sCap) || mSupportedCaps.count(sCap)) {
m_ssPendingCaps.insert(sCap);
}
for (const CString& sToken : vsTokens) {
CString sCap, sValue;
int eq = sToken.find('=');
if (eq == std::string::npos) {
sCap = sToken;
} else {
sCap = sToken.substr(0, eq);
sValue = sToken.substr(eq + 1);
}
} else if (sSubCmd == "ACK") {
sArgs.Trim();
IRCSOCKMODULECALL(OnServerCapResult(sArgs, true), NOTHING);
const auto& it = mSupportedCaps.find(sArgs);
if (it != mSupportedCaps.end()) {
it->second(true);
m_msCapLsValues[sCap] = sValue;
if (OnServerCapAvailable(sCap, sValue) || mSupportedCaps.count(sCap)) {
m_ssPendingCaps.insert(sCap);
}
m_ssAcceptedCaps.insert(sArgs);
} else if (sSubCmd == "NAK") {
// This should work because there's no [known]
// capability with length of name more than 100 characters.
sArgs.Trim();
IRCSOCKMODULECALL(OnServerCapResult(sArgs, false), NOTHING);
}
} else if (sSubCmd == "ACK") {
sArgs.Trim();
IRCSOCKMODULECALL(OnServerCapResult(sArgs, true), NOTHING);
auto it = mSupportedCaps.find(sArgs);
if (it != mSupportedCaps.end()) {
it->second(true);
}
m_ssAcceptedCaps.insert(sArgs);
} else if (sSubCmd == "NAK") {
// This should work because there's no [known]
// capability with length of name more than 100 characters.
sArgs.Trim();
RemoveCap(sArgs);
} else if (sSubCmd == "DEL") {
VCString vsTokens;
sArgs.Split(" ", vsTokens, false);
for (const CString& sCap : vsTokens) {
RemoveCap(sCap);
m_msCapLsValues.erase(sCap);
}
}
if (bSendNext) {
SendNextCap();
}
// Don't forward any CAP stuff to the client
@@ -428,7 +467,12 @@ bool CIRCSock::OnCTCPMessage(CCTCPMessage& Message) {
if (pChan) {
Message.SetChan(pChan);
FixupChanNick(Message.GetNick(), pChan);
IRCSOCKMODULECALL(OnChanCTCPMessage(Message), &bResult);
if (Message.IsReply()) {
IRCSOCKMODULECALL(OnCTCPReplyMessage(Message), &bResult);
return bResult;
} else {
IRCSOCKMODULECALL(OnChanCTCPMessage(Message), &bResult);
}
if (bResult) return true;
}
}
@@ -478,10 +522,77 @@ bool CIRCSock::OnCTCPMessage(CCTCPMessage& Message) {
return (pChan && pChan->IsDetached());
}
bool CIRCSock::OnChgHostMessage(CChgHostMessage& Message) {
// The emulation of QUIT+JOIN would be cleaner inside CClient::PutClient()
// but computation of new modes is difficult enough so that I don't want to
// repeat it for every client
//
// TODO: make CNick store modes (v, o) instead of perm chars (+, @), that
// would simplify this
bool bNeedEmulate = false;
for (CClient* pClient : m_pNetwork->GetClients()) {
if (pClient->HasChgHost()) {
pClient->PutClient(Message);
} else {
bNeedEmulate = true;
pClient->PutClient(CMessage(Message.GetNick(), "QUIT",
{"Changing hostname"},
Message.GetTags()));
}
}
CNick NewNick = Message.GetNick();
NewNick.SetIdent(Message.GetNewIdent());
NewNick.SetHost(Message.GetNewHost());
for (CChan* pChan : m_pNetwork->GetChans()) {
if (CNick* pNick = pChan->FindNick(Message.GetNick().GetNick())) {
pNick->SetIdent(Message.GetNewIdent());
pNick->SetHost(Message.GetNewHost());
}
if (!bNeedEmulate) continue;
if (pChan->IsDisabled()) continue;
if (pChan->IsDetached()) continue;
if (CNick* pNick = pChan->FindNick(NewNick.GetNick())) {
VCString vsModeParams = {pChan->GetName(), "+"};
for (char cPerm : pNick->GetPermStr()) {
char cMode = GetModeFromPerm(cPerm);
if (cMode) {
vsModeParams[1].append(1, cMode);
vsModeParams.push_back(NewNick.GetNick());
}
}
CTargetMessage ModeMsg;
ModeMsg.SetNick(CNick(":irc.znc.in"));
ModeMsg.SetTags(Message.GetTags());
ModeMsg.SetCommand("MODE");
ModeMsg.SetParams(std::move(vsModeParams));
for (CClient* pClient : m_pNetwork->GetClients()) {
if (!pClient->HasChgHost()) {
// TODO: send account name and real name too, for
// extended-join
pClient->PutClient(CMessage(NewNick, "JOIN",
{pChan->GetName()},
Message.GetTags()));
if (ModeMsg.GetParams().size() > 2) {
pClient->PutClient(ModeMsg);
}
}
}
}
}
return true;
}
bool CIRCSock::OnErrorMessage(CMessage& Message) {
// ERROR :Closing Link: nick[24.24.24.24] (Excess Flood)
CString sError = Message.GetParam(0);
m_pNetwork->PutStatus("Error from Server [" + sError + "]");
m_pNetwork->PutStatus(t_f("Error from server: {1}")(sError));
return true;
}
@@ -549,34 +660,34 @@ bool CIRCSock::OnKickMessage(CKickMessage& Message) {
bool CIRCSock::OnModeMessage(CModeMessage& Message) {
const CNick& Nick = Message.GetNick();
CString sTarget = Message.GetTarget();
CString sModes = Message.GetModes();
VCString vsModes = Message.GetModeParams();
CString sModes = Message.GetModeList();
CChan* pChan = m_pNetwork->FindChan(sTarget);
if (pChan) {
pChan->ModeChange(sModes, &Nick);
pChan->ModeChange(sModes, vsModes, &Nick);
if (pChan->IsDetached()) {
return true;
}
} else if (sTarget == m_Nick.GetNick()) {
CString sModeArg = sModes.Token(0);
bool bAdd = true;
/* no module call defined (yet?)
MODULECALL(OnRawUserMode(*pOpNick, *this, sModeArg, sArgs),
m_pNetwork->GetUser(), nullptr, );
*/
for (unsigned int a = 0; a < sModeArg.size(); a++) {
const unsigned char& uMode = sModeArg[a];
for (unsigned int a = 0; a < sModes.size(); a++) {
const char& cMode = sModes[a];
if (uMode == '+') {
if (cMode == '+') {
bAdd = true;
} else if (uMode == '-') {
} else if (cMode == '-') {
bAdd = false;
} else {
if (bAdd) {
m_scUserModes.insert(uMode);
m_scUserModes.insert(cMode);
} else {
m_scUserModes.erase(uMode);
m_scUserModes.erase(cMode);
}
}
}
@@ -682,8 +793,8 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
if (m_bAuthed && sServer == "irc.znc.in") {
// m_bAuthed == true => we already received another 001 => we
// might be in a traffic loop
m_pNetwork->PutStatus(
"ZNC seems to be connected to itself, disconnecting...");
m_pNetwork->PutStatus(t_s(
"ZNC seems to be connected to itself, disconnecting..."));
Quit();
return true;
}
@@ -694,7 +805,6 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
PutIRC("WHO " + sNick);
m_bAuthed = true;
m_pNetwork->PutStatus("Connected!");
const vector<CClient*>& vClients = m_pNetwork->GetClients();
@@ -712,6 +822,7 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
SetNick(sNick);
m_pNetwork->PutStatus("Connected!");
IRCSOCKMODULECALL(OnIRCConnected(), NOTHING);
m_pNetwork->ClearRawBuffer();
@@ -730,11 +841,11 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
CString sPort = Message.GetParam(2);
CString sInfo = Message.GetParam(3);
m_pNetwork->PutStatus(
"Server [" + m_pNetwork->GetCurrentServer()->GetString(false) +
"] redirects us to [" + sHost + ":" + sPort +
"] with reason [" + sInfo + "]");
t_f("Server {1} redirects us to {2}:{3} with reason: {4}")(
m_pNetwork->GetCurrentServer()->GetString(false), sHost,
sPort, sInfo));
m_pNetwork->PutStatus(
"Perhaps you want to add it as a new server.");
t_s("Perhaps you want to add it as a new server."));
// Don't send server redirects to the client
return true;
}
@@ -761,7 +872,7 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1));
if (pChan) {
pChan->SetModes(Message.GetParams(2));
pChan->SetModes(Message.GetParam(2), Message.GetParamsSplit(3));
// We don't SetModeKnown(true) here,
// because a 329 will follow
@@ -856,6 +967,12 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
m_pNetwork->SetIRCNick(m_Nick);
m_pNetwork->SetIRCServer(sServer);
// A nick can only have one ident and hostname. Yes, you can query
// this information per-channel, but it is still global. For
// example, if the client supports UHNAMES, but the IRC server does
// not, then AFAIR "passive snooping of WHO replies" is the only way
// that ZNC can figure out the ident and host for the UHNAMES
// replies.
const vector<CChan*>& vChans = m_pNetwork->GetChans();
for (CChan* pChan : vChans) {
@@ -957,9 +1074,9 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
}
if (pChan) {
pChan->Disable();
m_pNetwork->PutStatus("Channel [" + pChan->GetName() +
"] is linked to "
"another channel and was thus disabled.");
m_pNetwork->PutStatus(
t_f("Channel {1} is linked to another channel and was thus "
"disabled.")(pChan->GetName()));
}
break;
}
@@ -970,7 +1087,7 @@ bool CIRCSock::OnNumericMessage(CNumericMessage& Message) {
// TLS
if (!GetSSL()) {
StartTLS();
m_pNetwork->PutStatus("Switched to SSL (STARTTLS)");
m_pNetwork->PutStatus(t_s("Switched to SSL (STARTTLS)"));
}
return true;
@@ -1023,7 +1140,7 @@ bool CIRCSock::OnQuitMessage(CQuitMessage& Message) {
bool bIsVisible = false;
if (Nick.NickEquals(GetNick())) {
m_pNetwork->PutStatus("You quit [" + Message.GetReason() + "]");
m_pNetwork->PutStatus(t_f("You quit: {1}")(Message.GetReason()));
// We don't call module hooks and we don't
// forward this quit to clients (Some clients
// disconnect if they receive such a QUIT)
@@ -1048,6 +1165,49 @@ bool CIRCSock::OnQuitMessage(CQuitMessage& Message) {
return !bIsVisible;
}
bool CIRCSock::OnTagMessage(CTargetMessage& Message) {
bool bResult = false;
CChan* pChan = nullptr;
CString sTarget = Message.GetTarget();
if (sTarget.Equals(GetNick())) {
IRCSOCKMODULECALL(OnPrivTagMessage(Message), &bResult);
if (bResult) return true;
if (!m_pNetwork->IsUserOnline() ||
!m_pNetwork->GetUser()->AutoClearQueryBuffer()) {
const CNick& Nick = Message.GetNick();
CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick());
if (pQuery) {
CTargetMessage Format;
Format.Clone(Message);
Format.SetNick(_NAMEDFMT(Nick.GetNickMask()));
Format.SetTarget("{target}");
pQuery->AddBuffer(Format);
}
}
} else {
pChan = m_pNetwork->FindChan(sTarget);
if (pChan) {
Message.SetChan(pChan);
FixupChanNick(Message.GetNick(), pChan);
IRCSOCKMODULECALL(OnChanTagMessage(Message), &bResult);
if (bResult) return true;
if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() ||
pChan->IsDetached()) {
CTargetMessage Format;
Format.Clone(Message);
Format.SetNick(_NAMEDFMT(Message.GetNick().GetNickMask()));
Format.SetTarget(_NAMEDFMT(Message.GetTarget()));
pChan->AddBuffer(Format);
}
}
}
return (pChan && pChan->IsDetached());
}
bool CIRCSock::OnTextMessage(CTextMessage& Message) {
bool bResult = false;
CChan* pChan = nullptr;
@@ -1125,14 +1285,18 @@ bool CIRCSock::OnWallopsMessage(CMessage& Message) {
}
void CIRCSock::PutIRC(const CString& sLine) {
PutIRC(CMessage(sLine));
}
void CIRCSock::PutIRC(const CMessage& Message) {
// Only print if the line won't get sent immediately (same condition as in
// TrySend()!)
if (m_bFloodProtection && m_iSendsAllowed <= 0) {
DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/"
DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/"
<< m_pNetwork->GetName() << ") ZNC -> IRC ["
<< CDebug::Filter(sLine) << "] (queued)");
<< CDebug::Filter(Message.ToString()) << "] (queued)");
}
m_vsSendQueue.push_back(sLine);
m_vSendQueue.push_back(Message);
TrySend();
}
@@ -1140,37 +1304,51 @@ void CIRCSock::PutIRCQuick(const CString& sLine) {
// Only print if the line won't get sent immediately (same condition as in
// TrySend()!)
if (m_bFloodProtection && m_iSendsAllowed <= 0) {
DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/"
DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/"
<< m_pNetwork->GetName() << ") ZNC -> IRC ["
<< CDebug::Filter(sLine) << "] (queued to front)");
}
m_vsSendQueue.push_front(sLine);
m_vSendQueue.emplace_front(sLine);
TrySend();
}
void CIRCSock::TrySend() {
// This condition must be the same as in PutIRC() and PutIRCQuick()!
while (!m_vsSendQueue.empty() &&
while (!m_vSendQueue.empty() &&
(!m_bFloodProtection || m_iSendsAllowed > 0)) {
m_iSendsAllowed--;
bool bSkip = false;
CString& sLine = m_vsSendQueue.front();
CMessage& Message = m_vSendQueue.front();
CMessage Message(sLine);
if (!m_bMessageTagCap) {
MCString mssTags;
for (const auto& it : Message.GetTags()) {
if (IsTagEnabled(it.first)) {
mssTags[it.first] = it.second;
}
}
Message.SetTags(mssTags);
}
Message.SetNetwork(m_pNetwork);
bool bSkip = false;
IRCSOCKMODULECALL(OnSendToIRCMessage(Message), &bSkip);
if (!bSkip) {
CString sCopy = Message.ToString();
IRCSOCKMODULECALL(OnSendToIRC(sCopy), &bSkip);
if (!bSkip) {
DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/"
<< m_pNetwork->GetName() << ") ZNC -> IRC ["
<< CDebug::Filter(sCopy) << "]");
Write(sCopy + "\r\n");
}
PutIRCRaw(Message.ToString());
}
m_vsSendQueue.pop_front();
m_vSendQueue.pop_front();
}
}
void CIRCSock::PutIRCRaw(const CString& sLine) {
CString sCopy = sLine;
bool bSkip = false;
IRCSOCKMODULECALL(OnSendToIRC(sCopy), &bSkip);
if (!bSkip) {
DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/"
<< m_pNetwork->GetName() << ") ZNC -> IRC ["
<< CDebug::Filter(sCopy) << "]");
Write(sCopy + "\r\n");
}
}
@@ -1192,10 +1370,10 @@ void CIRCSock::Connected() {
&bReturn);
if (bReturn) return;
PutIRC("CAP LS");
PutIRC("CAP LS 302");
if (!sPass.empty()) {
PutIRC("PASS " + sPass);
PutIRC(CMessage(CNick(), "PASS", {sPass}));
}
PutIRC("NICK " + sNick);
@@ -1213,7 +1391,7 @@ void CIRCSock::Disconnected() {
if (!m_pNetwork->GetUser()->IsBeingDeleted() &&
m_pNetwork->GetIRCConnectEnabled() &&
m_pNetwork->GetServers().size() != 0) {
m_pNetwork->PutStatus("Disconnected from IRC. Reconnecting...");
m_pNetwork->PutStatus(t_s("Disconnected from IRC. Reconnecting..."));
}
m_pNetwork->ClearRawBuffer();
m_pNetwork->ClearMotdBuffer();
@@ -1224,7 +1402,7 @@ void CIRCSock::Disconnected() {
// otherwise, on reconnect, it might think it still
// had user modes that it actually doesn't have.
CString sUserMode;
for (unsigned char cMode : m_scUserModes) {
for (char cMode : m_scUserModes) {
sUserMode += cMode;
}
if (!sUserMode.empty()) {
@@ -1243,46 +1421,15 @@ void CIRCSock::SockError(int iErrno, const CString& sDescription) {
DEBUG(GetSockName() << " == SockError(" << iErrno << " " << sError << ")");
if (!m_pNetwork->GetUser()->IsBeingDeleted()) {
if (GetConState() != CST_OK) {
m_pNetwork->PutStatus("Cannot connect to IRC (" + sError +
"). Retrying...");
m_pNetwork->PutStatus(
t_f("Cannot connect to IRC ({1}). Retrying...")(sError));
} else {
m_pNetwork->PutStatus("Disconnected from IRC (" + sError +
"). Reconnecting...");
m_pNetwork->PutStatus(
t_f("Disconnected from IRC ({1}). Reconnecting...")(sError));
}
#ifdef HAVE_LIBSSL
if (iErrno == errnoBadSSLCert) {
// Stringify bad cert
X509* pCert = GetX509();
if (pCert) {
BIO* mem = BIO_new(BIO_s_mem());
X509_print(mem, pCert);
X509_free(pCert);
char* pCertStr = nullptr;
long iLen = BIO_get_mem_data(mem, &pCertStr);
CString sCert(pCertStr, iLen);
BIO_free(mem);
VCString vsCert;
sCert.Split("\n", vsCert);
for (const CString& s : vsCert) {
// It shouldn't contain any bad characters, but let's be
// safe...
m_pNetwork->PutStatus("|" + s.Escape_n(CString::EDEBUG));
}
CString sSHA1;
if (GetPeerFingerprint(sSHA1))
m_pNetwork->PutStatus(
"SHA1: " +
sSHA1.Escape_n(CString::EHEXCOLON, CString::EHEXCOLON));
CString sSHA256 = GetSSLPeerFingerprint();
m_pNetwork->PutStatus("SHA-256: " + sSHA256);
m_pNetwork->PutStatus(
"If you trust this certificate, do /znc "
"AddTrustedServerFingerprint " +
sSHA256);
}
}
#endif
}
for (const CString& s : m_vsSSLError) {
m_pNetwork->PutStatus(s);
}
m_pNetwork->ClearRawBuffer();
m_pNetwork->ClearMotdBuffer();
@@ -1291,10 +1438,39 @@ void CIRCSock::SockError(int iErrno, const CString& sDescription) {
m_scUserModes.clear();
}
#ifdef HAVE_LIBSSL
void CIRCSock::SSLCertError(X509* pCert) {
BIO* mem = BIO_new(BIO_s_mem());
X509_print(mem, pCert);
char* pCertStr = nullptr;
long iLen = BIO_get_mem_data(mem, &pCertStr);
CString sCert(pCertStr, iLen);
BIO_free(mem);
VCString vsCert;
sCert.Split("\n", vsCert);
for (const CString& s : vsCert) {
// It shouldn't contain any bad characters, but let's be
// safe...
m_vsSSLError.push_back("|" + s.Escape_n(CString::EDEBUG));
}
CString sSHA1;
if (GetPeerFingerprint(sSHA1))
m_vsSSLError.push_back(
"SHA1: " + sSHA1.Escape_n(CString::EHEXCOLON, CString::EHEXCOLON));
CString sSHA256 = GetSSLPeerFingerprint(pCert);
m_vsSSLError.push_back("SHA-256: " + sSHA256);
m_vsSSLError.push_back(
t_f("If you trust this certificate, do /znc "
"AddTrustedServerFingerprint {1}")(sSHA256));
}
#endif
void CIRCSock::Timeout() {
DEBUG(GetSockName() << " == Timeout()");
if (!m_pNetwork->GetUser()->IsBeingDeleted()) {
m_pNetwork->PutStatus("IRC connection timed out. Reconnecting...");
m_pNetwork->PutStatus(
t_s("IRC connection timed out. Reconnecting..."));
}
m_pNetwork->ClearRawBuffer();
m_pNetwork->ClearMotdBuffer();
@@ -1306,7 +1482,7 @@ void CIRCSock::Timeout() {
void CIRCSock::ConnectionRefused() {
DEBUG(GetSockName() << " == ConnectionRefused()");
if (!m_pNetwork->GetUser()->IsBeingDeleted()) {
m_pNetwork->PutStatus("Connection Refused. Reconnecting...");
m_pNetwork->PutStatus(t_s("Connection Refused. Reconnecting..."));
}
m_pNetwork->ClearRawBuffer();
m_pNetwork->ClearMotdBuffer();
@@ -1314,7 +1490,7 @@ void CIRCSock::ConnectionRefused() {
void CIRCSock::ReachedMaxBuffer() {
DEBUG(GetSockName() << " == ReachedMaxBuffer()");
m_pNetwork->PutStatus("Received a too long line from the IRC server!");
m_pNetwork->PutStatus(t_s("Received a too long line from the IRC server!"));
Quit();
}
@@ -1351,13 +1527,13 @@ void CIRCSock::ParseISupport(const CMessage& Message) {
}
} else if (sName.Equals("CHANMODES")) {
if (!sValue.empty()) {
m_mueChanModes.clear();
m_mceChanModes.clear();
for (unsigned int a = 0; a < 4; a++) {
CString sModes = sValue.Token(a, false, ",");
for (unsigned int b = 0; b < sModes.size(); b++) {
m_mueChanModes[sModes[b]] = (EChanModeArgs)a;
m_mceChanModes[sModes[b]] = (EChanModeArgs)a;
}
}
}
@@ -1383,6 +1559,16 @@ CString CIRCSock::GetISupport(const CString& sKey,
}
}
CString CIRCSock::GetCapLsValue(const CString& sKey,
const CString& sDefault) const {
MCString::const_iterator i = m_msCapLsValues.find(sKey);
if (i == m_msCapLsValues.end()) {
return sDefault;
} else {
return i->second;
}
}
void CIRCSock::SendAltNick(const CString& sBadNick) {
const CString& sLastNick = m_Nick.GetNick();
@@ -1418,7 +1604,7 @@ void CIRCSock::SendAltNick(const CString& sBadNick) {
} else {
char cLetter = 0;
if (sBadNick.empty()) {
m_pNetwork->PutUser("No free nick available");
m_pNetwork->PutUser(t_s("No free nick available"));
Quit();
return;
}
@@ -1426,7 +1612,7 @@ void CIRCSock::SendAltNick(const CString& sBadNick) {
cLetter = sBadNick.back();
if (cLetter == 'z') {
m_pNetwork->PutUser("No free nick found");
m_pNetwork->PutUser(t_s("No free nick found"));
Quit();
return;
}
@@ -1439,10 +1625,10 @@ void CIRCSock::SendAltNick(const CString& sBadNick) {
m_Nick.SetNick(sNewNick);
}
unsigned char CIRCSock::GetPermFromMode(unsigned char uMode) const {
char CIRCSock::GetPermFromMode(char cMode) const {
if (m_sPermModes.size() == m_sPerms.size()) {
for (unsigned int a = 0; a < m_sPermModes.size(); a++) {
if (m_sPermModes[a] == uMode) {
if (m_sPermModes[a] == cMode) {
return m_sPerms[a];
}
}
@@ -1451,11 +1637,23 @@ unsigned char CIRCSock::GetPermFromMode(unsigned char uMode) const {
return 0;
}
CIRCSock::EChanModeArgs CIRCSock::GetModeType(unsigned char uMode) const {
map<unsigned char, EChanModeArgs>::const_iterator it =
m_mueChanModes.find(uMode);
char CIRCSock::GetModeFromPerm(char cPerm) const {
if (m_sPermModes.size() == m_sPerms.size()) {
for (unsigned int a = 0; a < m_sPermModes.size(); a++) {
if (m_sPerms[a] == cPerm) {
return m_sPermModes[a];
}
}
}
if (it == m_mueChanModes.end()) {
return 0;
}
CIRCSock::EChanModeArgs CIRCSock::GetModeType(char cMode) const {
map<char, EChanModeArgs>::const_iterator it =
m_mceChanModes.find(cMode);
if (it == m_mceChanModes.end()) {
return NoArg;
}
@@ -1467,3 +1665,12 @@ void CIRCSock::ResetChans() {
it.second->Reset();
}
}
void CIRCSock::SetTagSupport(const CString& sTag, bool bState) {
if (bState) {
m_ssSupportedTags.insert(sTag);
} else {
m_ssSupportedTags.erase(sTag);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
+80 -23
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -14,6 +14,8 @@
* limitations under the License.
*/
#include <string_view>
#include <znc/Message.h>
#include <znc/Utils.h>
@@ -48,7 +50,7 @@ void CMessage::SetCommand(const CString& sCommand) {
InitType();
}
CString CMessage::GetParams(unsigned int uIdx, unsigned int uLen) const {
CString CMessage::GetParamsColon(unsigned int uIdx, unsigned int uLen) const {
if (m_vsParams.empty() || uLen == 0) {
return "";
}
@@ -79,6 +81,16 @@ void CMessage::SetParams(const VCString& vsParams) {
}
}
void CMessage::SetParams(VCString&& vsParams) {
m_vsParams = std::move(vsParams);
m_bColon = false;
if (m_eType == Type::Text || m_eType == Type::Notice ||
m_eType == Type::Action || m_eType == Type::CTCP) {
InitType();
}
}
CString CMessage::GetParam(unsigned int uIdx) const {
if (uIdx >= m_vsParams.size()) {
return "";
@@ -151,25 +163,49 @@ CString CMessage::ToString(unsigned int uFlags) const {
if (!sMessage.empty()) {
sMessage += " ";
}
sMessage += GetParams(0);
sMessage += GetParamsColon(0);
}
return sMessage;
}
void CMessage::Parse(CString sMessage) {
void CMessage::Parse(const CString& sMessage) {
const char* begin = sMessage.c_str();
const char* const end = begin + sMessage.size();
auto next_word = [&]() {
// Find the end of the first word
const char* p = begin;
while (p < end && *p != ' ') ++p;
std::string_view result(begin, p - begin);
begin = p;
// Prepare for the following word
while (begin < end && *begin == ' ') ++begin;
return result;
};
// <tags>
m_mssTags.clear();
if (sMessage.StartsWith("@")) {
VCString vsTags;
sMessage.Token(0).TrimPrefix_n("@").Split(";", vsTags, false);
for (const CString& sTag : vsTags) {
CString sKey = sTag.Token(0, false, "=", true);
CString sValue = sTag.Token(1, true, "=", true);
if (begin < end && *begin == '@') {
std::string_view svTags = next_word().substr(1);
std::vector<std::string_view> vsTags;
// Split by ';'
while (true) {
auto delim = svTags.find_first_of(';');
if (delim == std::string_view::npos) {
vsTags.push_back(svTags);
break;
}
vsTags.push_back(svTags.substr(0, delim));
svTags = svTags.substr(delim + 1);
}
// Save key and value
for (std::string_view svTag : vsTags) {
auto delim = svTag.find_first_of('=');
CString sKey = std::string(delim == std::string_view::npos ? svTag : svTag.substr(0, delim));
CString sValue = delim == std::string_view::npos ? std::string() : std::string(svTag.substr(delim + 1));
m_mssTags[sKey] =
sValue.Escape(CString::EMSGTAG, CString::CString::EASCII);
}
sMessage = sMessage.Token(1, true);
}
// <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
@@ -183,26 +219,24 @@ void CMessage::Parse(CString sMessage) {
// NUL or CR or LF>
// <prefix>
if (sMessage.TrimPrefix(":")) {
m_Nick.Parse(sMessage.Token(0));
sMessage = sMessage.Token(1, true);
if (begin < end && *begin == ':') {
m_Nick.Parse(std::string(next_word().substr(1)));
}
// <command>
m_sCommand = sMessage.Token(0);
sMessage = sMessage.Token(1, true);
m_sCommand = std::string(next_word());
// <params>
m_bColon = false;
m_vsParams.clear();
while (!sMessage.empty()) {
m_bColon = sMessage.TrimPrefix(":");
while (begin < end) {
m_bColon = *begin == ':';
if (m_bColon) {
m_vsParams.push_back(sMessage);
sMessage.clear();
++begin;
m_vsParams.push_back(std::string(begin, end - begin));
begin = end;
} else {
m_vsParams.push_back(sMessage.Token(0));
sMessage = sMessage.Token(1, true);
m_vsParams.push_back(std::string(next_word()));
}
}
@@ -242,10 +276,12 @@ void CMessage::InitType() {
m_eType = Type::Notice;
}
} else {
std::map<CString, Type> mTypes = {
static std::map<CString, Type> mTypes = {
{"ACCOUNT", Type::Account},
{"AUTHENTICATE", Type::Authenticate},
{"AWAY", Type::Away},
{"CAP", Type::Capability},
{"CHGHOST", Type::ChgHost},
{"ERROR", Type::Error},
{"INVITE", Type::Invite},
{"JOIN", Type::Join},
@@ -256,6 +292,7 @@ void CMessage::InitType() {
{"PING", Type::Ping},
{"PONG", Type::Pong},
{"QUIT", Type::Quit},
{"TAGMSG", Type::TagMsg},
{"TOPIC", Type::Topic},
{"WALLOPS", Type::Wallops},
};
@@ -267,3 +304,23 @@ void CMessage::InitType() {
}
}
}
VCString CMessage::GetParamsSplit(unsigned int uIdx, unsigned int uLen) const {
VCString splitParams;
const VCString &params = GetParams();
if (params.empty() || uLen == 0 || uIdx >= params.size()) {
return splitParams;
}
if (uLen > params.size() - uIdx - 1) {
uLen = params.size() - uIdx;
}
VCString::const_iterator startIt = params.begin() + uIdx;
VCString::const_iterator endIt = startIt + uLen;
splitParams.assign(startIt, endIt);
return splitParams;
}
+310 -66
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <znc/IRCSock.h>
#include <znc/Modules.h>
#include <znc/FileUtils.h>
#include <znc/Template.h>
@@ -160,6 +161,31 @@ CModule::CModule(ModHandle pDLL, CUser* pUser, CIRCNetwork* pNetwork,
}
CModule::~CModule() {
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
// pCap->OnClientChangedSupport is useless (and even dangerous) to call
// from the destructor, since the derived CModule class is gone already.
// But still need to tell clients via cap-notify that the cap is gone.
switch (GetType()) {
case CModInfo::NetworkModule:
GetNetwork()->NotifyClientsAboutServerDependentCap(sName,
false);
break;
case CModInfo::UserModule:
for (CIRCNetwork* pNetwork : GetUser()->GetNetworks()) {
pNetwork->NotifyClientsAboutServerDependentCap(sName,
false);
}
break;
case CModInfo::GlobalModule:
for (auto& [_, pUser] : CZNC::Get().GetUserMap()) {
for (CIRCNetwork* pNetwork : pUser->GetNetworks()) {
pNetwork->NotifyClientsAboutServerDependentCap(sName,
false);
}
}
}
}
while (!m_sTimers.empty()) {
RemTimer(*m_sTimers.begin());
}
@@ -234,13 +260,13 @@ CString CModule::GetWebFilesPath() {
}
bool CModule::LoadRegistry() {
// CString sPrefix = (m_pUser) ? m_pUser->GetUserName() : ".global";
// CString sPrefix = (m_pUser) ? m_pUser->GetUsername() : ".global";
return (m_mssRegistry.ReadFromDisk(GetSavePath() + "/.registry") ==
MCString::MCS_SUCCESS);
}
bool CModule::SaveRegistry() const {
// CString sPrefix = (m_pUser) ? m_pUser->GetUserName() : ".global";
// CString sPrefix = (m_pUser) ? m_pUser->GetUsername() : ".global";
return (m_mssRegistry.WriteToDisk(GetSavePath() + "/.registry", 0600) ==
MCString::MCS_SUCCESS);
}
@@ -525,8 +551,9 @@ bool CModule::AddCommand(const CString& sCmd, const COptionalTranslation& Args,
}
void CModule::AddHelpCommand() {
AddCommand("Help", &CModule::HandleHelpCommand, "search",
"Generate this output");
AddCommand("Help", t_d("<search>", "modhelpcmd"),
t_d("Generate this output", "modhelpcmd"),
[=](const CString& sLine) { HandleHelpCommand(sLine); });
}
bool CModule::RemCommand(const CString& sCmd) {
@@ -569,7 +596,7 @@ void CModule::HandleHelpCommand(const CString& sLine) {
}
}
if (Table.empty()) {
PutModule("No matches for '" + sFilter + "'");
PutModule(t_f("No matches for '{1}'")(sFilter));
} else {
PutModule(Table);
}
@@ -605,7 +632,24 @@ bool CModule::OnBoot() { return true; }
void CModule::OnPreRehash() {}
void CModule::OnPostRehash() {}
void CModule::OnIRCDisconnected() {}
void CModule::InternalServerDependentCapsOnIRCDisconnected() {
OnIRCDisconnected();
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
GetNetwork()->NotifyClientsAboutServerDependentCap(sName, false);
for (CClient* pClient : GetNetwork()->GetClients()) {
pCap->OnClientChangedSupport(pClient, false);
}
}
}
void CModule::OnIRCConnected() {}
void CModule::InternalServerDependentCapsOnIRCConnected() {
OnIRCConnected();
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
if (GetNetwork()->IsServerCapAccepted(sName)) {
GetNetwork()->NotifyClientsAboutServerDependentCap(sName, true);
}
}
}
CModule::EModRet CModule::OnIRCConnecting(CIRCSock* IRCSock) {
return CONTINUE;
}
@@ -617,6 +661,11 @@ CModule::EModRet CModule::OnIRCRegistration(CString& sPass, CString& sNick,
}
CModule::EModRet CModule::OnBroadcast(CString& sMessage) { return CONTINUE; }
void CModule::OnChanPermission3(const CNick* pOpNick, const CNick& Nick,
CChan& Channel, char cMode,
bool bAdded, bool bNoChange) {
OnChanPermission2(pOpNick, Nick, Channel, cMode, bAdded, bNoChange);
}
void CModule::OnChanPermission2(const CNick* pOpNick, const CNick& Nick,
CChan& Channel, unsigned char uMode,
bool bAdded, bool bNoChange) {
@@ -682,9 +731,9 @@ void CModule::OnUnknownModCommand(const CString& sLine) {
// This function is only called if OnModCommand wasn't
// overriden, so no false warnings for modules which don't use
// CModCommand for command handling.
PutModule("This module doesn't implement any commands.");
PutModule(t_s("This module doesn't implement any commands."));
else
PutModule("Unknown command!");
PutModule(t_s("Unknown command!"));
}
void CModule::OnQuit(const CNick& Nick, const CString& sMessage,
@@ -832,6 +881,15 @@ CModule::EModRet CModule::OnUserNoticeMessage(CNoticeMessage& Message) {
Message.SetText(sText);
return ret;
}
CModule::EModRet CModule::OnUserTagMessage(CTargetMessage& Message) {
return CONTINUE;
}
CModule::EModRet CModule::OnPrivTagMessage(CTargetMessage& Message) {
return CONTINUE;
}
CModule::EModRet CModule::OnChanTagMessage(CTargetMessage& Message) {
return CONTINUE;
}
CModule::EModRet CModule::OnUserJoin(CString& sChannel, CString& sKey) {
return CONTINUE;
}
@@ -991,18 +1049,71 @@ CModule::EModRet CModule::OnSendToIRC(CString& sLine) { return CONTINUE; }
CModule::EModRet CModule::OnSendToIRCMessage(CMessage& Message) {
return CONTINUE;
}
void CModule::OnClientAttached() {}
void CModule::InternalServerDependentCapsOnClientAttached() {
OnClientAttached();
if (!GetNetwork()) return;
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
if (GetNetwork()->IsServerCapAccepted(sName)) {
GetClient()->NotifyServerDependentCap(sName, true, GetNetwork()->GetIRCSock()->GetCapLsValue(sName));
}
}
}
void CModule::OnClientDetached() {}
void CModule::InternalServerDependentCapsOnClientDetached() {
OnClientDetached();
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
GetClient()->NotifyServerDependentCap(sName, false, "");
pCap->OnClientChangedSupport(GetClient(), false);
}
}
bool CModule::OnServerCapAvailable(const CString& sCap) { return false; }
bool CModule::OnServerCap302Available(const CString& sCap,
const CString& sValue) {
return OnServerCapAvailable(sCap);
}
bool CModule::InternalServerDependentCapsOnServerCap302Available(const CString& sCap,
const CString& sValue) {
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end())
return OnServerCap302Available(sCap, sValue);
if (GetNetwork()->IsServerCapAccepted(sCap)) {
// This can happen when server sent CAP NEW with another value.
GetNetwork()->NotifyClientsAboutServerDependentCap(sCap, true);
// It's enabled already, no need to REQ it again.
return false;
}
return true;
}
void CModule::OnServerCapResult(const CString& sCap, bool bSuccess) {}
void CModule::InternalServerDependentCapsOnServerCapResult(const CString& sCap,
bool bSuccess) {
OnServerCapResult(sCap, bSuccess);
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end()) return;
it->second->OnServerChangedSupport(GetNetwork(), bSuccess);
if (GetNetwork()->GetIRCSock()->IsAuthed()) {
GetNetwork()->NotifyClientsAboutServerDependentCap(sCap, bSuccess);
if (!bSuccess) {
for (CClient* pClient : GetNetwork()->GetClients()) {
it->second->OnClientChangedSupport(pClient, false);
}
}
}
}
bool CModule::PutIRC(const CString& sLine) {
return (m_pNetwork) ? m_pNetwork->PutIRC(sLine) : false;
return m_pNetwork ? m_pNetwork->PutIRC(sLine) : false;
}
bool CModule::PutIRC(const CMessage& Message) {
return m_pNetwork ? m_pNetwork->PutIRC(Message) : false;
}
bool CModule::PutUser(const CString& sLine) {
return (m_pNetwork) ? m_pNetwork->PutUser(sLine, m_pClient) : false;
return m_pNetwork ? m_pNetwork->PutUser(sLine, m_pClient) : false;
}
bool CModule::PutStatus(const CString& sLine) {
return (m_pNetwork) ? m_pNetwork->PutStatus(sLine, m_pClient) : false;
return m_pNetwork ? m_pNetwork->PutStatus(sLine, m_pClient) : false;
}
unsigned int CModule::PutModule(const CTable& table) {
if (!m_pUser) return 0;
@@ -1060,12 +1171,60 @@ CModule::EModRet CModule::OnUnknownUserRawMessage(CMessage& Message) {
return CONTINUE;
}
void CModule::OnClientCapLs(CClient* pClient, SCString& ssCaps) {}
void CModule::InternalServerDependentCapsOnClientCapLs(CClient* pClient, SCString& ssCaps) {
for (const auto& [sName, pCap] : m_mServerDependentCaps) {
if (GetNetwork() && GetNetwork()->IsServerCapAccepted(sName)) {
if (pClient->HasCap302()) {
CString sValue =
GetNetwork()->GetIRCSock()->GetCapLsValue(sName);
if (!sValue.empty()) {
ssCaps.insert(sName + '=' + sValue);
} else {
ssCaps.insert(sName);
}
} else {
ssCaps.insert(sName);
}
}
}
OnClientCapLs(pClient, ssCaps);
}
bool CModule::IsClientCapSupported(CClient* pClient, const CString& sCap,
bool bState) {
return false;
bool bState) { return false; }
bool CModule::InternalServerDependentCapsIsClientCapSupported(
CClient* pClient, const CString& sCap, bool bState) {
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end())
return IsClientCapSupported(pClient, sCap, bState);
if (!bState) return true;
return GetNetwork() && GetNetwork()->IsServerCapAccepted(sCap);
}
void CModule::OnClientCapRequest(CClient* pClient, const CString& sCap,
bool bState) {}
void CModule::InternalServerDependentCapsOnClientCapRequest(CClient* pClient,
const CString& sCap,
bool bState) {
OnClientCapRequest(pClient, sCap, bState);
auto it = m_mServerDependentCaps.find(sCap);
if (it == m_mServerDependentCaps.end()) return;
it->second->OnClientChangedSupport(pClient, bState);
}
CModule::EModRet CModule::OnClientSASLAuthenticate(
const CString& sMechanism, const CString& sBuffer) {
return CONTINUE;
}
CModule::EModRet CModule::OnClientSASLServerInitialChallenge(
const CString& sMechanism, CString& sResponse) {
return CONTINUE;
}
void CModule::OnClientGetSASLMechanisms(SCString& ssMechanisms) {}
void CModule::OnClientSASLAborted() {}
CModule::EModRet CModule::OnModuleLoading(const CString& sModName,
const CString& sArgs,
CModInfo::EModuleType eType,
@@ -1083,6 +1242,11 @@ CModule::EModRet CModule::OnGetModInfo(CModInfo& ModInfo,
}
void CModule::OnGetAvailableMods(set<CModInfo>& ssMods,
CModInfo::EModuleType eType) {}
void CModule::AddServerDependentCapability(const CString& sName,
std::unique_ptr<CCapability> pCap) {
pCap->SetModule(this);
m_mServerDependentCaps[sName] = std::move(pCap);
}
CModules::CModules()
: m_pUser(nullptr), m_pNetwork(nullptr), m_pClient(nullptr) {}
@@ -1122,7 +1286,7 @@ bool CModules::OnPostRehash() {
return false;
}
bool CModules::OnIRCConnected() {
MODUNLOADCHK(OnIRCConnected());
MODUNLOADCHK(InternalServerDependentCapsOnIRCConnected());
return false;
}
bool CModules::OnIRCConnecting(CIRCSock* pIRCSock) {
@@ -1140,10 +1304,17 @@ bool CModules::OnBroadcast(CString& sMessage) {
MODHALTCHK(OnBroadcast(sMessage));
}
bool CModules::OnIRCDisconnected() {
MODUNLOADCHK(OnIRCDisconnected());
MODUNLOADCHK(InternalServerDependentCapsOnIRCDisconnected());
return false;
}
bool CModules::OnChanPermission3(const CNick* pOpNick, const CNick& Nick,
CChan& Channel, char cMode,
bool bAdded, bool bNoChange) {
MODUNLOADCHK(
OnChanPermission3(pOpNick, Nick, Channel, cMode, bAdded, bNoChange));
return false;
}
bool CModules::OnChanPermission2(const CNick* pOpNick, const CNick& Nick,
CChan& Channel, unsigned char uMode,
bool bAdded, bool bNoChange) {
@@ -1268,6 +1439,15 @@ bool CModules::OnUserNotice(CString& sTarget, CString& sMessage) {
bool CModules::OnUserNoticeMessage(CNoticeMessage& Message) {
MODHALTCHK(OnUserNoticeMessage(Message));
}
bool CModules::OnUserTagMessage(CTargetMessage& Message) {
MODHALTCHK(OnUserTagMessage(Message));
}
bool CModules::OnPrivTagMessage(CTargetMessage& Message) {
MODHALTCHK(OnPrivTagMessage(Message));
}
bool CModules::OnChanTagMessage(CTargetMessage& Message) {
MODHALTCHK(OnChanTagMessage(Message));
}
bool CModules::OnUserJoin(CString& sChannel, CString& sKey) {
MODHALTCHK(OnUserJoin(sChannel, sKey));
}
@@ -1473,9 +1653,17 @@ bool CModules::OnModCTCP(const CString& sMessage) {
MODUNLOADCHK(OnModCTCP(sMessage));
return false;
}
bool CModules::OnClientAttached() {
MODUNLOADCHK(InternalServerDependentCapsOnClientAttached());
return false;
}
bool CModules::OnClientDetached() {
MODUNLOADCHK(InternalServerDependentCapsOnClientDetached());
return false;
}
// Why MODHALTCHK works only with functions returning EModRet ? :(
bool CModules::OnServerCapAvailable(const CString& sCap) {
bool CModules::OnServerCapAvailable(const CString& sCap, const CString& sValue) {
bool bResult = false;
for (CModule* pMod : *this) {
try {
@@ -1483,12 +1671,19 @@ bool CModules::OnServerCapAvailable(const CString& sCap) {
pMod->SetClient(m_pClient);
if (m_pUser) {
CUser* pOldUser = pMod->GetUser();
CIRCNetwork* pOldNetwork = pMod->GetNetwork();
pMod->SetUser(m_pUser);
bResult |= pMod->OnServerCapAvailable(sCap);
pMod->SetNetwork(m_pNetwork);
bResult |=
pMod->InternalServerDependentCapsOnServerCap302Available(
sCap, sValue);
pMod->SetUser(pOldUser);
pMod->SetNetwork(pOldNetwork);
} else {
// WTF? Is that possible?
bResult |= pMod->OnServerCapAvailable(sCap);
bResult |=
pMod->InternalServerDependentCapsOnServerCap302Available(
sCap, sValue);
}
pMod->SetClient(pOldClient);
} catch (const CModule::EModException& e) {
@@ -1501,7 +1696,7 @@ bool CModules::OnServerCapAvailable(const CString& sCap) {
}
bool CModules::OnServerCapResult(const CString& sCap, bool bSuccess) {
MODUNLOADCHK(OnServerCapResult(sCap, bSuccess));
MODUNLOADCHK(InternalServerDependentCapsOnServerCapResult(sCap, bSuccess));
return false;
}
@@ -1539,7 +1734,7 @@ bool CModules::OnUnknownUserRawMessage(CMessage& Message) {
}
bool CModules::OnClientCapLs(CClient* pClient, SCString& ssCaps) {
MODUNLOADCHK(OnClientCapLs(pClient, ssCaps));
MODUNLOADCHK(InternalServerDependentCapsOnClientCapLs(pClient, ssCaps));
return false;
}
@@ -1553,12 +1748,19 @@ bool CModules::IsClientCapSupported(CClient* pClient, const CString& sCap,
pMod->SetClient(m_pClient);
if (m_pUser) {
CUser* pOldUser = pMod->GetUser();
CIRCNetwork* pOldNetwork = pMod->GetNetwork();
pMod->SetUser(m_pUser);
bResult |= pMod->IsClientCapSupported(pClient, sCap, bState);
pMod->SetNetwork(m_pNetwork);
bResult |=
pMod->InternalServerDependentCapsIsClientCapSupported(
pClient, sCap, bState);
pMod->SetUser(pOldUser);
pMod->SetNetwork(pOldNetwork);
} else {
// WTF? Is that possible?
bResult |= pMod->IsClientCapSupported(pClient, sCap, bState);
bResult |=
pMod->InternalServerDependentCapsIsClientCapSupported(
pClient, sCap, bState);
}
pMod->SetClient(pOldClient);
} catch (const CModule::EModException& e) {
@@ -1572,7 +1774,28 @@ bool CModules::IsClientCapSupported(CClient* pClient, const CString& sCap,
bool CModules::OnClientCapRequest(CClient* pClient, const CString& sCap,
bool bState) {
MODUNLOADCHK(OnClientCapRequest(pClient, sCap, bState));
MODUNLOADCHK(
InternalServerDependentCapsOnClientCapRequest(pClient, sCap, bState));
return false;
}
bool CModules::OnClientSASLAuthenticate(const CString& sMechanism,
const CString& sBuffer) {
MODHALTCHK(OnClientSASLAuthenticate(sMechanism, sBuffer));
}
bool CModules::OnClientSASLServerInitialChallenge(const CString& sMechanism,
CString& sResponse) {
MODHALTCHK(OnClientSASLServerInitialChallenge(sMechanism, sResponse));
}
bool CModules::OnClientGetSASLMechanisms(SCString& ssMechanisms) {
MODUNLOADCHK(OnClientGetSASLMechanisms(ssMechanisms));
return false;
}
bool CModules::OnClientSASLAborted() {
MODUNLOADCHK(OnClientSASLAborted());
return false;
}
@@ -1608,13 +1831,32 @@ CModule* CModules::FindModule(const CString& sModule) const {
return nullptr;
}
bool CModules::ValidateModuleName(const CString& sModule, CString& sRetMsg) {
for (unsigned int a = 0; a < sModule.length(); a++) {
if (((sModule[a] < '0') || (sModule[a] > '9')) &&
((sModule[a] < 'a') || (sModule[a] > 'z')) &&
((sModule[a] < 'A') || (sModule[a] > 'Z')) && (sModule[a] != '_')) {
sRetMsg =
t_f("Module names can only contain letters, numbers and "
"underscores, [{1}] is invalid")(sModule);
return false;
}
}
return true;
}
bool CModules::LoadModule(const CString& sModule, const CString& sArgs,
CModInfo::EModuleType eType, CUser* pUser,
CIRCNetwork* pNetwork, CString& sRetMsg) {
sRetMsg = "";
if (!ValidateModuleName(sModule, sRetMsg)) {
return false;
}
if (FindModule(sModule) != nullptr) {
sRetMsg = "Module [" + sModule + "] already loaded.";
sRetMsg = t_f("Module {1} already loaded.")(sModule);
return false;
}
@@ -1628,7 +1870,7 @@ bool CModules::LoadModule(const CString& sModule, const CString& sArgs,
CModInfo Info;
if (!FindModPath(sModule, sModPath, sDataPath)) {
sRetMsg = "Unable to find module [" + sModule + "]";
sRetMsg = t_f("Unable to find module {1}")(sModule);
return false;
}
Info.SetName(sModule);
@@ -1640,20 +1882,20 @@ bool CModules::LoadModule(const CString& sModule, const CString& sArgs,
if (!Info.SupportsType(eType)) {
dlclose(p);
sRetMsg = "Module [" + sModule + "] does not support module type [" +
CModInfo::ModuleTypeToString(eType) + "].";
sRetMsg = t_f("Module {1} does not support module type {2}.")(
sModule, CModInfo::ModuleTypeToString(eType));
return false;
}
if (!pUser && eType == CModInfo::UserModule) {
dlclose(p);
sRetMsg = "Module [" + sModule + "] requires a user.";
sRetMsg = t_f("Module {1} requires a user.")(sModule);
return false;
}
if (!pNetwork && eType == CModInfo::NetworkModule) {
dlclose(p);
sRetMsg = "Module [" + sModule + "] requires a network.";
sRetMsg = t_f("Module {1} requires a network.")(sModule);
return false;
}
@@ -1669,15 +1911,15 @@ bool CModules::LoadModule(const CString& sModule, const CString& sArgs,
bLoaded = pModule->OnLoad(sArgs, sRetMsg);
} catch (const CModule::EModException&) {
bLoaded = false;
sRetMsg = "Caught an exception";
sRetMsg = t_s("Caught an exception");
}
if (!bLoaded) {
UnloadModule(sModule, sModPath);
if (!sRetMsg.empty())
sRetMsg = "Module [" + sModule + "] aborted: " + sRetMsg;
sRetMsg = t_f("Module {1} aborted: {2}")(sModule, sRetMsg);
else
sRetMsg = "Module [" + sModule + "] aborted.";
sRetMsg = t_f("Module {1} aborted.")(sModule);
return false;
}
@@ -1701,7 +1943,7 @@ bool CModules::UnloadModule(const CString& sModule, CString& sRetMsg) {
sRetMsg = "";
if (!pModule) {
sRetMsg = "Module [" + sMod + "] not loaded.";
sRetMsg = t_f("Module [{1}] not loaded.")(sMod);
return false;
}
@@ -1725,12 +1967,12 @@ bool CModules::UnloadModule(const CString& sModule, CString& sRetMsg) {
}
dlclose(p);
sRetMsg = "Module [" + sMod + "] unloaded";
sRetMsg = t_f("Module {1} unloaded.")(sMod);
return true;
}
sRetMsg = "Unable to unload module [" + sMod + "]";
sRetMsg = t_f("Unable to unload module {1}.")(sMod);
return false;
}
@@ -1743,7 +1985,7 @@ bool CModules::ReloadModule(const CString& sModule, const CString& sArgs,
CModule* pModule = FindModule(sMod);
if (!pModule) {
sRetMsg = "Module [" + sMod + "] not loaded";
sRetMsg = t_f("Module [{1}] not loaded.")(sMod);
return false;
}
@@ -1759,12 +2001,16 @@ bool CModules::ReloadModule(const CString& sModule, const CString& sArgs,
return false;
}
sRetMsg = "Reloaded module [" + sMod + "]";
sRetMsg = t_f("Reloaded module {1}.")(sMod);
return true;
}
bool CModules::GetModInfo(CModInfo& ModInfo, const CString& sModule,
CString& sRetMsg) {
if (!ValidateModuleName(sModule, sRetMsg)) {
return false;
}
CString sModPath, sTmp;
bool bSuccess;
@@ -1774,7 +2020,7 @@ bool CModules::GetModInfo(CModInfo& ModInfo, const CString& sModule,
if (bHandled) return bSuccess;
if (!FindModPath(sModule, sModPath, sTmp)) {
sRetMsg = "Unable to find module [" + sModule + "]";
sRetMsg = t_f("Unable to find module {1}.")(sModule);
return false;
}
@@ -1783,6 +2029,10 @@ bool CModules::GetModInfo(CModInfo& ModInfo, const CString& sModule,
bool CModules::GetModPathInfo(CModInfo& ModInfo, const CString& sModule,
const CString& sModPath, CString& sRetMsg) {
if (!ValidateModuleName(sModule, sRetMsg)) {
return false;
}
ModInfo.SetName(sModule);
ModInfo.SetPath(sModPath);
@@ -1833,6 +2083,8 @@ void CModules::GetDefaultMods(set<CModInfo>& ssMods,
const map<CString, CModInfo::EModuleType> ns = {
{"chansaver", CModInfo::UserModule},
{"controlpanel", CModInfo::UserModule},
{"corecaps", CModInfo::GlobalModule},
{"saslplainauth", CModInfo::GlobalModule},
{"simple_away", CModInfo::NetworkModule},
{"webadmin", CModInfo::GlobalModule}};
@@ -1895,16 +2147,8 @@ ModHandle CModules::OpenModule(const CString& sModule, const CString& sModPath,
// Some sane defaults in case anything errors out below
sRetMsg.clear();
for (unsigned int a = 0; a < sModule.length(); a++) {
if (((sModule[a] < '0') || (sModule[a] > '9')) &&
((sModule[a] < 'a') || (sModule[a] > 'z')) &&
((sModule[a] < 'A') || (sModule[a] > 'Z')) && (sModule[a] != '_')) {
sRetMsg =
"Module names can only contain letters, numbers and "
"underscores, [" +
sModule + "] is invalid.";
return nullptr;
}
if (!ValidateModuleName(sModule, sRetMsg)) {
return nullptr;
}
// The second argument to dlopen() has a long history. It seems clear
@@ -1925,8 +2169,8 @@ ModHandle CModules::OpenModule(const CString& sModule, const CString& sModPath,
// dlerror() returns pointer to static buffer, which may be overwritten
// very soon with another dl call also it may just return null.
const char* cDlError = dlerror();
CString sDlError = cDlError ? cDlError : "Unknown error";
sRetMsg = "Unable to open module [" + sModule + "] [" + sDlError + "]";
CString sDlError = cDlError ? cDlError : t_s("Unknown error");
sRetMsg = t_f("Unable to open module {1}: {2}")(sModule, sDlError);
return nullptr;
}
@@ -1935,30 +2179,28 @@ ModHandle CModules::OpenModule(const CString& sModule, const CString& sModPath,
*reinterpret_cast<void**>(&fpZNCModuleEntry) = dlsym(p, "ZNCModuleEntry");
if (!fpZNCModuleEntry) {
dlclose(p);
sRetMsg = "Could not find ZNCModuleEntry in module [" + sModule + "]";
sRetMsg = t_f("Could not find ZNCModuleEntry in module {1}")(sModule);
return nullptr;
}
const CModuleEntry* pModuleEntry = fpZNCModuleEntry();
if (std::strcmp(pModuleEntry->pcVersion, VERSION_STR) ||
std::strcmp(pModuleEntry->pcVersionExtra, VERSION_EXTRA)) {
sRetMsg = "Version mismatch for module [" + sModule +
"] (core is " VERSION_STR VERSION_EXTRA
", module is built for " +
CString(pModuleEntry->pcVersion) +
pModuleEntry->pcVersionExtra + "), recompile this module.";
sRetMsg = t_f(
"Version mismatch for module {1}: core is {2}, module is built for "
"{3}. Recompile this module.")(
sModule, VERSION_STR VERSION_EXTRA,
CString(pModuleEntry->pcVersion) + pModuleEntry->pcVersionExtra);
dlclose(p);
return nullptr;
}
if (std::strcmp(pModuleEntry->pcCompileOptions,
ZNC_COMPILE_OPTIONS_STRING)) {
sRetMsg =
"Module [" + sModule +
"] is built incompatibly (core is '" ZNC_COMPILE_OPTIONS_STRING
"', module is '" +
CString(pModuleEntry->pcCompileOptions) +
"'), recompile this module.";
sRetMsg = t_f(
"Module {1} is built incompatibly: core is '{2}', module is '{3}'. "
"Recompile this module.")(sModule, ZNC_COMPILE_OPTIONS_STRING,
pModuleEntry->pcCompileOptions);
dlclose(p);
return nullptr;
}
@@ -1986,14 +2228,16 @@ CModCommand::CModCommand(const CString& sCmd, CmdFunc func,
: m_sCmd(sCmd), m_pFunc(std::move(func)), m_Args(Args), m_Desc(Desc) {}
void CModCommand::InitHelp(CTable& Table) {
Table.AddColumn("Command");
Table.AddColumn("Description");
Table.SetStyle(CTable::ListStyle);
Table.AddColumn(t_s("Command", "modhelpcmd"));
Table.AddColumn(t_s("Description", "modhelpcmd"));
}
void CModCommand::AddHelp(CTable& Table) const {
Table.AddRow();
Table.SetCell("Command", GetCommand() + " " + GetArgs());
Table.SetCell("Description", GetDescription());
Table.SetCell(t_s("Command", "modhelpcmd"),
GetCommand() + (GetArgs().empty() ? "" : " ") + GetArgs());
Table.SetCell(t_s("Description", "modhelpcmd"), GetDescription());
}
CString CModule::t_s(const CString& sEnglish, const CString& sContext) const {
+13 -13
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -91,22 +91,22 @@ void CNick::SetNick(const CString& s) { m_sNick = s; }
void CNick::SetIdent(const CString& s) { m_sIdent = s; }
void CNick::SetHost(const CString& s) { m_sHost = s; }
bool CNick::HasPerm(unsigned char uPerm) const {
return (uPerm && m_sChanPerms.find(uPerm) != CString::npos);
bool CNick::HasPerm(char cPerm) const {
return (cPerm && m_sChanPerms.find(cPerm) != CString::npos);
}
bool CNick::AddPerm(unsigned char uPerm) {
if (!uPerm || HasPerm(uPerm)) {
bool CNick::AddPerm(char cPerm) {
if (!cPerm || HasPerm(cPerm)) {
return false;
}
m_sChanPerms.append(1, uPerm);
m_sChanPerms.append(1, cPerm);
return true;
}
bool CNick::RemPerm(unsigned char uPerm) {
CString::size_type uPos = m_sChanPerms.find(uPerm);
bool CNick::RemPerm(char cPerm) {
CString::size_type uPos = m_sChanPerms.find(cPerm);
if (uPos == CString::npos) {
return false;
}
@@ -116,12 +116,12 @@ bool CNick::RemPerm(unsigned char uPerm) {
return true;
}
unsigned char CNick::GetPermChar() const {
char CNick::GetPermChar() const {
CIRCSock* pIRCSock = (!m_pNetwork) ? nullptr : m_pNetwork->GetIRCSock();
const CString& sChanPerms = (!pIRCSock) ? "@+" : pIRCSock->GetPerms();
const CString sChanPerms = (!pIRCSock) ? "@+" : pIRCSock->GetPerms();
for (unsigned int a = 0; a < sChanPerms.size(); a++) {
const unsigned char& c = sChanPerms[a];
const char& c = sChanPerms[a];
if (HasPerm(c)) {
return c;
}
@@ -132,11 +132,11 @@ unsigned char CNick::GetPermChar() const {
CString CNick::GetPermStr() const {
CIRCSock* pIRCSock = (!m_pNetwork) ? nullptr : m_pNetwork->GetIRCSock();
const CString& sChanPerms = (!pIRCSock) ? "@+" : pIRCSock->GetPerms();
const CString sChanPerms = (!pIRCSock) ? "@+" : pIRCSock->GetPerms();
CString sRet;
for (unsigned int a = 0; a < sChanPerms.size(); a++) {
const unsigned char& c = sChanPerms[a];
const char& c = sChanPerms[a];
if (HasPerm(c)) {
sRet += c;
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
+43 -6
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -15,6 +15,8 @@
*/
#include <znc/SSLVerifyHost.h>
#include <znc/Translation.h>
#include <arpa/inet.h>
#ifdef HAVE_LIBSSL
#if defined(OPENSSL_VERSION_NUMBER) && !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100007
@@ -368,6 +370,14 @@ static HostnameValidationResult matches_subject_alternative_name(
}
san_names_nb = sk_GENERAL_NAME_num(san_names);
// Precompute binary representation of hostname in case if it's IP address.
// Not the other way around, because there can be multiple text
// representation of the same IP.
char ip4[4] = {};
char ip6[16] = {};
const int ip4try = inet_pton(AF_INET, hostname, ip4);
const int ip6try = inet_pton(AF_INET6, hostname, ip6);
// Check each name within the extension
for (i = 0; i < san_names_nb; i++) {
const GENERAL_NAME* current_name = sk_GENERAL_NAME_value(san_names, i);
@@ -380,16 +390,40 @@ static HostnameValidationResult matches_subject_alternative_name(
// Make sure there isn't an embedded NUL character in the DNS name
if (ASN1_STRING_length(current_name->d.dNSName) !=
static_cast<int>(strlen(dns_name))) {
static_cast<int>(strnlen(
dns_name, ASN1_STRING_length(current_name->d.dNSName)))) {
DEBUG("SSLVerifyHost: embedded null in DNS SAN");
result = MalformedCertificate;
break;
} else { // Compare expected hostname with the DNS name
DEBUG("SSLVerifyHost: Found SAN " << dns_name);
DEBUG("SSLVerifyHost: Found DNS SAN " << dns_name);
if (ZNC_Curl::Curl_cert_hostcheck(dns_name, hostname)) {
result = MatchFound;
break;
}
}
} else if (current_name->type == GEN_IPADD) {
CString ip(reinterpret_cast<const char*>(
ASN1_STRING_get0_data(current_name->d.iPAddress)),
ASN1_STRING_length(current_name->d.iPAddress));
DEBUG("SSLVerifyHost: Found IP SAN "
<< ip.Escape_n(CString::EHEXCOLON));
if (ip4try && ASN1_STRING_length(current_name->d.iPAddress) == 4) {
if (memcmp(ip4,
ASN1_STRING_get0_data(current_name->d.iPAddress),
4) == 0) {
result = MatchFound;
break;
}
} else if (ip6try &&
ASN1_STRING_length(current_name->d.iPAddress) == 16) {
if (memcmp(ip6,
ASN1_STRING_get0_data(current_name->d.iPAddress),
16) == 0) {
result = MatchFound;
break;
}
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
@@ -432,6 +466,9 @@ static HostnameValidationResult validate_hostname(const char* hostname,
bool ZNC_SSLVerifyHost(const CString& sHost, const X509* pCert,
CString& sError) {
struct Tr : CCoreTranslationMixin {
using CCoreTranslationMixin::t_s;
};
DEBUG("SSLVerifyHost: checking " << sHost);
ZNC_iSECPartners::HostnameValidationResult eResult =
ZNC_iSECPartners::validate_hostname(sHost.c_str(), pCert);
@@ -441,15 +478,15 @@ bool ZNC_SSLVerifyHost(const CString& sHost, const X509* pCert,
return true;
case ZNC_iSECPartners::MatchNotFound:
DEBUG("SSLVerifyHost: host doesn't match");
sError = "hostname doesn't match";
sError = Tr::t_s("hostname doesn't match");
return false;
case ZNC_iSECPartners::MalformedCertificate:
DEBUG("SSLVerifyHost: malformed cert");
sError = "malformed hostname in certificate";
sError = Tr::t_s("malformed hostname in certificate");
return false;
default:
DEBUG("SSLVerifyHost: error");
sError = "hostname verification error";
sError = Tr::t_s("hostname verification error");
return false;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
+47 -43
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -30,19 +30,13 @@
#ifdef HAVE_LIBSSL
// Copypasted from
// https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
// at 2016-06-03
// at 2024-02-08 (version 5.7)
static CString ZNC_DefaultCipher() {
return "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-"
"ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-"
"AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-"
"SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-"
"RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:"
"ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-"
"SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:"
"DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:"
"ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:"
"AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-"
"SHA:DES-CBC3-SHA:!DSS";
// This is TLS1.2 only, because TLS1.3 ciphers are probably not configurable here yet
return "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:"
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:"
"ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:"
"DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305";
}
#endif
@@ -76,11 +70,9 @@ CZNCSock::CZNCSock(const CString& sHost, u_short port, int timeout)
}
unsigned int CSockManager::GetAnonConnectionCount(const CString& sIP) const {
const_iterator it;
unsigned int ret = 0;
for (it = begin(); it != end(); ++it) {
Csock* pSock = *it;
for (const Csock* pSock : *this) {
// Logged in CClients have "USR::<username>" as their sockname
if (pSock->GetType() == Csock::INBOUND && pSock->GetRemoteIP() == sIP &&
!pSock->GetSockName().StartsWith("USR::")) {
@@ -111,55 +103,63 @@ int CZNCSock::VerifyPeerCertificate(int iPreVerify, X509_STORE_CTX* pStoreCTX) {
}
void CZNCSock::SSLHandShakeFinished() {
X509* pCert = GetX509();
if (!CheckSSLCert(pCert)) {
Close();
}
X509_free(pCert);
}
bool CZNCSock::CheckSSLCert(X509* pCert) {
if (GetType() != ETConn::OUTBOUND) {
return;
return true;
}
X509* pCert = GetX509();
if (!pCert) {
DEBUG(GetSockName() + ": No cert");
CallSockError(errnoBadSSLCert, "Anonymous SSL cert is not allowed");
Close();
return;
return false;
}
if (GetTrustAllCerts()) {
DEBUG(GetSockName() + ": Verification disabled, trusting all.");
return;
return true;
}
CString sHostVerifyError;
if (!ZNC_SSLVerifyHost(m_sHostToVerifySSL, pCert, sHostVerifyError)) {
m_ssCertVerificationErrors.insert(sHostVerifyError);
}
X509_free(pCert);
if (GetTrustPKI() && m_ssCertVerificationErrors.empty()) {
DEBUG(GetSockName() + ": Good cert (PKI valid)");
return;
return true;
}
CString sFP = GetSSLPeerFingerprint();
CString sFP = GetSSLPeerFingerprint(pCert);
if (m_ssTrustedFingerprints.count(sFP) != 0) {
DEBUG(GetSockName() + ": Cert explicitly trusted by user: " << sFP);
return;
return true;
}
DEBUG(GetSockName() + ": Bad cert");
CString sErrorMsg = "Invalid SSL certificate: ";
sErrorMsg += CString(", ").Join(begin(m_ssCertVerificationErrors),
end(m_ssCertVerificationErrors));
SSLCertError(pCert);
CallSockError(errnoBadSSLCert, sErrorMsg);
Close();
return false;
}
bool CZNCSock::SNIConfigureClient(CString& sHostname) {
sHostname = m_sHostToVerifySSL;
return true;
}
#endif
CString CZNCSock::GetSSLPeerFingerprint() const {
#ifdef HAVE_LIBSSL
CString CZNCSock::GetSSLPeerFingerprint(X509* pCert) const {
// Csocket's version returns insecure SHA-1
// This one is SHA-256
const EVP_MD* evp = EVP_sha256();
X509* pCert = GetX509();
bool bOwned = false;
if (!pCert) {
pCert = GetX509();
bOwned = true;
}
if (!pCert) {
DEBUG(GetSockName() + ": GetSSLPeerFingerprint: Anonymous cert");
return "";
@@ -167,17 +167,17 @@ CString CZNCSock::GetSSLPeerFingerprint() const {
unsigned char buf[256 / 8];
unsigned int _32 = 256 / 8;
int iSuccess = X509_digest(pCert, evp, buf, &_32);
X509_free(pCert);
if (bOwned) {
X509_free(pCert);
}
if (!iSuccess) {
DEBUG(GetSockName() + ": GetSSLPeerFingerprint: Couldn't find digest");
return "";
}
return CString(reinterpret_cast<const char*>(buf), sizeof buf)
.Escape_n(CString::EASCII, CString::EHEXCOLON);
#else
return "";
#endif
}
#endif
void CZNCSock::SetEncoding(const CString& sEncoding) {
#ifdef HAVE_ICU
@@ -225,7 +225,7 @@ void CSockManager::CDNSJob::runThread() {
void CSockManager::CDNSJob::runMain() {
if (0 != this->iRes) {
DEBUG("Error in threaded DNS: " << gai_strerror(this->iRes));
DEBUG("Error in threaded DNS: " << gai_strerror(this->iRes) << " while trying to resolve " << this->sHostname);
if (this->aiResult) {
DEBUG("And aiResult is not nullptr...");
}
@@ -333,16 +333,19 @@ void CSockManager::SetTDNSThreadFinished(TDNSTask* task, bool bBind,
try {
if (ssTargets4.empty() && ssTargets6.empty()) {
throw "Can't resolve server hostname";
throw t_s("Can't resolve server hostname");
} else if (task->sBindhost.empty()) {
// Choose random target
std::tie(sTargetHost, std::ignore) =
RandomFrom2SetsWithBias(ssTargets4, ssTargets6, gen);
} else if (ssBinds4.empty() && ssBinds6.empty()) {
throw "Can't resolve bind hostname. Try /znc ClearBindHost and /znc ClearUserBindHost";
throw t_s(
"Can't resolve bind hostname. Try /znc ClearBindHost and /znc "
"ClearUserBindHost");
} else if (ssBinds4.empty()) {
if (ssTargets6.empty()) {
throw "Server address is IPv4-only, but bindhost is IPv6-only";
throw t_s(
"Server address is IPv4-only, but bindhost is IPv6-only");
} else {
// Choose random target and bindhost from IPv6-only sets
sTargetHost = RandomFromSet(ssTargets6, gen);
@@ -350,7 +353,8 @@ void CSockManager::SetTDNSThreadFinished(TDNSTask* task, bool bBind,
}
} else if (ssBinds6.empty()) {
if (ssTargets4.empty()) {
throw "Server address is IPv6-only, but bindhost is IPv4-only";
throw t_s(
"Server address is IPv6-only, but bindhost is IPv4-only");
} else {
// Choose random target and bindhost from IPv4-only sets
sTargetHost = RandomFromSet(ssTargets4, gen);
@@ -370,7 +374,7 @@ void CSockManager::SetTDNSThreadFinished(TDNSTask* task, bool bBind,
<< "] using bindhost [" << sBindhost << "]");
FinishConnect(sTargetHost, task->iPort, task->sSockName, task->iTimeout,
task->bSSL, sBindhost, task->pcSock);
} catch (const char* s) {
} catch (const CString& s) {
DEBUG(task->sSockName << ", dns resolving error: " << s);
task->pcSock->SetSockName(task->sSockName);
task->pcSock->SockError(-1, s);
@@ -508,7 +512,7 @@ void CSocket::ReachedMaxBuffer() {
DEBUG(GetSockName() << " == ReachedMaxBuffer()");
if (m_pModule)
m_pModule->PutModule(
"Some socket reached its max buffer limit and was closed!");
t_s("Some socket reached its max buffer limit and was closed!"));
Close();
}
@@ -539,7 +543,7 @@ bool CSocket::Connect(const CString& sHostname, unsigned short uPort, bool bSSL,
CString sBindHost;
if (pUser) {
sSockName += "::" + pUser->GetUserName();
sSockName += "::" + pUser->GetUsername();
sBindHost = pUser->GetBindHost();
CIRCNetwork* pNetwork = m_pModule->GetNetwork();
if (pNetwork) {
@@ -570,7 +574,7 @@ bool CSocket::Listen(unsigned short uPort, bool bSSL, unsigned int uTimeout) {
CString sSockName = "MOD::L::" + m_pModule->GetModName();
if (pUser) {
sSockName += "::" + pUser->GetUserName();
sSockName += "::" + pUser->GetUsername();
}
// Don't overwrite the socket name if one is already set
if (!GetSockName().empty()) {
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
+3 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -16,6 +16,7 @@
#include <znc/Translation.h>
#include <znc/FileUtils.h>
#include <znc/znc.h>
#ifdef HAVE_I18N
#include <boost/locale.hpp>
@@ -88,6 +89,7 @@ const std::locale& CTranslation::LoadTranslation(const CString& sDomain) {
if (lang_it == domain.end()) {
boost::locale::generator gen;
gen.add_messages_path(LOCALE_DIR);
gen.add_messages_path(CZNC::Get().GetModPath() + "/locale");
gen.add_messages_domain(sDomain);
std::tie(lang_it, std::ignore) =
domain.emplace(sLanguage, gen(sLanguage + ".UTF-8"));
+160 -55
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -25,13 +25,17 @@
#include <time.h>
#include <algorithm>
#ifdef ZNC_HAVE_ARGON
#include <argon2.h>
#endif
using std::vector;
using std::set;
class CUserTimer : public CCron {
public:
CUserTimer(CUser* pUser) : CCron(), m_pUser(pUser) {
SetName("CUserTimer::" + m_pUser->GetUserName());
SetName("CUserTimer::" + m_pUser->GetUsername());
Start(m_pUser->GetPingSlack());
}
~CUserTimer() override {}
@@ -59,12 +63,12 @@ class CUserTimer : public CCron {
CUser* m_pUser;
};
CUser::CUser(const CString& sUserName)
: m_sUserName(sUserName),
m_sCleanUserName(MakeCleanUserName(sUserName)),
m_sNick(m_sCleanUserName),
CUser::CUser(const CString& sUsername)
: m_sUsername(sUsername),
m_sCleanUsername(MakeCleanUsername(sUsername)),
m_sNick(m_sCleanUsername),
m_sAltNick(""),
m_sIdent(m_sCleanUserName),
m_sIdent(m_sCleanUsername),
m_sRealName(""),
m_sBindHost(""),
m_sDCCBindHost(""),
@@ -78,16 +82,22 @@ CUser::CUser(const CString& sUserName)
m_sTimestampFormat("[%H:%M:%S]"),
m_sTimezone(""),
m_eHashType(HASH_NONE),
m_sUserPath(CZNC::Get().GetUserPath() + "/" + sUserName),
m_sUserPath(CZNC::Get().GetUserPath() + "/" + sUsername),
m_bMultiClients(true),
m_bDenyLoadMod(false),
m_bAdmin(false),
m_bDenySetBindHost(false),
m_bDenySetIdent(false),
m_bDenySetNetwork(false),
m_bDenySetRealName(false),
m_bDenySetQuitMsg(false),
m_bDenySetCTCPReplies(false),
m_bAutoClearChanBuffer(true),
m_bAutoClearQueryBuffer(true),
m_bBeingDeleted(false),
m_bAppendTimestamp(false),
m_bPrependTimestamp(true),
m_bAuthOnlyViaModule(false),
m_pUserTimer(nullptr),
m_vIRCNetworks(),
m_vClients(),
@@ -129,11 +139,13 @@ CUser::~CUser() {
CZNC::Get().AddBytesWritten(m_uBytesWritten);
}
namespace {
template <class T>
struct TOption {
const char* name;
void (CUser::*pSetter)(T);
};
}
bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
TOption<const CString&> StringOptions[] = {
@@ -168,8 +180,14 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
{"admin", &CUser::SetAdmin},
{"denysetbindhost", &CUser::SetDenySetBindHost},
{"denysetvhost", &CUser::SetDenySetBindHost},
{"denysetident", &CUser::SetDenySetIdent},
{"denysetnetwork", &CUser::SetDenySetNetwork},
{"denysetrealname", &CUser::SetDenySetRealName},
{"denysetquitmsg", &CUser::SetDenySetQuitMsg},
{"denysetctcpreplies", &CUser::SetDenySetCTCPReplies},
{"appendtimestamp", &CUser::SetTimestampAppend},
{"prependtimestamp", &CUser::SetTimestampPrepend},
{"authonlyviamodule", &CUser::SetAuthOnlyViaModule},
};
for (const auto& Option : StringOptions) {
@@ -332,7 +350,12 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
method = CUser::HASH_MD5;
else if (sMethod.Equals("sha256"))
method = CUser::HASH_SHA256;
else {
else if (sMethod.Equals("Argon2id")) {
method = CUser::HASH_ARGON2ID;
#ifndef ZNC_HAVE_ARGON
CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " won't be able to authenticate");
#endif
} else {
sError = "Invalid hash method";
CUtils::PrintError(sError);
return false;
@@ -502,11 +525,11 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
CIRCNetwork* CUser::AddNetwork(const CString& sNetwork, CString& sErrorRet) {
if (!CIRCNetwork::IsValidNetwork(sNetwork)) {
sErrorRet =
"Invalid network name. It should be alphanumeric. Not to be "
"confused with server name";
t_s("Invalid network name. It should be alphanumeric. Not to be "
"confused with server name");
return nullptr;
} else if (FindNetwork(sNetwork)) {
sErrorRet = "Network [" + sNetwork.Token(0) + "] already exists";
sErrorRet = t_f("Network {1} already exists")(sNetwork);
return nullptr;
}
@@ -586,7 +609,7 @@ CString& CUser::ExpandString(const CString& sStr, CString& sRet) const {
sRet.Replace("%realname%", GetRealName());
sRet.Replace("%time%", sTime);
sRet.Replace("%uptime%", CZNC::Get().GetUptime());
sRet.Replace("%user%", GetUserName());
sRet.Replace("%user%", GetUsername());
sRet.Replace("%version%", CZNC::GetVersion());
sRet.Replace("%vhost%", GetBindHost());
sRet.Replace("%znc%", CZNC::GetTag(false));
@@ -603,17 +626,25 @@ CString& CUser::ExpandString(const CString& sStr, CString& sRet) const {
}
CString CUser::AddTimestamp(const CString& sStr) const {
time_t tm;
return AddTimestamp(time(&tm), sStr);
timeval tv;
gettimeofday(&tv, nullptr);
return AddTimestamp(tv, sStr);
}
CString CUser::AddTimestamp(time_t tm, const CString& sStr) const {
timeval tv;
tv.tv_sec = tm;
tv.tv_usec = 0;
return AddTimestamp(tv, sStr);
}
CString CUser::AddTimestamp(timeval tv, const CString& sStr) const {
CString sRet = sStr;
if (!GetTimestampFormat().empty() &&
(m_bAppendTimestamp || m_bPrependTimestamp)) {
CString sTimestamp =
CUtils::FormatTime(tm, GetTimestampFormat(), m_sTimezone);
CUtils::FormatTime(tv, GetTimestampFormat(), m_sTimezone);
if (sTimestamp.empty()) {
return sRet;
}
@@ -664,8 +695,8 @@ void CUser::UserConnected(CClient* pClient) {
BounceAllClients();
}
pClient->PutClient(":irc.znc.in 001 " + pClient->GetNick() +
" :- Welcome to ZNC -");
pClient->PutClient(":irc.znc.in 001 " + pClient->GetNick() + " :" +
t_s("Welcome to ZNC"));
m_vClients.push_back(pClient);
}
@@ -724,9 +755,9 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
// user names can only specified for the constructor, changing it later
// on breaks too much stuff (e.g. lots of paths depend on the user name)
if (GetUserName() != User.GetUserName()) {
if (GetUsername() != User.GetUsername()) {
DEBUG("Ignoring username in CUser::Clone(), old username ["
<< GetUserName() << "]; New username [" << User.GetUserName()
<< GetUsername() << "]; New username [" << User.GetUsername()
<< "]");
}
@@ -764,8 +795,8 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
for (CClient* pSock : m_vClients) {
if (!IsHostAllowed(pSock->GetRemoteIP())) {
pSock->PutStatusNotice(
"You are being disconnected because your IP is no longer "
"allowed to connect to this user");
t_s("You are being disconnected because your IP is no longer "
"allowed to connect to this user"));
pSock->Close();
}
}
@@ -793,6 +824,12 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
SetDenyLoadMod(User.DenyLoadMod());
SetAdmin(User.IsAdmin());
SetDenySetBindHost(User.DenySetBindHost());
SetDenySetIdent(User.DenySetIdent());
SetDenySetNetwork(User.DenySetNetwork());
SetDenySetRealName(User.DenySetRealName());
SetDenySetQuitMsg(User.DenySetQuitMsg());
SetDenySetCTCPReplies(User.DenySetCTCPReplies());
SetAuthOnlyViaModule(User.AuthOnlyViaModule());
SetTimestampAppend(User.GetTimestampAppend());
SetTimestampPrepend(User.GetTimestampPrepend());
SetTimestampFormat(User.GetTimestampFormat());
@@ -866,11 +903,16 @@ const CString& CUser::GetTimestampFormat() const { return m_sTimestampFormat; }
bool CUser::GetTimestampAppend() const { return m_bAppendTimestamp; }
bool CUser::GetTimestampPrepend() const { return m_bPrependTimestamp; }
bool CUser::IsValidUserName(const CString& sUserName) {
// /^[a-zA-Z][a-zA-Z@._\-]*$/
const char* p = sUserName.c_str();
bool CUser::IsValidUsername(const CString& sUsername) {
return CUser::IsValidUserName(sUsername);
}
if (sUserName.empty()) {
/// @deprecated
bool CUser::IsValidUserName(const CString& sUsername) {
// /^[a-zA-Z][a-zA-Z@._\-]*$/
const char* p = sUsername.c_str();
if (sUsername.empty()) {
return false;
}
@@ -893,17 +935,17 @@ bool CUser::IsValid(CString& sErrMsg, bool bSkipPass) const {
sErrMsg.clear();
if (!bSkipPass && m_sPass.empty()) {
sErrMsg = "Pass is empty";
sErrMsg = t_s("Password is empty");
return false;
}
if (m_sUserName.empty()) {
sErrMsg = "Username is empty";
if (m_sUsername.empty()) {
sErrMsg = t_s("Username is empty");
return false;
}
if (!CUser::IsValidUserName(m_sUserName)) {
sErrMsg = "Username is invalid";
if (!CUser::IsValidUsername(m_sUsername)) {
sErrMsg = t_s("Username is invalid");
return false;
}
@@ -925,6 +967,9 @@ CConfig CUser::ToConfig() const {
case HASH_SHA256:
sHash = "SHA256";
break;
case HASH_ARGON2ID:
sHash = "Argon2id";
break;
}
passConfig.AddKeyValuePair("Salt", m_sPassSalt);
passConfig.AddKeyValuePair("Method", sHash);
@@ -952,9 +997,15 @@ CConfig CUser::ToConfig() const {
config.AddKeyValuePair("DenyLoadMod", CString(DenyLoadMod()));
config.AddKeyValuePair("Admin", CString(IsAdmin()));
config.AddKeyValuePair("DenySetBindHost", CString(DenySetBindHost()));
config.AddKeyValuePair("DenySetIdent", CString(DenySetIdent()));
config.AddKeyValuePair("DenySetNetwork", CString(DenySetNetwork()));
config.AddKeyValuePair("DenySetRealName", CString(DenySetRealName()));
config.AddKeyValuePair("DenySetQuitMsg", CString(DenySetQuitMsg()));
config.AddKeyValuePair("DenySetCTCPReplies", CString(DenySetCTCPReplies()));
config.AddKeyValuePair("TimestampFormat", GetTimestampFormat());
config.AddKeyValuePair("AppendTimestamp", CString(GetTimestampAppend()));
config.AddKeyValuePair("PrependTimestamp", CString(GetTimestampPrepend()));
config.AddKeyValuePair("AuthOnlyViaModule", CString(AuthOnlyViaModule()));
config.AddKeyValuePair("Timezone", m_sTimezone);
config.AddKeyValuePair("JoinTries", CString(m_uMaxJoinTries));
config.AddKeyValuePair("MaxNetworks", CString(m_uMaxNetworks));
@@ -1003,22 +1054,49 @@ CConfig CUser::ToConfig() const {
return config;
}
bool CUser::CheckPass(const CString& sPass) const {
bool CUser::CheckPass(const CString& sPass) {
if(AuthOnlyViaModule() || CZNC::Get().GetAuthOnlyViaModule()) {
return false;
}
bool bResult = false;
bool bUpgrade = false;
switch (m_eHashType) {
case HASH_MD5:
return m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
bResult = m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
bUpgrade = true;
break;
case HASH_SHA256:
return m_sPass.Equals(CUtils::SaltedSHA256Hash(sPass, m_sPassSalt));
bResult = m_sPass.Equals(CUtils::SaltedSHA256Hash(sPass, m_sPassSalt));
#if ZNC_HAVE_ARGON
bUpgrade = true;
#endif
break;
case HASH_ARGON2ID:
#if ZNC_HAVE_ARGON
return argon2id_verify(m_sPass.c_str(), sPass.data(), sPass.length()) == ARGON2_OK;
#else
CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " cannot authenticate");
return false;
#endif
case HASH_NONE:
default:
// Don't upgrade hash, since the only valid use case for plain are
// manual tests, where it's simpler this way
return (sPass == m_sPass);
}
if (bResult && bUpgrade) {
CString sSalt = CUtils::GetSalt();
CString sHash = CUser::SaltedHash(sPass, sSalt);
SetPass(sHash, CUser::HASH_DEFAULT, sSalt);
}
return bResult;
}
/*CClient* CUser::GetClient() {
// Todo: optimize this by saving a pointer to the sock
CSockManager& Manager = CZNC::Get().GetManager();
CString sSockName = "USR::" + m_sUserName;
CString sSockName = "USR::" + m_sUsername;
for (unsigned int a = 0; a < Manager.size(); a++) {
Csock* pSock = Manager[a];
@@ -1062,13 +1140,6 @@ bool CUser::PutUser(const CString& sLine, CClient* pClient,
}
}
return (pClient == nullptr);
}
bool CUser::PutAllUser(const CString& sLine, CClient* pClient,
CClient* pSkipClient) {
PutUser(sLine, pClient, pSkipClient);
for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
if (pNetwork->PutUser(sLine, pClient, pSkipClient)) {
return true;
@@ -1114,7 +1185,7 @@ bool CUser::PutStatusNotice(const CString& sLine, CClient* pClient,
bool CUser::PutModule(const CString& sModule, const CString& sLine,
CClient* pClient, CClient* pSkipClient) {
for (CClient* pEachClient : m_vClients) {
for (CClient* pEachClient : GetAllClients()) {
if ((!pClient || pClient == pEachClient) &&
pSkipClient != pEachClient) {
pEachClient->PutModule(sModule, sLine);
@@ -1130,7 +1201,7 @@ bool CUser::PutModule(const CString& sModule, const CString& sLine,
bool CUser::PutModNotice(const CString& sModule, const CString& sLine,
CClient* pClient, CClient* pSkipClient) {
for (CClient* pEachClient : m_vClients) {
for (CClient* pEachClient : GetAllClients()) {
if ((!pClient || pClient == pEachClient) &&
pSkipClient != pEachClient) {
pEachClient->PutModNotice(sModule, sLine);
@@ -1144,8 +1215,14 @@ bool CUser::PutModNotice(const CString& sModule, const CString& sLine,
return (pClient == nullptr);
}
CString CUser::MakeCleanUserName(const CString& sUserName) {
return sUserName.Token(0, false, "@").Replace_n(".", "");
CString CUser::MakeCleanUsername(const CString& sUsername) {
return CUser::MakeCleanUserName(sUsername);
}
/// @deprecated
CString CUser::MakeCleanUserName(const CString& sUsername) {
return sUsername.Token(0, false, "@").Replace_n(".", "");
}
bool CUser::IsUserAttached() const {
@@ -1169,7 +1246,7 @@ bool CUser::LoadModule(const CString& sModName, const CString& sArgs,
CModInfo ModInfo;
if (!CZNC::Get().GetModules().GetModInfo(ModInfo, sModName, sModRet)) {
sError = "Unable to find modinfo [" + sModName + "] [" + sModRet + "]";
sError = t_f("Unable to find modinfo {1}: {2}")(sModName, sModRet);
return false;
}
@@ -1229,17 +1306,32 @@ void CUser::SetDCCBindHost(const CString& s) { m_sDCCBindHost = s; }
void CUser::SetPass(const CString& s, eHashType eHash, const CString& sSalt) {
m_sPass = s;
m_eHashType = eHash;
m_sPassSalt = sSalt;
switch (eHash) {
case HASH_NONE:
case HASH_ARGON2ID:
// Salt is embedded in the encoded "hash" in argon
m_sPassSalt = "";
break;
case HASH_MD5:
case HASH_SHA256:
m_sPassSalt = sSalt;
break;
}
}
void CUser::SetMultiClients(bool b) { m_bMultiClients = b; }
void CUser::SetDenyLoadMod(bool b) { m_bDenyLoadMod = b; }
void CUser::SetAdmin(bool b) { m_bAdmin = b; }
void CUser::SetDenySetBindHost(bool b) { m_bDenySetBindHost = b; }
void CUser::SetDenySetIdent(bool b) { m_bDenySetIdent = b; }
void CUser::SetDenySetNetwork(bool b) { m_bDenySetNetwork = b; }
void CUser::SetDenySetRealName(bool b) { m_bDenySetRealName = b; }
void CUser::SetDenySetQuitMsg(bool b) { m_bDenySetQuitMsg = b; }
void CUser::SetDenySetCTCPReplies(bool b) { m_bDenySetCTCPReplies = b; }
void CUser::SetDefaultChanModes(const CString& s) { m_sDefaultChanModes = s; }
void CUser::SetClientEncoding(const CString& s) {
m_sClientEncoding = s;
m_sClientEncoding = CZNC::Get().FixupEncoding(s);
for (CClient* pClient : GetAllClients()) {
pClient->SetEncoding(s);
pClient->SetEncoding(m_sClientEncoding);
}
}
void CUser::SetQuitMsg(const CString& s) { m_sQuitMsg = s; }
@@ -1306,14 +1398,19 @@ bool CUser::SetStatusPrefix(const CString& s) {
}
bool CUser::SetLanguage(const CString& s) {
// They look like ru_RU
// They look like ru-RU
for (char c : s) {
if (isalpha(c) || c == '_') {
if (isalpha(c) || c == '-' || c == '_') {
} else {
return false;
}
}
m_sLanguage = s;
// 1.7.0 accidentally used _ instead of -, which made language
// non-selectable. But it's possible that someone put _ to znc.conf
// manually.
// TODO: cleanup _ some time later.
m_sLanguage.Replace("_", "-");
return true;
}
// !Setters
@@ -1335,8 +1432,10 @@ vector<CClient*> CUser::GetAllClients() const {
return vClients;
}
const CString& CUser::GetUserName() const { return m_sUserName; }
const CString& CUser::GetCleanUserName() const { return m_sCleanUserName; }
/// @deprecated
const CString& CUser::GetUserName() const { return m_sUsername; }
const CString& CUser::GetUsername() const { return m_sUsername; }
const CString& CUser::GetCleanUserName() const { return m_sCleanUsername; }
const CString& CUser::GetNick(bool bAllowDefault) const {
return (bAllowDefault && m_sNick.empty()) ? GetCleanUserName() : m_sNick;
}
@@ -1361,7 +1460,13 @@ const CString& CUser::GetPassSalt() const { return m_sPassSalt; }
bool CUser::DenyLoadMod() const { return m_bDenyLoadMod; }
bool CUser::IsAdmin() const { return m_bAdmin; }
bool CUser::DenySetBindHost() const { return m_bDenySetBindHost; }
bool CUser::DenySetIdent() const { return m_bDenySetIdent; }
bool CUser::DenySetNetwork() const { return m_bDenySetNetwork; }
bool CUser::DenySetRealName() const { return m_bDenySetRealName; }
bool CUser::DenySetQuitMsg() const { return m_bDenySetQuitMsg; }
bool CUser::DenySetCTCPReplies() const { return m_bDenySetCTCPReplies; }
bool CUser::MultiClients() const { return m_bMultiClients; }
bool CUser::AuthOnlyViaModule() const { return m_bAuthOnlyViaModule; }
const CString& CUser::GetStatusPrefix() const { return m_sStatusPrefix; }
const CString& CUser::GetDefaultChanModes() const {
return m_sDefaultChanModes;
+199 -125
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -27,11 +27,21 @@
#include <znc/Message.h>
#ifdef HAVE_LIBSSL
#include <openssl/ssl.h>
#include <memory>
#include <openssl/bn.h>
#include <openssl/rsa.h>
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x20700000L))
#define X509_getm_notBefore X509_get_notBefore
#define X509_getm_notAfter X509_get_notAfter
#endif
#endif /* HAVE_LIBSSL */
#include <memory>
#include <unistd.h>
#include <time.h>
#ifdef ZNC_HAVE_GETHOSTNAME
#include <limits.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
@@ -46,10 +56,21 @@
#include <unicode/errorcode.h>
#endif
#ifdef ZNC_HAVE_ARGON
#include <argon2.h>
#endif
#ifdef ZNC_HAVE_UNAME
#include <sys/utsname.h>
#endif
// Required with GCC 4.3+ if openssl is disabled
#include <cstring>
#include <cstdlib>
#include <iomanip>
#include <chrono>
#include "cctz/time_zone.h"
using std::map;
using std::vector;
@@ -69,7 +90,7 @@ constexpr const char* szDefaultDH2048 =
"cvUyzAEcCQYHmiYjp2hoZbSa8b690TQaAwIBAg==\n"
"-----END DH PARAMETERS-----\n";
void CUtils::GenerateCert(FILE* pOut, const CString& sHost) {
void CUtils::GenerateCert(FILE* pOut) {
const int days = 365;
const int years = 10;
@@ -93,35 +114,28 @@ void CUtils::GenerateCert(FILE* pOut, const CString& sHost) {
X509_set_version(pCert.get(), 2);
ASN1_INTEGER_set(X509_get_serialNumber(pCert.get()), serial);
X509_gmtime_adj(X509_get_notBefore(pCert.get()), 0);
X509_gmtime_adj(X509_get_notAfter(pCert.get()),
X509_gmtime_adj(X509_getm_notBefore(pCert.get()), 0);
X509_gmtime_adj(X509_getm_notAfter(pCert.get()),
(long)60 * 60 * 24 * days * years);
X509_set_pubkey(pCert.get(), pKey.get());
const char* pLogName = getenv("LOGNAME");
const char* pHostName = nullptr;
const CString sHostName = GetHostName();
if (!pLogName) pLogName = "Unknown";
if (!sHost.empty()) pHostName = sHost.c_str();
if (!pHostName) pHostName = getenv("HOSTNAME");
if (!pHostName) pHostName = "host.unknown";
CString sEmailAddr = pLogName;
sEmailAddr += "@";
sEmailAddr += pHostName;
sEmailAddr += sHostName;
X509_NAME* pName = X509_get_subject_name(pCert.get());
X509_NAME_add_entry_by_txt(pName, "OU", MBSTRING_ASC,
(unsigned char*)pLogName, -1, -1, 0);
X509_NAME_add_entry_by_txt(pName, "CN", MBSTRING_ASC,
(unsigned char*)pHostName, -1, -1, 0);
(unsigned char*)sHostName.c_str(), -1, -1, 0);
X509_NAME_add_entry_by_txt(pName, "emailAddress", MBSTRING_ASC,
(unsigned char*)sEmailAddr.c_str(), -1, -1, 0);
X509_set_subject_name(pCert.get(), pName);
X509_set_issuer_name(pCert.get(), pName);
if (!X509_sign(pCert.get(), pKey.get(), EVP_sha256())) return;
@@ -168,12 +182,48 @@ unsigned long CUtils::GetLongIP(const CString& sIP) {
return ret;
}
// If you change this here and in GetSaltedHashPass(),
// don't forget CUser::HASH_DEFAULT!
// TODO refactor this
const CString CUtils::sDefaultHash = "sha256";
CString CUtils::GetSaltedHashPass(CString& sSalt) {
sSalt = GetSalt();
CString CUtils::GetHostName() {
const char *pEnv;
pEnv = getenv("HOSTNAME");
if (pEnv && pEnv[0])
return pEnv;
#if defined(ZNC_HAVE_GETHOSTNAME) && defined(_POSIX_HOST_NAME_MAX)
char szBuffer[_POSIX_HOST_NAME_MAX + 1];
szBuffer[_POSIX_HOST_NAME_MAX] = 0;
if (gethostname(szBuffer, _POSIX_HOST_NAME_MAX) == 0)
return std::string(szBuffer);
#endif
#if defined(ZNC_HAVE_UNAME)
struct utsname UnameBuffer;
if (uname(&UnameBuffer) == 0 && UnameBuffer.nodename[0] != '\0')
return UnameBuffer.nodename;
#endif
return "host.unknown";
}
#ifdef ZNC_HAVE_ARGON
static CString SaltedArgonHash(const CString& sPass, const CString& sSalt) {
#define ZNC_ARGON_PARAMS /* iterations */ 6, /* memory */ 6144, /* parallelism */ 1
constexpr int iHashLen = 32;
CString sOut;
sOut.resize(argon2_encodedlen(ZNC_ARGON_PARAMS, sSalt.length(), iHashLen, Argon2_id) + 1);
int err = argon2id_hash_encoded(ZNC_ARGON_PARAMS, sPass.data(), sPass.length(), sSalt.data(), sSalt.length(), iHashLen, &sOut[0], sOut.length());
if (err) {
CUtils::PrintError(argon2_error_message(err));
sOut.clear();
}
return sOut;
}
#undef ZNC_ARGON_PARAMS
#endif
CString CUtils::AskSaltedHashPassForConfig() {
CString sSalt = GetSalt();
while (true) {
CString pass1;
@@ -187,7 +237,18 @@ CString CUtils::GetSaltedHashPass(CString& sSalt) {
CUtils::PrintError("The supplied passwords did not match");
} else {
// Construct the salted pass
return SaltedSHA256Hash(pass1, sSalt);
VCString vsLines;
vsLines.push_back("<Pass password>");
#if ZNC_HAVE_ARGON
vsLines.push_back("\tMethod = Argon2id");
vsLines.push_back("\tHash = " + SaltedArgonHash(pass1, sSalt));
#else
vsLines.push_back("\tMethod = SHA256");
vsLines.push_back("\tHash = " + SaltedSHA256Hash(pass1, sSalt));
vsLines.push_back("\tSalt = " + sSalt);
#endif
vsLines.push_back("</Pass>");
return CString("\n").Join(vsLines.begin(), vsLines.end());
}
}
}
@@ -202,6 +263,14 @@ CString CUtils::SaltedSHA256Hash(const CString& sPass, const CString& sSalt) {
return CString(sPass + sSalt).SHA256();
}
CString CUtils::SaltedHash(const CString& sPass, const CString& sSalt) {
#ifdef ZNC_HAVE_ARGON
return SaltedArgonHash(sPass, sSalt);
#else
return SaltedSHA256Hash(sPass, sSalt);
#endif
}
CString CUtils::GetPass(const CString& sPrompt) {
#ifdef HAVE_TCSETATTR
// Disable echo
@@ -390,29 +459,6 @@ void CUtils::PrintStatus(bool bSuccess, const CString& sMessage) {
fflush(stdout);
}
namespace {
/* Switch GMT-X and GMT+X
*
* See https://en.wikipedia.org/wiki/Tz_database#Area
*
* "In order to conform with the POSIX style, those zone names beginning
* with "Etc/GMT" have their sign reversed from what most people expect.
* In this style, zones west of GMT have a positive sign and those east
* have a negative sign in their name (e.g "Etc/GMT-14" is 14 hours
* ahead/east of GMT.)"
*/
inline CString FixGMT(CString sTZ) {
if (sTZ.length() >= 4 && sTZ.StartsWith("GMT")) {
if (sTZ[3] == '+') {
sTZ[3] = '-';
} else if (sTZ[3] == '-') {
sTZ[3] = '+';
}
}
return sTZ;
}
} // namespace
timeval CUtils::GetTime() {
#ifdef HAVE_CLOCK_GETTIME
timespec ts;
@@ -431,105 +477,104 @@ timeval CUtils::GetTime() {
}
unsigned long long CUtils::GetMillTime() {
struct timeval tv = GetTime();
unsigned long long iTime = 0;
iTime = (unsigned long long)tv.tv_sec * 1000;
iTime += ((unsigned long long)tv.tv_usec / 1000);
return iTime;
std::chrono::time_point<std::chrono::steady_clock> time = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(time.time_since_epoch()).count();
}
CString CUtils::CTime(time_t t, const CString& sTimezone) {
char s[30] = {}; // should have at least 26 bytes
if (sTimezone.empty()) {
ctime_r(&t, s);
// ctime() adds a trailing newline
return CString(s).Trim_n();
}
CString sTZ = FixGMT(sTimezone);
// backup old value
char* oldTZ = getenv("TZ");
if (oldTZ) oldTZ = strdup(oldTZ);
setenv("TZ", sTZ.c_str(), 1);
tzset();
ctime_r(&t, s);
// restore old value
if (oldTZ) {
setenv("TZ", oldTZ, 1);
free(oldTZ);
} else {
unsetenv("TZ");
}
tzset();
return CString(s).Trim_n();
return FormatTime(t, "%c", sTimezone);
}
CString CUtils::FormatTime(time_t t, const CString& sFormat,
const CString& sTimezone) {
char s[1024] = {};
tm m;
cctz::time_zone tz;
if (sTimezone.empty()) {
localtime_r(&t, &m);
strftime(s, sizeof(s), sFormat.c_str(), &m);
return s;
}
CString sTZ = FixGMT(sTimezone);
// backup old value
char* oldTZ = getenv("TZ");
if (oldTZ) oldTZ = strdup(oldTZ);
setenv("TZ", sTZ.c_str(), 1);
tzset();
localtime_r(&t, &m);
strftime(s, sizeof(s), sFormat.c_str(), &m);
// restore old value
if (oldTZ) {
setenv("TZ", oldTZ, 1);
free(oldTZ);
tz = cctz::local_time_zone();
} else if (sTimezone.StartsWith("GMT")) {
int offset = CString(sTimezone.substr(3)).ToInt();
tz = cctz::fixed_time_zone(cctz::seconds(offset * 60 * 60));
} else {
unsetenv("TZ");
cctz::load_time_zone(sTimezone, &tz);
}
tzset();
return s;
return cctz::format(sFormat, std::chrono::system_clock::from_time_t(t), tz);
}
CString CUtils::FormatTime(const timeval& tv, const CString& sFormat,
const CString& sTimezone) {
// Parse additional format specifiers before passing them to
// strftime, since the way strftime treats unknown format
// specifiers is undefined.
// TODO: consider using cctz's %E#f instead.
CString sFormat2;
// Make sure %% is parsed correctly, i.e. %%f is passed through to
// strftime to become %f, and not 123.
bool bInFormat = false;
int iDigits;
CString::size_type uLastCopied = 0, uFormatStart;
for (CString::size_type i = 0; i < sFormat.length(); i++) {
if (!bInFormat) {
if (sFormat[i] == '%') {
uFormatStart = i;
bInFormat = true;
iDigits = 3;
}
} else {
switch (sFormat[i]) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
iDigits = sFormat[i] - '0';
break;
case 'f': {
int iVal = tv.tv_usec;
int iDigitDelta = iDigits - 6; // tv_user is in 10^-6 seconds
for (; iDigitDelta > 0; iDigitDelta--)
iVal *= 10;
for (; iDigitDelta < 0; iDigitDelta++)
iVal /= 10;
sFormat2 += sFormat.substr(uLastCopied,
uFormatStart - uLastCopied);
CString sVal = CString(iVal);
sFormat2 += CString(iDigits - sVal.length(), '0');
sFormat2 += sVal;
uLastCopied = i + 1;
bInFormat = false;
break;
}
default:
bInFormat = false;
}
}
}
if (uLastCopied) {
sFormat2 += sFormat.substr(uLastCopied);
return FormatTime(tv.tv_sec, sFormat2, sTimezone);
} else {
// If there are no extended format specifiers, avoid doing any
// memory allocations entirely.
return FormatTime(tv.tv_sec, sFormat, sTimezone);
}
}
CString CUtils::FormatServerTime(const timeval& tv) {
CString s_msec(tv.tv_usec / 1000);
while (s_msec.length() < 3) {
s_msec = "0" + s_msec;
}
// TODO support leap seconds properly
// TODO support message-tags properly
struct tm stm;
memset(&stm, 0, sizeof(stm));
// OpenBSD has tv_sec as int, so explicitly convert it to time_t to make
// gmtime_r() happy
const time_t secs = tv.tv_sec;
gmtime_r(&secs, &stm);
char sTime[20] = {};
strftime(sTime, sizeof(sTime), "%Y-%m-%dT%H:%M:%S", &stm);
return CString(sTime) + "." + s_msec + "Z";
using namespace std::chrono;
system_clock::time_point time{duration_cast<system_clock::duration>(
seconds(tv.tv_sec) + microseconds(tv.tv_usec))};
return cctz::format("%Y-%m-%dT%H:%M:%E3SZ", time, cctz::utc_time_zone());
}
timeval CUtils::ParseServerTime(const CString& sTime) {
struct tm stm;
memset(&stm, 0, sizeof(stm));
const char* cp = strptime(sTime.c_str(), "%Y-%m-%dT%H:%M:%S", &stm);
using namespace std::chrono;
system_clock::time_point tp;
cctz::parse("%Y-%m-%dT%H:%M:%E*SZ", sTime, cctz::utc_time_zone(), &tp);
struct timeval tv;
memset(&tv, 0, sizeof(tv));
if (cp) {
tv.tv_sec = mktime(&stm);
CString s_usec(cp);
if (s_usec.TrimPrefix(".") && s_usec.TrimSuffix("Z")) {
tv.tv_usec = s_usec.ToULong() * 1000;
}
}
microseconds usec = duration_cast<microseconds>(tp.time_since_epoch());
tv.tv_sec = usec.count() / 1000000;
tv.tv_usec = usec.count() % 1000000;
return tv;
}
@@ -697,6 +742,8 @@ void CUtils::SetMessageTags(CString& sLine, const MCString& mssTags) {
}
bool CTable::AddColumn(const CString& sName) {
if (eStyle == ListStyle && m_vsHeaders.size() >= 2)
return false;
for (const CString& sHeader : m_vsHeaders) {
if (sHeader.Equals(sName)) {
return false;
@@ -709,6 +756,19 @@ bool CTable::AddColumn(const CString& sName) {
return true;
}
bool CTable::SetStyle(EStyle eNewStyle) {
switch (eNewStyle) {
case GridStyle:
break;
case ListStyle:
if (m_vsHeaders.size() > 2) return false;
break;
}
eStyle = eNewStyle;
return true;
}
CTable::size_type CTable::AddRow() {
// Don't add a row if no headers are defined
if (m_vsHeaders.empty()) {
@@ -749,6 +809,20 @@ bool CTable::GetLine(unsigned int uIdx, CString& sLine) const {
return false;
}
if (eStyle == ListStyle) {
if (m_vsHeaders.size() > 2) return false; // definition list mode can only do up to two columns
if (uIdx >= size()) return false;
const std::vector<CString>& mRow = (*this)[uIdx];
ssRet << "\x02" << mRow[0] << "\x0f"; //bold first column
if (m_vsHeaders.size() >= 2 && mRow[1] != "") {
ssRet << ": " << mRow[1];
}
sLine = ssRet.str();
return true;
}
if (uIdx == 1) {
ssRet.fill(' ');
ssRet << "| ";
+11 -9
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -177,7 +177,7 @@ void CWebAuth::AcceptedLogin(CUser& User) {
m_pWebSock->Redirect("/?cookie_check=true");
}
DEBUG("Successful login attempt ==> USER [" + User.GetUserName() +
DEBUG("Successful login attempt ==> USER [" + User.GetUsername() +
"] ==> SESSION [" + spSession->GetId() + "]");
}
}
@@ -446,7 +446,7 @@ bool CWebSock::AddModLoop(const CString& sLoopName, CModule& Module,
}
if (Module.GetUser()) {
Row["Username"] = Module.GetUser()->GetUserName();
Row["Username"] = Module.GetUser()->GetUsername();
}
VWebSubPages& vSubPages = Module.GetSubPages();
@@ -557,13 +557,15 @@ CWebSock::EPageReqResult CWebSock::PrintTemplate(const CString& sPageName,
}
CString CWebSock::GetSkinPath(const CString& sSkinName) {
CString sRet = CZNC::Get().GetZNCPath() + "/webskins/" + sSkinName;
const CString sSkin = sSkinName.Replace_n("/", "_").Replace_n(".", "_");
CString sRet = CZNC::Get().GetZNCPath() + "/webskins/" + sSkin;
if (!CFile::IsDir(sRet)) {
sRet = CZNC::Get().GetCurPath() + "/webskins/" + sSkinName;
sRet = CZNC::Get().GetCurPath() + "/webskins/" + sSkin;
if (!CFile::IsDir(sRet)) {
sRet = CString(_SKINDIR_) + "/" + sSkinName;
sRet = CString(_SKINDIR_) + "/" + sSkin;
}
}
@@ -670,7 +672,7 @@ CWebSock::EPageReqResult CWebSock::OnPageRequestInternal(const CString& sURI,
SendCookie("SessionId", GetSession()->GetId());
if (GetSession()->IsLoggedIn()) {
m_sUser = GetSession()->GetUser()->GetUserName();
m_sUser = GetSession()->GetUser()->GetUsername();
m_bLoggedIn = true;
}
CLanguageScope user_language(
@@ -830,7 +832,7 @@ CWebSock::EPageReqResult CWebSock::OnPageRequestInternal(const CString& sURI,
pModule->GetUser() != GetSession()->GetUser()) {
PrintErrorPage(403, "Forbidden",
"You must login as " +
pModule->GetUser()->GetUserName() +
pModule->GetUser()->GetUsername() +
" in order to view this page");
return PAGE_DONE;
} else if (pModule->OnWebPreRequest(*this, m_sPage)) {
@@ -942,7 +944,7 @@ std::shared_ptr<CWebSession> CWebSock::GetSession() {
DEBUG("Found existing session from cookie: [" + sCookieSessionId +
"] IsLoggedIn(" +
CString((*pSession)->IsLoggedIn()
? "true, " + ((*pSession)->GetUser()->GetUserName())
? "true, " + ((*pSession)->GetUser()->GetUsername())
: "false") +
")");
return *pSession;
+8 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -29,11 +29,16 @@ CString CDebug::Filter(const CString& sUnfilteredLine) {
// If the line is a PASS command to authenticate to a server / znc
if (sUnfilteredLine.StartsWith("PASS ")) {
CString sPrefix = sUnfilteredLine.substr(0, sUnfilteredLine[5] == ':' ? 6 : 5);
CString sRest = sUnfilteredLine.substr(sPrefix.length());
VCString vsSafeCopy;
sUnfilteredLine.Split(":", vsSafeCopy);
sRest.Split(":", vsSafeCopy);
if (vsSafeCopy.size() > 1) {
sFilteredLine = vsSafeCopy[0] + ":<censored>";
sFilteredLine = sPrefix + vsSafeCopy[0] + ":<censored>";
} else {
sFilteredLine = sPrefix + "<censored>";
}
}
+8 -8
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -258,7 +258,7 @@ CString CString::Escape_n(EEscape eFrom, EEscape eTo) const {
const unsigned char* p = (const unsigned char*)data();
size_type iLength = length();
sRet.reserve(iLength * 3);
unsigned char pTmp[21];
unsigned char pTmp[21] = {};
unsigned int iCounted = 0;
for (unsigned int a = 0; a < iLength; a++, p = pStart + a) {
@@ -294,13 +294,13 @@ CString CString::Escape_n(EEscape eFrom, EEscape eTo) const {
}
if (ch == 0) {
if (!strncasecmp((const char*)&pTmp, "&lt;", 2))
if (!strncasecmp((const char*)&pTmp, "&lt;", 4))
ch = '<';
else if (!strncasecmp((const char*)&pTmp, "&gt;", 2))
else if (!strncasecmp((const char*)&pTmp, "&gt;", 4))
ch = '>';
else if (!strncasecmp((const char*)&pTmp, "&quot;", 4))
else if (!strncasecmp((const char*)&pTmp, "&quot;", 6))
ch = '"';
else if (!strncasecmp((const char*)&pTmp, "&amp;", 3))
else if (!strncasecmp((const char*)&pTmp, "&amp;", 5))
ch = '&';
}
@@ -1022,7 +1022,7 @@ bool CString::Base64Encode(CString& sRet, unsigned int uWrap) const {
return 0;
}
p = output = new unsigned char[toalloc];
p = output = new unsigned char[toalloc]{};
while (i < len - mod) {
*p++ = b64table[input[i++] >> 2];
@@ -1072,7 +1072,7 @@ unsigned long CString::Base64Decode(CString& sRet) const {
char c, c1, *p;
unsigned long i;
unsigned long uLen = sTmp.size();
char* out = new char[uLen + 1];
char* out = new char[uLen + 1]{};
for (i = 0, p = out; i < uLen; i++) {
c = (char)base64_table[(unsigned char)in[i++]];
+14 -9
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -46,9 +46,15 @@ static void locking_callback(int mode, int type, const char* file, int line) {
}
}
#if OPENSSL_VERSION_NUMBER >= 0x10000000
static void thread_id_callback(CRYPTO_THREADID *id) {
CRYPTO_THREADID_set_numeric(id, (unsigned long)pthread_self());
}
#else
static unsigned long thread_id_callback() {
return (unsigned long)pthread_self();
}
#endif
static CRYPTO_dynlock_value* dyn_create_callback(const char* file, int line) {
return (CRYPTO_dynlock_value*)new CMutex;
@@ -78,7 +84,11 @@ static void thread_setup() {
for (std::unique_ptr<CMutex>& mtx : lock_cs)
mtx = std::unique_ptr<CMutex>(new CMutex());
#if OPENSSL_VERSION_NUMBER >= 0x10000000
CRYPTO_THREADID_set_callback(&thread_id_callback);
#else
CRYPTO_set_id_callback(&thread_id_callback);
#endif
CRYPTO_set_locking_callback(&locking_callback);
CRYPTO_set_dynlock_create_callback(&dyn_create_callback);
@@ -373,20 +383,15 @@ int main(int argc, char** argv) {
#endif /* HAVE_LIBSSL */
if (bMakePass) {
CString sSalt;
CUtils::PrintMessage("Type your new password.");
CString sHash = CUtils::GetSaltedHashPass(sSalt);
CString sPass = CUtils::AskSaltedHashPassForConfig();
CUtils::PrintMessage("Kill ZNC process, if it's running.");
CUtils::PrintMessage(
"Then replace password in the <User> section of your config with "
"this:");
// Not PrintMessage(), to remove [**] from the beginning, to ease
// copypasting
std::cout << "<Pass password>" << std::endl;
std::cout << "\tMethod = " << CUtils::sDefaultHash << std::endl;
std::cout << "\tHash = " << sHash << std::endl;
std::cout << "\tSalt = " << sSalt << std::endl;
std::cout << "</Pass>" << std::endl;
std::cout << sPass << std::endl;
CUtils::PrintMessage(
"After that start ZNC again, and you should be able to login with "
"the new password.");
@@ -462,7 +467,7 @@ int main(int argc, char** argv) {
if (bForeground) {
int iPid = getpid();
CUtils::PrintMessage("Staying open for debugging [pid: " +
CUtils::PrintMessage("Running in foreground [pid: " +
CString(iPid) + "]");
pZNC->WritePidFile(iPid);
+1 -1
View File
@@ -1,5 +1,5 @@
#
# Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
# Copyright (C) 2004-2025 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.
+2095
View File
File diff suppressed because it is too large Load Diff
+2042
View File
File diff suppressed because it is too large Load Diff
+2126
View File
File diff suppressed because it is too large Load Diff
+2042
View File
File diff suppressed because it is too large Load Diff
+2104
View File
File diff suppressed because it is too large Load Diff
+2119
View File
File diff suppressed because it is too large Load Diff
+2052
View File
File diff suppressed because it is too large Load Diff
+2138
View File
File diff suppressed because it is too large Load Diff
+2121
View File
File diff suppressed because it is too large Load Diff
+2127
View File
File diff suppressed because it is too large Load Diff
+1986 -16
View File
File diff suppressed because it is too large Load Diff
+2102
View File
File diff suppressed because it is too large Load Diff
+2110
View File
File diff suppressed because it is too large Load Diff
+2068
View File
File diff suppressed because it is too large Load Diff
-78
View File
@@ -1,78 +0,0 @@
# Alexey Sokolov, 2016.
msgid ""
msgstr ""
"Project-Id-Version: znc 1.7.x\n"
"PO-Revision-Date: 2016-02-04 22:06+0000\n"
"Last-Translator: Alexey Sokolov\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Lokalize 1.5\n"
#: webskins/_default_/tmpl/index.tmpl:6
msgid "Welcome to ZNC's web interface!"
msgstr "Добро пожаловать в веб-интерфейс ZNC!"
#: webskins/_default_/tmpl/index.tmpl:11
msgid ""
"No Web-enabled modules have been loaded. Load modules from IRC (“<code>/msg "
"*status help</code>” and “<code>/msg *status loadmod &lt;module&gt;</"
"code>”). Once you have loaded some Web-enabled modules, the menu will expand."
msgstr ""
"Нет загруженных модулей с поддержкой веб-интерфейса. Вы можете загрузить их "
"из IRC («<code>/msg *status help</code>» и «<code>/msg *status loadmod "
"&lt;модуль&gt;</code>»). Когда такие модули будут загружены, они будут "
"доступны в меню сбоку."
#: webskins/_default_/tmpl/InfoBar.tmpl:2
msgid "-Guest-"
msgstr ""
#: webskins/_default_/tmpl/InfoBar.tmpl:5
msgid "Logged in as: {1} (from {2})"
msgstr "Вы вошли как: {1} (c {2})"
#: webskins/_default_/tmpl/Menu.tmpl:4
msgid "Home"
msgstr "Домой"
#: webskins/_default_/tmpl/Menu.tmpl:7
msgid "Global Modules"
msgstr "Глобальные модули"
#: webskins/_default_/tmpl/Menu.tmpl:20
msgid "User Modules"
msgstr "Пользовательские модули"
#: webskins/_default_/tmpl/Menu.tmpl:35
msgid "Network Modules ({1})"
msgstr "Модули сети {1}"
#: webskins/_default_/tmpl/LoginBar.tmpl:3
msgid "Logout"
msgstr "Выйти"
#: ClientCommand.cpp:51
msgid "You must be connected with a network to use this command"
msgstr ""
#: ClientCommand.cpp:58
msgid "Usage: ListNicks <#chan>"
msgstr ""
#: ClientCommand.cpp:65
msgid "You are not on [{1}]"
msgstr ""
#: ClientCommand.cpp:70
msgid "You are not on [{1}] (trying)"
msgstr ""
#: ClientCommand.cpp:79
msgid "No nicks on [{1}]"
msgstr ""
+2098
View File
File diff suppressed because it is too large Load Diff
+2103
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
+95 -74
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
* Copyright (C) 2004-2025 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.
@@ -34,12 +34,6 @@ 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()
: m_TimeStarted(time(nullptr)),
m_eConfigState(ECONFIG_NOTHING),
@@ -65,7 +59,7 @@ CZNC::CZNC()
m_uiConnectDelay(5),
m_uiAnonIPLimit(10),
m_uiMaxBufferSize(500),
m_uDisabledSSLProtocols(Csock::EDP_SSL),
m_uDisabledSSLProtocols(Csock::EDP_SSL | Csock::EDP_TLSv1 | Csock::EDP_TLSv1_1),
m_pModules(new CModules),
m_uBytesRead(0),
m_uBytesWritten(0),
@@ -76,6 +70,7 @@ CZNC::CZNC()
m_sConnectThrottle(),
m_bProtectWebSessions(true),
m_bHideVersion(false),
m_bAuthOnlyViaModule(false),
m_Translation("znc"),
m_uiConfigWriteDelay(0),
m_pConfigTimer(nullptr) {
@@ -139,15 +134,7 @@ CString CZNC::GetTag(bool bIncludeVersion, bool bHTML) {
}
CString CZNC::GetCompileOptionsString() {
// Build system doesn't affect ABI
return ZNC_COMPILE_OPTIONS_STRING + CString(
", build: "
#ifdef BUILD_WITH_CMAKE
"cmake"
#else
"autoconf"
#endif
);
return ZNC_COMPILE_OPTIONS_STRING;
}
CString CZNC::GetUptime() const {
@@ -174,7 +161,7 @@ bool CZNC::HandleUserDeletion() {
pUser->SetBeingDeleted(false);
continue;
}
m_msUsers.erase(pUser->GetUserName());
m_msUsers.erase(pUser->GetUsername());
CWebSock::FinishUserSessions(*pUser);
delete pUser;
}
@@ -329,7 +316,7 @@ bool CZNC::WritePemFile() {
return false;
}
CUtils::GenerateCert(f, "");
CUtils::GenerateCert(f);
fclose(f);
CUtils::PrintStatus(true);
@@ -380,8 +367,7 @@ void CZNC::InitDirs(const CString& sArgvPath, const CString& sDataDir) {
m_sZNCPath = sDataDir;
}
m_sSSLCertFile = m_sSSLKeyFile = m_sSSLDHParamFile =
m_sZNCPath + "/znc.pem";
m_sSSLCertFile = m_sZNCPath + "/znc.pem";
}
CString CZNC::GetConfPath(bool bAllowMkDir) const {
@@ -429,11 +415,13 @@ CString CZNC::GetPemLocation() const {
}
CString CZNC::GetKeyLocation() const {
return CDir::ChangeDir("", m_sSSLKeyFile);
return CDir::ChangeDir(
"", m_sSSLKeyFile.empty() ? m_sSSLCertFile : m_sSSLKeyFile);
}
CString CZNC::GetDHParamLocation() const {
return CDir::ChangeDir("", m_sSSLDHParamFile);
return CDir::ChangeDir(
"", m_sSSLDHParamFile.empty() ? m_sSSLCertFile : m_sSSLDHParamFile);
}
CString CZNC::ExpandConfigPath(const CString& sConfigFile, bool bAllowMkDir) {
@@ -486,12 +474,13 @@ bool CZNC::WriteConfig() {
CConfig config;
config.AddKeyValuePair("AnonIPLimit", CString(m_uiAnonIPLimit));
config.AddKeyValuePair("MaxBufferSize", CString(m_uiMaxBufferSize));
config.AddKeyValuePair("SSLCertFile", CString(m_sSSLCertFile));
config.AddKeyValuePair("SSLKeyFile", CString(m_sSSLKeyFile));
config.AddKeyValuePair("SSLDHParamFile", CString(m_sSSLDHParamFile));
config.AddKeyValuePair("SSLCertFile", CString(GetPemLocation()));
config.AddKeyValuePair("SSLKeyFile", CString(GetKeyLocation()));
config.AddKeyValuePair("SSLDHParamFile", CString(GetDHParamLocation()));
config.AddKeyValuePair("ProtectWebSessions",
CString(m_bProtectWebSessions));
config.AddKeyValuePair("HideVersion", CString(m_bHideVersion));
config.AddKeyValuePair("AuthOnlyViaModule", CString(m_bAuthOnlyViaModule));
config.AddKeyValuePair("Version", CString(VERSION_STR));
config.AddKeyValuePair("ConfigWriteDelay", CString(m_uiConfigWriteDelay));
@@ -555,7 +544,7 @@ bool CZNC::WriteConfig() {
continue;
}
config.AddSubConfig("User", it.second->GetUserName(),
config.AddSubConfig("User", it.second->GetUsername(),
it.second->ToConfig());
}
@@ -643,14 +632,14 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) {
65534)) {
continue;
}
if (uListenPort == 6667) {
if (uListenPort == 6667 || uListenPort == 6697) {
CUtils::PrintStatus(false,
"WARNING: Some web browsers reject port "
"6667. If you intend to");
"WARNING: Some web browsers reject ports "
"6667 and 6697. 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?",
if (!CUtils::GetBoolInput("Proceed anyway?",
true)) {
continue;
}
@@ -721,17 +710,15 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) {
CString sNick;
do {
CUtils::GetInput("Username", sUser, "", "alphanumeric");
} while (!CUser::IsValidUserName(sUser));
} while (!CUser::IsValidUsername(sUser));
vsLines.push_back("<User " + sUser + ">");
CString sSalt;
sAnswer = CUtils::GetSaltedHashPass(sSalt);
vsLines.push_back("\tPass = " + CUtils::sDefaultHash + "#" + sAnswer +
"#" + sSalt + "#");
sAnswer = CUtils::AskSaltedHashPassForConfig();
vsLines.push_back(sAnswer);
vsLines.push_back("\tAdmin = true");
CUtils::GetInput("Nick", sNick, CUser::MakeCleanUserName(sUser));
CUtils::GetInput("Nick", sNick, CUser::MakeCleanUsername(sUser));
vsLines.push_back("\tNick = " + sNick);
CUtils::GetInput("Alternate nick", sAnswer, sNick + "_");
if (!sAnswer.empty()) {
@@ -768,7 +755,7 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) {
CUtils::PrintMessage("");
do {
CUtils::GetInput("Name", sNetwork, "freenode");
CUtils::GetInput("Name", sNetwork, "libera");
} while (!CIRCNetwork::IsValidNetwork(sNetwork));
vsLines.push_back("\t<Network " + sNetwork + ">");
@@ -785,8 +772,8 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) {
bool bSSL = false;
unsigned int uServerPort = 0;
if (sNetwork.Equals("freenode")) {
sHost = "chat.freenode.net";
if (sNetwork.Equals("libera")) {
sHost = "irc.libera.chat";
#ifdef HAVE_LIBSSL
bSSL = true;
#endif
@@ -1042,6 +1029,7 @@ bool CZNC::ReadConfig(CConfig& config, CString& sError) {
// create a backup file if necessary
CString sSavedVersion;
config.FindStringEntry("version", sSavedVersion);
config.AddKeyValuePair("version", sSavedVersion);
if (sSavedVersion.empty()) {
CUtils::PrintError(
"Config does not contain a version identifier. It may be be too "
@@ -1084,20 +1072,28 @@ bool CZNC::RehashConfig(CString& sError) {
bool CZNC::LoadGlobal(CConfig& config, CString& sError) {
sError.clear();
CString sSavedVersion;
config.FindStringEntry("version", sSavedVersion);
tuple<unsigned int, unsigned int> tSavedVersion =
make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(),
sSavedVersion.Token(1, false, ".").ToUInt());
MCString msModules; // Modules are queued for later loading
VCString vsList;
config.FindStringVector("loadmodule", vsList);
// Automatically load corecaps if config was upgraded from old version, but
// don't force it if user explicitly unloaded it
if (tSavedVersion < make_tuple(1, 9)) {
vsList.push_back("corecaps");
}
for (const CString& sModLine : vsList) {
CString sModName = sModLine.Token(0);
CString sArgs = sModLine.Token(1, true);
// compatibility for pre-1.0 configs
CString sSavedVersion;
config.FindStringEntry("version", sSavedVersion);
tuple<unsigned int, unsigned int> tSavedVersion =
make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(),
sSavedVersion.Token(1, false, ".").ToUInt());
if (sModName == "saslauth" && tSavedVersion < make_tuple(0, 207)) {
CUtils::PrintMessage(
"saslauth module was renamed to cyrusauth. Loading cyrusauth "
@@ -1194,6 +1190,8 @@ bool CZNC::LoadGlobal(CConfig& config, CString& sError) {
m_bProtectWebSessions = sVal.ToBool();
if (config.FindStringEntry("hideversion", sVal))
m_bHideVersion = sVal.ToBool();
if (config.FindStringEntry("authonlyviamodule", sVal))
m_bAuthOnlyViaModule = sVal.ToBool();
if (config.FindStringEntry("sslprotocols", sVal)) {
if (!SetSSLProtocols(sVal)) {
VCString vsProtocols = GetAvailableSSLProtocols();
@@ -1226,12 +1224,12 @@ bool CZNC::LoadUsers(CConfig& config, CString& sError) {
config.FindSubConfig("user", subConf);
for (const auto& subIt : subConf) {
const CString& sUserName = subIt.first;
const CString& sUsername = subIt.first;
CConfig* pSubConf = subIt.second.m_pSubConfig;
CUtils::PrintMessage("Loading user [" + sUserName + "]");
CUtils::PrintMessage("Loading user [" + sUsername + "]");
std::unique_ptr<CUser> pUser(new CUser(sUserName));
std::unique_ptr<CUser> pUser(new CUser(sUsername));
if (!m_sStatusPrefix.empty()) {
if (!pUser->SetStatusPrefix(m_sStatusPrefix)) {
@@ -1248,20 +1246,19 @@ bool CZNC::LoadUsers(CConfig& config, CString& sError) {
}
if (!pSubConf->empty()) {
sError = "Unhandled lines in config for User [" + sUserName + "]!";
sError = "Unhandled lines in config for User [" + sUsername + "]!";
CUtils::PrintError(sError);
DumpConfig(pSubConf);
return false;
}
CString sErr;
if (!AddUser(pUser.release(), sErr, true)) {
sError = "Invalid user [" + sUserName + "] " + sErr;
}
if (!sError.empty()) {
CUser* pRawUser = pUser.release();
if (!AddUser(pRawUser, sErr, true)) {
sError = "Invalid user [" + sUsername + "] " + sErr;
CUtils::PrintError(sError);
pUser->SetBeingDeleted(true);
pRawUser->SetBeingDeleted(true);
delete pRawUser;
return false;
}
}
@@ -1493,7 +1490,7 @@ bool CZNC::UpdateModule(const CString& sModule) {
if (!pUser->GetModules().LoadModule(
sModule, sArgs, CModInfo::UserModule, pUser, nullptr, sErr)) {
DEBUG("Failed to reload [" << sModule << "] for ["
<< pUser->GetUserName() << "] [" << sErr
<< pUser->GetUsername() << "] [" << sErr
<< "]");
bError = true;
}
@@ -1508,7 +1505,7 @@ bool CZNC::UpdateModule(const CString& sModule) {
sModule, sArgs, CModInfo::NetworkModule, pNetwork->GetUser(),
pNetwork, sErr)) {
DEBUG("Failed to reload ["
<< sModule << "] for [" << pNetwork->GetUser()->GetUserName()
<< sModule << "] for [" << pNetwork->GetUser()->GetUsername()
<< "/" << pNetwork->GetName() << "] [" << sErr << "]");
bError = true;
}
@@ -1534,18 +1531,18 @@ bool CZNC::DeleteUser(const CString& sUsername) {
return false;
}
m_msDelUsers[pUser->GetUserName()] = pUser;
m_msDelUsers[pUser->GetUsername()] = pUser;
return true;
}
bool CZNC::AddUser(CUser* pUser, CString& sErrorRet, bool bStartup) {
if (FindUser(pUser->GetUserName()) != nullptr) {
sErrorRet = "User already exists";
DEBUG("User [" << pUser->GetUserName() << "] - already exists");
if (FindUser(pUser->GetUsername()) != nullptr) {
sErrorRet = t_s("User already exists");
DEBUG("User [" << pUser->GetUsername() << "] - already exists");
return false;
}
if (!pUser->IsValid(sErrorRet)) {
DEBUG("Invalid user [" << pUser->GetUserName() << "] - [" << sErrorRet
DEBUG("Invalid user [" << pUser->GetUsername() << "] - [" << sErrorRet
<< "]");
return false;
}
@@ -1557,11 +1554,11 @@ bool CZNC::AddUser(CUser* pUser, CString& sErrorRet, bool bStartup) {
}
if (bFailed) {
DEBUG("AddUser [" << pUser->GetUserName() << "] aborted by a module ["
DEBUG("AddUser [" << pUser->GetUsername() << "] aborted by a module ["
<< sErrorRet << "]");
return false;
}
m_msUsers[pUser->GetUserName()] = pUser;
m_msUsers[pUser->GetUsername()] = pUser;
return true;
}
@@ -1650,7 +1647,7 @@ bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost,
#ifndef HAVE_IPV6
if (ADDR_IPV6ONLY == eAddr) {
sError = "IPV6 is not enabled";
sError = t_s("IPv6 is not enabled");
CUtils::PrintStatus(false, sError);
return false;
}
@@ -1658,7 +1655,7 @@ bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost,
#ifndef HAVE_LIBSSL
if (bSSL) {
sError = "SSL is not enabled";
sError = t_s("SSL is not enabled");
CUtils::PrintStatus(false, sError);
return false;
}
@@ -1666,7 +1663,7 @@ bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost,
CString sPemFile = GetPemLocation();
if (bSSL && !CFile::Exists(sPemFile)) {
sError = "Unable to locate pem file: [" + sPemFile + "]";
sError = t_f("Unable to locate pem file: {1}")(sPemFile);
CUtils::PrintStatus(false, sError);
// If stdin is e.g. /dev/null and we call GetBoolInput(),
@@ -1685,7 +1682,7 @@ bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost,
}
#endif
if (!uPort) {
sError = "Invalid port";
sError = t_s("Invalid port");
CUtils::PrintStatus(false, sError);
return false;
}
@@ -1869,6 +1866,12 @@ bool CZNC::DelListener(CListener* pListener) {
return false;
}
CString CZNC::FormatBindError() {
CString sError = (errno == 0 ? t_s(("unknown error, check the host name"))
: CString(strerror(errno)));
return t_f("Unable to bind: {1}")(sError);
}
static CZNC* s_pZNC = nullptr;
void CZNC::CreateInstance() {
@@ -1911,8 +1914,8 @@ CZNC::TrafficStatsMap CZNC::GetTrafficStats(TrafficStatsPair& Users,
}
if (pUser) {
ret[pUser->GetUserName()].first += pSock->GetBytesRead();
ret[pUser->GetUserName()].second += pSock->GetBytesWritten();
ret[pUser->GetUsername()].first += pSock->GetBytesRead();
ret[pUser->GetUsername()].second += pSock->GetBytesWritten();
uiUsers_in += pSock->GetBytesRead();
uiUsers_out += pSock->GetBytesWritten();
} else {
@@ -2139,18 +2142,36 @@ void CZNC::ForceEncoding() {
m_uiForceEncoding++;
#ifdef HAVE_ICU
for (Csock* pSock : GetManager()) {
if (pSock->GetEncoding().empty()) {
pSock->SetEncoding("UTF-8");
}
pSock->SetEncoding(FixupEncoding(pSock->GetEncoding()));
}
#endif
}
void CZNC::UnforceEncoding() { m_uiForceEncoding--; }
bool CZNC::IsForcingEncoding() const { return m_uiForceEncoding; }
CString CZNC::FixupEncoding(const CString& sEncoding) const {
if (sEncoding.empty() && m_uiForceEncoding) {
if (!m_uiForceEncoding) {
return sEncoding;
}
if (sEncoding.empty()) {
return "UTF-8";
}
const char* sRealEncoding = sEncoding.c_str();
if (sEncoding[0] == '*' || sEncoding[0] == '^') {
sRealEncoding++;
}
if (!*sRealEncoding) {
return "UTF-8";
}
#ifdef HAVE_ICU
UErrorCode e = U_ZERO_ERROR;
UConverter* cnv = ucnv_open(sRealEncoding, &e);
if (cnv) {
ucnv_close(cnv);
}
if (U_FAILURE(e)) {
return "UTF-8";
}
#endif
return sEncoding;
}