]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
implement IP blocking feature, fixes #3088
authorJaroslav Kysela <perex@perex.cz>
Tue, 22 Sep 2015 15:38:25 +0000 (17:38 +0200)
committerJaroslav Kysela <perex@perex.cz>
Tue, 22 Sep 2015 15:38:25 +0000 (17:38 +0200)
src/access.c
src/access.h
src/api/api_access.c
src/webui/static/app/acleditor.js
src/webui/static/app/tvheadend.js

index 0252a4d8dc6fadd22a6ed8c2204d6e89c0454152..3708ca6fe5d7d51179b24254b01ba5262c131bbd 100644 (file)
@@ -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);
index 64f6849fbeeba6a8c47a2fa5852aea8c99493127..08eafabb70a51e1caef4da02e298574a174c561d 100644 (file)
@@ -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);
+
 /**
  *
  */
index 98056e1a9678ba43c0901020b4416a75ac608ffb..d24d1e033f83e12f9c74465ab206b9bd2a7d5af7 100644 (file)
@@ -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 },
index 0b178c0f53679bf2bce93f190730690dafde2194..4ea3455d338b7814b48d6d91e2c01ea12d7f930b 100644 (file)
@@ -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');
+        }
+    });
+};
index fb603c9f72b00a9f4f4ce491102fa2e632fa70e4..f7786fa9c4eb4667b1c6cb6910c3f9766dd55355 100644 (file)
@@ -408,6 +408,7 @@ function accessUpdate(o) {
 
         tvheadend.acleditor(users);
         tvheadend.passwdeditor(users);
+        tvheadend.ipblockeditor(users);
         
         cp.add(users);