NSD ACL Plugin

After having enough time to put some finishing touches to an ACL plugin
for NSD I know have something suitable for looking at. It isnt entirely
functionaly just yet, but it does compile, load and work to certain
extent.

After untarring the source, it needs to be configured as follows:

  ./configure --with-nsd-source=[path] --with-nsd-modules=[path]

each being the path to an unpacked NSD-1.2.0 source tree, and
the location you want the module installed to, respectively.

There are two main parts to the plugin. aclc and nsd_acl_plugin.so

Aclc, aclc is the Access Control List Compiler, it's documented
in it's manpage, aclc.8. But basically it takes a plaintext
access control list, and compiles it into something suitable for
the plugin to load later on.

nsd_acl_plugin.so is a standard NSD 1.2 plugin. It takes one argument,
which is the location of the compiled acl database.

aclc sometextfile nsd.acldb

nsd [option] -Xnsd_acl_plugin.so=nsd.acldb

for example. The current implementation has two main limitations,
I'm hoping someone may be able to help me rectify these. The plugin
currently only honours "all" type ACLs, ie you can't block just
"NS", "MX" .. or whatever queries. This is because I can't find
an easy way to determine query_type from with the plugin. Is there
an easy way ?

Rules are currently not honoured for subzones. So, if you have
a rule that says:

  deny all for example.com from 0.0.0.0/0

"host -t any example.com" will be refused by the plugin, but
"host -t any www.example.com" will not.

Fixing this is a matter of finding out how to get a list of all the records
in a particular zone and registering data for each. Since the AXFR code
must have method of finding out all this data, I'm assuming this will
be relatively doable. I just havnt figured out how yet. Though the
plugin has been written with this approach in mind. (see the top of
nsd_acl_plugin.c). I'd appreciate any insights anyone has to offer in
this regard.

I havnt tested this plugin extensively, and it may not even compile
on systems that arnt either my laptop or the servers I use for testing.

But it's a start :slight_smile: I hope to get up to useful-grade within a reasonable
period of time.

Colm MacCarthaigh wrote:

After having enough time to put some finishing touches to an ACL plugin
for NSD I know have something suitable for looking at. It isnt entirely
functionaly just yet, but it does compile, load and work to certain
extent.

After untarring the source, it needs to be configured as follows:

Is there a place I can get the source? :slight_smile:

for example. The current implementation has two main limitations,
I'm hoping someone may be able to help me rectify these. The plugin
currently only honours "all" type ACLs, ie you can't block just
"NS", "MX" .. or whatever queries. This is because I can't find
an easy way to determine query_type from with the plugin. Is there
an easy way ?

Not yet. Right now the class and type of the query are passed explicitly to functions that need it inside NSD. They should be stored in "struct query" after the query has been analyzed. Another problem is that currently the query is overwritten with the answer... so by the time your plugin is called the original class and type are no longer available :frowning:

Finally, what do you plan to do when a client issues a query for type "ANY" but is not allowed to see MX records? Filter out the MX records from the answer? Reject the original ANY query?

Rules are currently not honoured for subzones. So, if you have
a rule that says:

  deny all for example.com from 0.0.0.0/0

"host -t any example.com" will be refused by the plugin, but "host -t any www.example.com" will not.

Fixing this is a matter of finding out how to get a list of all the records in a particular zone and registering data for each. Since the AXFR code
must have method of finding out all this data, I'm assuming this will
be relatively doable. I just havnt figured out how yet. Though the
plugin has been written with this approach in mind. (see the top of
nsd_acl_plugin.c). I'd appreciate any insights anyone has to offer in
this regard.

Probably the easiest way is to use the HEAP_WALK macro in heap.h (which redefines RBTREE_WALK in rbtree.h) on interface->nsd->db->heap. This will give you all the domains (not just the ones specific to a zone).

Obviously the internal plugin APIs aren't very well defined yet. That's one reason the plugin support is marked experimental :slight_smile: But it will be a good thing to make more of the internal functionality of NSD available to plugins in a documented manner. But this will take some time.

Erik

Colm MacCarthaigh wrote:
>After having enough time to put some finishing touches to an ACL plugin
>for NSD I know have something suitable for looking at. It isnt entirely
>functionaly just yet, but it does compile, load and work to certain
>extent.
>
>After untarring the source, it needs to be configured as follows:

Is there a place I can get the source? :slight_smile:

Gah! This has been happening me the least few days, Mutt plus our
virus scanner hasnt been playing nicely. I've attached it now, from
a different machine. Please shout if it isnt attached :slight_smile:

>for example. The current implementation has two main limitations,
>I'm hoping someone may be able to help me rectify these. The plugin
>currently only honours "all" type ACLs, ie you can't block just
>"NS", "MX" .. or whatever queries. This is because I can't find
>an easy way to determine query_type from with the plugin. Is there
>an easy way ?

Not yet. Right now the class and type of the query are passed explicitly
to functions that need it inside NSD. They should be stored in "struct
query" after the query has been analyzed. Another problem is that
currently the query is overwritten with the answer... so by the time your
plugin is called the original class and type are no longer available :frowning:

Finally, what do you plan to do when a client issues a query for type "ANY"
but is not allowed to see MX records? Filter out the MX records from the
answer? Reject the original ANY query?

I was planning on simply rejecting the ANY. I'm not sure how useful
denying specific record types is, but since it was so easy to add
, I did :slight_smile:

>Rules are currently not honoured for subzones. So, if you have
>a rule that says:
>
> deny all for example.com from 0.0.0.0/0
>
>"host -t any example.com" will be refused by the plugin, but
>"host -t any www.example.com" will not.
>
>Fixing this is a matter of finding out how to get a list of all the
>records in a particular zone and registering data for each. Since the AXFR
>code
>must have method of finding out all this data, I'm assuming this will
>be relatively doable. I just havnt figured out how yet. Though the
>plugin has been written with this approach in mind. (see the top of
>nsd_acl_plugin.c). I'd appreciate any insights anyone has to offer in
>this regard.

Probably the easiest way is to use the HEAP_WALK macro in heap.h (which
redefines RBTREE_WALK in rbtree.h) on interface->nsd->db->heap. This will
give you all the domains (not just the ones specific to a zone).

O.k., I'll give that a go.

Obviously the internal plugin APIs aren't very well defined yet. That's
one reason the plugin support is marked experimental :slight_smile: But it will be a
good thing to make more of the internal functionality of NSD available to
plugins in a documented manner. But this will take some time.

Completely understandable!

(attachments)

nsd_acl_plugin.tar.gz (61.4 KB)

I've been playing with this, but getting very far. My recursive
register_data replacement now looks like this:

/* Recursively register data for a zone */
int register_zone_data (
  const struct nsd_plugin_interface *nsd,
  nsd_plugin_id_type id,
  const uint8_t * domain_name,
  void * data)
{
  void * key , * key_data;
  int klen, dlen;

  /* Register the data for the super zone */
  if ( ! nsd->register_data (nsd, id, domain_name, data) )
  {
    /* Zone doest exist at all */
    return 0;
  }

  /* Now register the data within that zone, but only if not
  ** already set */

  /* Skip past the first byte */
  domain_name ++;

  /* The length of our domain_name */
  dlen = strlen((char *) domain_name);

  nsd->nsd->db->heap->_node = rbtree_first(nsd->nsd->db->heap);

  /*
  ** if nsd knows about www.example.com, ftp.example.com and ns.example.com
  ** and domain_name = example.com , we need to register data for
  ** www.example.com, ftp.example.com and ns.example.com
  */
   HEAP_WALK(nsd->nsd->db->heap, key, key_data) {
    klen = *key;

    if ( ! bcmp((char *) key + (dlen - klen), (char *) domain_name) )
    {
      printf("%s matched %s\n", dnamestr(key), dnamestr(domain_name));
    }
  }

  return 1;
}

I know the code within the HEAP_WALK loop needs changing, but the
problem I'm seeing is that the code never gets inside the loop,
nothing in there ever runs. NSD just sits there, it seems that
rbtree_first gets itself into an infinite loop and never exists.

Before I go debugging this properly, am I actually using HEAP_WALK
correctly ?

> > Probably the easiest way is to use the HEAP_WALK macro in heap.h (which
> > redefines RBTREE_WALK in rbtree.h) on interface->nsd->db->heap. This will
> > give you all the domains (not just the ones specific to a zone).
>
> O.k., I'll give that a go.

I've been playing with this, but getting very far. My recursive

                                  ^
                                 not

  nsd->nsd->db->heap->_node = rbtree_first(nsd->nsd->db->heap);

And ignore this, that's me testing rbtree_first explicitly :slight_smile: seems
to go into an infinite loop :slight_smile:

Colm MacCarthaigh wrote:

Probably the easiest way is to use the HEAP_WALK macro in heap.h (which redefines RBTREE_WALK in rbtree.h) on interface->nsd->db->heap. This will give you all the domains (not just the ones specific to a zone).

O.k., I'll give that a go.

I've been playing with this, but getting very far. My recursive
register_data replacement now looks like this:

[... code snipped ...]

I know the code within the HEAP_WALK loop needs changing, but the
problem I'm seeing is that the code never gets inside the loop,
nothing in there ever runs. NSD just sits there, it seems that
rbtree_first gets itself into an infinite loop and never exists.

Before I go debugging this properly, am I actually using HEAP_WALK
correctly ?

Yes, you are. However, it is hard to debug this with shared objects. So I tried the following:

0. I modified example-plugin.so to include the following lines in the reload function:

  void *key;
  void *data;
  
  HEAP_WALK(nsd->nsd->db->heap, key, data) {
    fprintf(stderr, "name %s\n", dnamestr(key));
  }
  
    This also resulted in an infinite loop that was hard to debug.

Then I made the following changes to make things easier to debug:

1. Add the RTLD_GLOBAL flag to the call to dlopen in plugin.c "handle = dlopen(name, RTLD_NOW | RTLD_GLOBAL)".
2. Configure with LDFLAGS='-rdynamic' to make global symbols in nsd available to the plugins.
3. Do _not_ link rbtree.o into the plugin.

But now the HEAP_WALK works. Although I don't know why :frowning: But hopefully this will help you anyway.

I'm planning to change the name lookup algorithm for NSD 1.3.0 and that should make it much easier to walk over a set of domain names given a parent domain name. But until then this is your best bet.

Erik

0. I modified example-plugin.so to include the following lines in the
reload function:

  void *key;
  void *data;
  
  HEAP_WALK(nsd->nsd->db->heap, key, data) {
    fprintf(stderr, "name %s\n", dnamestr(key));
  }
  
   This also resulted in an infinite loop that was hard to debug.

Then I made the following changes to make things easier to debug:

1. Add the RTLD_GLOBAL flag to the call to dlopen in plugin.c "handle =
dlopen(name, RTLD_NOW | RTLD_GLOBAL)".
2. Configure with LDFLAGS='-rdynamic' to make global symbols in nsd
available to the plugins.
3. Do _not_ link rbtree.o into the plugin.

But now the HEAP_WALK works. Although I don't know why :frowning: But hopefully
this will help you anyway.

It does, any chance of making those the defaults for 1.2.2 ? The
"why" of it seems to be the RBTREE_NULL macro which expands to a
pointer to an rbnode_t. The two compiles produce differing
addresses for this node, so the code never matches.

I'm planning to change the name lookup algorithm for NSD 1.3.0 and that
should make it much easier to walk over a set of domain names given a
parent domain name. But until then this is your best bet.

I'm getting somewhere now :slight_smile:

Colm MacCarthaigh wrote:

But now the HEAP_WALK works. Although I don't know why :frowning: But hopefully this will help you anyway.

It does, any chance of making those the defaults for 1.2.2 ? The
"why" of it seems to be the RBTREE_NULL macro which expands to a
pointer to an rbnode_t. The two compiles produce differing
addresses for this node, so the code never matches.

That would explain it.

So the two options seem to be: use NULL instead of RBTREE_NULL or make all NSD symbols available to the plugins. I'm kind of hesistant about making all symbols available:

1. I'd like to keep a clear separation between NSD and plugins.
2. -rdynamic is probably not a portable option. This would either require messy configure scripts or the user must specify LDFLAGS manually when enabling plugins. Or maybe libtool can do the job (time to read the info pages).

But I'm not looking forward to changing the rbtree implementation either.

Erik

Understandable, there is a third option in that placing the
functionality in plugins.c and adding it to the interface spec
gets around the problem, though obviously this doesnt avoid
the problems that may arise from similar symbol addressing conflicts.