diff options
Diffstat (limited to 'plugin/slapi-dnsnotify.c')
-rw-r--r-- | plugin/slapi-dnsnotify.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/plugin/slapi-dnsnotify.c b/plugin/slapi-dnsnotify.c new file mode 100644 index 0000000..b871c86 --- /dev/null +++ b/plugin/slapi-dnsnotify.c @@ -0,0 +1,491 @@ + +#include "config.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> + +#include <lber.h> +#include <ldap.h> + +#ifdef HAVE_SLAPI_PLUGIN_H +#include <slapi-plugin.h> +#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 int +fork_notify_process (void) +{ + pid_t pid; + int status; + int commpipe[2]; + char *args[3]; + + 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]); + + /* hang onto write end */ + if (dnsnotify_pipe != -1) + close (dnsnotify_pipe); + dnsnotify_pipe = commpipe[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 = 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)); + + 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 = strspn (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: ", 5) < 0 || + write_all (dnsnotify_pipe, soa, strlen (soa)) < 0 || + write_all (dnsnotify_pipe, " ", 1) < 0 || + write_all (dnsnotify_pipe, *ns, strlen (soa)) < 0 || + write_all (dnsnotify_pipe, "\n", 2) < 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); + + /* 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; + 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 */ +}; + +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) { + 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; +} + |