From: Jaroslav Kysela Date: Fri, 19 Oct 2018 20:40:58 +0000 (+0200) Subject: add pernament tickets for the authentization, fixes #5274 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ee714fc11fbebc4c620230df4053f9a833c49eb7;p=thirdparty%2Ftvheadend.git add pernament tickets for the authentization, fixes #5274 --- diff --git a/src/access.c b/src/access.c index 5c524a82e..a0c0cd6bf 100644 --- a/src/access.c +++ b/src/access.c @@ -51,7 +51,7 @@ const char *superuser_password; int access_noacl; -static int passwd_verify(const char *username, verify_callback_t verify, void *aux); +static int passwd_verify(access_t *a, const char *username, verify_callback_t verify, void *aux); static int passwd_verify2(const char *username, verify_callback_t verify, void *aux, const char *username2, const char *passwd2); static void access_ticket_destroy(access_ticket_t *at); @@ -206,6 +206,42 @@ access_ticket_verify2(const char *id, const char *resource) return access_copy(at->at_access); } +/** + * + */ +static int +passwd_auth_exists(const char *id) +{ + passwd_entry_t *pw; + + if (id == NULL) + return 0; + TAILQ_FOREACH(pw, &passwd_entries, pw_link) { + if (strempty(pw->pw_auth)) continue; + if (strcmp(id, pw->pw_auth) == 0) return 1; + } + return 0; +} + +/** + * + */ +static passwd_entry_t * +passwd_auth_find(const char *id) +{ + passwd_entry_t *pw; + + if (id == NULL) + return NULL; + TAILQ_FOREACH(pw, &passwd_entries, pw_link) { + if (!pw->pw_enabled) continue; + if (!pw->pw_auth_enabled) continue; + if (strempty(pw->pw_auth)) continue; + if (strcmp(id, pw->pw_auth) == 0) return pw; + } + return NULL; +} + /** * */ @@ -315,6 +351,7 @@ access_destroy(access_t *a) free(a->aa_lang_ui); free(a->aa_theme); free(a->aa_chrange); + free(a->aa_auth); htsmsg_destroy(a->aa_profiles); htsmsg_destroy(a->aa_dvrcfgs); htsmsg_destroy(a->aa_chtags); @@ -683,7 +720,7 @@ access_get(struct sockaddr_storage *src, const char *username, verify_callback_t if (!access_noacl && access_ip_blocked(src)) return a; - if (!passwd_verify(username, verify, aux)) { + if (!passwd_verify(a, username, verify, aux)) { a->aa_username = strdup(username); a->aa_representative = strdup(username); if(!passwd_verify2(username, verify, aux, @@ -807,6 +844,32 @@ access_get_by_addr(struct sockaddr_storage *src) return a; } +/** + * + */ +static int +access_get_by_auth_verify(void *aux, const char *passwd) +{ + if (passwd == superuser_password) + return 0; + return 1; +} + +/** + * + */ +access_t * +access_get_by_auth(struct sockaddr_storage *src, const char *id) +{ + access_t *a; + passwd_entry_t *pw = passwd_auth_find(id); + if (!pw) + return NULL; + a = access_get(src, pw->pw_username, access_get_by_auth_verify, NULL); + a->aa_rights &= ACCESS_STREAMING; + return a; +} + /** * */ @@ -1878,18 +1941,52 @@ passwd_verify2 static int passwd_verify - (const char *username, verify_callback_t verify, void *aux) + (access_t *a, const char *username, verify_callback_t verify, void *aux) { passwd_entry_t *pw; TAILQ_FOREACH(pw, &passwd_entries, pw_link) if (pw->pw_enabled && !passwd_verify2(username, verify, aux, - pw->pw_username, pw->pw_password)) + pw->pw_username, pw->pw_password)) { + if (pw->pw_auth_enabled) + tvh_str_set(&a->aa_auth, pw->pw_auth); return 0; + } return -1; } +static void +passwd_entry_new_auth(passwd_entry_t *pw) +{ + static const char table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; + uint8_t buf[20], *in; + char id[42], *dst; + unsigned int bits; + int len, shift; + + do { + id[0] = 'P'; + uuid_random(in = buf, sizeof(buf)); + /* convert random bits to URL safe characters, modified base64 encoding */ + bits = 0; + shift = 0; + in = buf; + dst = id + 1; + for (len = sizeof(buf); len > 0; len--) { + bits = (bits << 8) + *in++; + shift += 8; + do { + *dst++ = table[(bits << 6 >> shift) & 0x3f]; + shift -= 6; + } while (shift > 6 || (len == 1 && shift > 0)); + } + *dst = '\0'; + } while (passwd_auth_exists(id)); + tvh_str_set(&pw->pw_auth, id); +} + passwd_entry_t * passwd_entry_create(const char *uuid, htsmsg_t *conf) { @@ -1937,6 +2034,7 @@ passwd_entry_destroy(passwd_entry_t *pw, int delconf) free(pw->pw_username); free(pw->pw_password); free(pw->pw_password2); + free(pw->pw_auth); free(pw->pw_comment); free(pw); } @@ -2014,6 +2112,70 @@ passwd_entry_class_password2_set(void *o, const void *v) return 0; } +static int +passwd_entry_class_auth_enabled_set ( void *obj, idnode_slist_t *entry, int val ) +{ + passwd_entry_t *pw = (passwd_entry_t *)obj; + val = !!val; + if (pw->pw_auth_enabled != val) { + pw->pw_auth_enabled = val; + if (val && strempty(pw->pw_auth)) + passwd_entry_new_auth((passwd_entry_t *)obj); + return 1; + } + return 0; +} + +static int +passwd_entry_class_auth_reset_set ( void *obj, idnode_slist_t *entry, int val ) +{ + if (val) { + passwd_entry_new_auth((passwd_entry_t *)obj); + return 1; + } + return 0; +} + +static idnode_slist_t passwd_entry_class_auth_slist[] = { + { + .id = "enable", + .name = N_("Enable"), + .off = offsetof(passwd_entry_t, pw_auth_enabled), + .set = passwd_entry_class_auth_enabled_set, + }, + { + .id = "reset", + .name = N_("Reset"), + .off = 0, + .set = passwd_entry_class_auth_reset_set, + }, + {} +}; + +static htsmsg_t * +passwd_entry_class_auth_enum ( void *obj, const char *lang ) +{ + return idnode_slist_enum(obj, passwd_entry_class_auth_slist, lang); +} + +static const void * +passwd_entry_class_auth_get ( void *obj ) +{ + return idnode_slist_get(obj, passwd_entry_class_auth_slist); +} + +static char * +passwd_entry_class_auth_rend ( void *obj, const char *lang ) +{ + return idnode_slist_rend(obj, passwd_entry_class_auth_slist, lang); +} + +static int +passwd_entry_class_auth_set ( void *obj, const void *p ) +{ + return idnode_slist_set(obj, passwd_entry_class_auth_slist, p); +} + CLASS_DOC(passwd) const idclass_t passwd_entry_class = { @@ -2059,6 +2221,26 @@ const idclass_t passwd_entry_class = { .opts = PO_PASSWORD | PO_HIDDEN | PO_EXPERT | PO_WRONCE | PO_NOUI, .set = passwd_entry_class_password2_set, }, + { + .type = PT_INT, + .islist = 1, + .id = "auth", + .name = N_("Persistent authentication"), + .desc = N_("Manage persistent authentication for HTTP streaming."), + .list = passwd_entry_class_auth_enum, + .get = passwd_entry_class_auth_get, + .set = passwd_entry_class_auth_set, + .rend = passwd_entry_class_auth_rend, + .opts = PO_DOC_NLIST, + }, + { + .type = PT_STR, + .id = "authcode", + .name = N_("Persistent authentication code"), + .desc = N_("The code which may be used for the HTTP streaming."), + .off = offsetof(passwd_entry_t, pw_auth), + .opts = PO_RDONLY, + }, { .type = PT_STR, .id = "comment", diff --git a/src/access.h b/src/access.h index 23fc768d8..75830657f 100644 --- a/src/access.h +++ b/src/access.h @@ -60,7 +60,10 @@ typedef struct passwd_entry { char *pw_password; char *pw_password2; + char *pw_auth; + int pw_enabled; + int pw_auth_enabled; int pw_wizard; char *pw_comment; @@ -174,6 +177,7 @@ typedef struct access { int aa_uilevel; int aa_uilevel_nochange; char *aa_theme; + char *aa_auth; } access_t; TAILQ_HEAD(access_ticket_queue, access_ticket); @@ -284,6 +288,12 @@ access_get_by_username(const char *username); access_t * access_get_by_addr(struct sockaddr_storage *src); +/** + * + */ +access_t * +access_get_by_auth(struct sockaddr_storage *src, const char *id); + /** * */ diff --git a/src/http.c b/src/http.c index 1a9df5069..57fcdcf02 100644 --- a/src/http.c +++ b/src/http.c @@ -957,6 +957,24 @@ http_access_verify_ticket(http_connection_t *hc) hc->hc_peer_ipstr, ticket_id, hc->hc_url); } +/** + * + */ +static void +http_access_verify_auth(http_connection_t *hc) +{ + const char *auth_id; + + if (hc->hc_access) + return; + auth_id = http_arg_get(&hc->hc_req_args, "auth"); + hc->hc_access = access_get_by_auth(hc->hc_peer, auth_id); + if (hc->hc_access == NULL) + return; + tvhinfo(hc->hc_subsys, "%s: using auth %s for %s", + hc->hc_peer_ipstr, auth_id, hc->hc_url); +} + /** * */ @@ -1087,6 +1105,11 @@ http_access_verify(http_connection_t *hc, int mask) http_access_verify_ticket(hc); if (hc->hc_access) res = access_verify2(hc->hc_access, mask); + if (res) { + http_access_verify_auth(hc); + if (hc->hc_access) + res = access_verify2(hc->hc_access, mask); + } if (res) { access_destroy(hc->hc_access); @@ -1119,10 +1142,14 @@ http_access_verify_channel(http_connection_t *hc, int mask, http_access_verify_ticket(hc); if (hc->hc_access) { res = access_verify2(hc->hc_access, mask); - - if (!res && !channel_access(ch, hc->hc_access, 0)) - res = -1; + if (res) { + http_access_verify_auth(hc); + if (hc->hc_access) + res = access_verify2(hc->hc_access, mask); + } } + if (!res && !channel_access(ch, hc->hc_access, 0)) + res = -1; if (res) { access_destroy(hc->hc_access); @@ -1135,13 +1162,11 @@ http_access_verify_channel(http_connection_t *hc, int mask, http_verify_free(&v); if (hc->hc_access) { res = access_verify2(hc->hc_access, mask); - if (!res && !channel_access(ch, hc->hc_access, 0)) res = -1; } } - return res; } diff --git a/src/idnode.c b/src/idnode.c index ace9f58c8..1d7c2b8c9 100644 --- a/src/idnode.c +++ b/src/idnode.c @@ -1570,11 +1570,16 @@ htsmsg_t * idnode_slist_get ( idnode_t *in, idnode_slist_t *options ) { htsmsg_t *l = htsmsg_create_list(); - int *ip; + int val; for (; options->id; options++) { - ip = (void *)in + options->off; - if (*ip) + if (options->get) + val = options->get(in, options); + else if (options->off) + val = *(int *)((void *)in + options->off); + else + val = 0; + if (val) htsmsg_add_str(l, NULL, options->id); } return l; @@ -1589,29 +1594,31 @@ idnode_slist_set ( idnode_t *in, idnode_slist_t *options, const htsmsg_t *vals ) const char *s; for (o = options; o->id; o++) { - ip = (void *)in + o->off; - if (!changed) { - HTSMSG_FOREACH(f, vals) { - if ((s = htsmsg_field_get_str(f)) == NULL) - continue; - if (strcmp(s, o->id)) - continue; - if (*ip == 0) changed = 1; - break; - } - if (f == NULL && *ip) changed = 1; - } - *ip = 0; - } - HTSMSG_FOREACH(f, vals) { - if ((s = htsmsg_field_get_str(f)) == NULL) - continue; - for (o = options; o->id; o++) { - if (strcmp(o->id, s)) continue; + if (o->off) { ip = (void *)in + o->off; - *ip = 1; + } else { + ip = NULL; + } + HTSMSG_FOREACH(f, vals) { + if ((s = htsmsg_field_get_str(f)) == NULL) + continue; + if (strcmp(s, o->id)) + continue; + if (o->set) { + changed |= o->set(in, o, 1); + } else if (*ip == 0) { + *ip = changed = 1; + } break; } + if (f == NULL) { + if (o->set) { + changed |= o->set(in, o, 0); + } else if (*ip) { + *ip = 0; + changed = 1; + } + } } return changed; } diff --git a/src/idnode.h b/src/idnode.h index 92e749620..d4262f475 100644 --- a/src/idnode.h +++ b/src/idnode.h @@ -117,11 +117,15 @@ struct idnode_save { /* * Simple list */ -typedef struct idnode_slist { +typedef struct idnode_slist idnode_slist_t; +struct idnode_slist { const char *id; const char *name; size_t off; -} idnode_slist_t; + + int (*set)(void *self, idnode_slist_t *entry, int val); + int (*get)(void *self, idnode_slist_t *entry); +}; /* * Node list mapping definition diff --git a/src/utils.c b/src/utils.c index ae588bba7..7af41da4e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -184,23 +184,24 @@ static const uint8_t map2[] = int base64_decode(uint8_t *out, const char *in, int out_size) { - int i, v; - uint8_t *dst = out; - - v = 0; - for (i = 0; in[i] && in[i] != '='; i++) { - unsigned int index= in[i]-43; - if (index >= sizeof(map2) || map2[index] == 0xff) - return -1; - v = (v << 6) + map2[index]; - if (i & 3) { - if (dst - out < out_size) { - *dst++ = v >> (6 - 2 * (i & 3)); - } - } + int i, v; + unsigned int index; + uint8_t *dst = out; + + v = 0; + for (i = 0; *in && *in != '='; i++, in++) { + index = *in - 43; + if (index >= sizeof(map2) || map2[index] == 0xff) + return -1; + v = (v << 6) + map2[index]; + if (i & 3) { + if (dst - out < out_size) { + *dst++ = v >> (6 - 2 * (i & 3)); + } } + } - return dst - out; + return dst - out; } /* @@ -208,35 +209,33 @@ base64_decode(uint8_t *out, const char *in, int out_size) * Simplified by Michael. * Fixed edge cases and made it work from data (vs. strings) by Ryan. */ - char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size) { - static const char b64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - char *ret, *dst; - unsigned i_bits = 0; - int i_shift = 0; - int bytes_remaining = in_size; - - if (in_size >= UINT_MAX / 4 || - out_size < BASE64_SIZE(in_size)) - return NULL; - ret = dst = out; - while (bytes_remaining) { - i_bits = (i_bits << 8) + *in++; - bytes_remaining--; - i_shift += 8; - - do { - *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; - i_shift -= 6; - } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); - } - while ((dst - ret) & 3) - *dst++ = '='; - *dst = '\0'; + static const char b64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char *ret, *dst; + unsigned i_bits = 0; + int i_shift = 0; + int bytes_remaining = in_size; + + if (in_size >= UINT_MAX / 4 || + out_size < BASE64_SIZE(in_size)) + return NULL; + ret = dst = out; + while (bytes_remaining) { + i_bits = (i_bits << 8) + *in++; + bytes_remaining--; + i_shift += 8; + do { + *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; + i_shift -= 6; + } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); + } + while ((dst - ret) & 3) + *dst++ = '='; + *dst = '\0'; - return ret; + return ret; } /** diff --git a/src/webui/static/app/acleditor.js b/src/webui/static/app/acleditor.js index 25e78e36c..bed8d54f2 100644 --- a/src/webui/static/app/acleditor.js +++ b/src/webui/static/app/acleditor.js @@ -63,7 +63,7 @@ tvheadend.acleditor = function(panel, index) tvheadend.passwdeditor = function(panel, index) { - var list = 'enabled,username,password,comment'; + var list = 'enabled,username,password,auth,authcode,comment'; tvheadend.idnode_grid(panel, { url: 'api/passwd/entry', @@ -73,7 +73,9 @@ tvheadend.passwdeditor = function(panel, index) columns: { enabled: { width: 120 }, username: { width: 250 }, - password: { width: 250 } + password: { width: 250 }, + auth: { width: 250 }, + authcode: { width: 250 } }, tabIndex: index, edit: { diff --git a/src/webui/static/app/chconf.js b/src/webui/static/app/chconf.js index 6f8840eeb..7f80c6565 100644 --- a/src/webui/static/app/chconf.js +++ b/src/webui/static/app/chconf.js @@ -249,7 +249,7 @@ tvheadend.channel_tab = function(panel, index) if (r.data['number']) title += r.data['number'] + ' : '; title += r.data['name']; - return tvheadend.playLink('play/stream/channel/' + r.id, title); + return tvheadend.playLink('stream/channel/' + r.id, title); } } ], diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index c36957de7..d8317de52 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -833,7 +833,7 @@ tvheadend.dvr_finished = function(panel, index) { var title = r.data['disp_title']; if (r.data['episode_disp']) title += ' / ' + r.data['episode_disp']; - return tvheadend.playLink('play/dvrfile/' + r.id, title); + return tvheadend.playLink('dvrfile/' + r.id, title); } }], tbar: [removeButton, downloadButton, rerecordButton, moveButton, groupingButton], @@ -952,7 +952,7 @@ tvheadend.dvr_failed = function(panel, index) { var title = r.data['disp_title']; if (r.data['episode_disp']) title += ' / ' + r.data['episode_disp']; - return tvheadend.playLink('play/dvrfile/' + r.id, title); + return tvheadend.playLink('dvrfile/' + r.id, title); } }], tbar: [downloadButton, rerecordButton, moveButton], diff --git a/src/webui/static/app/mpegts.js b/src/webui/static/app/mpegts.js index 07ef42061..6834b002e 100644 --- a/src/webui/static/app/mpegts.js +++ b/src/webui/static/app/mpegts.js @@ -126,7 +126,7 @@ tvheadend.muxes = function(panel, index) if (title) title += ' / '; title += r.data['network']; } - return tvheadend.playLink('play/stream/mux/' + r.id, title); + return tvheadend.playLink('stream/mux/' + r.id, title); } } ], @@ -398,7 +398,7 @@ tvheadend.services = function(panel, index) if (title) title += ' / '; title += r.data['provider']; } - return tvheadend.playLink('play/stream/service/' + r.id, title); + return tvheadend.playLink('stream/service/' + r.id, title); } }, { diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 4e2ff8dbb..7bc17a1f8 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -798,7 +798,7 @@ tvheadend.niceDateYearMonth = function(dt, refdate) { */ tvheadend.playLink = function(link, title) { if (title) title = '?title=' + encodeURIComponent(title); - return '' + + return '' + '' + _('Play') + ''; } diff --git a/src/webui/webui.c b/src/webui/webui.c index f1b2fa0c3..731bf9f57 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -67,6 +67,12 @@ enum { PLAYLIST_SATIP_M3U\ }; +enum { + URLAUTH_NONE, + URLAUTH_TICKET, + URLAUTH_CODE +}; + static int webui_xspf; /** @@ -88,6 +94,20 @@ is_client_simple(http_connection_t *hc) return 0; } +/** + * + */ +static const char * +page_playlist_authpath(int urlauth) +{ + switch (urlauth) { + case URLAUTH_NONE: return ""; + case URLAUTH_TICKET: return "/ticket"; + case URLAUTH_CODE: return "/auth"; + default: assert(0); return ""; + }; +} + /** * Root page, we direct the client to different pages depending * on if it is a full blown browser or just some mobile app @@ -490,7 +510,8 @@ static void http_m3u_playlist_add(htsbuf_queue_t *hq, const char *hostpath, const char *url_remain, const char *profile, const char *svcname, const char *chnum, - const char *logo, const char *epgid, access_t *access) + const char *logo, const char *epgid, + int urlauth, access_t *access) { htsbuf_append_str(hq, "#EXTINF:-1"); if (logo) { @@ -503,8 +524,18 @@ http_m3u_playlist_add(htsbuf_queue_t *hq, const char *hostpath, htsbuf_qprintf(hq, " tvg-id=\"%s\"", epgid); if (chnum) htsbuf_qprintf(hq, " tvg-chno=\"%s\"", chnum); - htsbuf_qprintf(hq, ",%s\n%s%s?ticket=%s", svcname, hostpath, url_remain, - access_ticket_create(url_remain, access)); + htsbuf_qprintf(hq, ",%s\n%s%s", svcname, hostpath, url_remain); + switch (urlauth) { + case URLAUTH_NONE: + break; + case URLAUTH_TICKET: + htsbuf_qprintf(hq, "?ticket=%s", access_ticket_create(url_remain, access)); + break; + case URLAUTH_CODE: + if (!strempty(access->aa_auth)) + htsbuf_qprintf(hq, "?auth=%s", access->aa_auth); + break; + } htsbuf_qprintf(hq, "&profile=%s\n", profile); } @@ -514,13 +545,15 @@ http_m3u_playlist_add(htsbuf_queue_t *hq, const char *hostpath, static void http_e2_playlist_add(htsbuf_queue_t *hq, const char *hostpath, const char *url_remain, const char *profile, - const char *svcname) + const char *svcname, int urlauth, access_t *access) { htsbuf_append_str(hq, "#SERVICE 1:0:0:0:0:0:0:0:0:0:"); htsbuf_append_and_escape_url(hq, hostpath); htsbuf_append_and_escape_url(hq, url_remain); - htsbuf_qprintf(hq, "&profile=%s:%s\n", profile, svcname); - htsbuf_qprintf(hq, "#DESCRIPTION %s\n", svcname); + htsbuf_qprintf(hq, "?profile=%s", profile); + if (urlauth == URLAUTH_CODE && !strempty(access->aa_auth)) + htsbuf_qprintf(hq, "&auth=%s", access->aa_auth); + htsbuf_qprintf(hq, ":%s\n#DESCRIPTION %s\n", svcname, svcname); } /* @@ -528,7 +561,8 @@ http_e2_playlist_add(htsbuf_queue_t *hq, const char *hostpath, */ static void http_satip_m3u_playlist_add(htsbuf_queue_t *hq, const char *hostpath, - channel_t *ch, const char *blank) + channel_t *ch, const char *blank, + int urlauth, access_t *access) { char buf[64]; const char *name, *logo; @@ -555,14 +589,17 @@ http_satip_m3u_playlist_add(htsbuf_queue_t *hq, const char *hostpath, else htsbuf_qprintf(hq, " logo=%s", logo); } - htsbuf_qprintf(hq, ",%s\n%s%s?profile=pass\n", name, hostpath, buf); + htsbuf_qprintf(hq, ",%s\n%s%s?profile=pass", name, hostpath, buf); + if (urlauth == URLAUTH_CODE && !strempty(access->aa_auth)) + htsbuf_qprintf(hq, "&auth=%s", access->aa_auth); + htsbuf_append_str(hq, "\n"); } /** * Output a playlist containing a single channel */ static int -http_channel_playlist(http_connection_t *hc, int pltype, channel_t *channel) +http_channel_playlist(http_connection_t *hc, int pltype, int urlauth, channel_t *channel) { htsbuf_queue_t *hq; char buf[255], chnum[32]; @@ -591,16 +628,16 @@ http_channel_playlist(http_connection_t *hc, int pltype, channel_t *channel) channel_get_number_as_str(channel, chnum, sizeof(chnum)), channel_get_icon(channel), channel_get_uuid(channel, ubuf), - hc->hc_access); + urlauth, hc->hc_access); } else if (pltype == PLAYLIST_E2) { htsbuf_qprintf(hq, "#NAME %s\n", name); - http_e2_playlist_add(hq, hostpath, buf, profile, name); + http_e2_playlist_add(hq, hostpath, buf, profile, name, urlauth, hc->hc_access); } else if (pltype == PLAYLIST_SATIP_M3U) { - http_satip_m3u_playlist_add(hq, hostpath, channel, blank); + http_satip_m3u_playlist_add(hq, hostpath, channel, blank, urlauth, hc->hc_access); } @@ -614,7 +651,7 @@ http_channel_playlist(http_connection_t *hc, int pltype, channel_t *channel) * Output a playlist containing all channels with a specific tag */ static int -http_tag_playlist(http_connection_t *hc, int pltype, channel_tag_t *tag) +http_tag_playlist(http_connection_t *hc, int pltype, int urlauth, channel_tag_t *tag) { htsbuf_queue_t *hq; char buf[255], chnum[32], ubuf[UUID_HEX_SIZE]; @@ -651,12 +688,12 @@ http_tag_playlist(http_connection_t *hc, int pltype, channel_tag_t *tag) channel_get_number_as_str(ch, chnum, sizeof(chnum)), channel_get_icon(ch), channel_get_uuid(ch, ubuf), - hc->hc_access); + urlauth, hc->hc_access); } else if (pltype == PLAYLIST_E2) { htsbuf_qprintf(hq, "#NAME %s\n", name); - http_e2_playlist_add(hq, hostpath, buf, profile, name); + http_e2_playlist_add(hq, hostpath, buf, profile, name, urlauth, hc->hc_access); } else if (pltype == PLAYLIST_SATIP_M3U) { - http_satip_m3u_playlist_add(hq, hostpath, ch, blank); + http_satip_m3u_playlist_add(hq, hostpath, ch, blank, urlauth, hc->hc_access); } } @@ -671,7 +708,7 @@ http_tag_playlist(http_connection_t *hc, int pltype, channel_tag_t *tag) * Output a playlist pointing to tag-specific playlists */ static int -http_tag_list_playlist(http_connection_t *hc, int pltype) +http_tag_list_playlist(http_connection_t *hc, int pltype, int urlauth) { htsbuf_queue_t *hq; char buf[255]; @@ -710,7 +747,7 @@ http_tag_list_playlist(http_connection_t *hc, int pltype) if (pltype == PLAYLIST_M3U) { snprintf(buf, sizeof(buf), "/playlist/tagid/%d", idnode_get_short_uuid(&ct->ct_id)); http_m3u_playlist_add(hq, hostpath, buf, profile, ct->ct_name, NULL, - channel_tag_get_icon(ct), NULL, hc->hc_access); + channel_tag_get_icon(ct), NULL, urlauth, hc->hc_access); } else if (pltype == PLAYLIST_E2) { htsbuf_qprintf(hq, "#SERVICE 1:64:%d:0:0:0:0:0:0:0::%s\n", labelidx++, ct->ct_name); htsbuf_qprintf(hq, "#DESCRIPTION %s\n", ct->ct_name); @@ -719,7 +756,7 @@ http_tag_list_playlist(http_connection_t *hc, int pltype) LIST_FOREACH(ilm, &ct->ct_ctms, ilm_in1_link) if (ch == (channel_t *)ilm->ilm_in2) { snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(ch)); - http_e2_playlist_add(hq, hostpath, buf, profile, channel_get_name(ch, blank)); + http_e2_playlist_add(hq, hostpath, buf, profile, channel_get_name(ch, blank), urlauth, hc->hc_access); break; } } @@ -728,7 +765,7 @@ http_tag_list_playlist(http_connection_t *hc, int pltype) ch = chlist[chidx]; LIST_FOREACH(ilm, &ct->ct_ctms, ilm_in1_link) if (ch == (channel_t *)ilm->ilm_in2) - http_satip_m3u_playlist_add(hq, hostpath, ch, blank); + http_satip_m3u_playlist_add(hq, hostpath, ch, blank, urlauth, hc->hc_access); } } } @@ -746,7 +783,7 @@ http_tag_list_playlist(http_connection_t *hc, int pltype) * Output a flat playlist with all channels */ static int -http_channel_list_playlist(http_connection_t *hc, int pltype) +http_channel_list_playlist(http_connection_t *hc, int pltype, int urlauth) { htsbuf_queue_t *hq; char buf[255], chnum[32], ubuf[UUID_HEX_SIZE]; @@ -784,11 +821,11 @@ http_channel_list_playlist(http_connection_t *hc, int pltype) channel_get_number_as_str(ch, chnum, sizeof(chnum)), channel_get_icon(ch), channel_get_uuid(ch, ubuf), - hc->hc_access); + urlauth, hc->hc_access); } else if (pltype == PLAYLIST_E2) { - http_e2_playlist_add(hq, hostpath, buf, profile, name); + http_e2_playlist_add(hq, hostpath, buf, profile, name, urlauth, hc->hc_access); } else if (pltype == PLAYLIST_SATIP_M3U) { - http_satip_m3u_playlist_add(hq, hostpath, ch, blank); + http_satip_m3u_playlist_add(hq, hostpath, ch, blank, urlauth, hc->hc_access); } } @@ -804,7 +841,7 @@ http_channel_list_playlist(http_connection_t *hc, int pltype) * Output a playlist of all recordings. */ static int -http_dvr_list_playlist(http_connection_t *hc, int pltype) +http_dvr_list_playlist(http_connection_t *hc, int pltype, int urlauth) { htsbuf_queue_t *hq; char buf[255], ubuf[UUID_HEX_SIZE]; @@ -847,8 +884,19 @@ http_dvr_list_playlist(http_connection_t *hc, int pltype) htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf); snprintf(buf, sizeof(buf), "/dvrfile/%s", uuid); - htsbuf_qprintf(hq, "%s%s?ticket=%s\n", hostpath, buf, - access_ticket_create(buf, hc->hc_access)); + htsbuf_qprintf(hq, "%s%s", hostpath, buf); + + switch (urlauth) { + case URLAUTH_TICKET: + htsbuf_qprintf(hq, "?ticket=%s\n", access_ticket_create(buf, hc->hc_access)); + break; + case URLAUTH_CODE: + if (!strempty(hc->hc_access->aa_auth)) + htsbuf_qprintf(hq, "?auth=%s\n", hc->hc_access->aa_auth); + break; + } + + htsbuf_append_str(hq, "\n"); } free(hostpath); @@ -859,7 +907,7 @@ http_dvr_list_playlist(http_connection_t *hc, int pltype) * Output a playlist with a http stream for a dvr entry (.m3u format) */ static int -http_dvr_playlist(http_connection_t *hc, int pltype, dvr_entry_t *de) +http_dvr_playlist(http_connection_t *hc, int pltype, int urlauth, dvr_entry_t *de) { htsbuf_queue_t *hq = &hc->hc_reply; char buf[255], ubuf[UUID_HEX_SIZE]; @@ -896,8 +944,19 @@ http_dvr_playlist(http_connection_t *hc, int pltype, dvr_entry_t *de) htsbuf_qprintf(hq, "#EXT-X-PROGRAM-DATE-TIME:%s\n", buf); snprintf(buf, sizeof(buf), "/dvrfile/%s", uuid); - ticket_id = access_ticket_create(buf, hc->hc_access); - htsbuf_qprintf(hq, "%s%s?ticket=%s\n", hostpath, buf, ticket_id); + htsbuf_qprintf(hq, "%s%s", hostpath, buf); + + switch (urlauth) { + case URLAUTH_TICKET: + ticket_id = access_ticket_create(buf, hc->hc_access); + htsbuf_qprintf(hq, "?ticket=%s", ticket_id); + break; + case URLAUTH_CODE: + if (!strempty(hc->hc_access->aa_auth)) + htsbuf_qprintf(hq, "?auth=%s", hc->hc_access->aa_auth); + break; + } + htsbuf_append_str(hq, "\n"); } else { ret = HTTP_STATUS_NOT_FOUND; } @@ -911,9 +970,11 @@ http_dvr_playlist(http_connection_t *hc, int pltype, dvr_entry_t *de) * Handle requests for playlists. */ static int -page_http_playlist(http_connection_t *hc, const char *remain, void *opaque) +page_http_playlist_ + (http_connection_t *hc, const char *remain, void *opaque, int urlauth) { - char *components[2], *cmd, *s; + char *components[2], *cmd, *s, buf[40]; + const char *cs; int nc, r, pltype = PLAYLIST_M3U; channel_t *ch = NULL; dvr_entry_t *de = NULL; @@ -939,12 +1000,14 @@ page_http_playlist(http_connection_t *hc, const char *remain, void *opaque) remain += 6; } - if(!remain || *remain == '\0') { - http_redirect(hc, pltype == PLAYLIST_E2 ? "/playlist/e2/channels" : - (pltype == PLAYLIST_SATIP_M3U ? - "/playlist/satip/channels" : - "/playlist/channels"), - &hc->hc_req_args, 0); + if (!remain || *remain == '\0') { + switch (pltype) { + case PLAYLIST_E2: cs = "e2/channels"; break; + case PLAYLIST_SATIP_M3U: cs = "satip/channels"; break; + default: cs = "channels"; break; + } + snprintf(buf, sizeof(buf), "playlist%s/%s", page_playlist_authpath(urlauth), cs); + http_redirect(hc, buf, &hc->hc_req_args, 0); return HTTP_STATUS_FOUND; } @@ -981,14 +1044,14 @@ page_http_playlist(http_connection_t *hc, const char *remain, void *opaque) } if(ch) - r = http_channel_playlist(hc, pltype, ch); + r = http_channel_playlist(hc, pltype, urlauth, ch); else if(tag) - r = http_tag_playlist(hc, pltype, tag); + r = http_tag_playlist(hc, pltype, urlauth, tag); else if(de) { if (pltype == PLAYLIST_SATIP_M3U) r = HTTP_STATUS_BAD_REQUEST; else - r = http_dvr_playlist(hc, pltype, de); + r = http_dvr_playlist(hc, pltype, urlauth, de); } else { cmd = s = tvh_strdupa(components[0]); while (*s && *s != '.') s++; @@ -999,12 +1062,12 @@ page_http_playlist(http_connection_t *hc, const char *remain, void *opaque) if (s[0] != '\0' && strcmp(s, "m3u") && strcmp(s, "m3u8")) r = HTTP_STATUS_BAD_REQUEST; else if(!strcmp(cmd, "tags")) - r = http_tag_list_playlist(hc, pltype); + r = http_tag_list_playlist(hc, urlauth, pltype); else if(!strcmp(cmd, "channels")) - r = http_channel_list_playlist(hc, pltype); + r = http_channel_list_playlist(hc, urlauth, pltype); else if(pltype != PLAYLIST_SATIP_M3U && !strcmp(cmd, "recordings")) - r = http_dvr_list_playlist(hc, pltype); + r = http_dvr_list_playlist(hc, urlauth, pltype); else { r = HTTP_STATUS_BAD_REQUEST; } @@ -1018,6 +1081,26 @@ page_http_playlist(http_connection_t *hc, const char *remain, void *opaque) return r; } +static int +page_http_playlist + (http_connection_t *hc, const char *remain, void *opaque) +{ + return page_http_playlist_(hc, remain, opaque, URLAUTH_NONE); +} + +static int +page_http_playlist_ticket + (http_connection_t *hc, const char *remain, void *opaque) +{ + return page_http_playlist_(hc, remain, opaque, URLAUTH_TICKET); +} + +static int +page_http_playlist_auth + (http_connection_t *hc, const char *remain, void *opaque) +{ + return page_http_playlist_(hc, remain, opaque, URLAUTH_CODE); +} /** * Subscribes to a service and starts the streaming loop @@ -1304,49 +1387,59 @@ http_stream(http_connection_t *hc, const char *remain, void *opaque) * http://en.wikipedia.org/wiki/XML_Shareable_Playlist_Format */ static int -page_xspf(http_connection_t *hc, const char *remain, void *opaque) +page_xspf(http_connection_t *hc, const char *remain, void *opaque, int urlauth) { - size_t maxlen; - char *buf, *hostpath = http_get_hostpath(hc); - const char *title, *profile, *ticket, *image; - size_t len; + htsbuf_queue_t *hq = &hc->hc_reply; + char buf[80], *hostpath; + const char *title, *profile, *ticket, *image, *delim = "?"; if ((title = http_arg_get(&hc->hc_req_args, "title")) == NULL) title = "TVHeadend Stream"; - profile = http_arg_get(&hc->hc_req_args, "profile"); - ticket = http_arg_get(&hc->hc_req_args, "ticket"); - image = http_arg_get(&hc->hc_req_args, "image"); - - maxlen = strlen(remain) + strlen(title) + 512; - buf = alloca(maxlen); - - pthread_mutex_lock(&global_lock); - if (ticket == NULL) { - snprintf(buf, maxlen, "/%s", remain); - ticket = access_ticket_create(buf, hc->hc_access); - } - snprintf(buf, maxlen, "\ + hostpath = http_get_hostpath(hc); + htsbuf_qprintf(hq, "\ \r\n\ \r\n\ \r\n\ \r\n\ %s\r\n\ - %s/%s%s%s%s%s\r\n%s%s%s\ + %s/%s", title, hostpath, remain); + free(hostpath); + profile = http_arg_get(&hc->hc_req_args, "profile"); + if (profile) { + htsbuf_qprintf(hq, "?profile=%s", profile); + delim = "&"; + } + switch (urlauth) { + case URLAUTH_TICKET: + ticket = http_arg_get(&hc->hc_req_args, "ticket"); + if (strempty(ticket)) { + snprintf(buf, sizeof(buf), "/%s", remain); + pthread_mutex_lock(&global_lock); + ticket = access_ticket_create(buf, hc->hc_access); + pthread_mutex_unlock(&global_lock); + } + htsbuf_qprintf(hq, "%sticket=%s", delim, ticket); + break; + case URLAUTH_CODE: + ticket = http_arg_get(&hc->hc_req_args, "auth"); + if (strempty(ticket)) + if (hc->hc_access && !strempty(hc->hc_access->aa_auth)) + ticket = hc->hc_access->aa_auth; + if (!strempty(ticket)) + htsbuf_qprintf(hq, "%sauth=%s", delim, ticket); + break; + default: + break; + } + htsbuf_append_str(hq, "\n"); + image = http_arg_get(&hc->hc_req_args, "image"); + if (image) + htsbuf_qprintf(hq, " %s\n", image); + htsbuf_append_str(hq, "\ \r\n\ \r\n\ -\r\n", title, hostpath, remain, - profile ? "?profile=" : "", profile ?: "", - profile ? "&ticket=" : "?ticket=", ticket, - image ? " " : "", image ?: "", image ? "\r\n" : ""); - pthread_mutex_unlock(&global_lock); - - len = strlen(buf); - http_send_begin(hc); - http_send_header(hc, 200, "application/xspf+xml", len, 0, NULL, 10, 0, NULL, NULL); - tvh_write(hc->hc_fd, buf, len); - http_send_end(hc); - - free(hostpath); +\r\n"); + http_output_content(hc, MIME_XSPF_XML); return 0; } @@ -1355,46 +1448,54 @@ page_xspf(http_connection_t *hc, const char *remain, void *opaque) * http://en.wikipedia.org/wiki/M3U */ static int -page_m3u(http_connection_t *hc, const char *remain, void *opaque) +page_m3u(http_connection_t *hc, const char *remain, void *opaque, int urlauth) { - size_t maxlen; - char *buf, *hostpath = http_get_hostpath(hc); - const char *title, *profile, *ticket; - size_t len; + htsbuf_queue_t *hq = &hc->hc_reply; + char buf[80], *hostpath; + const char *title, *profile, *ticket, *delim = "?"; if ((title = http_arg_get(&hc->hc_req_args, "title")) == NULL) title = "TVHeadend Stream"; - profile = http_arg_get(&hc->hc_req_args, "profile"); - ticket = http_arg_get(&hc->hc_req_args, "ticket"); - - maxlen = strlen(remain) + strlen(title) + 256; - buf = alloca(maxlen); - - pthread_mutex_lock(&global_lock); - if (ticket == NULL) { - snprintf(buf, maxlen, "/%s", remain); - ticket = access_ticket_create(buf, hc->hc_access); - } - snprintf(buf, maxlen, "\ + hostpath = http_get_hostpath(hc); + htsbuf_qprintf(hq, "\ #EXTM3U\r\n\ #EXTINF:-1,%s\r\n\ -%s/%s%s%s%s%s\r\n", title, hostpath, remain, - profile ? "?profile=" : "", profile ?: "", - profile ? "&ticket=" : "?ticket=", ticket); - pthread_mutex_unlock(&global_lock); - - len = strlen(buf); - http_send_begin(hc); - http_send_header(hc, 200, MIME_M3U, len, 0, NULL, 10, 0, NULL, NULL); - tvh_write(hc->hc_fd, buf, len); - http_send_end(hc); - +%s/%s", title, hostpath, remain); free(hostpath); + profile = http_arg_get(&hc->hc_req_args, "profile"); + if (profile) { + htsbuf_qprintf(hq, "?profile=%s", profile); + delim = "&"; + } + switch (urlauth) { + case URLAUTH_TICKET: + ticket = http_arg_get(&hc->hc_req_args, "ticket"); + if (strempty(ticket)) { + snprintf(buf, sizeof(buf), "/%s", remain); + pthread_mutex_lock(&global_lock); + ticket = access_ticket_create(buf, hc->hc_access); + pthread_mutex_unlock(&global_lock); + } + htsbuf_qprintf(hq, "%sticket=%s", delim, ticket); + break; + case URLAUTH_CODE: + ticket = http_arg_get(&hc->hc_req_args, "auth"); + if (strempty(ticket)) + if (hc->hc_access && !strempty(hc->hc_access->aa_auth)) + ticket = hc->hc_access->aa_auth; + if (!strempty(ticket)) + htsbuf_qprintf(hq, "%sauth=%s", delim, ticket); + break; + default: + break; + } + htsbuf_append_str(hq, "\n"); + http_output_content(hc, MIME_M3U); return 0; } static char * -page_play_path_modify(http_connection_t *hc, const char *path, int *cut) +page_play_path_modify_(http_connection_t *hc, const char *path, int *cut, int skip) { /* * For curl, wget and TVHeadend do not send the playlist, stream directly @@ -1418,12 +1519,30 @@ page_play_path_modify(http_connection_t *hc, const char *path, int *cut) direct = 1; if (direct) - return strdup(path + 5); /* note: skip the /play */ + return strdup(path + skip); /* note: skip the prefix */ return NULL; } +static char * +page_play_path_modify5(http_connection_t *hc, const char *path, int *cut) +{ + return page_play_path_modify_(hc, path, cut, 5); +} + +static char * +page_play_path_modify12(http_connection_t *hc, const char *path, int *cut) +{ + return page_play_path_modify_(hc, path, cut, 12); +} + +static char * +page_play_path_modify16(http_connection_t *hc, const char *path, int *cut) +{ + return page_play_path_modify_(hc, path, cut, 12); +} + static int -page_play(http_connection_t *hc, const char *remain, void *opaque) +page_play_(http_connection_t *hc, const char *remain, void *opaque, int urlauth) { char *playlist; @@ -1440,13 +1559,31 @@ page_play(http_connection_t *hc, const char *remain, void *opaque) playlist = http_arg_get(&hc->hc_req_args, "playlist"); if (playlist) { if (strcmp(playlist, "xspf") == 0) - return page_xspf(hc, remain, opaque); + return page_xspf(hc, remain, opaque, urlauth); if (strcmp(playlist, "m3u") == 0) - return page_m3u(hc, remain, opaque); + return page_m3u(hc, remain, opaque, urlauth); } if (webui_xspf) - return page_xspf(hc, remain, opaque); - return page_m3u(hc, remain, opaque); + return page_xspf(hc, remain, opaque, urlauth); + return page_m3u(hc, remain, opaque, urlauth); +} + +static int +page_play(http_connection_t *hc, const char *remain, void *opaque) +{ + return page_play_(hc, remain, opaque, URLAUTH_NONE); +} + +static int +page_play_ticket(http_connection_t *hc, const char *remain, void *opaque) +{ + return page_play_(hc, remain, opaque, URLAUTH_TICKET); +} + +static int +page_play_auth(http_connection_t *hc, const char *remain, void *opaque) +{ + return page_play_(hc, remain, opaque, URLAUTH_CODE); } /** @@ -1887,10 +2024,14 @@ webui_init(int xspf) http_path_add("/satip_server", NULL, satip_server_http_page, ACCESS_ANONYMOUS); #endif - http_path_add_modify("/play", NULL, page_play, ACCESS_ANONYMOUS, page_play_path_modify); + http_path_add_modify("/play", NULL, page_play, ACCESS_ANONYMOUS, page_play_path_modify5); + http_path_add_modify("/play/ticket", NULL, page_play_ticket, ACCESS_ANONYMOUS, page_play_path_modify12); + http_path_add_modify("/play/auth", NULL, page_play_auth, ACCESS_ANONYMOUS, page_play_path_modify16); http_path_add("/dvrfile", NULL, page_dvrfile, ACCESS_ANONYMOUS); http_path_add("/favicon.ico", NULL, favicon, ACCESS_WEB_INTERFACE); http_path_add("/playlist", NULL, page_http_playlist, ACCESS_ANONYMOUS); + http_path_add("/playlist/ticket", NULL, page_http_playlist_ticket, ACCESS_ANONYMOUS); + http_path_add("/playlist/auth", NULL, page_http_playlist_auth, ACCESS_ANONYMOUS); http_path_add("/xmltv", NULL, page_xmltv, ACCESS_ANONYMOUS); http_path_add("/markdown", NULL, page_markdown, ACCESS_ANONYMOUS); diff --git a/src/webui/webui.h b/src/webui/webui.h index fa5578137..8706d766c 100644 --- a/src/webui/webui.h +++ b/src/webui/webui.h @@ -23,8 +23,9 @@ #include "idnode.h" #include "http.h" -#define MIME_M3U "audio/x-mpegurl" -#define MIME_E2 "application/x-e2-bouquet" +#define MIME_M3U "audio/x-mpegurl" +#define MIME_E2 "application/x-e2-bouquet" +#define MIME_XSPF_XML "application/xspf+xml" void webui_init(int xspf); void webui_done(void);