Unix listener: chmod and change group

Fix #1955
This commit is contained in:
Alexey Sokolov
2025-06-24 09:04:29 +01:00
parent 4b12c0dc3c
commit c46bd41037
7 changed files with 237 additions and 60 deletions

View File

@@ -97,9 +97,7 @@ class CTCPListener : public CListener {
class CUnixListener : public CListener {
public:
CUnixListener(const CString& sPath, const CString& sURIPrefix, bool bSSL,
EAcceptType eAccept)
: CListener(sURIPrefix, bSSL, eAccept),
m_sPath(sPath) {}
EAcceptType eAccept, const CString& sGid, const CString& sMode);
~CUnixListener();
CUnixListener(const CUnixListener&) = delete;
@@ -107,6 +105,8 @@ class CUnixListener : public CListener {
// Getters
const CString& GetPath() const { return m_sPath; }
const CString& GetGroup() const { return m_sGid; }
CString GetMode() const;
// !Getters
bool Listen() override;
@@ -114,6 +114,8 @@ class CUnixListener : public CListener {
protected:
CString m_sPath;
CString m_sGid;
int m_iMode;
};
class CRealListener : public CZNCSock {

View File

@@ -209,8 +209,9 @@ class CZNC : private CCoreTranslationMixin {
bool AddTCPListener(unsigned short uPort, const CString& sBindHost,
const CString& sURIPrefix, bool bSSL, EAddrType eAddr,
CListener::EAcceptType eAccept, CString& sError);
bool AddUnixListener(const CString& sPath, const CString& sURIPrefix, bool bSSL,
CListener::EAcceptType eAccept, CString& sError);
bool AddUnixListener(const CString& sPath, const CString& sURIPrefix,
bool bSSL, CListener::EAcceptType eAccept,
const CString& sGroup, const CString& sMode, CString& sError);
bool DelListener(CListener*);
// For backwards-compatibility TODO: Remove

View File

@@ -21,10 +21,9 @@
<th><? FORMAT "Delete" ?></th>
</tr>
</thead>
<? LOOP ListenLoop ?>
<? LOOP ListenLoop ?>
<? IF Type == "TCP" ?>
<tr class="<? IF __EVEN__ ?>evenrow<? ELSE ?>oddrow<? ENDIF ?>">
<? IF Type == "TCP" ?>
<td><? VAR Port ?></td>
<td><? VAR BindHost DEFAULT=** ?></td>
<td>
@@ -33,9 +32,6 @@
<td>
<div class="checkbox"><input type="checkbox" disabled="disabled" <? IF IsIPV6 ?>checked="checked"<? ENDIF ?>/></div>
</td>
<? ELSE ?>
<td colspan="4">unix:<? VAR Path ?></td>
<? ENDIF ?>
<td class="listener_show_ssl">
<div class="checkbox"><input type="checkbox" disabled="disabled" <? IF IsSSL ?>checked="checked"<? ENDIF ?>/></div>
</td>
@@ -51,14 +47,10 @@
<form action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>del_listener" method="post">
<? INC _csrf_check.tmpl ?>
<input name="type" type="hidden" value="<? VAR Type ?>"/>
<? IF Type == "TCP" ?>
<input name="host" type="hidden" value="<? VAR BindHost ?>"/>
<input name="port" type="hidden" value="<? VAR Port ?>"/>
<input name="ipv4" type="hidden" value="<? VAR IsIPV4 ?>"/>
<input name="ipv6" type="hidden" value="<? VAR IsIPV6 ?>"/>
<? ELSE ?>
<input name="path" type="hidden" value="<? VAR Path ?>"/>
<? ENDIF ?>
<input type="submit" value="<? FORMAT "Del" ?>"/>
</form>
<? ELSE ?>
@@ -66,7 +58,8 @@
<? ENDIF ?>
</td>
</tr>
<? ENDLOOP ?>
<? ENDIF ?>
<? ENDLOOP ?>
<tr>
<form action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>add_listener" method="post">
<? INC _csrf_check.tmpl ?>
@@ -82,11 +75,58 @@
<td><input type="submit" value="<? FORMAT "Add" ?>"/></td>
</form>
</tr>
</table>
</div>
<div class="subsection">
<table>
<thead>
<tr>
<th><? FORMAT "Path" ?></th>
<th><? FORMAT "Mode" ?></th>
<th><? FORMAT "Group" ?></th>
<th><? FORMAT "SSL" ?></th>
<th><? FORMAT "IRC" ?></th>
<th><? FORMAT "HTTP" ?></th>
<th><? FORMAT "URIPrefix" ?></th>
<th><? FORMAT "Delete" ?></th>
</tr>
</thead>
<? LOOP ListenLoop ?>
<? IF Type == "Unix" ?>
<tr class="<? IF __EVEN__ ?>evenrow<? ELSE ?>oddrow<? ENDIF ?>">
<td>unix:<? VAR Path ?></td>
<td><? VAR Mode ?></td>
<td><? VAR Group ?></td>
<td class="listener_show_ssl">
<div class="checkbox"><input type="checkbox" disabled="disabled" <? IF IsSSL ?>checked="checked"<? ENDIF ?>/></div>
</td>
<td>
<div class="checkbox"><input type="checkbox" disabled="disabled" <? IF IsIRC ?>checked="checked"<? ENDIF ?>/></div>
</td>
<td>
<div class="checkbox"><input type="checkbox" disabled="disabled" <? IF IsHTTP ?>checked="checked"<? ENDIF ?>/></div>
</td>
<td><? VAR URIPrefix ?></td>
<td>
<form action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>del_listener" method="post">
<? INC _csrf_check.tmpl ?>
<input name="type" type="hidden" value="<? VAR Type ?>"/>
<input name="path" type="hidden" value="<? VAR Path ?>"/>
<input type="submit" value="<? FORMAT "Del" ?>"/>
</form>
</td>
</tr>
<? ENDIF ?>
<? ENDLOOP ?>
<tr>
<form action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>add_listener" method="post">
<? INC _csrf_check.tmpl ?>
<input name="type" type="hidden" value="Unix"/>
<td colspan="4">unix:<input name="path" type="text" value="/" class="third"/></td>
<td>unix:<input name="path" type="text" value="/" class="third"/></td>
<td><input name="mode" type="number" min="-1" max="777" class="number third" value="-1" title="<? FORMAT "Octal number; -1 means do not chmod" ?>"/></td>
<td><input name="group" type="text" value="" class="sixth"/></td>
<td><div class="checkbox"><input name="ssl" type="checkbox"/></div></td>
<td><div class="checkbox"><input name="irc" type="checkbox" checked="checked"/></div></td>
<td><div class="checkbox"><input name="web" type="checkbox" checked="checked"/></div></td>

View File

@@ -1910,8 +1910,10 @@ class CWebAdminMod : public CModule {
eAddr, eAccept, sMessage);
} else {
CString sPath = WebSock.GetParam("path");
CString sMode = WebSock.GetParam("mode");
CString sGroup = WebSock.GetParam("group");
bResult = CZNC::Get().AddUnixListener(sPath, sURIPrefix, bSSL,
eAccept, sMessage);
eAccept, sGroup, sMode, sMessage);
}
if (bResult) {
@@ -2030,6 +2032,8 @@ class CWebAdminMod : public CModule {
dynamic_cast<const CUnixListener*>(pListener)) {
l["Type"] = "Unix";
l["Path"] = pUnixListener->GetPath();
l["Mode"] = pUnixListener->GetMode();
l["Group"] = pUnixListener->GetGroup();
// We can't determine whether it's the same port, as it's
// always "localhost". Just assume the user knows what he's
// doing. Unix sockets are advanced topic anyway.

View File

@@ -1641,29 +1641,39 @@ void CClient::UserPortCommand(CString& sLine) {
const CString sCommand = sLine.Token(0);
if (sCommand.Equals("LISTPORTS")) {
CTable Table;
Table.AddColumn(t_s("Port", "listports"));
Table.AddColumn(t_s("BindHost", "listports"));
Table.AddColumn(t_s("SSL", "listports"));
Table.AddColumn(t_s("Protocol", "listports"));
Table.AddColumn(t_s("IRC", "listports"));
Table.AddColumn(t_s("Web", "listports"));
CTable TableT;
TableT.AddColumn(t_s("Port", "listports"));
TableT.AddColumn(t_s("BindHost", "listports"));
TableT.AddColumn(t_s("Protocol", "listports"));
TableT.AddColumn(t_s("SSL", "listports"));
TableT.AddColumn(t_s("IRC", "listports"));
TableT.AddColumn(t_s("Web", "listports"));
CTable TableU;
TableU.AddColumn(t_s("Path", "listports"));
TableU.AddColumn(t_s("Mode", "listports"));
TableU.AddColumn(t_s("Group", "listports"));
TableU.AddColumn(t_s("SSL", "listports"));
TableU.AddColumn(t_s("IRC", "listports"));
TableU.AddColumn(t_s("Web", "listports"));
const vector<CListener*>& vpListeners = CZNC::Get().GetListeners();
for (const CListener* pListener : vpListeners) {
Table.AddRow();
CTable* pTable;
if (const CTCPListener* pTCPListener =
dynamic_cast<const CTCPListener*>(pListener)) {
Table.SetCell(t_s("Port", "listports"),
CString(pTCPListener->GetPort()));
Table.SetCell(t_s("BindHost", "listports"),
(pTCPListener->GetBindHost().empty()
? CString("*")
: pTCPListener->GetBindHost()));
TableT.AddRow();
pTable = &TableT;
TableT.SetCell(t_s("Port", "listports"),
CString(pTCPListener->GetPort()));
TableT.SetCell(t_s("BindHost", "listports"),
(pTCPListener->GetBindHost().empty()
? CString("*")
: pTCPListener->GetBindHost()));
EAddrType eAddr = pTCPListener->GetAddrType();
Table.SetCell(
TableT.SetCell(
t_s("Protocol", "listports"),
eAddr == ADDR_ALL
? t_s("IPv4 and IPv6", "listports")
@@ -1671,28 +1681,37 @@ void CClient::UserPortCommand(CString& sLine) {
: t_s("IPv6", "listports")));
} else if (const CUnixListener* pUnixListener =
dynamic_cast<const CUnixListener*>(pListener)) {
Table.SetCell(t_s("Port", "listports"),
pUnixListener->GetPath());
TableU.AddRow();
pTable = &TableU;
TableU.SetCell(t_s("Path", "listports"),
pUnixListener->GetPath());
TableU.SetCell(t_s("Mode", "listports"),
pUnixListener->GetMode());
TableU.SetCell(t_s("Group", "listports"),
pUnixListener->GetGroup());
} else {
continue;
}
Table.SetCell(t_s("SSL", "listports"),
pListener->IsSSL() ? t_s("yes", "listports|ssl")
: t_s("no", "listports|ssl"));
pTable->SetCell(t_s("SSL", "listports"),
pListener->IsSSL() ? t_s("yes", "listports|ssl")
: t_s("no", "listports|ssl"));
CListener::EAcceptType eAccept = pListener->GetAcceptType();
Table.SetCell(t_s("IRC", "listports"),
eAccept == CListener::ACCEPT_ALL ||
eAccept == CListener::ACCEPT_IRC
? t_s("yes", "listports|irc")
: t_s("no", "listports|irc"));
Table.SetCell(t_s("Web", "listports"),
eAccept == CListener::ACCEPT_ALL ||
eAccept == CListener::ACCEPT_HTTP
? t_f("yes, on {1}", "listports|irc")(
pListener->GetURIPrefix() + "/")
: t_s("no", "listports|web"));
pTable->SetCell(t_s("IRC", "listports"),
eAccept == CListener::ACCEPT_ALL ||
eAccept == CListener::ACCEPT_IRC
? t_s("yes", "listports|irc")
: t_s("no", "listports|irc"));
pTable->SetCell(t_s("Web", "listports"),
eAccept == CListener::ACCEPT_ALL ||
eAccept == CListener::ACCEPT_HTTP
? t_f("yes, on {1}", "listports|irc")(
pListener->GetURIPrefix() + "/")
: t_s("no", "listports|web"));
}
PutStatus(Table);
PutStatus(TableT);
PutStatus(TableU);
return;
}
@@ -1732,12 +1751,30 @@ void CClient::UserPortCommand(CString& sLine) {
std::unique_ptr<CListener> pListener;
if (sPort.TrimPrefix("unix:")) {
bool bSSL = sPort.TrimPrefix("ssl:");
bool bSSL = false;
CString sMode;
CString sGroup;
if (auto colon = sPort.find_first_of(':'); colon != std::string::npos) {
VCString vsOpts;
CString(sPort.substr(0, colon)).Split(",", vsOpts, false);
for (CString& sOpt : vsOpts) {
if (sOpt == "ssl") {
bSSL = true;
} else if (sOpt.TrimPrefix("mode=")) {
sMode = sOpt;
} else if (sOpt.TrimPrefix("group=")) {
sGroup = sOpt;
} else {
throw PortCommandUsage{};
}
}
sPort = sPort.substr(colon + 1);
}
const CString& sPath = sPort;
CListener::EAcceptType eAccept = ParseEAccept(sLine.Token(2));
CString sURIPrefix = sLine.Token(3);
pListener.reset(new CUnixListener(sPath, sURIPrefix, bSSL, eAccept));
pListener.reset(new CUnixListener(sPath, sURIPrefix, bSSL, eAccept, sGroup, sMode));
} else {
bool bSSL = sPort.StartsWith("+");
EAddrType eAddr = ParseEAddr(sLine.Token(2));
@@ -1765,7 +1802,7 @@ void CClient::UserPortCommand(CString& sLine) {
"[bindhost [uriprefix]]"));
PutStatus(t_s("+ means SSL"));
PutStatus(
t_s("Or: AddPort unix:[ssl:]/path/to/socket <web|irc|all> "
t_s("Or: AddPort unix:[ssl,mode=NNN,group=foo]:/path/to/socket <web|irc|all> "
"[uriprefix]"));
}
} else if (sCommand.Equals("DELPORT")) {

View File

@@ -14,6 +14,12 @@
* limitations under the License.
*/
#include <sys/types.h>
#include <grp.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <znc/Listener.h>
#include <znc/Config.h>
#include <znc/znc.h>
@@ -85,9 +91,21 @@ CConfig CTCPListener::ToConfig() const {
return listenerConfig;
}
CUnixListener::~CUnixListener() {
CUnixListener::CUnixListener(const CString& sPath, const CString& sURIPrefix,
bool bSSL, EAcceptType eAccept,
const CString& sGid, const CString& sMode)
: CListener(sURIPrefix, bSSL, eAccept),
m_sPath(sPath),
m_sGid(sGid),
m_iMode(-1) {
if (!sMode.empty()) {
std::istringstream s(sMode);
s >> std::oct >> m_iMode;
}
}
CUnixListener::~CUnixListener() {}
bool CUnixListener::Listen() {
if (m_pListener) {
errno = EINVAL;
@@ -97,18 +115,85 @@ bool CUnixListener::Listen() {
m_pListener = new CRealListener(*this);
SetupSSL();
return CZNC::Get().GetManager().ListenUnix("UNIX_LISTENER", m_sPath,
m_pListener);
if (!CZNC::Get().GetManager().ListenUnix("UNIX_LISTENER", m_sPath,
m_pListener))
return false;
if (!m_sGid.empty()) {
bool bSuccess = [&]() -> bool {
std::vector<char> buffer(100);
group gr{};
group* result;
retrysize:
int err = getgrnam_r(m_sGid.c_str(), &gr, buffer.data(),
buffer.size(), &result);
switch (err) {
case ERANGE: {
if (buffer.size() > 10000) {
DEBUG("Can't get gid due to memory size");
return false;
}
buffer.resize(buffer.size() + 100);
goto retrysize;
}
case 0: {
if (!result) {
DEBUG("Group not found");
return false;
}
if (chown(m_sPath.c_str(), -1, result->gr_gid)) {
char* e = strerror(errno);
DEBUG("Can't chmod: " << e);
return false;
}
break;
}
default: {
char* e = strerror(err);
DEBUG("Error while getting gid " << e);
return false;
}
}
return true;
}();
if (!bSuccess) {
m_pListener->Close();
return false;
}
}
if (m_iMode != -1) {
if (chmod(m_sPath.c_str(), m_iMode)) {
char* e = strerror(errno);
DEBUG("Error while chmod " << e);
m_pListener->Close();
return false;
}
}
return true;
}
CConfig CUnixListener::ToConfig() const {
CConfig listenerConfig = CListener::ToConfig();
listenerConfig.AddKeyValuePair("Path", GetPath());
if (!m_sGid.empty()) listenerConfig.AddKeyValuePair("Group", m_sGid);
if (m_iMode != -1) {
listenerConfig.AddKeyValuePair("Mode", GetMode());
}
return listenerConfig;
}
CString CUnixListener::GetMode() const {
std::ostringstream s;
if (m_iMode != -1) {
s << std::oct << m_iMode;
}
return s.str();
}
void CListener::ResetRealListener() { m_pListener = nullptr; }
CRealListener::~CRealListener() { m_Listener.ResetRealListener(); }

View File

@@ -1744,13 +1744,16 @@ bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost,
bool CZNC::AddUnixListener(const CString& sPath, const CString& sURIPrefix,
bool bSSL, CListener::EAcceptType eAccept,
CString& sError) {
CUtils::PrintAction("Binding to path [" + sPath + "]" + (bSSL ? " with SSL" : ""));
const CString& sGroup, const CString& sMode, CString& sError) {
CUtils::PrintAction("Binding to path [" + sPath + "]" +
(bSSL ? " with SSL" : "") +
(sGroup.empty() ? CString() : " gid=" + sGroup) +
(sMode.empty() ? CString() : " mode=" + sMode));
if (!CheckSslAndPemFile(bSSL, sError)) return false;
CListener* pListener =
new CUnixListener(sPath, sURIPrefix, bSSL, eAccept);
new CUnixListener(sPath, sURIPrefix, bSSL, eAccept, sGroup, sMode);
return FinishAddingListener(pListener, sError);
}
@@ -1841,8 +1844,13 @@ bool CZNC::AddListener(CConfig* pConfig, CString& sError) {
return AddTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr,
eAccept, sError);
} else {
CString sGroup;
CString sMode;
pConfig->FindStringEntry("group", sGroup);
pConfig->FindStringEntry("mode", sMode);
return AddUnixListener(sPath, sURIPrefix, bSSL, eAccept, sGroup, sMode, sError);
}
return AddUnixListener(sPath, sURIPrefix, bSSL, eAccept, sError);
}
bool CZNC::AddListener(CListener* pListener) {