]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
add pernament tickets for the authentization, fixes #5274
authorJaroslav Kysela <perex@perex.cz>
Fri, 19 Oct 2018 20:40:58 +0000 (22:40 +0200)
committerJaroslav Kysela <perex@perex.cz>
Sat, 20 Oct 2018 23:26:55 +0000 (01:26 +0200)
13 files changed:
src/access.c
src/access.h
src/http.c
src/idnode.c
src/idnode.h
src/utils.c
src/webui/static/app/acleditor.js
src/webui/static/app/chconf.js
src/webui/static/app/dvr.js
src/webui/static/app/mpegts.js
src/webui/static/app/tvheadend.js
src/webui/webui.c
src/webui/webui.h

index 5c524a82ec4a9d9d0e2425eb2efea58b1abb9121..a0c0cd6bf301b8e2a8df8df6fef053ad4a1645df 100644 (file)
@@ -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",
index 23fc768d85437d3d00c8ae64e20f423ee3089233..75830657f63cdc3ba04ed8095cc9305b6f815ba6 100644 (file)
@@ -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);
+
 /**
  *
  */
index 1a9df5069e64e5a762bf630f64609ef2eb589971..57fcdcf02ba81068dba4622978394d90657f06bf 100644 (file)
@@ -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;
 }
 
index ace9f58c8ad5bbea0732d62cf79942a338919412..1d7c2b8c9082fb404b13898b03a3126cd44acbc2 100644 (file)
@@ -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;
 }
index 92e7496200a0d929ac71e8ddf0122594b5127ae4..d4262f4753bfdf0d03e2f5aa13e5e111a7c3fcbb 100644 (file)
@@ -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
index ae588bba791985d1b18434e246b91b553e953ae9..7af41da4ef1068141b0eab45c33cc84fe8bec437 100644 (file)
@@ -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;
 }
 
 /**
index 25e78e36cec5fa6d76bdf3c64572c5f577e622ac..bed8d54f2d1e829a4a530881c04c647920af73eb 100644 (file)
@@ -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: {
index 6f8840eeb9503aa43d0c2327bbb925d30451ec4f..7f80c65657c54ad38a6376a5508c9f18f3c8c12a 100644 (file)
@@ -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);
                 }
             }
         ],
index c36957de7cb3ec9cbfeac0cdecfc3401d00b0d95..d8317de52dff5506c6a4b16be9f0eb419593ba18 100644 (file)
@@ -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],
index 07ef420615ca2c1274288fbc08d1b8e7410e7665..6834b002e9133dee68ae07a70784d82a205dd950 100644 (file)
@@ -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);
                 }
             },
             {
index 4e2ff8dbb8ac7ffa13b405add9fe0b294949f94c..7bc17a1f8d2729bf92f827c9ae98b73814097ecc 100644 (file)
@@ -798,7 +798,7 @@ tvheadend.niceDateYearMonth = function(dt, refdate) {
  */
 tvheadend.playLink = function(link, title) {
     if (title) title = '?title=' + encodeURIComponent(title);
-    return '<a href="' + link + title + '">' +
+    return '<a href="play/ticket/' + link + title + '">' +
            '<img src="static/icons/control_play.png" class="playlink" title="' +
            _('Play this stream') + '" alt="' + _('Play') + '"/></a>';
 }
index f1b2fa0c3d2182e2e7ad3a6f8e664feae8f143f6..731bf9f57aa2427eaf08f0ab4c64d1e39e49ac1c 100644 (file)
@@ -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, "\
 <?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\
 <playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\r\n\
   <trackList>\r\n\
      <track>\r\n\
        <title>%s</title>\r\n\
-       <location>%s/%s%s%s%s%s</location>\r\n%s%s%s\
+       <location>%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, "</location>\n");
+  image = http_arg_get(&hc->hc_req_args, "image");
+  if (image)
+    htsbuf_qprintf(hq, "       <image>%s</image>\n", image);
+  htsbuf_append_str(hq, "\
      </track>\r\n\
   </trackList>\r\n\
-</playlist>\r\n", title, hostpath, remain,
-    profile ? "?profile=" : "", profile ?: "",
-    profile ? "&ticket=" : "?ticket=", ticket,
-    image ? "       <image>" : "", 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);
+</playlist>\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);
 
index fa557813727e64ad228a7664e86946b4bcc09200..8706d766c365915453e6c07e5025e543f94add21 100644 (file)
@@ -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);