#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 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; }