Commit 77d74b40 authored by Max Kellermann's avatar Max Kellermann

Permission: add option "host_permissions"

parent a636d212
...@@ -25,6 +25,7 @@ ver 0.23 (not yet released) ...@@ -25,6 +25,7 @@ ver 0.23 (not yet released)
* tags * tags
- new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location" - new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location"
* split permission "player" from "control" * split permission "player" from "control"
* add option "host_permissions"
* new build-time dependency: libfmt * new build-time dependency: libfmt
ver 0.22.11 (2021/08/24) ver 0.22.11 (2021/08/24)
......
...@@ -650,6 +650,9 @@ By default, all clients are unauthenticated and have a full set of permissions. ...@@ -650,6 +650,9 @@ By default, all clients are unauthenticated and have a full set of permissions.
:code:`local_permissions` may be used to assign other permissions to clients connecting on a local socket. :code:`local_permissions` may be used to assign other permissions to clients connecting on a local socket.
:code:`host_permissions` may be used to assign permissions to clients
with a certain IP address.
:code:`password` allows the client to send a password to gain other permissions. This option may be specified multiple times with different passwords. :code:`password` allows the client to send a password to gain other permissions. This option may be specified multiple times with different passwords.
Note that the :code:`password` option is not secure: passwords are sent in clear-text over the connection, and the client cannot verify the server's identity. Note that the :code:`password` option is not secure: passwords are sent in clear-text over the connection, and the client cannot verify the server's identity.
...@@ -659,6 +662,8 @@ Example: ...@@ -659,6 +662,8 @@ Example:
.. code-block:: none .. code-block:: none
default_permissions "read" default_permissions "read"
host_permissions "192.168.0.100 read,add,control,admin"
host_permissions "2003:1234:4567::1 read,add,control,admin"
password "the_password@read,add,control" password "the_password@read,add,control"
password "the_admin_password@read,add,control,admin" password "the_admin_password@read,add,control,admin"
......
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
#include "config/Param.hxx" #include "config/Param.hxx"
#include "config/Data.hxx" #include "config/Data.hxx"
#include "config/Option.hxx" #include "config/Option.hxx"
#include "net/AddressInfo.hxx"
#include "net/Resolver.hxx"
#include "net/ToString.hxx"
#include "util/IterableSplitString.hxx" #include "util/IterableSplitString.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringView.hxx" #include "util/StringView.hxx"
...@@ -55,6 +58,10 @@ static unsigned permission_default; ...@@ -55,6 +58,10 @@ static unsigned permission_default;
static unsigned local_permissions; static unsigned local_permissions;
#endif #endif
#ifdef HAVE_TCP
static std::map<std::string, unsigned> host_passwords;
#endif
static unsigned static unsigned
ParsePermission(StringView s) ParsePermission(StringView s)
{ {
...@@ -66,10 +73,9 @@ ParsePermission(StringView s) ...@@ -66,10 +73,9 @@ ParsePermission(StringView s)
int(s.size), s.data); int(s.size), s.data);
} }
static unsigned parsePermissions(const char *string) static unsigned
parsePermissions(std::string_view string)
{ {
assert(string != nullptr);
unsigned permission = 0; unsigned permission = 0;
for (const auto i : IterableSplitString(string, PERMISSION_SEPARATOR)) for (const auto i : IterableSplitString(string, PERMISSION_SEPARATOR))
...@@ -120,8 +126,40 @@ initPermissions(const ConfigData &config) ...@@ -120,8 +126,40 @@ initPermissions(const ConfigData &config)
: permission_default; : permission_default;
}); });
#endif #endif
#ifdef HAVE_TCP
for (const auto &param : config.GetParamList(ConfigOption::HOST_PERMISSIONS)) {
permission_default = 0;
param.With([](StringView value){
auto [host_sv, permissions_s] = value.Split(' ');
unsigned permissions = parsePermissions(permissions_s);
const std::string host_s{host_sv};
for (const auto &i : Resolve(host_s.c_str(), 0,
AI_PASSIVE, SOCK_STREAM))
host_passwords.emplace(HostToString(i),
permissions);
});
}
#endif
}
#ifdef HAVE_TCP
int
GetPermissionsFromAddress(SocketAddress address) noexcept
{
if (auto i = host_passwords.find(HostToString(address));
i != host_passwords.end())
return i->second;
return -1;
} }
#endif
int int
getPermissionFromPassword(const char *password, unsigned *permission) noexcept getPermissionFromPassword(const char *password, unsigned *permission) noexcept
{ {
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "config.h" #include "config.h"
struct ConfigData; struct ConfigData;
class SocketAddress;
static constexpr unsigned PERMISSION_NONE = 0; static constexpr unsigned PERMISSION_NONE = 0;
static constexpr unsigned PERMISSION_READ = 1; static constexpr unsigned PERMISSION_READ = 1;
...@@ -45,6 +46,12 @@ unsigned ...@@ -45,6 +46,12 @@ unsigned
GetLocalPermissions() noexcept; GetLocalPermissions() noexcept;
#endif #endif
#ifdef HAVE_TCP
[[gnu::pure]]
int
GetPermissionsFromAddress(SocketAddress address) noexcept;
#endif
void void
initPermissions(const ConfigData &config); initPermissions(const ConfigData &config);
......
...@@ -32,8 +32,12 @@ GetPermissions(SocketAddress address, int uid) noexcept ...@@ -32,8 +32,12 @@ GetPermissions(SocketAddress address, int uid) noexcept
#ifdef HAVE_UN #ifdef HAVE_UN
if (address.GetFamily() == AF_LOCAL) if (address.GetFamily() == AF_LOCAL)
return GetLocalPermissions(); return GetLocalPermissions();
#else #endif
(void)address;
#ifdef HAVE_TCP
if (int permissions = GetPermissionsFromAddress(address);
permissions >= 0)
return permissions;
#endif #endif
return getDefaultPermissions(); return getDefaultPermissions();
......
...@@ -48,6 +48,7 @@ enum class ConfigOption { ...@@ -48,6 +48,7 @@ enum class ConfigOption {
ZEROCONF_NAME, ZEROCONF_NAME,
ZEROCONF_ENABLED, ZEROCONF_ENABLED,
PASSWORD, PASSWORD,
HOST_PERMISSIONS,
LOCAL_PERMISSIONS, LOCAL_PERMISSIONS,
DEFAULT_PERMS, DEFAULT_PERMS,
AUDIO_OUTPUT_FORMAT, AUDIO_OUTPUT_FORMAT,
......
...@@ -44,6 +44,7 @@ const ConfigTemplate config_param_templates[] = { ...@@ -44,6 +44,7 @@ const ConfigTemplate config_param_templates[] = {
{ "zeroconf_name" }, { "zeroconf_name" },
{ "zeroconf_enabled" }, { "zeroconf_enabled" },
{ "password", true }, { "password", true },
{ "host_permissions", true },
{ "local_permissions" }, { "local_permissions" },
{ "default_permissions" }, { "default_permissions" },
{ "audio_output_format" }, { "audio_output_format" },
......
...@@ -116,3 +116,32 @@ ToString(SocketAddress address) noexcept ...@@ -116,3 +116,32 @@ ToString(SocketAddress address) noexcept
result.append(serv); result.append(serv);
return result; return result;
} }
std::string
HostToString(SocketAddress address) noexcept
{
if (address.IsNull())
return "null";
#ifdef HAVE_UN
if (address.GetFamily() == AF_LOCAL)
/* return path of local socket */
return LocalAddressToString(address.CastTo<struct sockaddr_un>(),
address.GetSize());
#endif
#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED)
IPv4Address ipv4_buffer;
if (address.IsV4Mapped())
address = ipv4_buffer = address.UnmapV4();
#endif
char host[NI_MAXHOST], serv[NI_MAXSERV];
int ret = getnameinfo(address.GetAddress(), address.GetSize(),
host, sizeof(host), serv, sizeof(serv),
NI_NUMERICHOST|NI_NUMERICSERV);
if (ret != 0)
return "unknown";
return host;
}
...@@ -42,4 +42,12 @@ class SocketAddress; ...@@ -42,4 +42,12 @@ class SocketAddress;
std::string std::string
ToString(SocketAddress address) noexcept; ToString(SocketAddress address) noexcept;
/**
* Generates the string representation of a #SocketAddress into the
* specified buffer, without the port number.
*/
[[gnu::pure]]
std::string
HostToString(SocketAddress address) noexcept;
#endif #endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment