diff options
Diffstat (limited to 'plugin/dnsnotify.c')
-rw-r--r-- | plugin/dnsnotify.c | 409 |
1 files changed, 409 insertions, 0 deletions
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 <sys/wait.h> + +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <ctype.h> + +/*---------------------------------------------------------------------------------- + * 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; + } +} |