From 3be76456d81e1cddc3feb3ae4cea2635414ebd7e Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 11 Aug 2014 17:37:13 +0200 Subject: [PATCH] ACL: Many improvements - added min-max range for channel numbers for the ACL entry - added channel tag match for the ACL entry - introduced channel_access() routine - moved the username channel tag to ae_rights - add advanced streaming checkbox to control service and mux streaming separately --- docs/html/config_access.html | 24 +++- src/access.c | 175 +++++++++++++++++++++++------- src/access.h | 58 +++++++--- src/channels.c | 41 +++++++ src/channels.h | 4 + src/htsp_server.c | 38 +++---- src/http.c | 43 +++++++- src/http.h | 3 + src/webui/static/app/acleditor.js | 79 +++++++++++++- src/webui/webui.c | 22 ++++ 10 files changed, 392 insertions(+), 95 deletions(-) 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 */