diff --git a/minidlna.c b/minidlna.c index 724f8ea..b0c88c4 100644 --- a/minidlna.c +++ b/minidlna.c @@ -750,6 +750,10 @@ init(int argc, char **argv) if (strtobool(ary_options[i].value)) SETFLAG(MERGE_MEDIA_DIRS_MASK); break; + case WIDE_LINKS: + if (strtobool(ary_options[i].value)) + SETFLAG(WIDE_LINKS_MASK); + break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); diff --git a/minidlna.conf b/minidlna.conf index 7e00e89..446ed3e 100644 --- a/minidlna.conf +++ b/minidlna.conf @@ -81,3 +81,6 @@ model_number=1 # maximum number of simultaneous connections # note: many clients open several simultaneous connections while streaming #max_connections=50 + +# set this to yes to allow symlinks that point outside user-defined media_dirs. +#wide_links=no diff --git a/minidlna.conf.5 b/minidlna.conf.5 index ce4b3fd..f5ecb97 100644 --- a/minidlna.conf.5 +++ b/minidlna.conf.5 @@ -163,6 +163,10 @@ force_sort_criteria=+upnp:class,+upnp:originalTrackNumber,+dc:title .fi +.IP "\fBwide_links\fP" +Set to 'yes' to allow symlinks that point outside user-defined media_dirs. +By default, wide symlinks are not followed. + .SH VERSION diff --git a/options.c b/options.c index 2fa8c06..15e05ee 100644 --- a/options.c +++ b/options.c @@ -64,7 +64,8 @@ static const struct { { USER_ACCOUNT, "user" }, { FORCE_SORT_CRITERIA, "force_sort_criteria" }, { MAX_CONNECTIONS, "max_connections" }, - { MERGE_MEDIA_DIRS, "merge_media_dirs" } + { MERGE_MEDIA_DIRS, "merge_media_dirs" }, + { WIDE_LINKS, "wide_links" } }; int diff --git a/options.h b/options.h index 159255f..e9ef921 100644 --- a/options.h +++ b/options.h @@ -57,7 +57,8 @@ enum upnpconfigoptions { USER_ACCOUNT, /* user account to run as */ FORCE_SORT_CRITERIA, /* force sorting by a given sort criteria */ MAX_CONNECTIONS, /* maximum number of simultaneous connections */ - MERGE_MEDIA_DIRS /* don't add an extra directory level when there are multiple media dirs */ + MERGE_MEDIA_DIRS, /* don't add an extra directory level when there are multiple media dirs */ + WIDE_LINKS /* allow following symlinks outside the defined media_dirs */ }; /* readoptionsfile() diff --git a/upnpglobalvars.h b/upnpglobalvars.h index c729a1e..c4b9158 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -191,6 +191,7 @@ extern uint32_t runtime_flags; #define NO_PLAYLIST_MASK 0x0008 #define SYSTEMD_MASK 0x0010 #define MERGE_MEDIA_DIRS_MASK 0x0020 +#define WIDE_LINKS_MASK 0x0040 #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) (runtime_flags & mask) diff --git a/upnphttp.c b/upnphttp.c index 418cde4..4184bb2 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -477,6 +477,21 @@ Send400(struct upnphttp * h) CloseSocket_upnphttp(h); } +/* very minimalistic 403 error message */ +static void +Send403(struct upnphttp * h) +{ + static const char body403[] = + "403 Forbidden" + "

Forbidden

You don't have permission to access this resource." + "\r\n"; + h->respflags = FLAG_HTML; + BuildResp2_upnphttp(h, 403, "Forbidden", + body403, sizeof(body403) - 1); + SendResp_upnphttp(h); + CloseSocket_upnphttp(h); +} + /* very minimalistic 404 error message */ static void Send404(struct upnphttp * h) @@ -1328,6 +1343,46 @@ start_dlna_header(struct string_s *str, int respcode, const char *tmode, const c respcode, date, tmode, mime); } +static int +_open_file(const char *orig_path) +{ + struct media_dir_s *media_path; + char buf[PATH_MAX]; + const char *path; + int fd; + + if (!GETFLAG(WIDE_LINKS_MASK)) + { + path = realpath(orig_path, buf); + if (!path) + { + DPRINTF(E_ERROR, L_HTTP, "Error resolving path %s: %s\n", + orig_path, strerror(errno)); + return -1; + } + + for (media_path = media_dirs; media_path; media_path = media_path->next) + { + if (strncmp(path, media_path->path, strlen(media_path->path)) == 0) + break; + } + if (!media_path && strncmp(path, db_path, strlen(db_path))) + { + DPRINTF(E_ERROR, L_HTTP, "Rejecting wide link %s -> %s\n", + orig_path, path); + return -403; + } + } + else + path = orig_path; + + fd = open(path, O_RDONLY); + if (fd < 0) + DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); + + return fd; +} + static void SendResp_icon(struct upnphttp * h, char * icon) { @@ -1413,11 +1468,13 @@ SendResp_albumArt(struct upnphttp * h, char * object) } DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %lld [%s]\n", id, path); - fd = open(path, O_RDONLY); + fd = _open_file(path); if( fd < 0 ) { - DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); sqlite3_free(path); - Send404(h); + if (fd == -403) + Send403(h); + else + Send404(h); return; } sqlite3_free(path); @@ -1461,11 +1518,13 @@ SendResp_caption(struct upnphttp * h, char * object) } DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %lld [%s]\n", id, path); - fd = open(path, O_RDONLY); + fd = _open_file(path); if( fd < 0 ) { - DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); sqlite3_free(path); - Send404(h); + if (fd == -403) + Send403(h); + else + Send404(h); return; } sqlite3_free(path); @@ -1914,10 +1973,12 @@ SendResp_dlnafile(struct upnphttp *h, char *object) } offset = h->req_RangeStart; - sendfh = open(last_file.path, O_RDONLY); + sendfh = _open_file(last_file.path); if( sendfh < 0 ) { - DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path); - Send404(h); + if (sendfh == -403) + Send403(h); + else + Send404(h); goto error; } size = lseek(sendfh, 0, SEEK_END);