From: Jaroslav Kysela Date: Tue, 22 Sep 2015 15:38:25 +0000 (+0200) Subject: implement IP blocking feature, fixes #3088 X-Git-Tag: v4.2.1~2074 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b7a0a8a032ede8703c05c33a5091d06cec1f90aa;p=thirdparty%2Ftvheadend.git implement IP blocking feature, fixes #3088 --- diff --git a/src/access.c b/src/access.c index 0252a4d8d..3708ca6fe 100644 --- a/src/access.c +++ b/src/access.c @@ -41,6 +41,7 @@ struct access_entry_queue access_entries; struct access_ticket_queue access_tickets; struct passwd_entry_queue passwd_entries; +struct ipblock_entry_queue ipblock_entries; const char *superuser_username; const char *superuser_password; @@ -262,7 +263,7 @@ access_destroy(access_t *a) * */ static int -netmask_verify(access_entry_t *ae, struct sockaddr *src) +netmask_verify(struct access_ipmask_queue *ais, struct sockaddr *src) { access_ipmask_t *ai; int isv4v6 = 0; @@ -277,7 +278,7 @@ netmask_verify(access_entry_t *ae, struct sockaddr *src) } } - TAILQ_FOREACH(ai, &ae->ae_ipmasks, ai_link) { + TAILQ_FOREACH(ai, ais, ai_link) { if (ai->ai_family == AF_INET && src->sa_family == AF_INET) { @@ -326,6 +327,20 @@ netmask_verify(access_entry_t *ae, struct sockaddr *src) return 0; } +/** + * + */ +static inline int +access_ip_blocked(struct sockaddr *src) +{ + ipblock_entry_t *ib; + + TAILQ_FOREACH(ib, &ipblock_entries, ib_link) + if (netmask_verify(&ib->ib_ipmasks, src)) + return 1; + return 0; +} + /** * */ @@ -340,6 +355,9 @@ access_verify(const char *username, const char *password, if (access_noacl) return 0; + if (access_ip_blocked(src)) + return -1; + if (!passwd_verify2(username, password, superuser_username, superuser_password)) return 0; @@ -358,7 +376,7 @@ access_verify(const char *username, const char *password, continue; /* Didn't get one */ } - if(!netmask_verify(ae, src)) + if(!netmask_verify(&ae->ae_ipmasks, src)) continue; /* IP based access mismatches */ if (ae->ae_username[0] != '*') @@ -565,6 +583,9 @@ access_get(const char *username, const char *password, struct sockaddr *src) access_entry_t *ae; int nouser = username == NULL || username[0] == '\0'; + if (!access_noacl && access_ip_blocked(src)) + return a; + if (!passwd_verify(username, password)) { a->aa_username = strdup(username); a->aa_representative = strdup(username); @@ -600,7 +621,7 @@ access_get(const char *username, const char *password, struct sockaddr *src) continue; /* Didn't get one */ } - if(!netmask_verify(ae, src)) + if(!netmask_verify(&ae->ae_ipmasks, src)) continue; /* IP based access mismatches */ if(ae->ae_username[0] != '*') @@ -633,6 +654,9 @@ access_get_hashed(const char *username, const uint8_t digest[20], access_entry_t *ae; int nouser = username == NULL || username[0] == '\0'; + if (!access_noacl && access_ip_blocked(src)) + return a; + if (!passwd_verify_digest(username, digest, challenge)) { a->aa_username = strdup(username); a->aa_representative = strdup(username); @@ -662,7 +686,7 @@ access_get_hashed(const char *username, const uint8_t digest[20], if(!ae->ae_enabled) continue; - if(!netmask_verify(ae, src)) + if(!netmask_verify(&ae->ae_ipmasks, src)) continue; /* IP based access mismatches */ if(ae->ae_username[0] != '*') { @@ -740,6 +764,9 @@ access_get_by_addr(struct sockaddr *src) return a; } + if (access_ip_blocked(src)) + return a; + TAILQ_FOREACH(ae, &access_entries, ae_link) { if(!ae->ae_enabled) @@ -748,7 +775,7 @@ access_get_by_addr(struct sockaddr *src) if(ae->ae_username[0] != '*') continue; - if(!netmask_verify(ae, src)) + if(!netmask_verify(&ae->ae_ipmasks, src)) continue; /* IP based access mismatches */ access_update(a, ae); @@ -761,17 +788,17 @@ access_get_by_addr(struct sockaddr *src) * */ static void -access_set_prefix_default(access_entry_t *ae) +access_set_prefix_default(struct access_ipmask_queue *ais) { access_ipmask_t *ai; ai = calloc(1, sizeof(access_ipmask_t)); ai->ai_family = AF_INET6; - TAILQ_INSERT_HEAD(&ae->ae_ipmasks, ai, ai_link); + TAILQ_INSERT_HEAD(ais, ai, ai_link); ai = calloc(1, sizeof(access_ipmask_t)); ai->ai_family = AF_INET; - TAILQ_INSERT_HEAD(&ae->ae_ipmasks, ai, ai_link); + TAILQ_INSERT_HEAD(ais, ai, ai_link); } /** @@ -820,7 +847,7 @@ static int access_addr6_empty(const char *s) * */ static void -access_set_prefix(access_entry_t *ae, const char *prefix) +access_set_prefix(struct access_ipmask_queue *ais, const char *prefix, int dflt) { static const char *delim = ",;| "; char buf[100]; @@ -830,8 +857,8 @@ access_set_prefix(access_entry_t *ae, const char *prefix) in_addr_t s_addr; access_ipmask_t *ai = NULL; - while((ai = TAILQ_FIRST(&ae->ae_ipmasks)) != NULL) { - TAILQ_REMOVE(&ae->ae_ipmasks, ai, ai_link); + while((ai = TAILQ_FIRST(ais)) != NULL) { + TAILQ_REMOVE(ais, ai, ai_link); free(ai); } @@ -885,7 +912,7 @@ access_set_prefix(access_entry_t *ae, const char *prefix) ai->ai_network = ntohl(s_addr) & ai->ai_netmask; } - TAILQ_INSERT_TAIL(&ae->ae_ipmasks, ai, ai_link); + TAILQ_INSERT_TAIL(ais, ai, ai_link); ai = NULL; tok = strtok_r(NULL, delim, &saveptr); @@ -899,8 +926,33 @@ fnext: } } - if (!TAILQ_FIRST(&ae->ae_ipmasks)) - access_set_prefix_default(ae); + if (dflt && !TAILQ_FIRST(ais)) + access_set_prefix_default(ais); +} + +/** + * + */ +static const char *access_get_prefix(struct access_ipmask_queue *ais) +{ + char addrbuf[50]; + access_ipmask_t *ai; + size_t pos = 0; + uint32_t s_addr; + + prop_sbuf[0] = prop_sbuf[1] = '\0'; + TAILQ_FOREACH(ai, ais, ai_link) { + if(PROP_SBUF_LEN-pos <= 0) + break; + if(ai->ai_family == AF_INET6) { + inet_ntop(AF_INET6, &ai->ai_ip6, addrbuf, sizeof(addrbuf)); + } else { + s_addr = htonl(ai->ai_network); + inet_ntop(AF_INET, &s_addr, addrbuf, sizeof(addrbuf)); + } + tvh_strlcatf(prop_sbuf, PROP_SBUF_LEN, pos, ",%s/%d", addrbuf, ai->ai_prefixlen); + } + return prop_sbuf + 1; } /** @@ -983,7 +1035,7 @@ access_entry_create(const char *uuid, htsmsg_t *conf) if (ae->ae_comment == NULL) ae->ae_comment = strdup("New entry"); if (TAILQ_FIRST(&ae->ae_ipmasks) == NULL) - access_set_prefix_default(ae); + access_set_prefix_default(&ae->ae_ipmasks); return ae; } @@ -1130,32 +1182,15 @@ access_entry_class_get_title (idnode_t *self, const char *lang) static int access_entry_class_prefix_set(void *o, const void *v) { - access_set_prefix((access_entry_t *)o, (const char *)v); + access_set_prefix(&((access_entry_t *)o)->ae_ipmasks, (const char *)v, 1); return 1; } static const void * access_entry_class_prefix_get(void *o) { - static char buf[4096], addrbuf[50], *ret = buf+1; - access_entry_t *ae = (access_entry_t *)o; - access_ipmask_t *ai; - size_t pos = 0; - uint32_t s_addr; - - buf[0] = buf[1] = '\0'; - TAILQ_FOREACH(ai, &ae->ae_ipmasks, ai_link) { - if(sizeof(buf)-pos <= 0) - break; - - if(ai->ai_family == AF_INET6) { - inet_ntop(AF_INET6, &ai->ai_ip6, addrbuf, sizeof(addrbuf)); - } else { - s_addr = htonl(ai->ai_network); - inet_ntop(AF_INET, &s_addr, addrbuf, sizeof(addrbuf)); - } - tvh_strlcatf(buf, sizeof(buf), pos, ",%s/%d", addrbuf, ai->ai_prefixlen); - } + static const char *ret; + ret = access_get_prefix(&((access_entry_t *)o)->ae_ipmasks); return &ret; } @@ -1705,6 +1740,130 @@ const idclass_t passwd_entry_class = { } }; +/** + * IP block list + */ + +ipblock_entry_t * +ipblock_entry_create(const char *uuid, htsmsg_t *conf) +{ + ipblock_entry_t *ib; + + lock_assert(&global_lock); + + ib = calloc(1, sizeof(ipblock_entry_t)); + + TAILQ_INIT(&ib->ib_ipmasks); + + if (idnode_insert(&ib->ib_id, uuid, &ipblock_entry_class, 0)) { + if (uuid) + tvherror("access", "invalid uuid '%s'", uuid); + free(ib); + return NULL; + } + + if (conf) { + ib->ib_enabled = 1; + idnode_load(&ib->ib_id, conf); + } + + TAILQ_INSERT_TAIL(&ipblock_entries, ib, ib_link); + + return ib; +} + +static void +ipblock_entry_destroy(ipblock_entry_t *ib) +{ + if (ib == NULL) + return; + TAILQ_REMOVE(&ipblock_entries, ib, ib_link); + idnode_unlink(&ib->ib_id); + free(ib->ib_comment); + free(ib); +} + +void +ipblock_entry_save(ipblock_entry_t *ib) +{ + htsmsg_t *c = htsmsg_create_map(); + idnode_save(&ib->ib_id, c); + hts_settings_save(c, "ipblock/%s", idnode_uuid_as_sstr(&ib->ib_id)); + htsmsg_destroy(c); +} + +static void +ipblock_entry_class_save(idnode_t *self) +{ + ipblock_entry_save((ipblock_entry_t *)self); +} + +static const char * +ipblock_entry_class_get_title (idnode_t *self, const char *lang) +{ + ipblock_entry_t *ib = (ipblock_entry_t *)self; + + if (ib->ib_comment && ib->ib_comment[0] != '\0') + return ib->ib_comment; + return N_("IP Blocking"); +} + +static void +ipblock_entry_class_delete(idnode_t *self) +{ + ipblock_entry_t *ib = (ipblock_entry_t *)self; + + hts_settings_remove("passwd/%s", idnode_uuid_as_sstr(&ib->ib_id)); + ipblock_entry_destroy(ib); +} + +static int +ipblock_entry_class_prefix_set(void *o, const void *v) +{ + access_set_prefix(&((ipblock_entry_t *)o)->ib_ipmasks, (const char *)v, 0); + return 1; +} + +static const void * +ipblock_entry_class_prefix_get(void *o) +{ + static const char *ret; + ret = access_get_prefix(&((ipblock_entry_t *)o)->ib_ipmasks); + return &ret; +} + +const idclass_t ipblock_entry_class = { + .ic_class = "ipblocking", + .ic_caption = N_("IP Blocking"), + .ic_event = "ipblocking", + .ic_perm_def = ACCESS_ADMIN, + .ic_save = ipblock_entry_class_save, + .ic_get_title = ipblock_entry_class_get_title, + .ic_delete = ipblock_entry_class_delete, + .ic_properties = (const property_t[]){ + { + .type = PT_BOOL, + .id = "enabled", + .name = N_("Enabled"), + .off = offsetof(ipblock_entry_t, ib_enabled), + }, + { + .type = PT_STR, + .id = "prefix", + .name = N_("Network prefix"), + .set = ipblock_entry_class_prefix_set, + .get = ipblock_entry_class_prefix_get, + }, + { + .type = PT_STR, + .id = "comment", + .name = N_("Comment"), + .off = offsetof(ipblock_entry_t, ib_comment), + }, + {} + } +}; + /** * */ @@ -1723,6 +1882,16 @@ access_init(int createdefault, int noacl) TAILQ_INIT(&access_entries); TAILQ_INIT(&access_tickets); TAILQ_INIT(&passwd_entries); + TAILQ_INIT(&ipblock_entries); + + /* Load ipblock entries */ + if ((c = hts_settings_load("ipblock")) != NULL) { + HTSMSG_FOREACH(f, c) { + if (!(m = htsmsg_field_get_map(f))) continue; + (void)ipblock_entry_create(f->hmf_name, m); + } + htsmsg_destroy(c); + } /* Load passwd entries */ if ((c = hts_settings_load("passwd")) != NULL) { @@ -1765,7 +1934,7 @@ access_init(int createdefault, int noacl) TAILQ_INIT(&ae->ae_ipmasks); - access_set_prefix_default(ae); + access_set_prefix_default(&ae->ae_ipmasks); access_entry_save(ae); @@ -1793,6 +1962,7 @@ access_done(void) access_entry_t *ae; access_ticket_t *at; passwd_entry_t *pw; + ipblock_entry_t *ib; pthread_mutex_lock(&global_lock); while ((ae = TAILQ_FIRST(&access_entries)) != NULL) @@ -1801,6 +1971,8 @@ access_done(void) access_ticket_destroy(at); while ((pw = TAILQ_FIRST(&passwd_entries)) != NULL) passwd_entry_destroy(pw); + while ((ib = TAILQ_FIRST(&ipblock_entries)) != NULL) + ipblock_entry_destroy(ib); free((void *)superuser_username); superuser_username = NULL; free((void *)superuser_password); diff --git a/src/access.h b/src/access.h index 64f6849fb..08eafabb7 100644 --- a/src/access.h +++ b/src/access.h @@ -26,6 +26,24 @@ struct profile; struct dvr_config; struct channel_tag; +TAILQ_HEAD(access_ipmask_queue, access_ipmask); + +TAILQ_HEAD(ipblock_entry_queue, ipblock_entry); + +extern struct ipblock_entry_queue ipblock_entries; + +typedef struct ipblock_entry { + idnode_t ib_id; + + TAILQ_ENTRY(ipblock_entry) ib_link; + + int ib_enabled; + struct access_ipmask_queue ib_ipmasks; + char *ib_comment; +} ipblock_entry_t; + +extern const idclass_t ipblock_entry_class; + TAILQ_HEAD(passwd_entry_queue, passwd_entry); extern struct passwd_entry_queue passwd_entries; @@ -109,7 +127,7 @@ typedef struct access_entry { uint32_t ae_rights; - TAILQ_HEAD(, access_ipmask) ae_ipmasks; + struct access_ipmask_queue ae_ipmasks; } access_entry_t; extern const idclass_t access_entry_class; @@ -266,6 +284,14 @@ passwd_entry_create(const char *uuid, htsmsg_t *conf); void passwd_entry_save(passwd_entry_t *pw); +/** + * + */ +ipblock_entry_t * +ipblock_entry_create(const char *uuid, htsmsg_t *conf); +void +ipblock_entry_save(ipblock_entry_t *pw); + /** * */ diff --git a/src/api/api_access.c b/src/api/api_access.c index 98056e1a9..d24d1e033 100644 --- a/src/api/api_access.c +++ b/src/api/api_access.c @@ -53,6 +53,38 @@ api_passwd_entry_create return 0; } +/* + * + */ + +static void +api_ipblock_entry_grid + ( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args ) +{ + ipblock_entry_t *ib; + + TAILQ_FOREACH(ib, &ipblock_entries, ib_link) + idnode_set_add(ins, (idnode_t*)ib, &conf->filter, perm->aa_lang); +} + +static int +api_ipblock_entry_create + ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + htsmsg_t *conf; + ipblock_entry_t *ib; + + if (!(conf = htsmsg_get_map(args, "conf"))) + return EINVAL; + + pthread_mutex_lock(&global_lock); + if ((ib = ipblock_entry_create(NULL, conf)) != NULL) + ipblock_entry_save(ib); + pthread_mutex_unlock(&global_lock); + + return 0; +} + /* * */ @@ -92,6 +124,10 @@ void api_access_init ( void ) { "passwd/entry/grid", ACCESS_ADMIN, api_idnode_grid, api_passwd_entry_grid }, { "passwd/entry/create", ACCESS_ADMIN, api_passwd_entry_create, NULL }, + { "ipblock/entry/class", ACCESS_ADMIN, api_idnode_class, (void*)&ipblock_entry_class }, + { "ipblock/entry/grid", ACCESS_ADMIN, api_idnode_grid, api_ipblock_entry_grid }, + { "ipblock/entry/create", ACCESS_ADMIN, api_ipblock_entry_create, NULL }, + { "access/entry/class", ACCESS_ADMIN, api_idnode_class, (void*)&access_entry_class }, { "access/entry/grid", ACCESS_ADMIN, api_idnode_grid, api_access_entry_grid }, { "access/entry/create", ACCESS_ADMIN, api_access_entry_create, NULL }, diff --git a/src/webui/static/app/acleditor.js b/src/webui/static/app/acleditor.js index 0b178c0f5..4ea3455d3 100644 --- a/src/webui/static/app/acleditor.js +++ b/src/webui/static/app/acleditor.js @@ -80,9 +80,9 @@ tvheadend.passwdeditor = function(panel, index) titleP: _('Passwords'), iconCls: 'pass', columns: { - enabled: { width: 120 }, - username: { width: 250 }, - password: { width: 250 } + enabled: { width: 120 }, + username: { width: 250 }, + password: { width: 250 } }, tabIndex: index, edit: { @@ -105,3 +105,42 @@ tvheadend.passwdeditor = function(panel, index) } }); }; + +/* + * IP Blocking Control + */ + +tvheadend.ipblockeditor = function(panel, index) +{ + var list = 'enabled,prefix,comment'; + + tvheadend.idnode_grid(panel, { + url: 'api/ipblock/entry', + titleS: _('IP Blocking Record'), + titleP: _('IP Blocking Records'), + iconCls: 'exclamation', + columns: { + enabled: { width: 120 }, + prefix: { width: 350 }, + comment: { width: 250 } + }, + tabIndex: index, + edit: { + params: { + list: list + } + }, + add: { + url: 'api/ipblock/entry', + params: { + list: list + }, + create: { } + }, + del: true, + list: list, + help: function() { + new tvheadend.help(_('IP Blocking Entries'), 'config_ipblock.html'); + } + }); +}; diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index fb603c9f7..f7786fa9c 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -408,6 +408,7 @@ function accessUpdate(o) { tvheadend.acleditor(users); tvheadend.passwdeditor(users); + tvheadend.ipblockeditor(users); cp.add(users);