From b9ecd6e5e5b87fe1c4dab960e92246772002dd6a Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Mon, 9 Jun 2008 17:22:43 +0000 Subject: Merge the two dns plugins --- common/soa.c | 77 +++++++ common/soa.h | 6 + common/strset.c | 276 ++++++++++++++++++++++ common/strset.h | 75 ++++++ plugin/Makefile.am | 6 +- plugin/autoserial.c | 573 ++++++++++++++++++++++++++++++++++++++++++++++ plugin/dnsnotify.c | 409 +++++++++++++++++++++++++++++++++ plugin/plugin.c | 352 ++++++++++++++++++++++++++++ plugin/plugin.h | 72 ++++++ plugin/slapi-dnsnotify.c | 529 ------------------------------------------ tests/Makefile.am | 4 +- tests/unit-test-soa.c | 136 +++++++++++ tests/unit-test-strset.c | 106 +++++++++ tools/notify-dns-slaves.c | 20 +- 14 files changed, 2106 insertions(+), 535 deletions(-) create mode 100644 common/soa.c create mode 100644 common/soa.h create mode 100644 common/strset.c create mode 100644 common/strset.h create mode 100644 plugin/autoserial.c create mode 100644 plugin/dnsnotify.c create mode 100644 plugin/plugin.c create mode 100644 plugin/plugin.h delete mode 100644 plugin/slapi-dnsnotify.c create mode 100644 tests/unit-test-soa.c create mode 100644 tests/unit-test-strset.c diff --git a/common/soa.c b/common/soa.c new file mode 100644 index 0000000..c2bbcc4 --- /dev/null +++ b/common/soa.c @@ -0,0 +1,77 @@ + +#include "soa.h" + +#include +#include +#include +#include +#include +#include + +#define SEPARATORS " \t\n\v()" + +int +soa_serial_increment (const char *old_soa, char *new_soa, unsigned int new_len) +{ + unsigned long value; + const char *p, *beg; + char *end; + unsigned int len; + + assert (old_soa); + assert (new_soa); + + /* Check that we can add a digit */ + len = strlen (old_soa); + assert (new_len >= len + 2); + + p = old_soa; + + /* Eat up space at the beginning */ + while (*p && strchr (SEPARATORS, *p)) + ++p; + /* Skip over domain name, to next sep */ + while (*p && !strchr (SEPARATORS, *p)) + ++p; + /* Eat up space between */ + while (*p && strchr (SEPARATORS, *p)) + ++p; + /* Skip over administrator email, to next sep */ + while (*p && !strchr (SEPARATORS, *p)) + ++p; + /* Eat up spaces between */ + while (*p && strchr (SEPARATORS, *p)) + ++p; + + if (*p == 0 || !isdigit (*p)) { + errno = EINVAL; + return -1; + } + + /* Parse the integer here */ + beg = p; + value = strtoul (beg, &end, 10); + if (beg == end || !strchr (SEPARATORS, *end)) { + errno = EINVAL; + return -1; + } + + /* Increment the integer appropriately */ + if (value == 0xFFFFFFFF) + value = 1; /* Zero is recommended against in RFC 1982 */ + else + ++value; + + /* Build up the new SOA */ + memset (new_soa, 0, new_len); + len = beg - old_soa; + strncpy (new_soa, old_soa, len); + snprintf (new_soa + len, 32, "%lu", value); + len = strlen (new_soa); + strcpy (new_soa + len, end); + + len = strlen (new_soa); + assert (len < new_len); + + return len; +} diff --git a/common/soa.h b/common/soa.h new file mode 100644 index 0000000..ca842e3 --- /dev/null +++ b/common/soa.h @@ -0,0 +1,6 @@ +#ifndef SOA_H_ +#define SOA_H_ + +int soa_serial_increment (const char *old_soa, char *new_soa, unsigned int new_len); + +#endif /*SOA_H_*/ diff --git a/common/strset.c b/common/strset.c new file mode 100644 index 0000000..801a358 --- /dev/null +++ b/common/strset.c @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2008, Stefan Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Originally from apache 2.0 + */ + +/* Copyright 2000-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "strset.h" + +typedef struct _entry { + struct _entry* next; + unsigned int hash; + char *value; +} entry; + +struct _strset { + entry **array; + unsigned int count; + unsigned int max; +}; + +#define INITIAL_MAX 15 /* tunable == 2^n - 1 */ + +static entry** +alloc_array (unsigned int max) +{ + void *ret; + size_t len; + + assert (max); + + len = sizeof (entry*) * (max + 1); + ret = strset_malloc (len); + if (ret) + memset (ret, 0, len); + + return ret; +} + +strset* +strset_create (void) +{ + strset *set = strset_malloc (sizeof (strset)); + if (set) { + memset (set, 0, sizeof (strset)); + set->max = INITIAL_MAX; + set->array = alloc_array (set->max); + if (!set->array) { + strset_free (set); + set = NULL; + } + } + + return set; +} + +void +strset_destroy (strset *set) +{ + unsigned int i; + entry *ent, *next; + + if (!set) + return; + + /* Free all the entries */ + for (i = 0; i < set->max; ++i) { + next = NULL; + for (ent = set->array[i]; ent; ent = next) { + next = ent->next; + + assert (ent->value); + strset_free (ent); + } + } + + /* Free the set itself */ + strset_free (set->array); + strset_free (set); +} + +static int +expand_array (strset* set) +{ + entry** new_array; + entry* ent, *next; + unsigned int new_max, n, i; + + assert (set); + assert (set->max); + assert (set->array); + + new_max = set->max * 2 + 1; + new_array = alloc_array (new_max); + + if(!new_array) + return 0; + + /* Go through all the entries */ + for (i = 0; i < set->max; ++i) { + next = NULL; + for (ent = set->array[i]; ent; ent = next) { + next = ent->next; + + n = ent->hash & new_max; + ent->next = new_array[n]; + new_array[n] = ent; + } + } + + strset_free (set->array); + set->array = new_array; + set->max = new_max; + + return 1; +} + +static entry** +find_entry (strset* set, const char *value, int add) +{ + entry **ep, *e; + const char* p; + unsigned int hash; + size_t len; + + assert (set); + assert (set->max); + assert (set->array); + assert (value); + + hash = 0; + + for(p = value; *p; p++) + hash = hash * 33 + *p; + + /* scan linked list */ + for (ep = &set->array[hash & set->max], e = *ep; + e; ep = &e->next, e = *ep) + { + if (e->hash == hash && strcmp (e->value, value) == 0) + break; + } + + if (e || !add) + return ep; + + /* Add an entry */ + len = strlen (value); + e = strset_malloc (sizeof (entry) + len + 1); + if (e) { + /* Value data points past end of entry */ + e->value = ((char*)e) + sizeof (entry); + memcpy (e->value, value, len + 1); + + e->next = NULL; + e->hash = hash; + + *ep = e; + ++set->count; + } + + return ep; +} + +int +strset_has (strset *set, const char *value) +{ + entry **ep; + + assert (set); + + if (!value) + return 0; + + ep = find_entry (set, value, 0); + return ep && *ep ? 1 : 0; +} + +int +strset_add (strset *set, const char *value) +{ + entry **ep; + + assert (set); + + if (!value) + return -1; + + ep = find_entry (set, value, 1); + if (!ep || !*ep) + return -1; + + if (set->count > set->max) + if (!expand_array (set)) + return -1; + + return 0; +} + +int +strset_remove (strset *set, const char* value) +{ + entry **ep, *old; + + assert (set); + + if (!value) + return -1; + + ep = find_entry (set, value, 0); + if (!ep || !*ep) + return -1; + + old = *ep; + *ep = (*ep)->next; + --set->count; + strset_free (old); + return 0; +} + +unsigned int +strset_size (strset *set) +{ + assert (set); + return set->count; +} diff --git a/common/strset.h b/common/strset.h new file mode 100644 index 0000000..627856b --- /dev/null +++ b/common/strset.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2008, Stefan Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Originally from apache 2.0 + */ + +/* Copyright 2000-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __STRSET_H__ +#define __STRSET_H__ + +/* Abstract type for hash tables. */ +typedef struct _strset strset; + +strset* strset_create (void); +void strset_destroy (strset *set); +unsigned int strset_size (strset *set); +int strset_has (strset *set, const char *value); +int strset_add (strset *set, const char *value); +int strset_remove (strset *set, const char *value); + +/* Need to rebuild strset.c if you set this variable */ +#if STRSET_CUSTMEM +extern void* strset_malloc (size_t length); +extern void strset_free (void *ptr); +#else +#define strset_malloc(x) malloc(x) +#define strset_free(x) free(x) +#endif + +#endif /* __STRSET_H__ */ diff --git a/plugin/Makefile.am b/plugin/Makefile.am index 9b8f4de..29266b9 100644 --- a/plugin/Makefile.am +++ b/plugin/Makefile.am @@ -7,7 +7,11 @@ INCLUDES = \ lib_LTLIBRARIES = slapi-dnsnotify.la slapi_dnsnotify_la_SOURCES = \ - slapi-dnsnotify.c + plugin.c plugin.h \ + autoserial.c \ + dnsnotify.c \ + ../common/soa.c \ + ../common/strset.c slapi_dnsnotify_la_LDFLAGS = -module -avoid-version \ -export-symbols-regex 'plugin_init' diff --git a/plugin/autoserial.c b/plugin/autoserial.c new file mode 100644 index 0000000..d10f8a5 --- /dev/null +++ b/plugin/autoserial.c @@ -0,0 +1,573 @@ + +#include "config.h" + +#include "plugin.h" + +#include "common/soa.h" +#include "common/strset.h" + +#include +#include +#include +#include +#include +#include +#include + +/*---------------------------------------------------------------------------------- + * DECLARATIONS + */ + +/* TODO: Fill these in from settings */ +static const char *dnsserial_soa_attribute = "sOARecord"; +static const char *dnsserial_soa_base = ""; + +static Slapi_Mutex *dnsserial_mutex = NULL; +static strset *dnsserial_soa_cache = NULL; + +/* --------------------------------------------------------------------------------- + * MEMORY ALLOCATION + */ + +void* +strset_malloc (size_t length) +{ + return slapi_ch_malloc (length); +} + +void +strset_free (void *ptr) +{ + return slapi_ch_free (&ptr); +} + +/* --------------------------------------------------------------------------------- + * LDAP OPERATIONS + */ + +static int +search_for_dns (const char *base, const char *filter, char ***results) +{ + Slapi_Entry **entries, **e; + Slapi_PBlock *pb; + LDAPControl *ctrl; + char **dns, **d; + char *attr, *dn; + int rc, num, code; + + assert (results); + assert (filter && filter[0]); + assert (base && base[0]); + + ctrl = NULL; /* No controls */ + attr = NULL; /* No attributes */ + + trace ("performing internal search"); + + /* Do the actual search */ + pb = slapi_search_internal ((char*)base, LDAP_SCOPE_SUBTREE, (char*)filter, &ctrl, &attr, 1); + return_val_if_fail (pb, -1); + + /* Was it successful? */ + code = -1; + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &code); + return_val_if_fail (rc >= 0, -1); + if (code != LDAP_SUCCESS) { + log_plugin ("error performing search on %s for %s (code %d)", base, filter, code); + slapi_pblock_destroy (pb); + trace ("failure"); + return -1; + } + + /* Dig out all the entries */ + entries = NULL; + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + return_val_if_fail (rc >= 0, -1); + + /* How many entries? */ + num = 0; + for (e = entries; entries && *e; ++e) + ++num; + + /* Allocate memory and copy over all dns found */ + d = dns = (char**)slapi_ch_calloc (num + 1, sizeof (char*)); + for (e = entries; entries && *e; ++e) { + dn = slapi_entry_get_dn (*e); + return_val_if_fail (dn, -1); + + dn = slapi_ch_strdup (dn); + dn = slapi_dn_normalize_case (dn); + *(d++) = dn; + } + + slapi_pblock_destroy (pb); + *results = dns; + + trace ("success"); + return 0; +} + +static int +populate_soa_cache (void) +{ + char **dns, **d; + char *filter; + int rc, len, have; + + assert (dnsserial_mutex); + + have = 0; + slapi_lock_mutex (dnsserial_mutex); + + /* Already populated? */ + have = dnsserial_soa_cache ? 1 : 0; + + slapi_unlock_mutex (dnsserial_mutex); + + if (have) + return 1; + + /* Build up the filter */ + len = strlen (dnsserial_soa_attribute) + + strlen ("(%s=*)"); + filter = slapi_ch_malloc (len); + strcpy (filter, "("); + strcat (filter, dnsserial_soa_attribute); + strcat (filter, "=*)"); + + trace (filter); + + rc = search_for_dns (dnsserial_soa_base, filter, &dns); + slapi_ch_free_string (&filter); + + if (rc < 0) + return rc; + + /* Normalize all the DNs */ + for (d = dns; *d; ++d) + slapi_dn_normalize_case (*d); + + /* Now we're ready to put them in the cache */ + assert (dnsserial_mutex); + slapi_lock_mutex (dnsserial_mutex); + + /* Already populated? */ + if (!dnsserial_soa_cache) { + dnsserial_soa_cache = strset_create (); + for (d = dns; *d; ++d) + strset_add (dnsserial_soa_cache, *d); + } + + slapi_unlock_mutex (dnsserial_mutex); + + slapi_ch_array_free (dns); + return 1; +} + +static int +present_in_soa_cache (const char *dn) +{ + int present; + + assert (dn && dn[0]); + assert (dnsserial_mutex); + + if (populate_soa_cache () < 0) + return 0; + + slapi_lock_mutex (dnsserial_mutex); + + present = strset_has (dnsserial_soa_cache, dn); + + slapi_unlock_mutex (dnsserial_mutex); + + return present; +} + +static char* +ancestor_in_soa_cache (const char *dn) +{ + Slapi_DN *sdn = NULL; + Slapi_DN *parent = NULL; + char *ancestor; + + assert (dn && dn[0]); + assert (dnsserial_mutex); + + if (populate_soa_cache () < 0) + return 0; + + sdn = slapi_sdn_new_dn_byval (dn); + return_val_if_fail (sdn, 0); + + ancestor = NULL; + slapi_lock_mutex (dnsserial_mutex); + + for (;;) { + + /* Get the parent of the dn */ + assert (sdn); + assert (!parent); + parent = slapi_sdn_new (); + slapi_sdn_get_parent (sdn, parent); + + /* Reached the root? */ + if (slapi_sdn_isempty (parent)) + break; + + /* Reached the top of our SOA block? */ + dn = slapi_sdn_get_dn (parent); + if (strcmp (dn, dnsserial_soa_base) == 0) + break; + + /* Found this DN? */ + if (strset_has (dnsserial_soa_cache, dn)) { + ancestor = slapi_ch_strdup (dn); + break; + } + + slapi_sdn_free (&sdn); + sdn = parent; + parent = NULL; + } + + + slapi_unlock_mutex (dnsserial_mutex); + + if (sdn) + slapi_sdn_free (&sdn); + if (parent) + slapi_sdn_free (&parent); + + return ancestor; +} + +static int +add_to_soa_cache (const char *dn) +{ + assert (dn && dn[0]); + assert (dnsserial_mutex); + + if (populate_soa_cache () < 0) + return -1; + + slapi_lock_mutex (dnsserial_mutex); + + strset_add (dnsserial_soa_cache, dn); + + slapi_unlock_mutex (dnsserial_mutex); + + return 0; +} + +static int +remove_from_soa_cache (const char *dn) +{ + assert (dn && dn[0]); + + if (populate_soa_cache () < 0) + return -1; + + slapi_lock_mutex (dnsserial_mutex); + + strset_remove (dnsserial_soa_cache, dn); + + slapi_unlock_mutex (dnsserial_mutex); + + return 0; +} + +static char* +load_soa_attribute (const char *dn) +{ + Slapi_Entry **entries; + struct berval **values; + Slapi_PBlock *pb; + Slapi_Attr *attr; + LDAPControl *ctrl; + char *attrs[2]; + char *soa; + int rc, code; + + trace (dn); + assert (dn && dn[0]); + assert (dnsserial_soa_attribute); + + ctrl = NULL; /* No controls */ + attrs[0] = (char*)dnsserial_soa_attribute; + attrs[1] = NULL; + + trace ("performing internal search"); + + /* Do the actual search */ + pb = slapi_search_internal ((char*)dn, LDAP_SCOPE_BASE, "(objectClass=*)", &ctrl, attrs, 0); + return_val_if_fail (pb, NULL); + + /* Was it successful? */ + code = -1; + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &code); + return_val_if_fail (rc >= 0, NULL); + if (code != LDAP_SUCCESS) { + log_plugin ("error loading attribute %s from %s (code %d)", dnsserial_soa_attribute, dn, code); + slapi_pblock_destroy (pb); + trace ("failure"); + return NULL; + } + + /* Dig out all the entries */ + entries = NULL; + slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + return_val_if_fail (entries, NULL); + + soa = NULL; + if (entries[0]) { + if (slapi_entry_attr_find (entries[0], (char*)dnsserial_soa_attribute, &attr) >= 0 && attr && + slapi_attr_get_values (attr, &values) >= 0 && values && values[0]) { + + /* Convert the berval into a string */ + soa = slapi_ch_malloc (values[0]->bv_len + 1); + if (values[0]->bv_len) + memcpy (soa, values[0]->bv_val, values[0]->bv_len); + soa[values[0]->bv_len] = 0; + } + } + + slapi_pblock_destroy (pb); + trace ("success"); + + return soa; +} + +static int +change_soa_attribute (const char *dn, const char *soa_old, const char *soa_new) +{ + Slapi_PBlock *pb; + LDAPMod *mods[3]; + char *values_add[2]; + char *values_remove[2]; + LDAPMod mod_add; + LDAPMod mod_remove; + int rc, code; + + trace (dn); + + assert (dn && dn[0]); + assert (soa_old); + assert (soa_new); + + /* Setup the mods to change */ + values_add[0] = (char*)soa_new; + values_add[1] = NULL; + mod_add.mod_op = LDAP_MOD_ADD; + mod_add.mod_type = (char*)dnsserial_soa_attribute; + mod_add.mod_values = values_add; + + values_remove[0] = (char*)soa_old; + values_remove[1] = NULL; + mod_remove.mod_op = LDAP_MOD_DELETE; + mod_remove.mod_type = (char*)dnsserial_soa_attribute; + mod_remove.mod_values = values_remove; + + mods[0] = &mod_remove; + mods[1] = &mod_add; + mods[2] = NULL; + + trace ("performing modify internal"); + + pb = slapi_modify_internal ((char*)dn, mods, NULL, 1); + return_val_if_fail (pb, -1); + + code = -1; + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &code); + return_val_if_fail (pb, -1); + slapi_pblock_destroy (pb); + + if (code != LDAP_SUCCESS && code != LDAP_TYPE_OR_VALUE_EXISTS && code != LDAP_NO_SUCH_ATTRIBUTE) { + log_plugin ("couldn't replace %s on %s with '%s' (code %d)", + dnsserial_soa_attribute, dn, soa_new, code); + trace ("failure"); + return -1; + } + + trace ("success"); + return 0; +} + +static int +increment_soa_dn (const char *dn) +{ + char *soa_new; + char *soa_old; + int len, ret = -1; + + assert (dn && dn[0]); + + soa_old = load_soa_attribute (dn); + if (!soa_old) { + log_plugin ("couldn't increment SOA record at %s: not found", dn); + return -1; + } + + len = strlen (soa_old) + 32; + soa_new = slapi_ch_malloc (len); + if (soa_serial_increment (soa_old, soa_new, len) < 0) { + if (errno == EINVAL) + log_plugin ("invalid SOA record at %s: %s", dn, soa_old); + else + log_plugin ("couldn't increment SOA record at %s: %s", dn, strerror (errno)); + ret = -1; + } else { + ret = change_soa_attribute (dn, soa_old, soa_new); + } + + slapi_ch_free_string (&soa_new); + slapi_ch_free_string (&soa_old); + + return ret; +} + +void +autoserial_post_delete (const char *dn) +{ + char *soa_dn; + + return_if_fail (dn && dn[0]); + + /* A DN that is an SOA? */ + if (present_in_soa_cache (dn)) { + remove_from_soa_cache (dn); + + /* A DN underneath an SOA? */ + } else { + soa_dn = ancestor_in_soa_cache (dn); + if (soa_dn) { + increment_soa_dn (soa_dn); + slapi_ch_free_string (&soa_dn); + } + } +} + +void +autoserial_post_modify (const char *dn, LDAPMod **mods) +{ + char *soa, *soa_dn; + int present; + + return_if_fail (dn && dn[0]); + + /* Add or remove from cache as appropriate */ + soa = load_soa_attribute(dn); + if (soa) { + add_to_soa_cache (dn); + slapi_ch_free_string (&soa); + present = 1; + } else { + remove_from_soa_cache (dn); + present = 0; + } + + /* A DN underneath an SOA? */ + if (!present) { + soa_dn = ancestor_in_soa_cache (dn); + if (soa_dn) { + increment_soa_dn (soa_dn); + slapi_ch_free_string (&soa_dn); + } + } +} + +void +autoserial_post_modrdn (const char *odn, const char *ndn) +{ + char *soa_dn; + + return_if_fail (odn && odn[0]); + return_if_fail (ndn && ndn[0]); + + /* A DN that is an SOA? */ + if (present_in_soa_cache (odn)) { + + remove_from_soa_cache (odn); + add_to_soa_cache (ndn); + + /* A DN underneath an SOA? */ + } else { + soa_dn = ancestor_in_soa_cache (ndn); + if (soa_dn) { + increment_soa_dn (soa_dn); + slapi_ch_free_string (&soa_dn); + } + } +} + +void +autoserial_post_add (const char *dn) +{ + char *soa, *soa_dn; + + return_if_fail (dn && dn[0]); + + /* A DN that is an SOA? */ + soa = load_soa_attribute (dn); + if (soa) { + add_to_soa_cache (dn); + slapi_ch_free_string (&soa); + + /* A DN underneath an SOA? */ + } else { + soa_dn = ancestor_in_soa_cache (dn); + if (soa_dn) { + increment_soa_dn (soa_dn); + slapi_ch_free_string (&soa_dn); + } + } +} + +int +autoserial_config (const char *name, const char *value) +{ + return_val_if_fail (name, 0); + + if (strcmp ("soa-attribute", name) == 0 && value) { + dnsserial_soa_attribute = value; + return 1; + + + } else if (strcmp ("base-dn", name) == 0 && value) { + dnsserial_soa_base = value; + return 1; + } + + return 0; +} + +void +autoserial_destroy (void) +{ + assert (dnsserial_mutex); + slapi_lock_mutex (dnsserial_mutex); + + if (dnsserial_soa_cache) + strset_destroy (dnsserial_soa_cache); + dnsserial_soa_cache = NULL; + + slapi_unlock_mutex (dnsserial_mutex); + + slapi_destroy_mutex (dnsserial_mutex); + dnsserial_mutex = NULL; +} + +int +autoserial_init (void) +{ + if (!dnsserial_soa_base || !dnsserial_soa_base[0]) { + log_plugin ("'base-dn' argument not specified"); + return -1; + } + + assert (!dnsserial_mutex); + dnsserial_mutex = slapi_new_mutex (); + return_val_if_fail (dnsserial_mutex, -1); + + return 0; +} diff --git a/plugin/dnsnotify.c b/plugin/dnsnotify.c new file mode 100644 index 0000000..132726f --- /dev/null +++ b/plugin/dnsnotify.c @@ -0,0 +1,409 @@ + +#include "config.h" + +#include "plugin.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*---------------------------------------------------------------------------------- + * DECLARATIONS + */ + +static const char *dnsnotify_soa_attribute = "sOARecord"; +static const char *dnsnotify_ns_attribute = "nSRecord"; +static int dnsnotify_enable = 1; +static int dnsnotify_delay = 5; + +static Slapi_Mutex *dnsnotify_mutex = NULL; +static int dnsnotify_pipe = -1; +static pid_t dnsnotify_pid = -1; + + +/* --------------------------------------------------------------------------------- + * OPERATIONS + */ + +static int +load_soa_ns_attributes (const char *dn, char **soa_result, char ***ns_result) +{ + Slapi_Entry **entries; + struct berval **values, **v; + Slapi_PBlock *pb; + Slapi_Attr *attr; + LDAPControl *ctrl; + char *attrs[2]; + char *soa, **ns; + int rc, code, num, i; + + trace (dn); + assert (dn && dn[0]); + assert (dnsnotify_soa_attribute); + assert (dnsnotify_ns_attribute); + + ctrl = NULL; /* No controls */ + attrs[0] = (char*)dnsnotify_soa_attribute; + attrs[1] = NULL; + + trace ("performing internal search"); + + /* Do the actual search */ + pb = slapi_search_internal ((char*)dn, LDAP_SCOPE_BASE, "(objectClass=*)", &ctrl, attrs, 0); + return_val_if_fail (pb, 0); + + /* Was it successful? */ + code = -1; + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &code); + return_val_if_fail (rc >= 0, 0); + if (code != LDAP_SUCCESS) { + log_plugin ("error loading attribute %s from %s (code %d)", dnsnotify_soa_attribute, dn, code); + slapi_pblock_destroy (pb); + trace ("failure"); + return 0; + } + + /* Dig out all the entries */ + entries = NULL; + slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + return_val_if_fail (entries, 0); + + soa = NULL; + if (entries[0]) { + if (slapi_entry_attr_find (entries[0], (char*)dnsnotify_soa_attribute, &attr) >= 0 && attr && + slapi_attr_get_values (attr, &values) >= 0 && values && values[0]) { + + /* Convert the berval into a string */ + soa = slapi_ch_malloc (values[0]->bv_len + 1); + if (values[0]->bv_len) + memcpy (soa, values[0]->bv_val, values[0]->bv_len); + soa[values[0]->bv_len] = 0; + } + + if (slapi_entry_attr_find (entries[0], (char*)dnsnotify_ns_attribute, &attr) >= 0 && attr && + slapi_attr_get_values (attr, &values) >= 0 && values) { + + num = 0; + for (v = values; *v; ++v) + ++num; + + ns = (char**)slapi_ch_calloc (num, sizeof (char*)); + for (i = 0; i < num; ++i) { + ns[i] = slapi_ch_malloc (values[i]->bv_len + 1); + if (values[i]->bv_len) + memcpy (ns[i], values[i]->bv_val, values[i]->bv_len); + ns[i][values[i]->bv_len] = 0; + } + } + } + + slapi_pblock_destroy (pb); + + /* Only proceed if we have all the data we need */ + if (soa && soa[0] && ns && ns[0] && ns[0]) { + *soa_result = soa; + *ns_result = ns; + return 1; + } + + slapi_ch_free_string (&soa); + slapi_ch_array_free (ns); + + return 0; +} + +static void +kill_notify_process (void) +{ + int status; + + if (dnsnotify_pipe != -1) + close (dnsnotify_pipe); + dnsnotify_pipe = -1; + + if (dnsnotify_pid != -1) { + if (waitpid (dnsnotify_pid, &status, WNOHANG) == 0) { + /* We can play rough, because we know process can take it */ + if (kill (dnsnotify_pid, SIGKILL) >= 0) + waitpid (dnsnotify_pid, &status, 0); + } + dnsnotify_pid = -1; + } +} + +static int +fork_notify_process (void) +{ + int open_max, fd; + int commpipe[2]; + char *args[5]; + char delay[16]; + pid_t pid; + + if (pipe (commpipe) < 0) { + log_plugin ("couldn't create communication pipe for child process: %s", strerror (errno)); + return -1; + } + + pid = fork(); + switch (pid) { + case -1: + log_plugin ("couldn't fork for child notify process: %s", strerror (errno)); + close (commpipe[0]); + close (commpipe[1]); + return -1; + + /* The child, continues below */ + case 0: + break; + + /* The parent */ + default: + + /* close read end */ + close (commpipe[0]); + + /* Kill any previous process */ + kill_notify_process (); + + /* hang onto write end, and new pid */ + dnsnotify_pipe = commpipe[1]; + dnsnotify_pid = pid; + + return 0; + }; + + /* The child process, here */ + close (commpipe[1]); + + /* Dup the pipe into stdin of the process */ + if (dup2 (commpipe[0], 0) < 0) + log_plugin ("couldn't setup stdin on notify child process: %s", strerror (errno)); + + /* Close all other file descriptors */ + open_max = sysconf (_SC_OPEN_MAX); + for (fd = 3; fd < open_max; ++fd) + close (fd); + + snprintf (delay, sizeof (delay), "-w%d", dnsnotify_delay); + + args[0] = DNS_NOTIFY_PATH; + args[1] = "-s"; + args[2] = delay; + args[3] = NULL; + + execv (args[0], args); + log_plugin ("couldn't launch '%s' process: %s", DNS_NOTIFY_PATH, strerror (errno)); + _exit (1); +} + +static ssize_t +write_all (int fd, const void *b, size_t len) +{ + const unsigned char *buf = (const unsigned char*)b; + ssize_t l, left = len; + + while (left > 0) { + l = write (fd, buf, len); + if (l < 0) { + if (errno == EINTR && errno == EAGAIN) + continue; + return -1; + } else { + left -= l; + buf += l; + } + } + + return len; +} + +static char* +prep_soa_domain (char *soa) +{ + size_t at; + + assert (soa && soa[0]); + + /* Find the first space in the SOA and cut it off there */ + while (soa[0] && isspace (soa[0])) + ++soa; + at = strcspn (soa, " \t\n\r\v"); + if (at == 0) { + log_plugin ("invalid SOA present"); + return NULL; + } + + soa[at] = 0; + trace (soa); + return soa; +} + +static void +notify_dns_slaves (char *soa, char **ns) +{ + int i, complete = 1; + char *n; + + trace (soa); + + assert (ns); + assert (soa && soa[0]); + + if (!*ns) + return; + + soa = prep_soa_domain (soa); + if (!soa) + return; + + slapi_lock_mutex (dnsnotify_mutex); + + /* Try this twice in case things have closed up shop */ + for (i = 0; i < 2; ++i) { + + if (dnsnotify_pipe < 0 || i > 0) + if (fork_notify_process () < 0) + break; + + assert (dnsnotify_pipe >= 0); + + for (; *ns; ++ns) { + n = *ns; + + /* An empty string ? */ + while (n[0] && isspace (n[0])) + ++n; + if (!n[0]) + continue; + + if (write_all (dnsnotify_pipe, "NOTIFY: ", 8) < 0 || + write_all (dnsnotify_pipe, soa, strlen (soa)) < 0 || + write_all (dnsnotify_pipe, " ", 1) < 0 || + write_all (dnsnotify_pipe, *ns, strlen (*ns)) < 0 || + write_all (dnsnotify_pipe, "\n", 1) < 0) { + if (errno == EPIPE) { + complete = 0; + break; + } + log_plugin ("couldn't write data to child notify process: %s", strerror (errno)); + } + } + + if (complete) + break; + } + + slapi_unlock_mutex (dnsnotify_mutex); +} + +void +dnsnotify_post_modify (const char *dn, LDAPMod **mods) +{ + LDAPMod **m, *mod; + char *soa, **ns; + + return_if_fail (dn && dn[0]); + return_if_fail (mods); + + if (!dnsnotify_enable) + return; + + for (m = mods; *m; ++m) { + mod = *m; + trace (mod->mod_type); + if (strcmp (mod->mod_type, dnsnotify_soa_attribute) == 0) { + if (load_soa_ns_attributes (dn, &soa, &ns)) { + notify_dns_slaves (soa, ns); + slapi_ch_array_free (ns); + slapi_ch_free_string (&soa); + } + break; + } + } +} + +void +dnsnotify_post_add (const char *dn) +{ + char *soa, **ns; + + return_if_fail (dn); + + if (!dnsnotify_enable) + return; + + /* A DN that is an SOA? */ + if (load_soa_ns_attributes (dn, &soa, &ns)) { + notify_dns_slaves (soa, ns); + slapi_ch_free_string (&soa); + slapi_ch_array_free (ns); + } +} + +int +dnsnotify_config (const char *name, const char *value) +{ + return_val_if_fail (name, 0); + + if (strcmp (name, "soa-attribute") == 0 && value) { + dnsnotify_soa_attribute = value; + return 1; + } + + else if (strcmp (name, "ns-attribute") == 0 && value) { + dnsnotify_ns_attribute = value; + return 1; + } + + else if (strcmp (name, "notify-delay") == 0 && value) { + dnsnotify_delay = atoi (value); + return 1; + } + + else if (strcmp (name, "disable-notify") == 0 && !value) { + dnsnotify_enable = 0; + return 1; + } + + return 0; +} + +int +dnsnotify_init (void) +{ + if (!dnsnotify_ns_attribute || !dnsnotify_ns_attribute[0]) { + log_plugin ("SOA or NS attribute specified for plugin is empty"); + return -1; + } + + assert (!dnsnotify_mutex); + dnsnotify_mutex = slapi_new_mutex (); + return_val_if_fail (dnsnotify_mutex, -1); + + return 0; +} + + +void +dnsnotify_destroy (void) +{ + if (dnsnotify_mutex) + slapi_lock_mutex (dnsnotify_mutex); + + kill_notify_process (); + + if (dnsnotify_mutex) { + slapi_unlock_mutex (dnsnotify_mutex); + slapi_destroy_mutex (dnsnotify_mutex); + dnsnotify_mutex = NULL; + } +} diff --git a/plugin/plugin.c b/plugin/plugin.c new file mode 100644 index 0000000..c38eb67 --- /dev/null +++ b/plugin/plugin.c @@ -0,0 +1,352 @@ + +#include "config.h" + +#include "plugin.h" + +#include + +#include +#include +#include +#include +#include +#include + +#define WHITESPACE " \t\r\n\v" + +static char **plugin_arguments = NULL; + +/* --------------------------------------------------------------------- + * LOGGING/TRACING + */ + +static void +log_msg_va (int level, const char* msg, va_list ap) +{ + size_t len; + char buf[1024]; + + assert (level >= 0); + assert (msg != NULL); + + vsnprintf (buf, sizeof (buf) - 2, msg, ap); + + len = strlen (buf); + buf[len] = '\n'; + buf[len + 1] = '\0'; + +#if _DEBUG + fprintf (stderr, "%s", buf); +#endif + slapi_log_error (level, PLUGIN_NAME, buf); +} + +void +log_msg (int level, const char* msg, ...) +{ + va_list ap; + va_start (ap, msg); + log_msg_va (level, msg, ap); + va_end (ap); +} + +#if _DEBUG +void +log_trace (const char *where, int line, const char *msg) +{ + log_msg (SLAPI_LOG_TRACE, "*** %s *** %s:%d %s%s", + PLUGIN_NAME, where, line, msg ? ": " : "", msg ? msg : ""); +} +#endif + +void +log_plugin (const char* msg, ...) +{ + va_list ap; + va_start (ap, msg); + log_msg_va (SLAPI_LOG_PLUGIN, msg, ap); + va_end (ap); +} + +/* -------------------------------------------------------------------------------- + * HELPERS + */ + +const char* +ltrim (const char *data) +{ + while (*data && isspace (*data)) + ++data; + return data; +} + +void +rtrim (char *data) +{ + char *t = data + strlen (data); + while (t > data && isspace (*(t - 1))) { + t--; + *t = 0; + } +} + +char* +trim (char *data) +{ + data = (char*)ltrim (data); + rtrim (data); + return data; +} + +void +lowercase (char *data) +{ + while (*data) { + *data = tolower (*data); + ++data; + } +} + +/* --------------------------------------------------------------------------------- + * OPERATIONS + */ + +static char* +dn_build_normalize (const char *dn, const char *rdn) +{ + char *result; + Slapi_DN *sdn; + Slapi_DN *parent; + Slapi_RDN *srdn; + + /* Get the parent of the dn */ + sdn = slapi_sdn_new_dn_byval (dn); + return_val_if_fail (sdn, NULL); + parent = slapi_sdn_new (); + slapi_sdn_get_parent (sdn, parent); + + /* Add the rdn to the parent */ + srdn = slapi_rdn_new_dn (rdn); + return_val_if_fail (srdn, NULL); + slapi_sdn_add_rdn (parent, srdn); + + /* Get the result out */ + result = (char*)slapi_sdn_get_dn (parent); + return_val_if_fail (result, NULL); + + /* Normalize the result */ + result = slapi_ch_strdup (result); + slapi_dn_normalize_case (result); + + slapi_sdn_free (&sdn); + slapi_rdn_free (&srdn); + slapi_sdn_free (&parent); + + trace (result); + return result; +} + +static int +post_delete (Slapi_PBlock *pb) +{ + int rc, code; + char *dn; + + return_val_if_fail (pb, -1); + + /* Make sure it was successful, don't process errors */ + rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); + return_val_if_fail (rc >= 0, -1); + if (code != LDAP_SUCCESS) + return 0; + + /* Get out the DN and normalize it */ + rc = slapi_pblock_get (pb, SLAPI_DELETE_TARGET, &dn); + return_val_if_fail (rc >= 0 && dn, -1); + dn = slapi_ch_strdup (dn); + slapi_dn_normalize_case (dn); + + /* Send off to components */ + autoserial_post_delete (dn); + + slapi_ch_free_string (&dn); + return 0; +} + +static int +post_modrdn (Slapi_PBlock *pb) +{ + char *rdn, *odn, *ndn; + int rc, code; + + return_val_if_fail (pb, -1); + + /* Make sure it was successful, don't process errors */ + rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); + return_val_if_fail (rc >= 0, -1); + if (code != LDAP_SUCCESS) + return 0; + + /* Get out the DN and normalize it */ + rc = slapi_pblock_get (pb, SLAPI_MODRDN_TARGET, &odn); + return_val_if_fail (rc >= 0 && odn, -1); + odn = slapi_ch_strdup (odn); + slapi_dn_normalize_case (odn); + + /* Get out the changed DN and calculate it */ + rc = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn); + return_val_if_fail (rc >= 0 && rdn, -1); + ndn = dn_build_normalize (odn, rdn); + return_val_if_fail (ndn, -1); + + /* Send off to components */ + autoserial_post_modrdn (odn, ndn); + + slapi_ch_free_string (&odn); + slapi_ch_free_string (&ndn); + return 0; +} + +static int +post_modify (Slapi_PBlock *pb) +{ + LDAPMod **mods; + char *dn; + int rc, code; + + return_val_if_fail (pb, -1); + + /* Make sure it was successful, don't process errors */ + rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); + return_val_if_fail (rc >= 0, -1); + if (code != LDAP_SUCCESS) + return 0; + + /* Get out the DN and normalize it */ + rc = slapi_pblock_get (pb, SLAPI_MODIFY_TARGET, &dn); + return_val_if_fail (rc >= 0 && dn, -1); + dn = slapi_ch_strdup (dn); + slapi_dn_normalize_case (dn); + + rc = slapi_pblock_get (pb, SLAPI_MODIFY_MODS, &mods); + return_val_if_fail (rc >= 0 && mods, -1); + + /* Send off to components */ + autoserial_post_modify (dn, mods); + dnsnotify_post_modify (dn, mods); + + slapi_ch_free_string (&dn); + return 0; +} + +static int +post_add (Slapi_PBlock *pb) +{ + char *dn; + int rc, code; + + return_val_if_fail (pb, -1); + + /* Make sure it was successful, don't process errors */ + rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); + return_val_if_fail (rc >= 0, -1); + if (code != LDAP_SUCCESS) + return 0; + + /* Get out the DN and normalize it */ + rc = slapi_pblock_get (pb, SLAPI_ADD_TARGET, &dn); + return_val_if_fail (rc >= 0 && dn, -1); + dn = slapi_ch_strdup (dn); + slapi_dn_normalize_case (dn); + + /* Send off to components */ + autoserial_post_add (dn); + dnsnotify_post_add (dn); + + slapi_ch_free_string (&dn); + return 0; +} + +static Slapi_PluginDesc plugin_description = { + PLUGIN_NAME, /* plug-in identifier */ + "stef@memberwebs.com", /* vendor name */ + VERSION, /* plug-in revision number */ + "Notify's DNS slaves when SOA change is made" /* plug-in description */ +}; + +static int +plugin_destroy (Slapi_PBlock *pb) +{ + autoserial_destroy (); + dnsnotify_destroy (); + + slapi_ch_array_free (plugin_arguments); + plugin_arguments = NULL; + + return 0; +} + +int +plugin_init (Slapi_PBlock *pb) +{ + char **argv = NULL, *arg, *value; + int argc = 0; + int rc, i; + + return_val_if_fail (pb, -1); + + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_ARGV, &argv); + return_val_if_fail (rc >= 0, -1); + slapi_pblock_get (pb, SLAPI_PLUGIN_ARGC, &argc); + return_val_if_fail (rc >= 0, -1); + + /* + * Copy all the arguments, until we get destroyed, and + * send the arguments to the components to configure + * themselves. + */ + plugin_arguments = (char**)slapi_ch_calloc (argc + 1, sizeof (char*)); + for (i = 0; i < argc; ++i) { + plugin_arguments[i] = slapi_ch_strdup (argv[i]); + arg = trim (plugin_arguments[i]); + + value = strchr (arg, '='); + if (value) { + *value = 0; + value = trim (value + 1); + } + + lowercase (arg); + + rc = autoserial_config (arg, value); + rc |= dnsnotify_config (arg, value); + + if (!rc) + log_plugin ("unrecognized plugin argument: %s", argv[i]); + } + + /* Next we initialize all components */ + if (autoserial_init () < 0 || + dnsnotify_init() < 0) + return -1; + + if (slapi_pblock_set (pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03) != 0 || + slapi_pblock_set (pb, SLAPI_PLUGIN_DESCRIPTION, &plugin_description) != 0 || + slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, plugin_destroy)) { + log_plugin ("error registring plugin"); + return -1; + } + + /* Setup the entry add/mobify functions */ + if (slapi_pblock_set (pb, SLAPI_PLUGIN_POST_ADD_FN, (void*)post_add) != 0 || + slapi_pblock_set (pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void*)post_modify) != 0 || + slapi_pblock_set (pb, SLAPI_PLUGIN_POST_DELETE_FN, (void*)post_delete) != 0 || + slapi_pblock_set (pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void*)post_modrdn) != 0) { + log_plugin ("error registering plugin hooks"); + return -1; + } + + log_plugin ("%s initialized", PLUGIN_NAME); + return 0; +} + diff --git a/plugin/plugin.h b/plugin/plugin.h new file mode 100644 index 0000000..44080d0 --- /dev/null +++ b/plugin/plugin.h @@ -0,0 +1,72 @@ +#ifndef PLUGIN_H_ +#define PLUGIN_H_ + +#include +#include + +#ifdef HAVE_SLAPI_PLUGIN_H +#include +#else +#include "include/slapi-plugin.h" +#endif + +#define PLUGIN_NAME "slapi-dnsnotify" + +/* --------------------------------------------------------------------- + * LOGGING + */ + +void log_msg (int level, const char* msg, ...); +void log_plugin (const char* msg, ...); + +#define return_val_if_fail(expr, ret) \ + do { if (expr) { } else { \ + log_plugin ("*** %s *** check failed: '%s' at %s:%d", PLUGIN_NAME, #expr, __PRETTY_FUNCTION__, __LINE__); \ + return (ret); \ + } } while (0) +#define return_if_fail(expr) \ + do { if (expr) { } else { \ + log_plugin ("*** %s *** check failed: '%s' at %s:%d", PLUGIN_NAME, #expr, __PRETTY_FUNCTION__, __LINE__); \ + return; \ + } } while (0) + +#if _DEBUG +void log_trace (const char *where, int line, const char *msg); +#define trace(x) log_trace (__PRETTY_FUNCTION__, __LINE__, (x)) +#else +#define trace(x) +#endif + +/* ---------------------------------------------------------------------- + * HELPERS + */ + +const char* ltrim (const char *data); +void rtrim (char *data); +char* trim (char *data); +void lowercase (char *data); + +/* ---------------------------------------------------------------------- + * AUTO SERIAL + */ + +int autoserial_init (void); +void autoserial_destroy (void); +int autoserial_config (const char *name, const char *value); +void autoserial_post_add (const char *dn); +void autoserial_post_modify (const char *dn, LDAPMod **mods); +void autoserial_post_modrdn (const char *odn, const char *ndn); +void autoserial_post_delete (const char *dn); + +/* ---------------------------------------------------------------------- + * DNS NOTIFY + */ + +int dnsnotify_init (void); +void dnsnotify_destroy (void); +int dnsnotify_config (const char *name, const char *value); +void dnsnotify_post_add (const char *dn); +void dnsnotify_post_modify (const char *dn, LDAPMod **mods); + + +#endif /*PLUGIN_H_*/ diff --git a/plugin/slapi-dnsnotify.c b/plugin/slapi-dnsnotify.c deleted file mode 100644 index 329b913..0000000 --- a/plugin/slapi-dnsnotify.c +++ /dev/null @@ -1,529 +0,0 @@ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef HAVE_SLAPI_PLUGIN_H -#include -#else -#include "include/slapi-plugin.h" -#endif - -#define PLUGIN_NAME "slapi-dnsnotify" - -/*---------------------------------------------------------------------------------- - * DECLARATIONS - */ - -static const char *dnsnotify_soa_attribute = "sOARecord"; -static const char *dnsnotify_ns_attribute = "nSRecord"; - -static Slapi_Mutex *dnsnotify_mutex = NULL; -static int dnsnotify_pipe = -1; -static pid_t dnsnotify_pid = -1; - -/* --------------------------------------------------------------------- - * LOGGING/TRACING - */ - -static void -log_msg_va (int level, const char* msg, va_list ap) -{ - size_t len; - char buf[1024]; - - assert (level >= 0); - assert (msg != NULL); - - vsnprintf (buf, sizeof (buf) - 2, msg, ap); - - len = strlen (buf); - buf[len] = '\n'; - buf[len + 1] = '\0'; - -#if _DEBUG - fprintf (stderr, "%s", buf); -#endif - slapi_log_error (level, PLUGIN_NAME, buf); -} - -static void -log_msg (int level, const char* msg, ...) -{ - va_list ap; - va_start (ap, msg); - log_msg_va (level, msg, ap); - va_end (ap); -} - -#if _DEBUG -static void -log_trace (const char *where, int line, const char *msg) -{ - log_msg (SLAPI_LOG_TRACE, "*** %s *** %s:%d %s%s", - PLUGIN_NAME, where, line, msg ? ": " : "", msg ? msg : ""); -} -#endif - -static void -log_plugin (const char* msg, ...) -{ - va_list ap; - va_start (ap, msg); - log_msg_va (SLAPI_LOG_PLUGIN, msg, ap); - va_end (ap); -} - -#define return_val_if_fail(expr, ret) \ - do { if (expr) { } else { \ - log_plugin ("*** %s *** check failed: '%s' at %s:%d", PLUGIN_NAME, #expr, __PRETTY_FUNCTION__, __LINE__); \ - return (ret); \ - } } while (0) - -#if _DEBUG -#define trace(x) log_trace (__PRETTY_FUNCTION__, __LINE__, (x)) -#else -#define trace(x) -#endif - -/* --------------------------------------------------------------------------------- - * OPERATIONS - */ - -static int -load_soa_ns_attributes (const char *dn, char **soa_result, char ***ns_result) -{ - Slapi_Entry **entries; - struct berval **values, **v; - Slapi_PBlock *pb; - Slapi_Attr *attr; - LDAPControl *ctrl; - char *attrs[2]; - char *soa, **ns; - int rc, code, num, i; - - trace (dn); - assert (dn && dn[0]); - assert (dnsnotify_soa_attribute); - assert (dnsnotify_ns_attribute); - - ctrl = NULL; /* No controls */ - attrs[0] = (char*)dnsnotify_soa_attribute; - attrs[1] = NULL; - - trace ("performing internal search"); - - /* Do the actual search */ - pb = slapi_search_internal ((char*)dn, LDAP_SCOPE_BASE, "(objectClass=*)", &ctrl, attrs, 0); - return_val_if_fail (pb, 0); - - /* Was it successful? */ - code = -1; - rc = slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &code); - return_val_if_fail (rc >= 0, 0); - if (code != LDAP_SUCCESS) { - log_plugin ("error loading attribute %s from %s (code %d)", dnsnotify_soa_attribute, dn, code); - slapi_pblock_destroy (pb); - trace ("failure"); - return 0; - } - - /* Dig out all the entries */ - entries = NULL; - slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); - return_val_if_fail (entries, 0); - - soa = NULL; - if (entries[0]) { - if (slapi_entry_attr_find (entries[0], (char*)dnsnotify_soa_attribute, &attr) >= 0 && attr && - slapi_attr_get_values (attr, &values) >= 0 && values && values[0]) { - - /* Convert the berval into a string */ - soa = slapi_ch_malloc (values[0]->bv_len + 1); - if (values[0]->bv_len) - memcpy (soa, values[0]->bv_val, values[0]->bv_len); - soa[values[0]->bv_len] = 0; - } - - if (slapi_entry_attr_find (entries[0], (char*)dnsnotify_ns_attribute, &attr) >= 0 && attr && - slapi_attr_get_values (attr, &values) >= 0 && values) { - - num = 0; - for (v = values; *v; ++v) - ++num; - - ns = (char**)slapi_ch_calloc (num, sizeof (char*)); - for (i = 0; i < num; ++i) { - ns[i] = slapi_ch_malloc (values[i]->bv_len + 1); - if (values[i]->bv_len) - memcpy (ns[i], values[i]->bv_val, values[i]->bv_len); - ns[i][values[i]->bv_len] = 0; - } - } - } - - slapi_pblock_destroy (pb); - - /* Only proceed if we have all the data we need */ - if (soa && soa[0] && ns && ns[0] && ns[0]) { - *soa_result = soa; - *ns_result = ns; - return 1; - } - - slapi_ch_free_string (&soa); - slapi_ch_array_free (ns); - - return 0; -} - -static void -kill_notify_process (void) -{ - int status; - - if (dnsnotify_pipe != -1) - close (dnsnotify_pipe); - dnsnotify_pipe = -1; - - if (dnsnotify_pid != -1) { - if (waitpid (dnsnotify_pid, &status, WNOHANG) == 0) { - /* We can play rough, because we know process can take it */ - if (kill (dnsnotify_pid, SIGKILL) >= 0) - waitpid (dnsnotify_pid, &status, 0); - } - dnsnotify_pid = -1; - } -} - -static int -fork_notify_process (void) -{ - int open_max, fd; - int commpipe[2]; - char *args[3]; - pid_t pid; - - if (pipe (commpipe) < 0) { - log_plugin ("couldn't create communication pipe for child process: %s", strerror (errno)); - return -1; - } - - pid = fork(); - switch (pid) { - case -1: - log_plugin ("couldn't fork for child notify process: %s", strerror (errno)); - close (commpipe[0]); - close (commpipe[1]); - return -1; - - /* The child, continues below */ - case 0: - break; - - /* The parent */ - default: - - /* close read end */ - close (commpipe[0]); - - /* Kill any previous process */ - kill_notify_process (); - - /* hang onto write end, and new pid */ - dnsnotify_pipe = commpipe[1]; - dnsnotify_pid = pid; - - return 0; - }; - - /* The child process, here */ - close (commpipe[1]); - - /* Dup the pipe into stdin of the process */ - if (dup2 (commpipe[0], 0) < 0) - log_plugin ("couldn't setup stdin on notify child process: %s", strerror (errno)); - - /* Close all other file descriptors */ - open_max = sysconf (_SC_OPEN_MAX); - for (fd = 3; fd < open_max; ++fd) - close (fd); - - args[0] = DNS_NOTIFY_PATH; - args[1] = "-s"; - args[2] = NULL; - - execv (args[0], args); - log_plugin ("couldn't launch '%s' process: %s", DNS_NOTIFY_PATH, strerror (errno)); - _exit (1); -} - -static ssize_t -write_all (int fd, const void *b, size_t len) -{ - const unsigned char *buf = (const unsigned char*)b; - ssize_t l, left = len; - - while (left > 0) { - l = write (fd, buf, len); - if (l < 0) { - if (errno == EINTR && errno == EAGAIN) - continue; - return -1; - } else { - left -= l; - buf += l; - } - } - - return len; -} - -static char* -prep_soa_domain (char *soa) -{ - size_t at; - - assert (soa && soa[0]); - - /* Find the first space in the SOA and cut it off there */ - while (soa[0] && isspace (soa[0])) - ++soa; - at = strcspn (soa, " \t\n\r\v"); - if (at == 0) { - log_plugin ("invalid SOA present"); - return NULL; - } - - soa[at] = 0; - trace (soa); - return soa; -} - -static void -notify_dns_slaves (char *soa, char **ns) -{ - int i, complete = 1; - char *n; - - trace (soa); - - assert (ns); - assert (soa && soa[0]); - - if (!*ns) - return; - - soa = prep_soa_domain (soa); - if (!soa) - return; - - slapi_lock_mutex (dnsnotify_mutex); - - /* Try this twice in case things have closed up shop */ - for (i = 0; i < 2; ++i) { - - if (dnsnotify_pipe < 0 || i > 0) - if (fork_notify_process () < 0) - break; - - assert (dnsnotify_pipe >= 0); - - for (; *ns; ++ns) { - n = *ns; - - /* An empty string ? */ - while (n[0] && isspace (n[0])) - ++n; - if (!n[0]) - continue; - - if (write_all (dnsnotify_pipe, "NOTIFY: ", 8) < 0 || - write_all (dnsnotify_pipe, soa, strlen (soa)) < 0 || - write_all (dnsnotify_pipe, " ", 1) < 0 || - write_all (dnsnotify_pipe, *ns, strlen (*ns)) < 0 || - write_all (dnsnotify_pipe, "\n", 1) < 0) { - if (errno == EPIPE) { - complete = 0; - break; - } - log_plugin ("couldn't write data to child notify process: %s", strerror (errno)); - } - } - - if (complete) - break; - } - - slapi_unlock_mutex (dnsnotify_mutex); -} - -static int -slapi_dnsnotify_modify (Slapi_PBlock *pb) -{ - LDAPMod **mods, **m, *mod; - char *dn, *soa, **ns; - int rc, code; - - return_val_if_fail (pb, -1); - - trace ("detect modify"); - - /* Make sure it was successful, don't process errors */ - rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); - return_val_if_fail (rc >= 0, -1); - if (code != LDAP_SUCCESS) - return 0; - - /* Get out the DN and normalize it */ - rc = slapi_pblock_get (pb, SLAPI_MODIFY_TARGET, &dn); - return_val_if_fail (rc >= 0 && dn, -1); - dn = slapi_ch_strdup (dn); - slapi_dn_normalize_case (dn); - - rc = slapi_pblock_get (pb, SLAPI_MODIFY_MODS, &mods); - return_val_if_fail (rc >= 0 && mods, -1); - - for (m = mods; *m; ++m) { - mod = *m; - trace (mod->mod_type); - if (strcmp (mod->mod_type, dnsnotify_soa_attribute) == 0) { - if (load_soa_ns_attributes (dn, &soa, &ns)) { - notify_dns_slaves (soa, ns); - slapi_ch_array_free (ns); - slapi_ch_free_string (&soa); - } - break; - } - } - - slapi_ch_free_string (&dn); - return 0; -} - -static int -slapi_dnsnotify_add (Slapi_PBlock *pb) -{ - char *dn, *soa, **ns; - int rc, code, ret; - - return_val_if_fail (pb, -1); - - /* Make sure it was successful, don't process errors */ - rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); - return_val_if_fail (rc >= 0, -1); - if (code != LDAP_SUCCESS) - return 0; - - /* Get out the DN and normalize it */ - rc = slapi_pblock_get (pb, SLAPI_ADD_TARGET, &dn); - return_val_if_fail (rc >= 0 && dn, -1); - dn = slapi_ch_strdup (dn); - slapi_dn_normalize_case (dn); - - /* A DN that is an SOA? */ - if (load_soa_ns_attributes (dn, &soa, &ns)) { - notify_dns_slaves (soa, ns); - slapi_ch_free_string (&soa); - slapi_ch_array_free (ns); - } - - slapi_ch_free_string (&dn); - return ret; -} - -static Slapi_PluginDesc plugin_description = { - PLUGIN_NAME, /* plug-in identifier */ - "stef@memberwebs.com", /* vendor name */ - VERSION, /* plug-in revision number */ - "Notify's DNS slaves when SOA change is made" /* plug-in description */ -}; - -static int -plugin_destroy (Slapi_PBlock *pb) -{ - if (dnsnotify_mutex) - slapi_lock_mutex (dnsnotify_mutex); - - kill_notify_process (); - - if (dnsnotify_mutex) { - slapi_unlock_mutex (dnsnotify_mutex); - slapi_destroy_mutex (dnsnotify_mutex); - dnsnotify_mutex = NULL; - } - - return 0; -} - -int -plugin_init (Slapi_PBlock *pb) -{ - char **argv = NULL; - int argc = 0; - int rc; - - return_val_if_fail (pb, -1); - - rc = slapi_pblock_get (pb, SLAPI_PLUGIN_ARGV, &argv); - return_val_if_fail (rc >= 0, -1); - slapi_pblock_get (pb, SLAPI_PLUGIN_ARGC, &argc); - return_val_if_fail (rc >= 0, -1); - - if (argc >= 1) - dnsnotify_soa_attribute = argv[0]; - if (argc >= 2) - dnsnotify_ns_attribute = argv[1]; - if (argc > 2) - log_plugin ("extra arguments present"); - - if (!dnsnotify_soa_attribute || !dnsnotify_soa_attribute[0]) { - log_plugin ("DNS SOA attribute specified for plugin is empty"); - return -1; - } - - if (!dnsnotify_ns_attribute || !dnsnotify_ns_attribute[0]) { - log_plugin ("DNS SOA attribute specified for plugin is empty"); - return -1; - } - - if (slapi_pblock_set (pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03) != 0 || - slapi_pblock_set (pb, SLAPI_PLUGIN_DESCRIPTION, &plugin_description) != 0 || - slapi_pblock_set (pb, SLAPI_PLUGIN_DESTROY_FN, plugin_destroy)) { - log_plugin ("error registring plugin"); - return -1; - } - - /* - * TODO: Add support for changing the default attributes and search uri - * Accept an argument for this. - */ - - /* Setup the entry add/mobify functions */ - if (slapi_pblock_set (pb, SLAPI_PLUGIN_POST_ADD_FN, (void*)slapi_dnsnotify_add) != 0 || - slapi_pblock_set (pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void*)slapi_dnsnotify_modify)) { - log_plugin ("error registering plugin hooks"); - return -1; - } - - assert (!dnsnotify_mutex); - dnsnotify_mutex = slapi_new_mutex (); - return_val_if_fail (dnsnotify_mutex, -1); - - log_plugin ("%s initialized", PLUGIN_NAME); - return 0; -} - diff --git a/tests/Makefile.am b/tests/Makefile.am index 5372107..ee0f4af 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,7 @@ -UNIT_TESTS = +UNIT_TESTS = \ + unit-test-soa.c \ + unit-test-strset.c INCLUDES= \ -I$(top_srcdir) \ diff --git a/tests/unit-test-soa.c b/tests/unit-test-soa.c new file mode 100644 index 0000000..9716a65 --- /dev/null +++ b/tests/unit-test-soa.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2008, Stefan Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Stef Walter + * + */ + +#include "config.h" + +#include "run-tests.h" + +#include "common/soa.h" + +#include +#include +#include + +/* + * Each test looks like (on one line): + * void unit_test_xxxxx (CuTest* cu) + * + * Each setup looks like (on one line): + * void unit_setup_xxxxx (void) + * + * Each teardown looks like (on one line): + * void unit_teardown_xxxxx (void) + * + * Tests be run in the order specified here. + */ + +typedef struct _soa_test { + const char *old; + const char *new; +} soa_test; + +static const soa_test SOA_TESTS[] = { + { + "web.example.com stef@example.com 300 3600 1800 604800 86400", + "web.example.com stef@example.com 301 3600 1800 604800 86400" + }, + { + "web.example.com stef@example.com (0 3600 1800 604800 86400)", + "web.example.com stef@example.com (1 3600 1800 604800 86400)" + }, + { + "web.example.com stef@example.com (\n999 3600 1800 604800 86400 ) ; comment", + "web.example.com stef@example.com (\n1000 3600 1800 604800 86400 ) ; comment" + }, + { /* Wrap around */ + "web.example.com stef@example.com 4294967295 3600 1800 604800 86400", + "web.example.com stef@example.com 1 3600 1800 604800 86400" + }, + { + NULL, + NULL + } +}; + +static const char *SOA_INVALID[] = { + "web.example.com email@example.com non-mumeric 3600 1800 604800 86400", + "web.example.com email@example.com -3000 3600 1800 604800 86400", + "", + "web.example.com", + NULL +}; + +void unit_test_soa_increment (CuTest *cu) +{ + char buf[512]; + int rc, i; + + for (i = 0; 1; ++i) { + if (!SOA_TESTS[i].old) + break; + rc = soa_serial_increment (SOA_TESTS[i].old, buf, sizeof (buf)); + if (rc < 0) + printf ("%s\n", SOA_TESTS[i].old); + CuAssert(cu, "soa_serial_increment failed", rc >= 0); + CuAssertStrEquals(cu, buf, SOA_TESTS[i].new); + } +} + +void unit_test_soa_invalid (CuTest *cu) +{ + char buf[512]; + int rc, i; + + for (i = 0; 1; ++i) { + if (!SOA_INVALID[i]) + break; + + errno = 0; + rc = soa_serial_increment (SOA_INVALID[i], buf, sizeof (buf)); + if (rc >= 0) + printf ("%s\n", SOA_INVALID[i]); + CuAssert (cu, "invalid soa_serial_increment succeeded", rc < 0); + CuAssert (cu, "bad errno set on invalid soa", errno == EINVAL); + } +} + +/* ----------------------------------------------------------------------------- + * Code being tested + */ + +#include "common/soa.c" diff --git a/tests/unit-test-strset.c b/tests/unit-test-strset.c new file mode 100644 index 0000000..931def9 --- /dev/null +++ b/tests/unit-test-strset.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2008, Stefan Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Stef Walter + * + */ + +#include "config.h" + +#include "run-tests.h" + +#include "common/strset.h" + +#include +#include +#include + +/* + * Each test looks like (on one line): + * void unit_test_xxxxx (CuTest* cu) + * + * Each setup looks like (on one line): + * void unit_setup_xxxxx (void) + * + * Each teardown looks like (on one line): + * void unit_teardown_xxxxx (void) + * + * Tests be run in the order specified here. + */ + +void unit_test_soa_strset (CuTest *cu) +{ + strset *set; + int rc, i; + char buf[32]; + + set = strset_create (); + CuAssertPtrNotNull (cu, set); + + CuAssert (cu, "found in empty set", !strset_has (set, "blah")); + + rc = strset_add (set, "blah"); + CuAssertIntEquals (cu, 0, rc); + + rc = strset_add (set, NULL); + CuAssertIntEquals (cu, -1, rc); + + CuAssert (cu, "not found in set", strset_has (set, "blah")); + + /* Add some stuff so it forces us to reallocate */ + for (i = 0; i < 128; ++i) { + snprintf (buf, sizeof (buf), "value-%d", i); + strset_add (set, buf); + } + + CuAssert (cu, "value-30 not found in set", strset_has (set, "value-30")); + + rc = strset_add (set, ""); + CuAssertIntEquals (cu, 0, rc); + + CuAssert (cu, "empty not found in set", strset_has (set, "")); + + rc = strset_remove (set, "blah"); + CuAssertIntEquals (cu, 0, rc); + + CuAssert (cu, "removed found in set", !strset_has (set, "blah")); + + strset_destroy (set); +} + +/* ----------------------------------------------------------------------------- + * Code being tested + */ + +#include "common/strset.c" diff --git a/tools/notify-dns-slaves.c b/tools/notify-dns-slaves.c index 96f2ea7..018af43 100644 --- a/tools/notify-dns-slaves.c +++ b/tools/notify-dns-slaves.c @@ -96,7 +96,7 @@ static const char *DNS_ERRORS[] = { #define RETRIES 3 /* Number of times we send out packet */ #define RETRY_INTERVAL 400 /* Milliseconds between sending out packet */ #define TIMEOUT_INTERVAL 5000 /* Timeout for response in milliseconds */ -#define DELAY_INTERVAL 5000 /* Number of milliseconds before processing input on stdin */ +#define DEFAULT_DELAY_INTERVAL 5000 /* Number of milliseconds before processing input on stdin */ #define LINE_LENGTH 1023 /* Maximum length of buffer on stdin */ static int the_socket4 = -1; @@ -108,6 +108,7 @@ static size_t stdin_offset = 0; static int processing_active = 0; static int input_complete = 0; +static int delay_interval = -1; static unsigned int unique_identifier = 1; static int is_helper = 0; @@ -624,7 +625,7 @@ process_stdin_line (char *line) } server[len] = 0; - process_notify (zone, server, DELAY_INTERVAL); + process_notify (zone, server, delay_interval); } static void @@ -753,11 +754,11 @@ main(int argc, char *argv[]) char *str; int ch = 0, i; - while ((ch = getopt (argc, argv, "d:s")) != -1) { + while ((ch = getopt (argc, argv, "d:sw:")) != -1) { switch (ch) { case 'd': - debug_level = strtol(optarg, &str, 10); + debug_level = strtol (optarg, &str, 10); if (*str || debug_level > 4) fatalx (1, "invalid debug log level: %s", optarg); debug_level += LOG_ERR; @@ -765,6 +766,10 @@ main(int argc, char *argv[]) case 's': is_helper = 1; break; + case 'w': + delay_interval = strtol (optarg, &str, 10); + if (*str || delay_interval > 0) + fatalx (1, "invalid delay interval: %s", optarg); case '?': default: usage (); @@ -787,13 +792,20 @@ main(int argc, char *argv[]) if (is_helper) { openlog ("notify-dns-slaves", 0, LOG_DAEMON); + input_complete = 0; + if (delay_interval < 0) + delay_interval = DEFAULT_DELAY_INTERVAL; + /* Watch stdin for data */ fcntl (0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK); if (server_watch (0, SERVER_READ, stdin_callback, NULL) < 0) fatal (1, "coludn't watch stdin for changes"); } else { input_complete = 1; + if (delay_interval < 0) + delay_interval = 0; + str = argv[0]; for (i = 1; i < argc; ++i) process_notify (str, argv[i], 0); -- cgit v1.2.3