/* * 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. */ #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 const char *dnsnotify_zone_attribute = "associatedDomain"; 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_zone_attributes (const char *dn, char **zone_result, char ***ns_result) { Slapi_Entry **entries; struct berval **values, **v; Slapi_PBlock *pb; Slapi_Attr *attr; LDAPControl *ctrl; char *attrs[4]; char *zone, **ns; int rc, code, num, i; trace (dn); assert (dn && dn[0]); assert (dnsnotify_ns_attribute); assert (dnsnotify_zone_attribute); ctrl = NULL; /* No controls */ attrs[0] = (char*)dnsnotify_zone_attribute; attrs[1] = (char*)dnsnotify_ns_attribute; attrs[2] = 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_zone_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); zone = NULL; ns = NULL; if (entries[0]) { if (slapi_entry_attr_find (entries[0], (char*)dnsnotify_zone_attribute, &attr) >= 0 && attr && slapi_attr_get_values (attr, &values) >= 0 && values && values[0]) { /* Convert the berval into a string */ zone = slapi_ch_malloc (values[0]->bv_len + 1); if (values[0]->bv_len) memcpy (zone, values[0]->bv_val, values[0]->bv_len); zone[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 + 1, 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; } /* Null terminate */ ns[i] = NULL; } } slapi_pblock_destroy (pb); /* Only proceed if we have all the data we need */ if (zone && zone[0] && ns && ns[0]) { *zone_result = zone; *ns_result = ns; return 1; } slapi_ch_free_string (&zone); 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 void notify_dns_slaves (char *zone, char **ns) { int i, complete = 1; char *n; trace (zone); assert (ns); assert (zone && zone[0]); if (!*ns) 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, zone, strlen (zone)) < 0 || write_all (dnsnotify_pipe, " ", 1) < 0 || write_all (dnsnotify_pipe, n, strlen (n)) < 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 *zone, **ns; return_if_fail (dn && dn[0]); return_if_fail (mods); if (!dnsnotify_enable) return; for (m = mods; *m; ++m) { mod = *m; if (mod->mod_type && strcasecmp (mod->mod_type, dnsnotify_soa_attribute) == 0) { if (load_zone_attributes (dn, &zone, &ns)) { notify_dns_slaves (zone, ns); slapi_ch_array_free (ns); slapi_ch_free_string (&zone); } break; } } } void dnsnotify_post_add (const char *dn) { char *zone, **ns; return_if_fail (dn); if (!dnsnotify_enable) return; /* A DN that is an SOA? */ if (load_zone_attributes (dn, &zone, &ns)) { notify_dns_slaves (zone, ns); slapi_ch_free_string (&zone); 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, "zone-attribute") == 0 && value) { dnsnotify_zone_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; } }