diff --git a/NEWS b/NEWS index b85d12751..214e54406 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,7 @@ ver 0.23 (not yet released) * tags - new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location" * split permission "player" from "control" +* add option "host_permissions" * new build-time dependency: libfmt ver 0.22.11 (2021/08/24) diff --git a/doc/user.rst b/doc/user.rst index 4b26ff192..1416b7be8 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -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:`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. 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: .. code-block:: none 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_admin_password@read,add,control,admin" diff --git a/src/Permission.cxx b/src/Permission.cxx index a76feb195..861f0e1a2 100644 --- a/src/Permission.cxx +++ b/src/Permission.cxx @@ -22,6 +22,9 @@ #include "config/Param.hxx" #include "config/Data.hxx" #include "config/Option.hxx" +#include "net/AddressInfo.hxx" +#include "net/Resolver.hxx" +#include "net/ToString.hxx" #include "util/IterableSplitString.hxx" #include "util/RuntimeError.hxx" #include "util/StringView.hxx" @@ -55,6 +58,10 @@ static unsigned permission_default; static unsigned local_permissions; #endif +#ifdef HAVE_TCP +static std::map host_passwords; +#endif + static unsigned ParsePermission(StringView s) { @@ -66,10 +73,9 @@ ParsePermission(StringView s) int(s.size), s.data); } -static unsigned parsePermissions(const char *string) +static unsigned +parsePermissions(std::string_view string) { - assert(string != nullptr); - unsigned permission = 0; for (const auto i : IterableSplitString(string, PERMISSION_SEPARATOR)) @@ -120,8 +126,40 @@ initPermissions(const ConfigData &config) : permission_default; }); #endif + +#ifdef HAVE_TCP + for (const auto ¶m : 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 getPermissionFromPassword(const char *password, unsigned *permission) noexcept { diff --git a/src/Permission.hxx b/src/Permission.hxx index 0161fc8f1..2d9dfb9ae 100644 --- a/src/Permission.hxx +++ b/src/Permission.hxx @@ -23,6 +23,7 @@ #include "config.h" struct ConfigData; +class SocketAddress; static constexpr unsigned PERMISSION_NONE = 0; static constexpr unsigned PERMISSION_READ = 1; @@ -45,6 +46,12 @@ unsigned GetLocalPermissions() noexcept; #endif +#ifdef HAVE_TCP +[[gnu::pure]] +int +GetPermissionsFromAddress(SocketAddress address) noexcept; +#endif + void initPermissions(const ConfigData &config); diff --git a/src/client/Listener.cxx b/src/client/Listener.cxx index b71055b86..4a15bf799 100644 --- a/src/client/Listener.cxx +++ b/src/client/Listener.cxx @@ -32,8 +32,12 @@ GetPermissions(SocketAddress address, int uid) noexcept #ifdef HAVE_UN if (address.GetFamily() == AF_LOCAL) return GetLocalPermissions(); -#else - (void)address; +#endif + +#ifdef HAVE_TCP + if (int permissions = GetPermissionsFromAddress(address); + permissions >= 0) + return permissions; #endif return getDefaultPermissions(); diff --git a/src/config/Option.hxx b/src/config/Option.hxx index e05c48fd1..a62fc691e 100644 --- a/src/config/Option.hxx +++ b/src/config/Option.hxx @@ -48,6 +48,7 @@ enum class ConfigOption { ZEROCONF_NAME, ZEROCONF_ENABLED, PASSWORD, + HOST_PERMISSIONS, LOCAL_PERMISSIONS, DEFAULT_PERMS, AUDIO_OUTPUT_FORMAT, diff --git a/src/config/Templates.cxx b/src/config/Templates.cxx index 5f3a34ddd..f3789a0e2 100644 --- a/src/config/Templates.cxx +++ b/src/config/Templates.cxx @@ -44,6 +44,7 @@ const ConfigTemplate config_param_templates[] = { { "zeroconf_name" }, { "zeroconf_enabled" }, { "password", true }, + { "host_permissions", true }, { "local_permissions" }, { "default_permissions" }, { "audio_output_format" }, diff --git a/src/net/ToString.cxx b/src/net/ToString.cxx index 8d595cd3d..b9935ea92 100644 --- a/src/net/ToString.cxx +++ b/src/net/ToString.cxx @@ -116,3 +116,32 @@ ToString(SocketAddress address) noexcept result.append(serv); 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(), + 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; +} diff --git a/src/net/ToString.hxx b/src/net/ToString.hxx index 78ed421c3..3f67785c9 100644 --- a/src/net/ToString.hxx +++ b/src/net/ToString.hxx @@ -42,4 +42,12 @@ class SocketAddress; std::string 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