Add support to connect to server via unix socket

The syntax for AddServer command and config is chosen to be unix:/path or unix:ssl:/path

For security reasons, only admins can add such servers, to prevent users from poking around the file system.
This commit is contained in:
Alexey Sokolov
2025-04-20 22:20:52 +01:00
parent 66b17926cc
commit 63d10ccb17
10 changed files with 212 additions and 81 deletions
+2
View File
@@ -114,6 +114,8 @@ class CIRCNetwork : private CCoreTranslationMixin {
CServer* FindServer(const CString& sName) const;
bool DelServer(const CString& sName, unsigned short uPort,
const CString& sPass);
bool DelServer(const CServer& Server);
bool AddServer(CServer Server);
bool AddServer(const CString& sName);
bool AddServer(const CString& sName, unsigned short uPort,
const CString& sPass = "", bool bSSL = false);
+10 -1
View File
@@ -23,13 +23,21 @@
class CServer {
public:
CServer(const CString& sName, unsigned short uPort = 6667,
const CString& sPass = "", bool bSSL = false);
const CString& sPass = "", bool bSSL = false,
bool bUnixSocket = false);
~CServer();
// TODO: use C++20's =default and <=>
bool operator==(const CServer&) const;
bool operator<(const CServer&) const;
static CServer Parse(CString sLine);
const CString& GetName() const;
unsigned short GetPort() const;
const CString& GetPass() const;
bool IsSSL() const;
bool IsUnixSocket() const;
CString GetString(bool bIncludePassword = true) const;
static bool IsValidHostName(const CString& sHostName);
@@ -39,6 +47,7 @@ class CServer {
unsigned short m_uPort;
CString m_sPass;
bool m_bSSL;
bool m_bUnixSocket;
};
#endif // !ZNC_SERVER_H
+13 -4
View File
@@ -21,6 +21,7 @@
#include <znc/IRCNetwork.h>
#include <znc/Chan.h>
#include <znc/IRCSock.h>
#include "znc/Server.h"
using std::map;
using std::vector;
@@ -1249,6 +1250,10 @@ class CAdminMod : public CModule {
PutModule(
t_s("Usage: AddServer <username> <network> <server> [[+]port] "
"[password]"));
if (GetUser()->IsAdmin()) {
PutModule(t_s("Or: AddServer <username> <network> unix:[ssl:]/path/to/socket"));
}
PutModule(t_s("+ means SSL"));
return;
}
@@ -1265,7 +1270,13 @@ class CAdminMod : public CModule {
return;
}
if (pNetwork->AddServer(sServer))
CServer Server = CServer::Parse(sServer);
if (Server.IsUnixSocket() && !GetUser()->IsAdmin()) {
PutModule(t_s("Access denied!"));
return;
}
if (pNetwork->AddServer(std::move(Server)))
PutModule(t_f("Added IRC Server {1} to network {2} for user {3}.")(
sServer, pNetwork->GetName(), pUser->GetUsername()));
else
@@ -1278,8 +1289,6 @@ class CAdminMod : public CModule {
CString sUsername = sLine.Token(1);
CString sNetwork = sLine.Token(2);
CString sServer = sLine.Token(3, true);
unsigned short uPort = sLine.Token(4).ToUShort();
CString sPass = sLine.Token(5);
if (sServer.empty()) {
PutModule(
@@ -1301,7 +1310,7 @@ class CAdminMod : public CModule {
return;
}
if (pNetwork->DelServer(sServer, uPort, sPass))
if (pNetwork->DelServer(CServer::Parse(sServer)))
PutModule(
t_f("Deleted IRC Server {1} from network {2} for user {3}.")(
sServer, pNetwork->GetName(), pUser->GetUsername()));
+42 -14
View File
@@ -106,9 +106,11 @@ function serverlist_init($) {
var pass = $(".servers_row_pass", $(this)).val();
if (host.length == 0) return;
text += host;
text += " ";
if (ssl) text += "+";
text += port;
if (!host.startsWith("unix:")) {
text += " ";
if (ssl) text += "+";
text += port;
}
text += " ";
text += pass;
text += "\n";
@@ -122,14 +124,15 @@ function serverlist_init($) {
serialize();
}
if (NetworkEdit) {
var disable = host.startsWith("unix:") && !EditUnixSockets;
row.append(
$("<td/>").append($("<input/>").attr({"type":"text"})
$("<td/>").append($("<input/>").attr({"type":"text","disabled":disable})
.addClass("servers_row_host").val(host)),
$("<td/>").append($("<input/>").attr({"type":"number"})
$("<td/>").append($("<input/>").attr({"type":"number","disabled":disable})
.addClass("servers_row_port").val(port)),
$("<td/>").append($("<input/>").attr({"type":"checkbox"})
$("<td/>").append($("<input/>").attr({"type":"checkbox","disabled":disable})
.addClass("servers_row_ssl").prop("checked", ssl)),
$("<td/>").append($("<input/>").attr({"type":"text"})
$("<td/>").append($("<input/>").attr({"type":"text","disabled":disable})
.addClass("servers_row_pass").val(pass)),
$("<td/>").append($("<input/>").attr({"type":"button"})
.val("X").click(delete_row))
@@ -147,6 +150,25 @@ function serverlist_init($) {
);
}
$("input", row).change(serialize);
$("input.servers_row_host", row).change(function (ev) {
var host = ev.target.value;
if (host.startsWith("unix:")) {
$("input.servers_row_ssl", row)[0].checked = host.startsWith("unix:ssl:");
}
});
$("input.servers_row_ssl", row).change(function (ev) {
var host = $("input.servers_row_host", row).val();
if (host.startsWith("unix:")) {
if (ev.target.checked != host.startsWith("unix:ssl:")) {
if (host.startsWith("unix:ssl:")) {
host = host.substr(9);
} else {
host = host.substr(5);
}
$("input.servers_row_host", row).val("unix:" + (ev.target.checked ? "ssl:" : "") + host);
}
}
});
$("#servers_tbody").append(row);
}
@@ -157,14 +179,20 @@ function serverlist_init($) {
if (line.length == 0) return;
line = line.split(" ");
var host = line[0];
var port = line[1] || "6667";
var pass = line[2] || "";
var ssl;
if (port.match(/^\+/)) {
ssl = true;
port = port.substr(1);
var unix = host.startsWith("unix:");
var port = "0";
var pass = line[unix ? 1 : 2] || "";
var ssl = false;
if (unix) {
if (host.startsWith("unix:ssl:")) {
ssl = true;
}
} else {
ssl = false;
port = line[1] || "6667";
if (port.match(/^\+/)) {
ssl = true;
port = port.substr(1);
}
}
add_row(host, port, ssl, pass);
});
@@ -5,6 +5,7 @@
<script>
var NetworkEdit = <? VAR NetworkEdit ?>;
var EditUnixSockets = <? VAR EditUnixSockets ?>;
</script>
<div class="section">
+19 -3
View File
@@ -972,6 +972,7 @@ class CWebAdminMod : public CModule {
Tmpl["NetworkEdit"] =
spSession->IsAdmin() || !spSession->GetUser()->DenySetNetwork()
? "true" : "false";
Tmpl["EditUnixSockets"] = spSession->IsAdmin() ? "true" : "false";
Tmpl["FloodProtection"] =
CString(CIRCSock::IsFloodProtected(pNetwork->GetFloodRate()));
@@ -1147,9 +1148,22 @@ class CWebAdminMod : public CModule {
VCString vsArgs;
if (spSession->IsAdmin() || !spSession->GetUser()->DenySetNetwork()) {
std::set<CServer> vAllowedUnixServers;
for (const CServer* pServer : pNetwork->GetServers()) {
if (pServer->IsUnixSocket()) {
vAllowedUnixServers.insert(*pServer);
}
}
pNetwork->DelServers();
WebSock.GetRawParam("servers").Split("\n", vsArgs);
for (const CString& sServer : vsArgs) {
CServer Server = CServer::Parse(sServer);
if (Server.IsUnixSocket() && !spSession->IsAdmin() &&
vAllowedUnixServers.count(Server) == 0) {
// For non-admins, allow unix sockets only if they had these
// exact servers before.
continue;
}
pNetwork->AddServer(sServer.Trim_n());
}
}
@@ -1404,9 +1418,11 @@ class CWebAdminMod : public CModule {
l["IRCNick"] = pNetwork->GetIRCNick().GetNick();
CServer* pServer = pNetwork->GetCurrentServer();
if (pServer) {
l["Server"] = pServer->GetName() + ":" +
(pServer->IsSSL() ? "+" : "") +
CString(pServer->GetPort());
l["Server"] = pServer->IsUnixSocket()
? "unix:" + pServer->GetName()
: pServer->GetName() + ":" +
(pServer->IsSSL() ? "+" : "") +
CString(pServer->GetPort());
}
}
+23 -10
View File
@@ -817,7 +817,7 @@ void CClient::UserCommand(CString& sLine) {
return;
}
CString sServer = sLine.Token(1);
CString sServer = sLine.Token(1, true);
if (!m_pNetwork) {
PutStatus(t_s(
@@ -827,10 +827,20 @@ void CClient::UserCommand(CString& sLine) {
if (sServer.empty()) {
PutStatus(t_s("Usage: AddServer <host> [[+]port] [pass]"));
if (m_pUser->IsAdmin()) {
PutStatus(t_s("Or: AddServer unix:[ssl:]/path/to/socket"));
}
PutStatus(t_s("+ means SSL"));
return;
}
if (m_pNetwork->AddServer(sLine.Token(1, true))) {
CServer Server = CServer::Parse(sServer);
if (Server.IsUnixSocket() && !m_pUser->IsAdmin()) {
PutStatus(t_s("Access denied!"));
return;
}
if (m_pNetwork->AddServer(std::move(Server))) {
PutStatus(t_s("Server added"));
} else {
PutStatus(
@@ -849,11 +859,9 @@ void CClient::UserCommand(CString& sLine) {
return;
}
CString sServer = sLine.Token(1);
unsigned short uPort = sLine.Token(2).ToUShort();
CString sPass = sLine.Token(3);
CServer Server = CServer::Parse(sLine.Token(1, true));
if (sServer.empty()) {
if (Server.GetName().empty()) {
PutStatus(t_s("Usage: DelServer <host> [port] [pass]"));
return;
}
@@ -863,7 +871,9 @@ void CClient::UserCommand(CString& sLine) {
return;
}
if (m_pNetwork->DelServer(sServer, uPort, sPass)) {
// Unix sockets can be removed with "unix:" prefix and without, both
// work.
if (m_pNetwork->DelServer(Server)) {
PutStatus(t_s("Server removed"));
} else {
PutStatus(t_s("No such server"));
@@ -888,9 +898,12 @@ void CClient::UserCommand(CString& sLine) {
Table.AddRow();
Table.SetCell(
t_s("Host", "listservers"),
pServer->GetName() + (pServer == pCurServ ? "*" : ""));
Table.SetCell(t_s("Port", "listservers"),
CString(pServer->GetPort()));
(pServer->IsUnixSocket() ? pServer->GetString(false)
: pServer->GetName()) +
(pServer == pCurServ ? "*" : ""));
if (!pServer->IsUnixSocket())
Table.SetCell(t_s("Port", "listservers"),
CString(pServer->GetPort()));
Table.SetCell(
t_s("SSL", "listservers"),
(pServer->IsSSL()) ? t_s("SSL", "listservers|cell") : "");
+38 -43
View File
@@ -215,8 +215,7 @@ void CIRCNetwork::Clone(const CIRCNetwork& Network, bool bCloneName) {
DelServers();
for (CServer* pServer : vServers) {
AddServer(pServer->GetName(), pServer->GetPort(), pServer->GetPass(),
pServer->IsSSL());
AddServer(*pServer);
}
m_uServerIdx = 0;
@@ -1155,6 +1154,11 @@ bool CIRCNetwork::DelServer(const CString& sName, unsigned short uPort,
return false;
}
CServer Server(sName, uPort, sPass);
return DelServer(Server);
}
bool CIRCNetwork::DelServer(const CServer& Server) {
unsigned int a = 0;
bool bSawCurrentServer = false;
CServer* pCurServer = GetCurrentServer();
@@ -1165,11 +1169,16 @@ bool CIRCNetwork::DelServer(const CString& sName, unsigned short uPort,
if (pServer == pCurServer) bSawCurrentServer = true;
if (!pServer->GetName().Equals(sName)) continue;
// Unix sockets can be removed with "unix:" prefix and without, both
// work - that's not part of GetName()
if (!pServer->GetName().Equals(Server.GetName())) continue;
if (uPort != 0 && pServer->GetPort() != uPort) continue;
// But it makes no sense to remove TCP server via "unix:hostname.com"
if (!pServer->IsUnixSocket() && Server.IsUnixSocket()) continue;
if (!sPass.empty() && pServer->GetPass() != sPass) continue;
if (Server.GetPort() != 6667 && pServer->GetPort() != Server.GetPort()) continue;
if (!Server.GetPass().empty() && pServer->GetPass() != Server.GetPass()) continue;
m_vServers.erase(it);
@@ -1205,21 +1214,23 @@ bool CIRCNetwork::AddServer(const CString& sName) {
return false;
}
bool bSSL = false;
CString sLine = sName;
sLine.Trim();
return AddServer(CServer::Parse(sName));
}
CString sHost = sLine.Token(0);
CString sPort = sLine.Token(1);
bool CIRCNetwork::AddServer(CServer Server) {
if (Server.GetName().empty()) return false;
#ifndef HAVE_LIBSSL
if (Server.IsSSL()) return false;
#endif
if (sPort.TrimPrefix("+")) {
bSSL = true;
// Check if server is already added
for (CServer* pServer : m_vServers) {
if (*pServer == Server) return false;
}
unsigned short uPort = sPort.ToUShort();
CString sPass = sLine.Token(2, true);
return AddServer(sHost, uPort, sPass, bSSL);
m_vServers.push_back(new CServer(std::move(Server)));
CheckIRCConnect();
return true;
}
bool CIRCNetwork::AddServer(const CString& sName, unsigned short uPort,
@@ -1234,30 +1245,7 @@ bool CIRCNetwork::AddServer(const CString& sName, unsigned short uPort,
return false;
}
if (!uPort) {
uPort = 6667;
}
// Check if server is already added
for (CServer* pServer : m_vServers) {
if (!sName.Equals(pServer->GetName())) continue;
if (uPort != pServer->GetPort()) continue;
if (sPass != pServer->GetPass()) continue;
if (bSSL != pServer->IsSSL()) continue;
// Server is already added
return false;
}
CServer* pServer = new CServer(sName, uPort, sPass, bSSL);
m_vServers.push_back(pServer);
CheckIRCConnect();
return true;
return AddServer(CServer(sName, uPort, sPass, bSSL));
}
CServer* CIRCNetwork::GetNextServer(bool bAdvance) {
@@ -1374,9 +1362,16 @@ bool CIRCNetwork::Connect() {
}
CString sSockName = "IRC::" + m_pUser->GetUsername() + "::" + m_sName;
CZNC::Get().GetManager().Connect(pServer->GetName(), pServer->GetPort(),
sSockName, 120, bSSL, GetBindHost(),
pIRCSock);
if (pServer->IsUnixSocket()) {
pIRCSock->SetSSL(bSSL);
CZNC::Get().GetManager().ConnectUnix(sSockName, pServer->GetName(),
pIRCSock);
} else {
CZNC::Get().GetManager().Connect(pServer->GetName(), pServer->GetPort(),
sSockName, 120, bSSL, GetBindHost(),
pIRCSock);
}
return true;
}
+63 -5
View File
@@ -17,11 +17,12 @@
#include <znc/Server.h>
CServer::CServer(const CString& sName, unsigned short uPort,
const CString& sPass, bool bSSL)
const CString& sPass, bool bSSL, bool bUnixSocket)
: m_sName(sName),
m_uPort((uPort) ? uPort : (unsigned short)6667),
m_sPass(sPass),
m_bSSL(bSSL) {}
m_bSSL(bSSL),
m_bUnixSocket(bUnixSocket) {}
CServer::~CServer() {}
@@ -33,9 +34,66 @@ const CString& CServer::GetName() const { return m_sName; }
unsigned short CServer::GetPort() const { return m_uPort; }
const CString& CServer::GetPass() const { return m_sPass; }
bool CServer::IsSSL() const { return m_bSSL; }
bool CServer::IsUnixSocket() const { return m_bUnixSocket; }
CString CServer::GetString(bool bIncludePassword) const {
return m_sName + " " + CString(m_bSSL ? "+" : "") + CString(m_uPort) +
CString(bIncludePassword ? (m_sPass.empty() ? "" : " " + m_sPass)
: "");
CString sResult;
if (m_bUnixSocket) {
sResult = "unix:" + CString(m_bSSL ? "ssl:" : "") + m_sName;
} else {
sResult = m_sName + " " + CString(m_bSSL ? "+" : "") + CString(m_uPort);
}
sResult +=
CString(bIncludePassword ? (m_sPass.empty() ? "" : " " + m_sPass) : "");
return sResult;
}
CServer CServer::Parse(CString sLine) {
bool bSSL = false;
sLine.Trim();
if (sLine.TrimPrefix("unix:")) {
if (sLine.TrimPrefix("ssl:")) {
bSSL = true;
}
CString sPath = sLine.Token(0);
CString sPass = sLine.Token(1, true);
return CServer(sPath, 0, sPass, bSSL, true);
}
CString sHost = sLine.Token(0);
CString sPort = sLine.Token(1);
if (sPort.TrimPrefix("+")) {
bSSL = true;
}
unsigned short uPort = sPort.ToUShort();
CString sPass = sLine.Token(2, true);
return CServer(sHost, uPort, sPass, bSSL, false);
}
bool CServer::operator==(const CServer& o) const {
if (m_sName != o.m_sName) return false;
if (m_uPort != o.m_uPort) return false;
if (m_sPass != o.m_sPass) return false;
if (m_bSSL != o.m_bSSL) return false;
if (m_bUnixSocket != o.m_bUnixSocket) return false;
return true;
}
bool CServer::operator<(const CServer& o) const {
if (m_sName < o.m_sName) return true;
if (m_sName > o.m_sName) return false;
if (m_uPort < o.m_uPort) return true;
if (m_uPort > o.m_uPort) return false;
if (m_sPass < o.m_sPass) return true;
if (m_sPass > o.m_sPass) return false;
if (m_bSSL < o.m_bSSL) return true;
if (m_bSSL > o.m_bSSL) return false;
if (m_bUnixSocket < o.m_bUnixSocket) return true;
if (m_bUnixSocket > o.m_bUnixSocket) return false;
return false;
}