diff --git a/docs/html/config_access.html b/docs/html/config_access.html
index 8377d45e..1fdaa4ba 100644
--- a/docs/html/config_access.html
+++ b/docs/html/config_access.html
@@ -59,11 +59,16 @@ The columns have the following functions:
Enables access to streaming function. The 'streaming' access is enough to
make Showtime (over HTSP) work.
+
Advanced Streaming
+
+ Enables access to advanced streaming function for HTTP - like direct
+ service or whole MPEG-TS stream (mux)..
+
Video Recorder
Enables access to all video recording functions. This also include administration of the auto recordings.
- User Configs (VR)
+ Username Configs (VR)
Use a DVR configuration profile matched to the authorized user by name
(the configuration profile must have same name as the user). If the DVR
@@ -78,13 +83,28 @@ The columns have the following functions:
Enables access to the Configuration tab.
- Channel Tag Only
+ Username Channel Tag Match
If enabled, the user will only be able to access channels with a tag the
same name as the username.
This provides a very rudimentary way of limiting access to certain channels.
+ Min Channel Num
+
+ If nonzero, the user will only be able to access channels with
+ a channel number equal or greater to this value.
+
+ Max Channel Num
+
+ If nonzero, the user will only be able to access channels with
+ a channel number equal or lower to this value.
+
+ Channel Tag
+
+ If set, the user will only be able to access channels with
+ a channel tag equal to this value.
+
Comment
Allows the administrator to set a comment only visible in this editor.
diff --git a/src/access.c b/src/access.c
index ebb8bfb4..fa442142 100644
--- a/src/access.c
+++ b/src/access.c
@@ -37,6 +37,7 @@
#include "access.h"
#include "dtable.h"
#include "settings.h"
+#include "htsmsg.h"
struct access_entry_queue access_entries;
struct access_ticket_queue access_tickets;
@@ -152,6 +153,16 @@ access_ticket_verify(const char *id, const char *resource)
return 0;
}
+/**
+ *
+ */
+void
+access_destroy(access_t *a)
+{
+ htsmsg_destroy(a->aa_chtags);
+ free(a);
+}
+
/**
*
*/
@@ -264,24 +275,96 @@ access_verify(const char *username, const char *password,
return (mask & bits) == mask ? 0 : -1;
}
+/*
+ *
+ */
+static void
+access_update(access_t *a, access_entry_t *ae)
+{
+ if(ae->ae_chmin || ae->ae_chmax) {
+ if(a->aa_chmin || a->aa_chmax) {
+ if (a->aa_chmin < ae->ae_chmin)
+ a->aa_chmin = ae->ae_chmin;
+ if (a->aa_chmax > ae->ae_chmax)
+ a->aa_chmax = ae->ae_chmax;
+ } else {
+ a->aa_chmin = ae->ae_chmin;
+ a->aa_chmax = ae->ae_chmax;
+ }
+ }
+ if(ae->ae_chtag) {
+ if (a->aa_chtags == NULL)
+ a->aa_chtags = htsmsg_create_list();
+ htsmsg_add_str(a->aa_chtags, NULL, ae->ae_chtag);
+ }
+
+ a->aa_rights |= ae->ae_rights;
+}
/**
*
*/
-uint32_t
-access_get_hashed(const char *username, const uint8_t digest[20],
- const uint8_t *challenge, struct sockaddr *src,
- int *entrymatch)
+access_t *
+access_get(const char *username, const char *password, struct sockaddr *src)
{
+ access_t *a = calloc(1, sizeof(*a));
+ access_entry_t *ae;
+
+ if (access_noacl) {
+ a->aa_rights = ACCESS_FULL;
+ return a;
+ }
+
+ if(username != NULL && superuser_username != NULL &&
+ password != NULL && superuser_password != NULL &&
+ !strcmp(username, superuser_username) &&
+ !strcmp(password, superuser_password)) {
+ a->aa_rights = ACCESS_FULL;
+ return a;
+ }
+
+ TAILQ_FOREACH(ae, &access_entries, ae_link) {
+
+ if(!ae->ae_enabled)
+ continue;
+
+ if(ae->ae_username[0] != '*') {
+ /* acl entry requires username to match */
+ if(username == NULL || password == NULL)
+ continue; /* Didn't get one */
+
+ if(strcmp(ae->ae_username, username) ||
+ strcmp(ae->ae_password, password))
+ continue; /* username/password mismatch */
+ }
+
+ if(!netmask_verify(ae, src))
+ continue; /* IP based access mismatches */
+
+ a->aa_match = 1;
+ access_update(a, ae);
+ }
+
+ return a;
+}
+
+/**
+ *
+ */
+access_t *
+access_get_hashed(const char *username, const uint8_t digest[20],
+ const uint8_t *challenge, struct sockaddr *src)
+{
+ access_t *a = calloc(1, sizeof(*a));
access_entry_t *ae;
SHA_CTX shactx;
uint8_t d[20];
- uint32_t r = 0;
- int match = 0;
- if(access_noacl)
- return 0xffffffff;
+ if(access_noacl) {
+ a->aa_rights = ACCESS_FULL;
+ return a;
+ }
if(superuser_username != NULL && superuser_password != NULL) {
@@ -291,8 +374,10 @@ access_get_hashed(const char *username, const uint8_t digest[20],
SHA1_Update(&shactx, challenge, 32);
SHA1_Final(d, &shactx);
- if(!strcmp(superuser_username, username) && !memcmp(d, digest, 20))
- return 0xffffffff;
+ if(!strcmp(superuser_username, username) && !memcmp(d, digest, 20)) {
+ a->aa_rights = ACCESS_FULL;
+ return a;
+ }
}
@@ -312,12 +397,12 @@ access_get_hashed(const char *username, const uint8_t digest[20],
if(strcmp(ae->ae_username, username) || memcmp(d, digest, 20))
continue;
- match = 1;
- r |= ae->ae_rights;
+
+ a->aa_match = 1;
+ access_update(a, ae);
}
- if(entrymatch != NULL)
- *entrymatch = match;
- return r;
+
+ return a;
}
@@ -325,11 +410,11 @@ access_get_hashed(const char *username, const uint8_t digest[20],
/**
*
*/
-uint32_t
+access_t *
access_get_by_addr(struct sockaddr *src)
{
+ access_t *a = calloc(1, sizeof(*a));
access_entry_t *ae;
- uint32_t r = 0;
TAILQ_FOREACH(ae, &access_entries, ae_link) {
@@ -342,29 +427,12 @@ access_get_by_addr(struct sockaddr *src)
if(!netmask_verify(ae, src))
continue; /* IP based access mismatches */
- r |= ae->ae_rights;
+ access_update(a, ae);
}
- return r;
+
+ return a;
}
-/**
- *
- */
-uint32_t
-access_tag_only(const char *username)
-{
- access_entry_t *ae;
- uint32_t r = 0;
-
- TAILQ_FOREACH(ae, &access_entries, ae_link) {
-
- if(!strcmp(ae->ae_username, username))
- return ae->ae_tagonly;
- }
- return r;
-}
-
-
/**
*
*/
@@ -536,6 +604,7 @@ access_entry_destroy(access_entry_t *ae)
free(ae->ae_username);
free(ae->ae_password);
free(ae->ae_comment);
+ free(ae->ae_chtag);
TAILQ_REMOVE(&access_entries, ae, ae_link);
free(ae);
}
@@ -579,11 +648,15 @@ access_record_build(access_entry_t *ae)
htsmsg_add_str(e, "prefix", buf + 1);
htsmsg_add_u32(e, "streaming", ae->ae_rights & ACCESS_STREAMING ? 1 : 0);
+ htsmsg_add_u32(e, "adv_streaming", ae->ae_rights & ACCESS_ADVANCED_STREAMING ? 1 : 0);
htsmsg_add_u32(e, "dvr" , ae->ae_rights & ACCESS_RECORDER ? 1 : 0);
htsmsg_add_u32(e, "dvrallcfg", ae->ae_rights & ACCESS_RECORDER_ALL ? 1 : 0);
htsmsg_add_u32(e, "webui" , ae->ae_rights & ACCESS_WEB_INTERFACE ? 1 : 0);
htsmsg_add_u32(e, "admin" , ae->ae_rights & ACCESS_ADMIN ? 1 : 0);
- htsmsg_add_u32(e, "tag_only" , ae->ae_tagonly);
+ htsmsg_add_u32(e, "tag_only" , ae->ae_rights & ACCESS_TAG_ONLY ? 1 : 0);
+ htsmsg_add_u32(e, "channel_min" , ae->ae_chmin);
+ htsmsg_add_u32(e, "channel_max" , ae->ae_chmax);
+ htsmsg_add_str(e, "channel_tag", ae->ae_chtag ?: "");
htsmsg_add_str(e, "id", ae->ae_id);
@@ -668,6 +741,12 @@ access_record_update(void *opaque, const char *id, htsmsg_t *values,
if(!htsmsg_get_u32(values, "streaming", &u32))
access_update_flag(ae, ACCESS_STREAMING, u32);
+ if(!htsmsg_get_u32(values, "adv_streaming", &u32))
+ access_update_flag(ae, ACCESS_ADVANCED_STREAMING, u32);
+ else
+ access_update_flag(ae, ACCESS_ADVANCED_STREAMING,
+ (ae->ae_rights & ACCESS_STREAMING));
+
if(!htsmsg_get_u32(values, "dvr", &u32))
access_update_flag(ae, ACCESS_RECORDER, u32);
@@ -686,7 +765,21 @@ access_record_update(void *opaque, const char *id, htsmsg_t *values,
access_update_flag(ae, ACCESS_WEB_INTERFACE, u32);
if(!htsmsg_get_u32(values, "tag_only", &u32))
- ae->ae_tagonly = u32;
+ access_update_flag(ae, ACCESS_TAG_ONLY, u32);
+
+ if(!htsmsg_get_u32(values, "channel_min", &u32))
+ ae->ae_chmin = u32;
+
+ if(!htsmsg_get_u32(values, "channel_max", &u32))
+ ae->ae_chmax = u32;
+
+ if((s = htsmsg_get_str(values, "channel_tag")) != NULL) {
+ free(ae->ae_chtag);
+ if (s[0] == 0)
+ ae->ae_chtag = NULL;
+ else
+ ae->ae_chtag = strdup(s);
+ }
return access_record_build(ae);
}
@@ -738,7 +831,6 @@ access_init(int createdefault, int noacl)
struct timeval tv;
} randseed;
- access_noacl = noacl;
if (noacl)
tvhlog(LOG_WARNING, "access", "Access control checking disabled");
@@ -759,8 +851,7 @@ access_init(int createdefault, int noacl)
ae->ae_comment = strdup("Default access entry");
ae->ae_enabled = 1;
- ae->ae_tagonly = 0;
- ae->ae_rights = 0xffffffff;
+ ae->ae_rights = ACCESS_FULL;
TAILQ_INIT(&ae->ae_ipmasks);
diff --git a/src/access.h b/src/access.h
index caea99a9..08c2e9e3 100644
--- a/src/access.h
+++ b/src/access.h
@@ -19,6 +19,7 @@
#ifndef ACCESS_H_
#define ACCESS_H_
+#include "htsmsg.h"
typedef struct access_ipmask {
TAILQ_ENTRY(access_ipmask) ai_link;
@@ -47,7 +48,9 @@ typedef struct access_entry {
char *ae_comment;
int ae_enabled;
int ae_tagonly;
-
+ uint32_t ae_chmin;
+ uint32_t ae_chmax;
+ char *ae_chtag;
uint32_t ae_rights;
@@ -67,13 +70,25 @@ typedef struct access_ticket {
char *at_resource;
} access_ticket_t;
-#define ACCESS_ANONYMOUS 0x0
-#define ACCESS_STREAMING 0x1
-#define ACCESS_WEB_INTERFACE 0x2
-#define ACCESS_RECORDER 0x4
-#define ACCESS_RECORDER_ALL 0x8
-#define ACCESS_ADMIN 0x10
-#define ACCESS_FULL 0x3f
+typedef struct access {
+ uint32_t aa_rights;
+ uint32_t aa_chmin;
+ uint32_t aa_chmax;
+ htsmsg_t *aa_chtags;
+ int aa_match;
+} access_t;
+
+#define ACCESS_ANONYMOUS 0
+#define ACCESS_STREAMING (1<<0)
+#define ACCESS_ADVANCED_STREAMING (1<<1)
+#define ACCESS_WEB_INTERFACE (1<<2)
+#define ACCESS_RECORDER (1<<3)
+#define ACCESS_RECORDER_ALL (1<<4)
+#define ACCESS_TAG_ONLY (1<<5)
+#define ACCESS_ADMIN (1<<6)
+
+#define ACCESS_FULL \
+ (ACCESS_STREAMING | ACCESS_WEB_INTERFACE | ACCESS_RECORDER | ACCESS_ADMIN)
/**
* Create a new ticket for the requested resource and generate a id for it
@@ -86,6 +101,12 @@ const char* access_ticket_create(const char *resource);
int access_ticket_verify(const char *id, const char *resource);
int access_ticket_delete(const char *ticket_id);
+
+/**
+ * Free the access structure
+ */
+void access_destroy(access_t *a);
+
/**
* Verifies that the given user in combination with the source ip
* complies with the requested mask
@@ -96,17 +117,23 @@ int access_verify(const char *username, const char *password,
struct sockaddr *src, uint32_t mask);
/**
- *
+ * Get the access structure
*/
-uint32_t access_get_hashed(const char *username, const uint8_t digest[20],
- const uint8_t *challenge, struct sockaddr *src,
- int *entrymatch);
+access_t *access_get(const char *username, const char *password,
+ struct sockaddr *src);
/**
*
*/
-uint32_t access_get_by_addr(struct sockaddr *src);
+access_t *
+access_get_hashed(const char *username, const uint8_t digest[20],
+ const uint8_t *challenge, struct sockaddr *src);
+/**
+ *
+ */
+access_t *
+access_get_by_addr(struct sockaddr *src);
/**
*
@@ -114,9 +141,4 @@ uint32_t access_get_by_addr(struct sockaddr *src);
void access_init(int createdefault, int noacl);
void access_done(void);
-/**
- *
- */
-uint32_t access_tag_only(const char *username);
-
#endif /* ACCESS_H_ */
diff --git a/src/channels.c b/src/channels.c
index 22ff546d..64995576 100644
--- a/src/channels.c
+++ b/src/channels.c
@@ -413,6 +413,47 @@ channel_find_by_number ( int no )
return ch;
}
+/**
+ * Check if user can access the channel
+ */
+int
+channel_access(channel_t *ch, access_t *a, const char *username)
+{
+ /* Channel number check */
+ if (ch && (a->aa_chmin || a->aa_chmax)) {
+ int chnum = channel_get_number(ch);
+ if (chnum < a->aa_chmin || chnum > a->aa_chmax)
+ return 0;
+ }
+
+ /* Channel tag check */
+ if (ch && a->aa_chtags) {
+ channel_tag_mapping_t *ctm;
+ htsmsg_field_t *f;
+ HTSMSG_FOREACH(f, a->aa_chtags) {
+ LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
+ if (!strcmp(htsmsg_field_get_str(f) ?: "", ctm->ctm_tag->ct_name))
+ goto chtags_ok;
+ }
+ }
+ return 0;
+ }
+chtags_ok:
+
+ /* Channel tag <-> user name match */
+ if (ch && (a->aa_rights & ACCESS_TAG_ONLY) != 0) {
+ channel_tag_mapping_t *ctm;
+ LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
+ if (!strcmp(username ?: "", ctm->ctm_tag->ct_name))
+ goto tagonly_ok;
+ }
+ return 0;
+ }
+tagonly_ok:
+
+ return 1;
+}
+
/* **************************************************************************
* Property updating
* *************************************************************************/
diff --git a/src/channels.h b/src/channels.h
index 370ab7c5..8cb66d79 100644
--- a/src/channels.h
+++ b/src/channels.h
@@ -22,6 +22,8 @@
#include "epg.h"
#include "idnode.h"
+struct access;
+
RB_HEAD(channel_tree, channel);
LIST_HEAD(channel_tag_mapping_list, channel_tag_mapping);
@@ -147,6 +149,8 @@ channel_tag_t *channel_tag_find_by_name(const char *name, int create);
channel_tag_t *channel_tag_find_by_identifier(uint32_t id);
+int channel_access(channel_t *ch, struct access *a, const char *username);
+
int channel_tag_map(channel_t *ch, channel_tag_t *ct);
void channel_save(channel_t *ch);
diff --git a/src/htsp_server.c b/src/htsp_server.c
index 6e22bc43..c5beecaf 100644
--- a/src/htsp_server.c
+++ b/src/htsp_server.c
@@ -159,7 +159,7 @@ typedef struct htsp_connection {
struct htsp_file_list htsp_files;
int htsp_file_id;
- uint32_t htsp_granted_access;
+ access_t *htsp_granted_access;
uint8_t htsp_challenge[32];
@@ -431,19 +431,10 @@ htsp_generate_challenge(htsp_connection_t *htsp)
/**
* Cehck if user can access the channel
*/
-static int
-htsp_user_access_channel(htsp_connection_t *htsp, channel_t *ch) {
- if (!access_tag_only(htsp->htsp_username ?: ""))
- return 1;
- else {
- if (!ch) return 0;
- channel_tag_mapping_t *ctm;
- LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
- if (!strcmp(htsp->htsp_username ?: "", ctm->ctm_tag->ct_name))
- return 1;
- }
- }
- return 0;
+static inline int
+htsp_user_access_channel(htsp_connection_t *htsp, channel_t *ch)
+{
+ return channel_access(ch, htsp->htsp_granted_access, htsp->htsp_username);
}
#define HTSP_CHECK_CHANNEL_ACCESS(htsp, ch)\
@@ -838,7 +829,7 @@ htsp_method_authenticate(htsp_connection_t *htsp, htsmsg_t *in)
{
htsmsg_t *r = htsmsg_create_map();
- if(!(htsp->htsp_granted_access & HTSP_PRIV_MASK))
+ if(!(htsp->htsp_granted_access->aa_rights & HTSP_PRIV_MASK))
htsmsg_add_u32(r, "noaccess", 1);
return r;
@@ -1972,9 +1963,8 @@ htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
const char *username;
const void *digest;
size_t digestlen;
- uint32_t access;
+ access_t *rights;
int privgain;
- int match;
if((username = htsmsg_get_str(m, "username")) == NULL)
return;
@@ -1990,15 +1980,18 @@ htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
if(htsmsg_get_bin(m, "digest", &digest, &digestlen))
return;
- access = access_get_hashed(username, digest, htsp->htsp_challenge,
- (struct sockaddr *)htsp->htsp_peer, &match);
+ rights = access_get_hashed(username, digest, htsp->htsp_challenge,
+ (struct sockaddr *)htsp->htsp_peer);
- privgain = (access | htsp->htsp_granted_access) != htsp->htsp_granted_access;
+ privgain = (rights->aa_rights |
+ htsp->htsp_granted_access->aa_rights) !=
+ htsp->htsp_granted_access->aa_rights;
if(privgain)
tvhlog(LOG_INFO, "htsp", "%s: Privileges raised", htsp->htsp_logname);
- htsp->htsp_granted_access |= access;
+ access_destroy(htsp->htsp_granted_access);
+ htsp->htsp_granted_access = rights;
}
/**
@@ -2081,7 +2074,7 @@ readmsg:
for(i = 0; i < NUM_METHODS; i++) {
if(!strcmp(method, htsp_methods[i].name)) {
- if((htsp->htsp_granted_access & htsp_methods[i].privmask) !=
+ if((htsp->htsp_granted_access->aa_rights & htsp_methods[i].privmask) !=
htsp_methods[i].privmask) {
pthread_mutex_unlock(&global_lock);
@@ -2281,6 +2274,7 @@ htsp_serve(int fd, void **opaque, struct sockaddr_storage *source,
free(htsp.htsp_peername);
free(htsp.htsp_username);
free(htsp.htsp_clientname);
+ access_destroy(htsp.htsp_granted_access);
*opaque = NULL;
}
diff --git a/src/http.c b/src/http.c
index 435cf49b..49dccb68 100644
--- a/src/http.c
+++ b/src/http.c
@@ -35,6 +35,7 @@
#include "http.h"
#include "access.h"
#include "notify.h"
+#include "channels.h"
static void *http_server;
@@ -395,26 +396,58 @@ http_redirect(http_connection_t *hc, const char *location,
}
/**
- * Return non-zero if no access
+ *
*/
-int
-http_access_verify(http_connection_t *hc, int mask)
+static int http_access_verify_ticket(http_connection_t *hc)
{
const char *ticket_id = http_arg_get(&hc->hc_req_args, "ticket");
- if(!access_ticket_verify(ticket_id, hc->hc_url))
- {
+ if(!access_ticket_verify(ticket_id, hc->hc_url)) {
char addrstr[50];
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrstr, 50);
tvhlog(LOG_INFO, "HTTP", "%s: using ticket %s for %s",
addrstr, ticket_id, hc->hc_url);
return 0;
}
+ return -1;
+}
+
+/**
+ * Return non-zero if no access
+ */
+int
+http_access_verify(http_connection_t *hc, int mask)
+{
+ if (!http_access_verify_ticket(hc))
+ return 0;
return access_verify(hc->hc_username, hc->hc_password,
(struct sockaddr *)hc->hc_peer, mask);
}
+/**
+ * Return non-zero if no access
+ */
+int
+http_access_verify_channel(http_connection_t *hc, int mask,
+ struct channel *ch)
+{
+ access_t *a;
+ int res = -1;
+
+ assert(ch);
+
+ if (!http_access_verify_ticket(hc))
+ return 0;
+
+ a = access_get(hc->hc_username, hc->hc_password,
+ (struct sockaddr *)hc->hc_peer);
+ if (channel_access(ch, a, hc->hc_username))
+ res = 0;
+ access_destroy(a);
+ return res;
+}
+
/**
* Execute url callback
*
diff --git a/src/http.h b/src/http.h
index 73791649..34abcfe7 100644
--- a/src/http.h
+++ b/src/http.h
@@ -23,6 +23,8 @@
#include "url.h"
#include "tvhpoll.h"
+struct channel;
+
typedef TAILQ_HEAD(http_arg_list, http_arg) http_arg_list_t;
typedef RB_HEAD(,http_arg) http_arg_tree_t;
@@ -203,6 +205,7 @@ void http_server_init(const char *bindaddr);
void http_server_done(void);
int http_access_verify(http_connection_t *hc, int mask);
+int http_access_verify_channel(http_connection_t *hc, int mask, struct channel *ch);
void http_deescape(char *s);
diff --git a/src/webui/static/app/acleditor.js b/src/webui/static/app/acleditor.js
index 0b08d330..deb92ade 100644
--- a/src/webui/static/app/acleditor.js
+++ b/src/webui/static/app/acleditor.js
@@ -34,6 +34,11 @@ tvheadend.acleditor = function() {
header: "Streaming",
dataIndex: 'streaming',
width: 100
+ }, {
+ xtype: 'checkcolumn',
+ header: "Advanced Streaming",
+ dataIndex: 'adv_streaming',
+ width: 100
}, {
xtype: 'checkcolumn',
header: "Video Recorder",
@@ -41,9 +46,9 @@ tvheadend.acleditor = function() {
width: 100
}, {
xtype: 'checkcolumn',
- header: "User Configs (VR)",
+ header: "Username Configs (VR)",
dataIndex: 'dvrallcfg',
- width: 100
+ width: 150
}, {
xtype: 'checkcolumn',
header: "Web Interface",
@@ -56,9 +61,51 @@ tvheadend.acleditor = function() {
width: 100
}, {
xtype: 'checkcolumn',
- header: "Channel Tag Only",
+ header: "Username Channel Tag Match",
dataIndex: 'tag_only',
- width: 200
+ width: 150
+ }, {
+ xtype: 'numbercolumn',
+ header: "Min Channel Num",
+ dataIndex: 'channel_min',
+ width: 120,
+ format: '0',
+ editor: {
+ xtype: 'numberfield',
+ allowBlank: 'false',
+ minValue: 0,
+ maxValue: 2147483647,
+ allowNegative: false,
+ allowDecimals: false
+ }
+ }, {
+ xtype: 'numbercolumn',
+ header: "Max Channel Num",
+ dataIndex: 'channel_max',
+ width: 120,
+ format: '0',
+ editor: {
+ xtype: 'numberfield',
+ allowBlank: 'false',
+ minValue: 0,
+ maxValue: 2147483647,
+ allowNegative: false,
+ allowDecimals: false
+ }
+ }, {
+ header: "Channel Tag",
+ dataIndex: 'channel_tag',
+ width: 200,
+ editor: new Ext.form.ComboBox({
+ displayField: 'name',
+ store: tvheadend.channelTags,
+ mode: 'local',
+ editable: true,
+ forceSelection: true,
+ typeAhead: true,
+ triggerAction: 'all',
+ emptyText: 'Match this channel tag...'
+ })
}, {
header: "Comment",
dataIndex: 'comment',
@@ -68,10 +115,30 @@ tvheadend.acleditor = function() {
var UserRecord = Ext.data.Record.create(
['enabled', 'streaming', 'dvr', 'dvrallcfg', 'admin', 'webui', 'username', 'tag_only',
- 'prefix', 'password', 'comment'
+ 'prefix', 'password', 'comment', 'channel_min', 'channel_max', 'channel_tag',
+ 'adv_streaming'
]);
+ tvheadend.accesscontrolStore = new Ext.data.JsonStore({
+ root: 'entries',
+ fields: UserRecord,
+ url: "tablemgr",
+ autoLoad: true,
+ id: 'id',
+ baseParams: {
+ table: "accesscontrol",
+ op: "get"
+ }
+ });
+
+ tvheadend.accesscontrolStore.on('update', function (store, record, operation) {
+ if (operation == 'edit') {
+ if (record.isModified('channel_tag') && record.data.channel_tag == '(Clear filter)')
+ record.set('channel_tag',"");
+ }
+ });
+
return new tvheadend.tableEditor('Access control', 'accesscontrol', cm,
- UserRecord, [], null, 'config_access.html',
+ UserRecord, [], tvheadend.accesscontrolStore, 'config_access.html',
'group');
};
diff --git a/src/webui/webui.c b/src/webui/webui.c
index 91cf0a14..7e57ba47 100644
--- a/src/webui/webui.c
+++ b/src/webui/webui.c
@@ -368,6 +368,9 @@ http_channel_playlist(http_connection_t *hc, channel_t *channel)
const char *host;
muxer_container_type_t mc;
+ if (http_access_verify_channel(hc, ACCESS_STREAMING, channel))
+ return HTTP_STATUS_UNAUTHORIZED;
+
mc = muxer_container_txt2type(http_arg_get(&hc->hc_req_args, "mux"));
if(mc == MC_UNKNOWN)
mc = dvr_config_find_by_name_default("")->dvr_mc;
@@ -431,6 +434,8 @@ http_tag_playlist(http_connection_t *hc, channel_tag_t *tag)
htsbuf_qprintf(hq, "#EXTM3U\n");
LIST_FOREACH(ctm, &tag->ct_ctms, ctm_tag_link) {
+ if (http_access_verify_channel(hc, ACCESS_STREAMING, ctm->ctm_channel))
+ continue;
snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(ctm->ctm_channel));
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ctm->ctm_channel));
htsbuf_qprintf(hq, "http://%s%s?ticket=%s", host, buf,
@@ -527,6 +532,10 @@ http_channel_list_playlist(http_connection_t *hc)
htsbuf_qprintf(hq, "#EXTM3U\n");
for (idx = 0; idx < count; idx++) {
ch = chlist[idx];
+
+ if (http_access_verify_channel(hc, ACCESS_STREAMING, ch))
+ continue;
+
snprintf(buf, sizeof(buf), "/stream/channelid/%d", channel_get_id(ch));
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", channel_get_name(ch));
@@ -568,6 +577,10 @@ http_dvr_list_playlist(http_connection_t *hc)
if(!fsize)
continue;
+ if (de->de_channel &&
+ http_access_verify_channel(hc, ACCESS_RECORDER, de->de_channel))
+ continue;
+
durration = de->de_stop - de->de_start;
durration += (de->de_stop_extra + de->de_start_extra)*60;
bandwidth = ((8*fsize) / (durration*1024.0));
@@ -721,6 +734,9 @@ http_stream_service(http_connection_t *hc, service_t *service, int weight)
const char *name;
char addrbuf[50];
+ if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
+ return HTTP_STATUS_UNAUTHORIZED;
+
cfg = dvr_config_find_by_name_default("");
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */
@@ -788,6 +804,9 @@ http_stream_mux(http_connection_t *hc, mpegts_mux_t *mm, int weight)
char addrbuf[50];
muxer_config_t muxcfg = { 0 };
+ if(http_access_verify(hc, ACCESS_ADVANCED_STREAMING))
+ return HTTP_STATUS_UNAUTHORIZED;
+
streaming_queue_init(&sq, SMT_PACKET);
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrbuf, 50);
@@ -833,6 +852,9 @@ http_stream_channel(http_connection_t *hc, channel_t *ch, int weight)
const char *name;
char addrbuf[50];
+ if (http_access_verify_channel(hc, ACCESS_STREAMING, ch))
+ return HTTP_STATUS_UNAUTHORIZED;
+
cfg = dvr_config_find_by_name_default("");
/* Build muxer config - this takes the defaults from the default dvr config, which is a hack */