mirror of
https://github.com/znc/znc.git
synced 2026-08-08 01:43:03 +02:00
Merge branch 'master' into unix
This commit is contained in:
+2
-2
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+782
-614
File diff suppressed because it is too large
Load Diff
+18
-8
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 ¶ms = 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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, "<", 2))
|
||||
if (!strncasecmp((const char*)&pTmp, "<", 4))
|
||||
ch = '<';
|
||||
else if (!strncasecmp((const char*)&pTmp, ">", 2))
|
||||
else if (!strncasecmp((const char*)&pTmp, ">", 4))
|
||||
ch = '>';
|
||||
else if (!strncasecmp((const char*)&pTmp, """, 4))
|
||||
else if (!strncasecmp((const char*)&pTmp, """, 6))
|
||||
ch = '"';
|
||||
else if (!strncasecmp((const char*)&pTmp, "&", 3))
|
||||
else if (!strncasecmp((const char*)&pTmp, "&", 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
@@ -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,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
File diff suppressed because it is too large
Load Diff
+2042
File diff suppressed because it is too large
Load Diff
+2126
File diff suppressed because it is too large
Load Diff
+2042
File diff suppressed because it is too large
Load Diff
+2104
File diff suppressed because it is too large
Load Diff
+2119
File diff suppressed because it is too large
Load Diff
+2052
File diff suppressed because it is too large
Load Diff
+2138
File diff suppressed because it is too large
Load Diff
+2121
File diff suppressed because it is too large
Load Diff
+2127
File diff suppressed because it is too large
Load Diff
+1986
-16
File diff suppressed because it is too large
Load Diff
+2102
File diff suppressed because it is too large
Load Diff
+2110
File diff suppressed because it is too large
Load Diff
+2068
File diff suppressed because it is too large
Load Diff
@@ -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 <module></"
|
||||
"code>”). Once you have loaded some Web-enabled modules, the menu will expand."
|
||||
msgstr ""
|
||||
"Нет загруженных модулей с поддержкой веб-интерфейса. Вы можете загрузить их "
|
||||
"из IRC («<code>/msg *status help</code>» и «<code>/msg *status loadmod "
|
||||
"<модуль></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
File diff suppressed because it is too large
Load Diff
+2103
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user