From c30f44fd01ebc51544cdb32d4d5a77ea9d4ff42b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 16 Jun 2024 01:17:11 +0200 Subject: [PATCH] [misc] New net_peer_address_is_trusted with cfg set default to "lan" New default for "trusted_networks" = "lan". This will check peer addresses against the addresses/netmasks of the interfaces to establish whether the peer is local. Fixes #1754 --- owntone.conf.in | 6 +-- src/conffile.c | 2 +- src/httpd.c | 13 +++-- src/httpd_artworkapi.c | 2 +- src/httpd_daap.c | 4 +- src/httpd_dacp.c | 2 +- src/httpd_internal.h | 12 ++++- src/httpd_jsonapi.c | 2 +- src/httpd_libevhttp.c | 16 ++++++ src/httpd_rsp.c | 2 +- src/misc.c | 119 +++++++++++++++++++++++++++++++++++++---- src/misc.h | 2 +- src/mpd.c | 29 +--------- 13 files changed, 156 insertions(+), 55 deletions(-) diff --git a/owntone.conf.in b/owntone.conf.in index a22447f3..1c1ad7b3 100644 --- a/owntone.conf.in +++ b/owntone.conf.in @@ -40,9 +40,9 @@ general { # Sets who is allowed to connect without authorisation. This applies to # client types like Remotes, DAAP clients (iTunes) and to the web - # interface. Options are "any", "localhost" or the prefix to one or - # more ipv4/6 networks. The default is { "localhost", "192.168", "fd" } -# trusted_networks = { "localhost", "192.168", "fd" } + # interface. Options are "any", "lan", "localhost" or the prefix to one + # or more ipv4/6 networks. The default is { "lan" } +# trusted_networks = { "lan" } # Enable/disable IPv6 # ipv6 = no diff --git a/src/conffile.c b/src/conffile.c index 68d20df1..c1c6dc05 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -52,7 +52,7 @@ static cfg_opt_t sec_general[] = CFG_STR("admin_password", NULL, CFGF_NONE), CFG_INT("websocket_port", 3688, CFGF_NONE), CFG_STR("websocket_interface", NULL, CFGF_NONE), - CFG_STR_LIST("trusted_networks", "{localhost,192.168,fd}", CFGF_NONE), + CFG_STR_LIST("trusted_networks", "{lan}", CFGF_NONE), CFG_BOOL("ipv6", cfg_false, CFGF_NONE), CFG_STR("bind_address", NULL, CFGF_NONE), CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE), diff --git a/src/httpd.c b/src/httpd.c index c6935611..be7f2301 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -463,8 +463,7 @@ serve_file(struct httpd_request *hreq) bool slashed; int ret; - /* Check authentication */ - if (!httpd_admin_check_auth(hreq)) + if (!httpd_request_is_authorized(hreq)) return; ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, hreq->path); @@ -1264,12 +1263,18 @@ httpd_send_error(struct httpd_request *hreq, int error, const char *reason) } bool -httpd_admin_check_auth(struct httpd_request *hreq) +httpd_request_is_trusted(struct httpd_request *hreq) +{ + return httpd_backend_peer_is_trusted(hreq->backend); +} + +bool +httpd_request_is_authorized(struct httpd_request *hreq) { const char *passwd; int ret; - if (net_peer_address_is_trusted(hreq->peer_address)) + if (httpd_request_is_trusted(hreq)) return true; passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password"); diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c index 48ed9161..5858336d 100644 --- a/src/httpd_artworkapi.c +++ b/src/httpd_artworkapi.c @@ -150,7 +150,7 @@ artworkapi_request(struct httpd_request *hreq) { int status_code; - if (!httpd_admin_check_auth(hreq)) + if (!httpd_request_is_authorized(hreq)) return; if (!hreq->handler) diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 6249b689..e5238938 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -693,7 +693,7 @@ daap_request_authorize(struct httpd_request *hreq) char *passwd; int ret; - if (net_peer_address_is_trusted(hreq->peer_address)) + if (httpd_request_is_trusted(hreq)) return 0; // Regular DAAP clients like iTunes will login with /login, and we will reply @@ -898,7 +898,7 @@ daap_reply_login(struct httpd_request *hreq) CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 32)); param = httpd_query_value_find(hreq->query, "pairing-guid"); - if (param && !net_peer_address_is_trusted(hreq->peer_address)) + if (param && !httpd_request_is_trusted(hreq)) { if (strlen(param) < 3) { diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 446c94fe..b861f61d 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -600,7 +600,7 @@ dacp_request_authorize(struct httpd_request *hreq) int32_t id; int ret; - if (net_peer_address_is_trusted(hreq->peer_address)) + if (httpd_request_is_trusted(hreq)) return 0; param = httpd_query_value_find(hreq->query, "session-id"); diff --git a/src/httpd_internal.h b/src/httpd_internal.h index cfa47dec..62e285b2 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -266,8 +266,15 @@ httpd_send_error(struct httpd_request *hreq, int error, const char *reason); void httpd_redirect_to(struct httpd_request *hreq, const char *path); +/* + * The request either came from a trusted peer (based on ip address) checked by + * httpd_request_is_trusted() or was WWW-authenticated via httpd_basic_auth() + */ bool -httpd_admin_check_auth(struct httpd_request *hreq); +httpd_request_is_authorized(struct httpd_request *hreq); + +bool +httpd_request_is_trusted(struct httpd_request *hreq); int httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passwd, const char *realm); @@ -348,6 +355,9 @@ httpd_backend_input_buffer_get(httpd_backend *backend); int httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data); +bool +httpd_backend_peer_is_trusted(httpd_backend *backend); + int httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend); diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index ee4b7551..24d3e7da 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -4644,7 +4644,7 @@ jsonapi_request(struct httpd_request *hreq) { int status_code; - if (!httpd_admin_check_auth(hreq)) + if (!httpd_request_is_authorized(hreq)) { return; } diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index a932bc9b..071034fc 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -537,6 +537,22 @@ httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend return 0; } +bool +httpd_backend_peer_is_trusted(httpd_backend *backend) +{ + const struct sockaddr *addr; + + httpd_connection *conn = evhttp_request_get_connection(backend); + if (!conn) + return false; + + addr = evhttp_connection_get_addr(conn); + if (!addr) + return false; + + return net_peer_address_is_trusted((union net_sockaddr *)addr); +} + int httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend) { diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index dc8fc2f7..2ea40213 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -288,7 +288,7 @@ rsp_request_authorize(struct httpd_request *hreq) char *passwd; int ret; - if (net_peer_address_is_trusted(hreq->peer_address)) + if (httpd_request_is_trusted(hreq)) return 0; passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password"); diff --git a/src/misc.c b/src/misc.c index c7c383ce..f662f3d1 100644 --- a/src/misc.c +++ b/src/misc.c @@ -124,20 +124,114 @@ static char *buildopts[] = /* ------------------------ Network utility functions ----------------------- */ +static bool +prefix_is_equal(uint8_t *addr1, uint8_t *addr2, uint8_t *mask, size_t len) +{ + int i; + + for (i = 0; i < len; i++) + { + if ((addr1[i] & mask[i]) != (addr2[i] & mask[i])) + return false; + } + + return true; +} + +// Checks if the address is in any of the interface subnets +static bool +net_address_is_local(union net_sockaddr *naddr) +{ + struct ifaddrs *ifaddrs; + struct ifaddrs *iap; + void *if_addr; + void *if_netmask; + void *addr; + int addr_len; + sa_family_t addr_family; + bool is_local = false; + + // Point addr to the actual address data + if (naddr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&naddr->sin6.sin6_addr)) + { + addr = naddr->sin6.sin6_addr.s6_addr + 12; // ipv4 over ipv6 prefix is 12 bytes + addr_len = sizeof(struct in6_addr) - 12; + addr_family = AF_INET; + } + else if (naddr->sa.sa_family == AF_INET6) + { + addr = naddr->sin6.sin6_addr.s6_addr; + addr_len = sizeof(struct in6_addr); + addr_family = AF_INET6; + } + else if (naddr->sa.sa_family == AF_INET) + { + addr = &naddr->sin.sin_addr.s_addr; + addr_len = sizeof(struct in_addr); + addr_family = AF_INET; + } + else + { + return false; + } + + getifaddrs(&ifaddrs); + + for (iap = ifaddrs; iap && !is_local; iap = iap->ifa_next) + { + if (!iap->ifa_addr || !iap->ifa_netmask) + continue; + if (iap->ifa_addr->sa_family != addr_family || iap->ifa_netmask->sa_family != addr_family) + continue; + + if (addr_family == AF_INET6) + { + if_addr = ((struct sockaddr_in6 *)iap->ifa_addr)->sin6_addr.s6_addr; + if_netmask = ((struct sockaddr_in6 *)iap->ifa_netmask)->sin6_addr.s6_addr; + } + else + { + if_addr = &((struct sockaddr_in *)iap->ifa_addr)->sin_addr.s_addr; + if_netmask = &((struct sockaddr_in *)iap->ifa_netmask)->sin_addr.s_addr; + } + + is_local = prefix_is_equal(addr, if_addr, if_netmask, addr_len); + } + + freeifaddrs(ifaddrs); + + return is_local; +} + +static bool +net_address_has_prefix(union net_sockaddr *naddr, const char *prefix) +{ + char buf[64]; + const char *addr = buf; + + if (net_address_get(buf, sizeof(buf), naddr) < 0) + return false; + + if (naddr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&naddr->sin6.sin6_addr)) + addr += strlen("::ffff:"); + + if (strncmp(addr, prefix, strlen(prefix)) == 0) + return true; + + return false; +} + bool -net_peer_address_is_trusted(const char *addr) +net_peer_address_is_trusted(union net_sockaddr *naddr) { cfg_t *section; const char *network; int i; int n; - if (!addr) + if (!naddr) return false; - if (strncmp(addr, "::ffff:", strlen("::ffff:")) == 0) - addr += strlen("::ffff:"); - section = cfg_getsec(cfg, "general"); n = cfg_size(section, "trusted_networks"); @@ -148,14 +242,17 @@ net_peer_address_is_trusted(const char *addr) if (!network || network[0] == '\0') return false; - if (strncmp(network, addr, strlen(network)) == 0) - return true; - - if ((strcmp(network, "localhost") == 0) && (strcmp(addr, "127.0.0.1") == 0 || strcmp(addr, "::1") == 0)) - return true; - if (strcmp(network, "any") == 0) return true; + + if (strcmp(network, "lan") == 0 && net_address_is_local(naddr)) + return true; + + if (strcmp(network, "localhost")) + network = (naddr->sa.sa_family == AF_INET6) ? "::1" : "127.0.0.1"; + + if (net_address_has_prefix(naddr, network)) + return true; } return false; diff --git a/src/misc.h b/src/misc.h index d77393f4..520fe896 100644 --- a/src/misc.h +++ b/src/misc.h @@ -25,7 +25,7 @@ union net_sockaddr // Checks if the address is in a network that is configured as trusted bool -net_peer_address_is_trusted(const char *addr); +net_peer_address_is_trusted(union net_sockaddr *naddr); int net_address_get(char *addr, size_t addr_len, union net_sockaddr *naddr); diff --git a/src/mpd.c b/src/mpd.c index 6e8b4471..10645409 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -4479,31 +4479,6 @@ mpd_input_filter(struct evbuffer *src, struct evbuffer *dst, ev_ssize_t lim, enu return BEV_OK; } -static const char * -sockaddr_to_string(const struct sockaddr *address, char *addr_str, int addr_str_len) -{ - struct sockaddr_in *addr; - struct sockaddr_in6 *addr6; - const char *ret; - - if (address->sa_family == AF_INET) - { - addr = (struct sockaddr_in *)address; - ret = evutil_inet_ntop(AF_INET, &addr->sin_addr, addr_str, addr_str_len); - } - else if (address->sa_family == AF_INET6) - { - addr6 = (struct sockaddr_in6 *)address; - ret = evutil_inet_ntop(AF_INET6, &addr6->sin6_addr, addr_str, addr_str_len); - } - else - { - ret = NULL; - } - - return ret; -} - /* * The connection listener callback function is invoked when a new connection was received. * @@ -4525,7 +4500,6 @@ mpd_accept_conn_cb(struct evconnlistener *listener, */ struct event_base *base = evconnlistener_get_base(listener); struct bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE); - char addr_str[INET6_ADDRSTRLEN]; struct mpd_client_ctx *client_ctx = calloc(1, sizeof(struct mpd_client_ctx)); if (!client_ctx) @@ -4538,8 +4512,7 @@ mpd_accept_conn_cb(struct evconnlistener *listener, client_ctx->authenticated = !cfg_getstr(cfg_getsec(cfg, "library"), "password"); if (!client_ctx->authenticated) { - sockaddr_to_string(address, addr_str, sizeof(addr_str)); - client_ctx->authenticated = net_peer_address_is_trusted(addr_str); + client_ctx->authenticated = net_peer_address_is_trusted((union net_sockaddr *)address); } client_ctx->next = mpd_clients;