summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--COPYING31
-rw-r--r--ChangeLog2
l---------INSTALL1
-rw-r--r--Makefile.am6
-rw-r--r--NEWS1
-rw-r--r--README3
-rw-r--r--acsite.m4227
-rwxr-xr-xautogen.sh11
-rw-r--r--common/async-resolver.c366
-rw-r--r--common/async-resolver.h48
-rw-r--r--common/server-mainloop.c434
-rw-r--r--common/server-mainloop.h56
-rw-r--r--common/sock-any.c406
-rw-r--r--common/sock-any.h103
-rw-r--r--configure.in92
-rw-r--r--include/slapi-plugin.h903
-rw-r--r--plugin/Makefile.am13
-rw-r--r--plugin/slapi-dnsnotify.c491
-rw-r--r--tests/Makefile.am29
-rw-r--r--tests/cu-test/AllTests.c25
-rw-r--r--tests/cu-test/CuTest.c309
-rw-r--r--tests/cu-test/CuTest.h111
-rw-r--r--tests/cu-test/CuTestTest.c709
-rw-r--r--tests/cu-test/README209
-rw-r--r--tests/cu-test/license.txt38
-rwxr-xr-xtests/cu-test/make-tests.sh51
-rwxr-xr-xtests/prep-tests.sh114
-rw-r--r--tests/test-helpers.c51
-rw-r--r--tests/test-helpers.h42
-rw-r--r--tools/Makefile.am17
-rw-r--r--tools/notify-slaves.c813
32 files changed, 5713 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..0dc24e0
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+stef@memberwebs.com
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..12d603c
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,31 @@
+
+Copyright (c) 2008, Stef 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.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..883c9fc
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,2 @@
+Version 0.1
+ - Initial version
diff --git a/INSTALL b/INSTALL
new file mode 120000
index 0000000..5bb6e7b
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1 @@
+/usr/share/automake-1.10/INSTALL \ No newline at end of file
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..5cc07b6
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,6 @@
+EXTRA_DIST = common include
+SUBDIRS = plugin tools tests
+
+dist-hook:
+ rm -rf `find $(distdir)/ -name .svn`
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..c7ab92a
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+See ChangeLog \ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..d1d8a13
--- /dev/null
+++ b/README
@@ -0,0 +1,3 @@
+=================================================================
+ SLAPI-DNSNOTIFY 0.1 README
+
diff --git a/acsite.m4 b/acsite.m4
new file mode 100644
index 0000000..c28ff75
--- /dev/null
+++ b/acsite.m4
@@ -0,0 +1,227 @@
+dnl Available from the GNU Autoconf Macro Archive at:
+dnl http://www.gnu.org/software/ac-archive/htmldoc/acx_pthread.html
+dnl
+AC_DEFUN([ACX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_LANG_SAVE
+AC_LANG_C
+acx_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on True64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
+ AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes)
+ AC_MSG_RESULT($acx_pthread_ok)
+ if test x"$acx_pthread_ok" = xno; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
+# -pthreads: Solaris/gcc
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads too;
+# also defines -D_REENTRANT)
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case "${host_cpu}-${host_os}" in
+ *solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (We need to link with -pthread or
+ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
+ # a function called by this macro, so we could check for that, but
+ # who knows whether they'll stub that too in a future libc.) So,
+ # we'll just look for -pthreads and -lpthread first:
+
+ acx_pthread_flags="-pthread -pthreads pthread -mt $acx_pthread_flags"
+ ;;
+esac
+
+if test x"$acx_pthread_ok" = xno; then
+for flag in $acx_pthread_flags; do
+
+ case $flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $flag])
+ PTHREAD_CFLAGS="$flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no)
+ if test x"$acx_pthread_config" = xno; then continue; fi
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$flag])
+ PTHREAD_LIBS="-l$flag"
+ ;;
+ esac
+
+ save_LIBS="$LIBS"
+ save_CFLAGS="$CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+ AC_TRY_LINK([#include <pthread.h>],
+ [pthread_t th; pthread_join(th, 0);
+ pthread_attr_init(0); pthread_cleanup_push(0, 0);
+ pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
+ [acx_pthread_ok=yes])
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ AC_MSG_RESULT($acx_pthread_ok)
+ if test "x$acx_pthread_ok" = xyes; then
+ break;
+ fi
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$acx_pthread_ok" = xyes; then
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Detect AIX lossage: threads are created detached by default
+ # and the JOINABLE attribute has a nonstandard name (UNDETACHED).
+ AC_MSG_CHECKING([for joinable pthread attribute])
+ AC_TRY_LINK([#include <pthread.h>],
+ [int attr=PTHREAD_CREATE_JOINABLE;],
+ ok=PTHREAD_CREATE_JOINABLE, ok=unknown)
+ if test x"$ok" = xunknown; then
+ AC_TRY_LINK([#include <pthread.h>],
+ [int attr=PTHREAD_CREATE_UNDETACHED;],
+ ok=PTHREAD_CREATE_UNDETACHED, ok=unknown)
+ fi
+ if test x"$ok" != xPTHREAD_CREATE_JOINABLE; then
+ AC_DEFINE(PTHREAD_CREATE_JOINABLE, $ok,
+ [Define to the necessary symbol if this constant
+ uses a non-standard name on your system.])
+ fi
+ AC_MSG_RESULT(${ok})
+ if test x"$ok" = xunknown; then
+ AC_MSG_WARN([we do not know how to create joinable pthreads])
+ fi
+
+ AC_MSG_CHECKING([if more special flags are required for pthreads])
+ flag=no
+ case "${host_cpu}-${host_os}" in
+ *-aix* | *-freebsd*) flag="-D_THREAD_SAFE";;
+ *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";;
+ esac
+ AC_MSG_RESULT(${flag})
+ if test "x$flag" != xno; then
+ PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
+ fi
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ # More AIX lossage: must compile with cc_r
+ AC_CHECK_PROG(PTHREAD_CC, cc_r, cc_r, ${CC})
+else
+ PTHREAD_CC="$CC"
+fi
+
+AC_SUBST(PTHREAD_LIBS)
+AC_SUBST(PTHREAD_CFLAGS)
+AC_SUBST(PTHREAD_CC)
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test x"$acx_pthread_ok" = xyes; then
+ ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1])
+ :
+else
+ acx_pthread_ok=no
+ $2
+fi
+AC_LANG_RESTORE
+])dnl ACX_PTHREAD
+
+
+AC_DEFUN(AC_CHECK_GLOBAL,
+[
+for ac_global in $1
+do
+ ac_tr_global=HAVE_`echo $ac_global | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ AC_MSG_CHECKING([for global variable ${ac_global}])
+ AC_CACHE_VAL(ac_cv_global_$ac_global,
+ [
+ AC_TRY_LINK(dnl
+ [/* no includes */],
+ [ extern long int $ac_global; exit((int)$ac_global)],
+ eval "ac_cv_global_${ac_global}=yes",
+ eval "ac_cv_global_${ac_global}=no"
+ )
+ ]
+ )
+ if eval "test \"`echo '$ac_cv_global_'$ac_global`\" = yes"; then
+ AC_MSG_RESULT(yes)
+ AC_DEFINE_UNQUOTED($ac_tr_global,1,Define if the global variable $ac_global is available)
+ else
+ AC_MSG_RESULT(no)
+ fi
+done
+])
+
+
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..100e70d
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -ex
+
+set -ex
+
+aclocal
+autoheader
+libtoolize --force
+automake -a
+autoconf
+./configure --enable-maintainer-mode "$@"
+
diff --git a/common/async-resolver.c b/common/async-resolver.c
new file mode 100644
index 0000000..8485bb2
--- /dev/null
+++ b/common/async-resolver.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2006, 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "async-resolver.h"
+#include "server-mainloop.h"
+
+/* -----------------------------------------------------------------------------
+ * THREAD COMMUNICATION
+ */
+
+#define TSIGNAL_UNITITIALIZED { -1, -1 }
+
+static int
+tsignal_init(int* sig)
+{
+ if(pipe(sig) == -1)
+ return -1;
+ fcntl(sig[0], F_SETFL, fcntl(sig[0], F_GETFL, 0) | O_NONBLOCK);
+ return 0;
+}
+
+static int
+tsignal_get_fd(int* sig)
+{
+ return sig[0];
+}
+
+static void
+tsignal_wake(int* sig)
+{
+ write(sig[1], "1", 1);
+}
+
+static void
+tsignal_clear(int* sig)
+{
+ char buf[16];
+ while(read(sig[0], buf, sizeof(buf)) > 0);
+}
+
+static void
+tsignal_wait(int* sig, struct timeval* tv)
+{
+ fd_set watch;
+ FD_ZERO(&watch);
+ FD_SET(sig[0], &watch);
+ select(sig[0], &watch, NULL, NULL, tv);
+}
+
+static void
+tsignal_uninit(int* sig)
+{
+ if(sig[1] != -1)
+ close(sig[1]);
+ sig[1] = -1;
+ if(sig[0] != -1)
+ close(sig[0]);
+ sig[0] = -1;
+}
+
+/* -----------------------------------------------------------------------------
+ * RESOLVER
+ */
+
+typedef struct _resolve_request
+{
+ char hostname[256];
+ char servname[256];
+ struct addrinfo hints;
+ async_resolve_callback cb;
+ void *arg;
+
+ int gaierr;
+ int errn;
+ struct addrinfo *ai;
+
+ struct _resolve_request *next;
+}
+resolve_request;
+
+/* The queues */
+static int res_quit = 0;
+static resolve_request* res_requests = NULL;
+static resolve_request* res_done = NULL;
+
+/* Thread communication */
+static pthread_t res_thread = 0;
+static pthread_mutex_t res_mutex = PTHREAD_MUTEX_INITIALIZER;
+static int res_request_signal[2] = TSIGNAL_UNITITIALIZED;
+static int res_done_signal[2] = TSIGNAL_UNITITIALIZED;
+
+static void*
+resolver_thread(void* arg)
+{
+ resolve_request* req;
+ resolve_request* r;
+ struct timeval tv;
+
+ while(!res_quit)
+ {
+ pthread_mutex_lock(&res_mutex);
+
+ /* Dig out any requests */
+ req = res_requests;
+ if(req)
+ {
+ res_requests = req->next;
+ req->next = NULL;
+ }
+
+ pthread_mutex_unlock(&res_mutex);
+
+ /* No requests, wait for a request */
+ if(!req)
+ {
+ tv.tv_sec = 0;
+ tv.tv_usec = 500000;
+ tsignal_wait(res_request_signal, &tv);
+ tsignal_clear(res_request_signal);
+ continue;
+ }
+
+ /* The actual resolve */
+ req->gaierr = getaddrinfo(req->hostname, req->servname[0] ? req->servname : NULL,
+ &req->hints, &req->ai);
+ req->errn = errno;
+
+ /* A timeout */
+ if(!req->gaierr && !req->ai)
+ {
+ req->gaierr = EAI_SYSTEM;
+ req->errn = ETIMEDOUT;
+ }
+
+ /* Append the result to done */
+ pthread_mutex_lock(&res_mutex);
+
+ if(!res_done)
+ {
+ res_done = req;
+ }
+ else
+ {
+ r = res_done;
+ while(r->next)
+ r = r->next;
+ r->next = req;
+ }
+
+ pthread_mutex_unlock(&res_mutex);
+
+ /* Tell the main thread to check outbound */
+ tsignal_wake(res_done_signal);
+ }
+
+ return NULL;
+}
+
+static void
+resolver_done(int fd, int type, void* arg)
+{
+ resolve_request* req;
+ resolve_request* r;
+
+ tsignal_clear(res_done_signal);
+
+ pthread_mutex_lock(&res_mutex);
+
+ req = res_done;
+ res_done = NULL;
+
+ pthread_mutex_unlock(&res_mutex);
+
+ while(req)
+ {
+ /* Send off the result */
+ errno = req->errn;
+ (req->cb)(req->gaierr, req->ai, req->arg);
+
+ /* And free it all */
+ r = req->next;
+ if(req->ai)
+ freeaddrinfo(req->ai);
+ free(req);
+
+ req = r;
+ }
+}
+
+int
+async_resolver_init()
+{
+ int r;
+
+ /* The signal pipes */
+ if(tsignal_init(res_request_signal) < 0)
+ return -1;
+ if(tsignal_init(res_done_signal) < 0)
+ return -1;
+
+ if(server_watch(tsignal_get_fd(res_done_signal), SERVER_READ, resolver_done, NULL) == -1)
+ return -1;
+
+ r = pthread_create(&res_thread, NULL, resolver_thread, NULL);
+ if(r != 0)
+ {
+ res_thread = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+async_resolver_queue(const char* hostname, const char* servname,
+ struct addrinfo* hints, async_resolve_callback cb, void* arg)
+{
+ resolve_request* req;
+ resolve_request* r;
+ char* t;
+
+ if(!res_thread)
+ {
+ /* All errors go to callback */
+ errno = ESRCH;
+ (cb)(EAI_SYSTEM, NULL, arg);
+ return;
+ }
+
+ req = calloc(1, sizeof(resolve_request));
+ if(!req)
+ {
+ /* All errors go to callback */
+ (cb)(EAI_MEMORY, NULL, arg);
+ return;
+ }
+
+ req->cb = cb;
+ req->arg = arg;
+
+ strncpy(req->hostname, hostname, sizeof(req->hostname));
+ req->hostname[sizeof(req->hostname) - 1] = 0;
+
+ /* A colon and we try to split */
+ t = strchr(req->hostname, ':');
+ if(t)
+ {
+ *t = 0;
+ strncpy(req->servname, t + 1, sizeof(req->servname));
+ }
+
+ if(servname && !req->servname[0])
+ strncpy(req->servname, servname, sizeof(req->servname));
+ req->servname[sizeof(req->servname) - 1] = 0;
+
+ if(hints)
+ memcpy(&(req->hints), hints, sizeof(req->hints));
+
+ /* Append the result to requests */
+ pthread_mutex_lock(&res_mutex);
+
+ if(!res_requests)
+ {
+ res_requests = req;
+ }
+ else
+ {
+ r = res_requests;
+ while(r->next)
+ r = r->next;
+ r->next = req;
+ }
+
+ pthread_mutex_unlock(&res_mutex);
+
+ tsignal_wake(res_request_signal);
+}
+
+void
+async_resolver_uninit()
+{
+ resolve_request* req;
+
+ /* No more responses from this point on */
+ if(tsignal_get_fd(res_done_signal) != -1)
+ server_unwatch(tsignal_get_fd(res_done_signal));
+
+ pthread_mutex_lock(&res_mutex);
+
+ while(res_requests)
+ {
+ req = res_requests->next;
+ if(res_requests->ai)
+ freeaddrinfo(res_requests->ai);
+ free(res_requests);
+ res_requests = req;
+ }
+
+ while(res_done)
+ {
+ req = res_done->next;
+ if(res_done->ai)
+ freeaddrinfo(res_done->ai);
+ free(res_done);
+ res_done = req;
+ }
+
+ pthread_mutex_unlock(&res_mutex);
+
+ /* Wake up the resolver thread */
+ res_quit = 1;
+ tsignal_uninit(res_request_signal);
+
+ /* Wait for it to finish */
+ if(res_thread)
+ {
+ pthread_join(res_thread, NULL);
+ res_thread = 0;
+ }
+
+ /* And close up the signals in the other direction */
+ tsignal_uninit(res_done_signal);
+}
diff --git a/common/async-resolver.h b/common/async-resolver.h
new file mode 100644
index 0000000..105c39a
--- /dev/null
+++ b/common/async-resolver.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2006, 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.
+ */
+
+#ifndef __ASYNC_RESOLVER_H__
+#define __ASYNC_RESOLVER_H__
+
+#include <netdb.h>
+
+typedef void (*async_resolve_callback)(int ecode, struct addrinfo* ai, void* arg);
+
+int async_resolver_init (void);
+void async_resolver_uninit (void);
+
+void async_resolver_queue(const char* hostname, const char* servname,
+ struct addrinfo* hints, async_resolve_callback cb, void* arg);
+
+
+#endif /* __ASYNC_RESOLVER_H__ */
diff --git a/common/server-mainloop.c b/common/server-mainloop.c
new file mode 100644
index 0000000..fcd471d
--- /dev/null
+++ b/common/server-mainloop.c
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2006, 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 "server-mainloop.h"
+
+#include <sys/time.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+typedef struct _socket_callback {
+ int fd;
+ server_socket_callback callback;
+ void *arg;
+
+ struct _socket_callback *next;
+} socket_callback;
+
+typedef struct _timer_callback {
+ struct timeval at;
+ struct timeval interval;
+ server_timer_callback callback;
+ void* arg;
+
+ struct _timer_callback *next;
+} timer_callback;
+
+typedef struct _server_context {
+ int stopped;
+ fd_set read_fds;
+ fd_set write_fds;
+ int max_fd;
+ socket_callback *callbacks;
+ timer_callback *timers;
+} server_context;
+
+/* Global context */
+static server_context ctx;
+
+static void
+timeval_add(struct timeval *t1, struct timeval *t2)
+{
+ assert (t1->tv_usec < 1000000);
+ assert (t2->tv_usec < 1000000);
+
+ t1->tv_sec += t2->tv_sec;
+ t1->tv_usec += t2->tv_usec;
+ if (t1->tv_usec >= 1000000) {
+ t1->tv_usec -= 1000000;
+ t1->tv_sec += 1;
+ }
+}
+
+static void
+timeval_subtract (struct timeval *t1, struct timeval *t2)
+{
+ assert (t1->tv_usec < 1000000);
+ assert (t2->tv_usec < 1000000);
+
+ t1->tv_sec -= t2->tv_sec;
+ if (t1->tv_usec < t2->tv_usec) {
+ t1->tv_usec += 1000000;
+ t1->tv_sec -= 1;
+ }
+ t1->tv_usec -= t2->tv_usec;
+}
+
+static int
+timeval_compare(struct timeval *t1, struct timeval *t2)
+{
+ assert (t1->tv_usec < 1000000);
+ assert (t2->tv_usec < 1000000);
+
+ if (t1->tv_sec > t2->tv_sec)
+ return 1;
+ else if (t1->tv_sec < t2->tv_sec)
+ return -1;
+ else {
+ if (t1->tv_usec > t2->tv_usec)
+ return 1;
+ else if (t1->tv_usec < t2->tv_usec)
+ return -1;
+ else
+ return 0;
+ }
+}
+
+#define timeval_empty(tv) \
+ ((tv)->tv_sec == 0 && (tv)->tv_usec == 0)
+
+#define timeval_to_ms(tv) \
+ ((((uint64_t)(tv).tv_sec) * 1000L) + (((uint64_t)(tv).tv_usec) / 1000L))
+
+#define timeval_dump(tv) \
+ (fprintf(stderr, "{ %d:%d }", (uint)((tv).tv_sec), (uint)((tv).tv_usec / 1000)))
+
+static int
+add_timer (int ms, int oneshot, server_timer_callback callback, void *arg)
+{
+ struct timeval interval;
+ timer_callback *cb;
+
+ assert (ms || oneshot);
+ assert (callback != NULL);
+
+ interval.tv_sec = ms / 1000;
+ interval.tv_usec = (ms % 1000) * 1000; /* into micro seconds */
+
+ cb = (timer_callback*)calloc (1, sizeof (*cb));
+ if(!cb) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (gettimeofday (&(cb->at), NULL) == -1) {
+ free(cb);
+ return -1;
+ }
+
+ timeval_add (&(cb->at), &interval);
+
+ if (oneshot)
+ memset (&(cb->interval), 0, sizeof (cb->interval));
+ else
+ memcpy (&(cb->interval), &interval, sizeof(cb->interval));
+
+ cb->callback = callback;
+ cb->arg = arg;
+
+ cb->next = ctx.timers;
+ ctx.timers = cb;
+
+ return 0;
+}
+
+static timer_callback*
+remove_timer(timer_callback *timcb)
+{
+ timer_callback *cb;
+ timer_callback *next;
+
+ if (!ctx.timers)
+ return NULL;
+
+ /* First in list */;
+ if (ctx.timers == timcb) {
+ cb = ctx.timers;
+ ctx.timers = ctx.timers->next;
+ free (cb);
+ return ctx.timers;
+ }
+
+ /* One ahead processing of rest */
+ for (cb = ctx.timers; cb->next; cb = cb->next) {
+ if(cb->next == timcb) {
+ next = cb->next->next;
+ free (cb->next);
+ cb->next = next;
+ return cb->next;
+ }
+ }
+
+ /* Couldn't remove, return self */
+ return timcb;
+}
+
+void
+server_init (void)
+{
+ memset (&ctx, 0, sizeof (ctx));
+ FD_ZERO (&ctx.read_fds);
+ FD_ZERO (&ctx.write_fds);
+
+ ctx.max_fd = -1;
+ ctx.stopped = 1;
+ ctx.callbacks = NULL;
+ ctx.timers = NULL;
+}
+
+void
+server_uninit (void)
+{
+ timer_callback *timcb;
+ timer_callback *timn;
+ socket_callback *sockcb;
+ socket_callback *sockn;
+
+ for(timcb = ctx.timers; timcb; timcb = timn) {
+ timn = timcb->next;
+ free (timcb);
+ }
+
+ ctx.timers = NULL;
+
+ for(sockcb = ctx.callbacks; sockcb; sockcb = sockn) {
+ sockn = sockcb->next;
+ free (sockcb);
+ }
+
+ ctx.timers = NULL;
+}
+
+uint64_t
+server_get_time (void)
+{
+ struct timeval tv;
+ if (gettimeofday (&tv, NULL) == -1)
+ return 0L;
+ return timeval_to_ms (tv);
+}
+
+int
+server_run (void)
+{
+ struct timeval *timeout;
+ struct timeval tv, current;
+ timer_callback *timcb;
+ socket_callback *sockcb, *socknx;
+ fd_set rfds, wfds;
+ int r;
+
+ /* No watches have been set */
+ assert (ctx.max_fd > -1);
+
+ ctx.stopped = 0;
+
+ while (!ctx.stopped) {
+ /* Watch for the various fds */
+ memcpy (&rfds, &ctx.read_fds, sizeof (rfds));
+ memcpy (&wfds, &ctx.write_fds, sizeof (wfds));
+
+ /* Prepare for timers */
+ timeout = NULL;
+ if (gettimeofday (&current, NULL) == -1)
+ return -1;
+
+ /* Cycle through timers */
+ for (timcb = ctx.timers; timcb; ) {
+ assert (timcb->callback);
+
+ /* Call any timers that have already passed */
+ if (timeval_compare (&current, &timcb->at) >= 0) {
+
+ /* Convert to milliseconds, and make the call */
+ r = (timcb->callback) (timeval_to_ms (current), timcb->arg);
+
+ /* Reset timer if so desired */
+ if (r == 1 && !timeval_empty (&timcb->interval)) {
+ timeval_add (&timcb->at, &timcb->interval);
+
+ /* If the time has already passed, just use current time */
+ if (timeval_compare (&(timcb->at), &current) <= 0)
+ memcpy (&(timcb->at), &current, sizeof (timcb->at));
+
+ /* Otherwise remove it. Either one shot, or returned 0 */
+ } else {
+ timcb = remove_timer (timcb);
+ continue;
+ }
+ }
+
+ /* Get soonest timer */
+ if (!timeout || timeval_compare (&timcb->at, timeout) < 0)
+ timeout = &timcb->at;
+
+ timcb = timcb->next;
+ }
+
+ /* Convert to an offset */
+ if (timeout) {
+ memcpy (&tv, timeout, sizeof (tv));
+ timeout = &tv;
+ timeval_subtract (timeout, &current);
+ }
+
+ if (ctx.stopped)
+ continue;
+
+ /* fprintf(stderr, "selecting with timeout: ");
+ timeval_dump(timeout);
+ fprintf(stderr, "\n"); */
+
+ r = select (ctx.max_fd, &rfds, &wfds, NULL, timeout);
+ if (r < 0) {
+ /* Interrupted so try again, and possibly exit */
+ if (errno == EINTR)
+ continue;
+
+ /* Programmer errors */
+ assert (errno != EBADF);
+ assert (errno != EINVAL);
+ return r;
+ }
+
+ /* Timeout, just jump to timeout processing */
+ if(r == 0)
+ continue;
+
+ for (sockcb = ctx.callbacks; sockcb; ) {
+ assert (sockcb->fd != -1);
+ socknx = sockcb->next;
+
+ /* Call any that are set */
+ if (FD_ISSET (sockcb->fd, &rfds))
+ (sockcb->callback) (sockcb->fd, SERVER_READ, sockcb->arg);
+ if (FD_ISSET (sockcb->fd, &wfds))
+ (sockcb->callback) (sockcb->fd, SERVER_WRITE, sockcb->arg);
+
+ sockcb = socknx;
+ }
+ }
+
+ return 0;
+}
+
+void
+server_stop (void)
+{
+ ctx.stopped = 1;
+}
+
+int
+server_stopped (void)
+{
+ return ctx.stopped;
+}
+
+int
+server_watch (int fd, int type, server_socket_callback callback, void *arg)
+{
+ socket_callback *cb;
+ assert (type != 0);
+ assert (fd != -1);
+ assert (callback != NULL);
+
+ cb = (socket_callback*)calloc (sizeof(*cb), 1);
+ if(!cb) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ cb->fd = fd;
+ cb->callback = callback;
+ cb->arg = arg;
+
+ cb->next = ctx.callbacks;
+ ctx.callbacks = cb;
+
+ if (type & SERVER_READ)
+ FD_SET (fd, &ctx.read_fds);
+ if (type & SERVER_WRITE)
+ FD_SET (fd, &ctx.write_fds);
+
+ if (fd >= ctx.max_fd)
+ ctx.max_fd = fd + 1;
+
+ return 0;
+}
+
+void
+server_unwatch (int fd)
+{
+ socket_callback* cb;
+
+ assert (fd != -1);
+
+ FD_CLR (fd, &ctx.read_fds);
+ FD_CLR (fd, &ctx.write_fds);
+
+ if (!ctx.callbacks)
+ return;
+
+ /* First in list */;
+ if (ctx.callbacks->fd == fd) {
+ cb = ctx.callbacks;
+ ctx.callbacks = cb->next;
+ free (cb);
+ return;
+ }
+
+ /* One ahead processing of rest */
+ for (cb = ctx.callbacks; cb->next; cb = cb->next) {
+ if (cb->next->fd == fd) {
+ cb->next = cb->next->next;
+ free (cb->next);
+ return;
+ }
+ }
+}
+
+int
+server_timer (int ms, server_timer_callback callback, void *arg)
+{
+ return add_timer (ms, 0, callback, arg);
+}
+
+int
+server_oneshot (int ms, server_timer_callback callback, void *arg)
+{
+ return add_timer (ms, 1, callback, arg);
+}
diff --git a/common/server-mainloop.h b/common/server-mainloop.h
new file mode 100644
index 0000000..09c7846
--- /dev/null
+++ b/common/server-mainloop.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2006, 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.
+ */
+
+#ifndef __SERVER_MAINLOOP_H__
+#define __SERVER_MAINLOOP_H__
+
+#include <stdint.h>
+
+#define SERVER_READ 0x01
+#define SERVER_WRITE 0x02
+
+typedef void (*server_socket_callback) (int fd, int type, void* arg);
+typedef int (*server_timer_callback) (uint64_t when, void* arg);
+
+void server_init (void);
+void server_uninit (void);
+int server_run (void);
+void server_stop (void);
+int server_stopped (void);
+int server_watch (int fd, int type, server_socket_callback callback, void* arg);
+void server_unwatch (int fd);
+int server_timer (int length, server_timer_callback callback, void* arg);
+int server_oneshot (int length, server_timer_callback callback, void* arg);
+uint64_t server_get_time (void);
+
+#endif /* __SERVER_MAINLOOP_H__ */
diff --git a/common/sock-any.c b/common/sock-any.c
new file mode 100644
index 0000000..4476fde
--- /dev/null
+++ b/common/sock-any.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2004, 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 <stef@memberwebs.com>
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "sock-any.h"
+
+#include <arpa/inet.h>
+
+#define LOCALHOST_ADDR 0x7F000001
+
+int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts)
+{
+ size_t l;
+ char buf[256];
+ char* t;
+ char* t2;
+ int defport = (opts & 0xFFFF);
+
+ memset(any, 0, sizeof(*any));
+
+ /* Just a port? */
+ do
+ {
+ #define PORT_CHARS "0123456789"
+ #define PORT_MIN 1
+ #define PORT_MAX 5
+
+ int port = 0;
+
+ l = strspn(addr, PORT_CHARS);
+ if(l < PORT_MIN || l > PORT_MAX || addr[l] != 0)
+ break;
+
+ port = strtol(addr, &t2, 10);
+ if(*t2 || port <= 0 || port >= 65536)
+ break;
+
+ any->s.in.sin_port = htons(port);
+
+ /* Fill in the type based on defaults */
+#ifdef HAVE_INET6
+ if(opts & SANY_OPT_DEFINET6)
+ any->s.in.sin_family = AF_INET6;
+ else
+#endif
+ any->s.in.sin_family = AF_INET;
+
+ /* Fill in the address based on defaults */
+ if(opts & SANY_OPT_DEFLOCAL)
+ {
+#ifdef HAVE_INET6
+ if(opts & SANY_OPT_DEFINET6)
+ memcpy(&(any->s.in.sin6_addr), &in6addr_loopback, sizeof(struct in6_addr));
+ else
+#endif
+ any->s.in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ }
+
+ /*
+ * Note the 'any' option is the default since we zero out
+ * the entire structure above.
+ */
+
+ any->namelen = sizeof(any->s.in);
+ return AF_INET;
+ }
+ while(0);
+
+ /* Look and see if we can parse an ipv4 address */
+ do
+ {
+ #define IPV4_PORT_CHARS
+ #define IPV4_CHARS "0123456789."
+ #define IPV4_MIN 3
+ #define IPV4_MAX 21
+
+ int port = 0;
+ t = NULL;
+
+ l = strlen(addr);
+ if(l < IPV4_MIN || l > IPV4_MAX)
+ break;
+
+ strcpy(buf, addr);
+
+ /* Find the last set that contains just numbers */
+ l = strspn(buf, IPV4_CHARS);
+ if(l < IPV4_MIN)
+ break;
+
+ /* Either end of string or port */
+ if(buf[l] != 0 && buf[l] != ':')
+ break;
+
+ /* Get the port out */
+ if(buf[l] != 0)
+ {
+ t = buf + l + 1;
+ buf[l] = 0;
+ }
+
+ if(t)
+ {
+ port = strtol(t, &t2, 10);
+ if(*t2 || port <= 0 || port >= 65536)
+ break;
+ }
+
+ any->s.in.sin_family = AF_INET;
+ any->s.in.sin_port = htons((unsigned short)(port <= 0 ? defport : port));
+
+ if(inet_pton(AF_INET, buf, &(any->s.in.sin_addr)) <= 0)
+ break;
+
+ any->namelen = sizeof(any->s.in);
+ return AF_INET;
+ }
+ while(0);
+
+#ifdef HAVE_INET6
+ do
+ {
+ #define IPV6_CHARS "0123456789:"
+ #define IPV6_MIN 3
+ #define IPV6_MAX 51
+
+ int port = -1;
+ t = NULL;
+
+ l = strlen(addr);
+ if(l < IPV6_MIN || l > IPV6_MAX)
+ break;
+
+ /* If it starts with a '[' then we can get port */
+ if(buf[0] == '[')
+ {
+ port = 0;
+ addr++;
+ }
+
+ strcpy(buf, addr);
+
+ /* Find the last set that contains just numbers */
+ l = strspn(buf, IPV6_CHARS);
+ if(l < IPV6_MIN)
+ break;
+
+ /* Either end of string or port */
+ if(buf[l] != 0)
+ {
+ /* If had bracket, then needs to end with a bracket */
+ if(port != 0 || buf[l] != ']')
+ break;
+
+ /* Get the port out */
+ t = buf + l + 1;
+
+ if(*t = ':')
+ t++;
+ }
+
+ if(t)
+ {
+ port = strtol(t, &t, 10);
+ if(*t || port <= 0 || port >= 65536)
+ break;
+ }
+
+ any->s.in6.sin6_family = AF_INET6;
+ any->s.in6.sin6_port = htons((unsigned short)port <= 0 : defport : port);
+
+ if(inet_pton(AF_INET6, buf, &(any->s.in6.sin6_addr)) >= 0)
+ break;
+
+ any->namelen = sizeof(any->s.in6);
+ return AF_INET6;
+ }
+ while(0);
+#endif
+
+ /* A unix socket path */
+ do
+ {
+ /* No colon and must have a path component */
+ if(strchr(addr, ':') || !strchr(addr, '/'))
+ break;
+
+ l = strlen(addr);
+ if(l >= sizeof(any->s.un.sun_path))
+ break;
+
+ any->s.un.sun_family = AF_UNIX;
+ strcpy(any->s.un.sun_path, addr);
+
+ any->namelen = sizeof(any->s.un) - (sizeof(any->s.un.sun_path) - l);
+ return AF_UNIX;
+ }
+ while(0);
+
+ /* A DNS name and a port? */
+ do
+ {
+ struct addrinfo* res;
+ int port = 0;
+ int family = 0;
+ t = NULL;
+
+ l = strlen(addr);
+ if(l >= 255 || !isalpha(addr[0]))
+ break;
+
+ /* Some basic illegal character checks */
+ if(strcspn(addr, " /\\") != l)
+ break;
+
+ strcpy(buf, addr);
+
+ /* Find the last set that contains just numbers */
+ t = strchr(buf, ':');
+ if(t)
+ {
+ *t = 0;
+ t++;
+ }
+
+ if(t)
+ {
+ port = strtol(t, &t2, 10);
+ if(*t2 || port <= 0 || port >= 65536)
+ break;
+ }
+
+ if(!(opts & SANY_OPT_NORESOLV))
+ {
+ /* Try and resolve the domain name */
+ if(getaddrinfo(buf, NULL, NULL, &res) != 0 || !res)
+ break;
+
+ memcpy(&(any->s.a), res->ai_addr, sizeof(struct sockaddr));
+ any->namelen = res->ai_addrlen;
+ family = any->s.a.sa_family;
+ freeaddrinfo(res);
+ }
+ else
+ {
+ family = SANY_AF_DNS;
+#ifdef HAVE_INET6
+ if(opt & SANY_OPT_DEFINET6)
+ {
+ any->s.a.sa_family = AF_INET6;
+ any->namelen = sizeof(any->s.in6);
+ }
+ else
+#endif
+ {
+ any->s.a.sa_family = AF_INET;
+ any->namelen = sizeof(any->s.in);
+ }
+ }
+
+ port = htons((unsigned short)(port <= 0 ? defport : port));
+
+ switch(any->s.a.sa_family)
+ {
+ case PF_INET:
+ any->s.in.sin_port = port;
+ break;
+#ifdef HAVE_INET6
+ case PF_INET6:
+ any->s.in6.sin6_port = port;
+ break;
+#endif
+ };
+
+ return family;
+ }
+ while(0);
+
+ return -1;
+}
+
+int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts)
+{
+ int len = 0;
+ int port = 0;
+
+ switch(any->s.a.sa_family)
+ {
+ case AF_UNIX:
+ len = strlen(any->s.un.sun_path);
+ if(addrlen < len + 1)
+ {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ strcpy(addr, any->s.un.sun_path);
+ break;
+
+ case AF_INET:
+ if(inet_ntop(any->s.a.sa_family, &(any->s.in.sin_addr), addr, addrlen) == NULL)
+ return -1;
+ port = ntohs(any->s.in.sin_port);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if(inet_ntop(any->s.a.sa_family, &(any->s.in6.sin6_addr), addr, addrlen) == NULL)
+ return -1;
+ port = ntohs(any->s.in6.sin6_port);
+ break;
+#endif
+
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ if(!(opts & SANY_OPT_NOPORT) && port != 0)
+ {
+ strncat(addr, ":", addrlen);
+ addr[addrlen - 1] = 0;
+
+ len = strlen(addr);
+ addr += len;
+ addrlen -= len;
+
+ snprintf(addr, addrlen, "%d", port);
+ }
+
+ return 0;
+}
+
+int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts)
+{
+ if(a1->s.a.sa_family != a2->s.a.sa_family)
+ return -1;
+
+ switch(a1->s.a.sa_family)
+ {
+ case AF_UNIX:
+ return strcmp(a1->s.un.sun_path, a2->s.un.sun_path);
+
+ case AF_INET:
+ if(memcmp(&(a1->s.in.sin_addr), &(a2->s.in.sin_addr), sizeof(a2->s.in.sin_addr)) != 0)
+ return -1;
+ if(!(opts && SANY_OPT_NOPORT) && a1->s.in.sin_port != a2->s.in.sin_port)
+ return -1;
+ return 0;
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if(memcmp(&(a1->s.in6.sin6_addr), &(a2->s.in6.sin6_addr), sizeof(a2->s.in6.sin6_addr)) != 0)
+ return -1;
+ if(!(opts && SANY_OPT_NOPORT) && a1->s.in6.sin6_port != a2->s.in6.sin6_port)
+ return -1;
+ return 0;
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+}
diff --git a/common/sock-any.h b/common/sock-any.h
new file mode 100644
index 0000000..e9b57ef
--- /dev/null
+++ b/common/sock-any.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2004, 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 <stef@memberwebs.com>
+ *
+ */
+
+#ifndef __SOCK_ANY_H__
+#define __SOCK_ANY_H__
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+
+struct sockaddr_any
+{
+ union _sockaddr_any
+ {
+ /* The header */
+ struct sockaddr a;
+
+ /* The different types */
+ struct sockaddr_un un;
+ struct sockaddr_in in;
+#ifdef HAVE_INET6
+ struct sockaddr_in6 in6;
+#endif
+ } s;
+ size_t namelen;
+};
+
+#define SANY_ADDR(any) ((any).s.a)
+#define SANY_LEN(any) ((any).namelen)
+#define SANY_TYPE(any) ((any).s.a.sa_family)
+
+/* -------------------------------------------------------------------------- */
+
+/* Returns AF_XXX family type or -1 */
+int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts);
+
+/* The default port to fill in when no IP/IPv6 port specified */
+#define SANY_OPT_DEFPORT(p) (int)((p) & 0xFFFF)
+
+/* When only port specified default to IPANY */
+#define SANY_OPT_DEFANY 0x00000000
+
+/* When only port specified default to LOCALHOST */
+#define SANY_OPT_DEFLOCAL 0x00100000
+
+/* When only port specified default to IPv6 */
+#define SANY_OPT_DEFINET6 0x00200000
+
+/* Don't resolve host name */
+#define SANY_OPT_NORESOLV 0x01000000
+
+/* The family type returned when resolving is needed */
+#define SANY_AF_DNS 0x01000000
+
+/* -------------------------------------------------------------------------- */
+
+/* Returns -1 when failed */
+int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts);
+
+/* Don't print or compare the port */
+#define SANY_OPT_NOPORT 0x01000000
+
+/* -------------------------------------------------------------------------- */
+
+/* Returns 0 for equal */
+int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts);
+
+#endif /* __SOCK_ANY_H__ */
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..b4bb00d
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,92 @@
+dnl
+dnl Copyright (c) 2008, Stef Walter
+dnl All rights reserved.
+dnl
+dnl Redistribution and use in source and binary forms, with or without
+dnl modification, are permitted provided that the following conditions
+dnl are met:
+dnl
+dnl * Redistributions of source code must retain the above
+dnl copyright notice, this list of conditions and the
+dnl following disclaimer.
+dnl * Redistributions in binary form must reproduce the
+dnl above copyright notice, this list of conditions and
+dnl the following disclaimer in the documentation and/or
+dnl other materials provided with the distribution.
+dnl * The names of contributors to this software may not be
+dnl used to endorse or promote products derived from this
+dnl software without specific prior written permission.
+dnl
+dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+dnl FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+dnl COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+dnl INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+dnl BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+dnl OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+dnl AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+dnl OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+dnl THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+dnl DAMAGE.
+dnl
+dnl
+dnl CONTRIBUTORS
+dnl Stef Walter <stef@memberwebs.com>
+dnl
+
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(slapi-dnsnotify, 0.1, stef@memberwebs.com)
+AM_INIT_AUTOMAKE(slapi-dnsnotify, 0.1)
+
+AC_CONFIG_SRCDIR([plugin/slapi-dnsnotify.c])
+AM_CONFIG_HEADER([config.h])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_LIBTOOL
+AC_PROG_INSTALL
+AC_PROG_LN_S
+
+ACX_PTHREAD( , [echo "ERROR: Pthread support not found."; exit 1] )
+
+# Note: We can't really check for libslapi as it's a library internal
+# to slapd and won't compile into any other executable directly.
+#
+# AC_CHECK_LIB(slapi, slapi_pblock_set, ,
+# [echo "ERROR: Must have SLAPI libraries installed."; exit 1])
+
+# Checks for header files.
+AC_FUNC_ALLOCA
+AC_HEADER_STDC
+AC_CHECK_HEADERS([ldap.h], ,
+ [echo "ERROR: Required LDAP header missing"; exit 1])
+
+AC_CHECK_HEADERS([slapi-plugin.h], ,
+ [echo "Missing SLAPI plugin headers, using our own."])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+# Required Functions
+AC_CHECK_FUNCS([vsnprintf], ,
+ [echo "ERROR: Required function missing"; exit 1])
+
+# Debug mode
+AC_ARG_ENABLE(debug,
+ AC_HELP_STRING([--enable-debug],
+ [Compile binaries in debug mode]))
+
+if test "$enable_debug" = "yes"; then
+ CFLAGS="$CFLAGS -g -O0 -D_DEBUG=1 -Wall -Werror"
+ echo "enabling debug compile mode"
+fi
+
+AC_CONFIG_FILES([
+ Makefile
+ plugin/Makefile
+ tests/Makefile
+ tools/Makefile
+ ])
+AC_OUTPUT
+
diff --git a/include/slapi-plugin.h b/include/slapi-plugin.h
new file mode 100644
index 0000000..0e49a48
--- /dev/null
+++ b/include/slapi-plugin.h
@@ -0,0 +1,903 @@
+/* $OpenLDAP: pkg/ldap/include/slapi-plugin.h,v 1.52.2.5 2008/02/11 23:26:40 kurt Exp $ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2008 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002,2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/*
+ * This header is used in development of SLAPI plugins for
+ * OpenLDAP slapd(8) and other directory servers supporting
+ * this interface. Your portability mileage may vary.
+ */
+
+#ifndef _SLAPI_PLUGIN_H
+#define _SLAPI_PLUGIN_H
+
+#include <ldap.h>
+
+typedef struct slapi_pblock Slapi_PBlock;
+typedef struct slapi_entry Slapi_Entry;
+typedef struct slapi_attr Slapi_Attr;
+typedef struct slapi_value Slapi_Value;
+typedef struct slapi_valueset Slapi_ValueSet;
+typedef struct slapi_filter Slapi_Filter;
+typedef struct BackendDB Slapi_Backend;
+typedef struct Operation Slapi_Operation;
+typedef struct Connection Slapi_Connection;
+typedef struct slapi_dn Slapi_DN;
+typedef struct slapi_rdn Slapi_RDN;
+typedef struct slapi_mod Slapi_Mod;
+typedef struct slapi_mods Slapi_Mods;
+typedef struct slapi_componentid Slapi_ComponentId;
+
+#define SLAPI_ATTR_UNIQUEID "entryUUID"
+#define SLAPI_ATTR_OBJECTCLASS "objectClass"
+
+/* pblock routines */
+int slapi_pblock_get( Slapi_PBlock *pb, int arg, void *value );
+int slapi_pblock_set( Slapi_PBlock *pb, int arg, void *value );
+Slapi_PBlock *slapi_pblock_new( void );
+void slapi_pblock_destroy( Slapi_PBlock *pb );
+
+/* entry/attr/dn routines */
+Slapi_Entry *slapi_str2entry( char *s, int flags );
+#define SLAPI_STR2ENTRY_REMOVEDUPVALS 1
+#define SLAPI_STR2ENTRY_ADDRDNVALS 2
+#define SLAPI_STR2ENTRY_BIGENTRY 4
+#define SLAPI_STR2ENTRY_TOMBSTONE_CHECK 8
+#define SLAPI_STR2ENTRY_IGNORE_STATE 16
+#define SLAPI_STR2ENTRY_INCLUDE_VERSION_STR 32
+#define SLAPI_STR2ENTRY_EXPAND_OBJECTCLASSES 64
+#define SLAPI_STR2ENTRY_NOT_WELL_FORMED_LDIF 128
+char *slapi_entry2str( Slapi_Entry *e, int *len );
+char *slapi_entry_get_dn( Slapi_Entry *e );
+int slapi_x_entry_get_id( Slapi_Entry *e );
+void slapi_entry_set_dn( Slapi_Entry *e, char *dn );
+Slapi_Entry *slapi_entry_dup( Slapi_Entry *e );
+int slapi_entry_attr_delete( Slapi_Entry *e, char *type );
+Slapi_Entry *slapi_entry_alloc();
+void slapi_entry_free( Slapi_Entry *e );
+int slapi_entry_attr_merge( Slapi_Entry *e, char *type, struct berval **vals );
+int slapi_entry_attr_find( Slapi_Entry *e, char *type, Slapi_Attr **attr );
+char *slapi_entry_attr_get_charptr( const Slapi_Entry *e, const char *type );
+int slapi_entry_attr_get_int( const Slapi_Entry *e, const char *type );
+long slapi_entry_attr_get_long( const Slapi_Entry *e, const char *type );
+unsigned int slapi_entry_attr_get_uint( const Slapi_Entry *e, const char *type );
+unsigned long slapi_entry_attr_get_ulong( const Slapi_Entry *e, const char *type );
+int slapi_attr_get_values( Slapi_Attr *attr, struct berval ***vals );
+char *slapi_dn_normalize( char *dn );
+char *slapi_dn_normalize_case( char *dn );
+int slapi_dn_issuffix( char *dn, char *suffix );
+char *slapi_dn_beparent( Slapi_PBlock *pb, const char *dn );
+int slapi_dn_isbesuffix( Slapi_PBlock *pb, char *dn );
+char *slapi_dn_parent( const char *dn );
+int slapi_dn_isparent( const char *parentdn, const char *childdn );
+char *slapi_dn_ignore_case( char *dn );
+int slapi_rdn2typeval( char *rdn, char **type, struct berval *bv );
+char *slapi_dn_plus_rdn(const char *dn, const char *rdn);
+
+/* DS 5.x SLAPI */
+int slapi_access_allowed( Slapi_PBlock *pb, Slapi_Entry *e, char *attr, struct berval *val, int access );
+int slapi_acl_check_mods( Slapi_PBlock *pb, Slapi_Entry *e, LDAPMod **mods, char **errbuf );
+Slapi_Attr *slapi_attr_new( void );
+Slapi_Attr *slapi_attr_init( Slapi_Attr *a, const char *type );
+void slapi_attr_free( Slapi_Attr **a );
+Slapi_Attr *slapi_attr_dup( const Slapi_Attr *attr );
+int slapi_attr_add_value( Slapi_Attr *a, const Slapi_Value *v );
+int slapi_attr_type2plugin( const char *type, void **pi );
+int slapi_attr_get_type( const Slapi_Attr *attr, char **type );
+int slapi_attr_get_oid_copy( const Slapi_Attr *attr, char **oidp );
+int slapi_attr_get_flags( const Slapi_Attr *attr, unsigned long *flags );
+int slapi_attr_flag_is_set( const Slapi_Attr *attr, unsigned long flag );
+int slapi_attr_value_cmp( const Slapi_Attr *attr, const struct berval *v1, const struct berval *v2 );
+int slapi_attr_value_find( const Slapi_Attr *a, struct berval *v );
+#define SLAPI_TYPE_CMP_EXACT 0
+#define SLAPI_TYPE_CMP_BASE 1
+#define SLAPI_TYPE_CMP_SUBTYPE 2
+int slapi_attr_type_cmp( const char *t1, const char *t2, int opt );
+int slapi_attr_types_equivalent( const char *t1, const char *t2 );
+int slapi_attr_first_value( Slapi_Attr *a, Slapi_Value **v );
+int slapi_attr_next_value( Slapi_Attr *a, int hint, Slapi_Value **v );
+int slapi_attr_get_numvalues( const Slapi_Attr *a, int *numValues );
+int slapi_attr_get_valueset( const Slapi_Attr *a, Slapi_ValueSet **vs );
+int slapi_attr_get_bervals_copy( Slapi_Attr *a, struct berval ***vals );
+int slapi_entry_attr_hasvalue( Slapi_Entry *e, const char *type, const char *value );
+int slapi_entry_attr_merge_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals );
+void slapi_entry_attr_set_charptr(Slapi_Entry* e, const char *type, const char *value);
+void slapi_entry_attr_set_int( Slapi_Entry* e, const char *type, int l);
+void slapi_entry_attr_set_uint( Slapi_Entry* e, const char *type, unsigned int l);
+void slapi_entry_attr_set_long(Slapi_Entry* e, const char *type, long l);
+void slapi_entry_attr_set_ulong(Slapi_Entry* e, const char *type, unsigned long l);
+int slapi_entry_has_children(const Slapi_Entry *e);
+size_t slapi_entry_size(Slapi_Entry *e);
+int slapi_is_rootdse( const char *dn );
+int slapi_entry_attr_merge_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals );
+int slapi_entry_add_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals );
+int slapi_entry_add_valueset(Slapi_Entry *e, const char *type, Slapi_ValueSet *vs);
+int slapi_entry_delete_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals );
+int slapi_entry_merge_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals );
+int slapi_entry_attr_replace_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals );
+int slapi_entry_add_value(Slapi_Entry *e, const char *type, const Slapi_Value *value);
+int slapi_entry_add_string(Slapi_Entry *e, const char *type, const char *value);
+int slapi_entry_delete_string(Slapi_Entry *e, const char *type, const char *value);
+int slapi_entry_first_attr( const Slapi_Entry *e, Slapi_Attr **attr );
+int slapi_entry_next_attr( const Slapi_Entry *e, Slapi_Attr *prevattr, Slapi_Attr **attr );
+const char *slapi_entry_get_uniqueid( const Slapi_Entry *e );
+void slapi_entry_set_uniqueid( Slapi_Entry *e, char *uniqueid );
+int slapi_entry_schema_check( Slapi_PBlock *pb, Slapi_Entry *e );
+int slapi_entry_rdn_values_present( const Slapi_Entry *e );
+int slapi_entry_add_rdn_values( Slapi_Entry *e );
+char *slapi_attr_syntax_normalize( const char *s );
+
+Slapi_Value *slapi_value_new( void );
+Slapi_Value *slapi_value_new_berval(const struct berval *bval);
+Slapi_Value *slapi_value_new_value(const Slapi_Value *v);
+Slapi_Value *slapi_value_new_string(const char *s);
+Slapi_Value *slapi_value_init(Slapi_Value *v);
+Slapi_Value *slapi_value_init_berval(Slapi_Value *v, struct berval *bval);
+Slapi_Value *slapi_value_init_string(Slapi_Value *v, const char *s);
+Slapi_Value *slapi_value_dup(const Slapi_Value *v);
+void slapi_value_free(Slapi_Value **value);
+const struct berval *slapi_value_get_berval( const Slapi_Value *value );
+Slapi_Value *slapi_value_set_berval( Slapi_Value *value, const struct berval *bval );
+Slapi_Value *slapi_value_set_value( Slapi_Value *value, const Slapi_Value *vfrom);
+Slapi_Value *slapi_value_set( Slapi_Value *value, void *val, unsigned long len);
+int slapi_value_set_string(Slapi_Value *value, const char *strVal);
+int slapi_value_set_int(Slapi_Value *value, int intVal);
+const char*slapi_value_get_string(const Slapi_Value *value);
+int slapi_value_get_int(const Slapi_Value *value);
+unsigned int slapi_value_get_uint(const Slapi_Value *value);
+long slapi_value_get_long(const Slapi_Value *value);
+unsigned long slapi_value_get_ulong(const Slapi_Value *value);
+size_t slapi_value_get_length(const Slapi_Value *value);
+int slapi_value_compare(const Slapi_Attr *a, const Slapi_Value *v1, const Slapi_Value *v2);
+
+Slapi_ValueSet *slapi_valueset_new( void );
+void slapi_valueset_free(Slapi_ValueSet *vs);
+void slapi_valueset_init(Slapi_ValueSet *vs);
+void slapi_valueset_done(Slapi_ValueSet *vs);
+void slapi_valueset_add_value(Slapi_ValueSet *vs, const Slapi_Value *addval);
+int slapi_valueset_first_value( Slapi_ValueSet *vs, Slapi_Value **v );
+int slapi_valueset_next_value( Slapi_ValueSet *vs, int index, Slapi_Value **v);
+int slapi_valueset_count( const Slapi_ValueSet *vs);
+void slapi_valueset_set_valueset(Slapi_ValueSet *vs1, const Slapi_ValueSet *vs2);
+
+/* DNs */
+Slapi_DN *slapi_sdn_new( void );
+Slapi_DN *slapi_sdn_new_dn_byval( const char *dn );
+Slapi_DN *slapi_sdn_new_ndn_byval( const char *ndn );
+Slapi_DN *slapi_sdn_new_dn_byref( const char *dn );
+Slapi_DN *slapi_sdn_new_ndn_byref( const char *ndn );
+Slapi_DN *slapi_sdn_new_dn_passin( const char *dn );
+Slapi_DN *slapi_sdn_set_dn_byval( Slapi_DN *sdn, const char *dn );
+Slapi_DN *slapi_sdn_set_dn_byref( Slapi_DN *sdn, const char *dn );
+Slapi_DN *slapi_sdn_set_dn_passin( Slapi_DN *sdn, const char *dn );
+Slapi_DN *slapi_sdn_set_ndn_byval( Slapi_DN *sdn, const char *ndn );
+Slapi_DN *slapi_sdn_set_ndn_byref( Slapi_DN *sdn, const char *ndn );
+void slapi_sdn_done( Slapi_DN *sdn );
+void slapi_sdn_free( Slapi_DN **sdn );
+const char * slapi_sdn_get_dn( const Slapi_DN *sdn );
+const char * slapi_sdn_get_ndn( const Slapi_DN *sdn );
+void slapi_sdn_get_parent( const Slapi_DN *sdn,Slapi_DN *sdn_parent );
+void slapi_sdn_get_backend_parent( const Slapi_DN *sdn, Slapi_DN *sdn_parent, const Slapi_Backend *backend );
+Slapi_DN * slapi_sdn_dup( const Slapi_DN *sdn );
+void slapi_sdn_copy( const Slapi_DN *from, Slapi_DN *to );
+int slapi_sdn_compare( const Slapi_DN *sdn1, const Slapi_DN *sdn2 );
+int slapi_sdn_isempty( const Slapi_DN *sdn );
+int slapi_sdn_issuffix(const Slapi_DN *sdn, const Slapi_DN *suffixsdn );
+int slapi_sdn_isparent( const Slapi_DN *parent, const Slapi_DN *child );
+int slapi_sdn_isgrandparent( const Slapi_DN *parent, const Slapi_DN *child );
+int slapi_sdn_get_ndn_len( const Slapi_DN *sdn );
+int slapi_sdn_scope_test( const Slapi_DN *dn, const Slapi_DN *base, int scope );
+void slapi_sdn_get_rdn( const Slapi_DN *sdn,Slapi_RDN *rdn );
+Slapi_DN *slapi_sdn_set_rdn( Slapi_DN *sdn, const Slapi_RDN *rdn );
+Slapi_DN *slapi_sdn_set_parent( Slapi_DN *sdn, const Slapi_DN *parentdn );
+int slapi_sdn_is_rdn_component( const Slapi_DN *rdn, const Slapi_Attr *a, const Slapi_Value *v );
+char * slapi_moddn_get_newdn( Slapi_DN *dn_olddn, char *newrdn, char *newsuperiordn );
+
+/* RDNs */
+Slapi_RDN *slapi_rdn_new( void );
+Slapi_RDN *slapi_rdn_new_dn( const char *dn );
+Slapi_RDN *slapi_rdn_new_sdn( const Slapi_DN *sdn );
+Slapi_RDN *slapi_rdn_new_rdn( const Slapi_RDN *fromrdn );
+void slapi_rdn_init( Slapi_RDN *rdn );
+void slapi_rdn_init_dn( Slapi_RDN *rdn, const char *dn );
+void slapi_rdn_init_sdn( Slapi_RDN *rdn, const Slapi_DN *sdn );
+void slapi_rdn_init_rdn( Slapi_RDN *rdn, const Slapi_RDN *fromrdn );
+void slapi_rdn_set_dn( Slapi_RDN *rdn, const char *dn );
+void slapi_rdn_set_sdn( Slapi_RDN *rdn, const Slapi_DN *sdn );
+void slapi_rdn_set_rdn( Slapi_RDN *rdn, const Slapi_RDN *fromrdn );
+void slapi_rdn_free( Slapi_RDN **rdn );
+void slapi_rdn_done( Slapi_RDN *rdn );
+int slapi_rdn_get_first( Slapi_RDN *rdn, char **type, char **value );
+int slapi_rdn_get_next( Slapi_RDN *rdn, int index, char **type, char **value );
+int slapi_rdn_get_index( Slapi_RDN *rdn, const char *type, const char *value, size_t length );
+int slapi_rdn_get_index_attr( Slapi_RDN *rdn, const char *type, char **value );
+int slapi_rdn_contains( Slapi_RDN *rdn, const char *type, const char *value,size_t length );
+int slapi_rdn_contains_attr( Slapi_RDN *rdn, const char *type, char **value );
+int slapi_rdn_add( Slapi_RDN *rdn, const char *type, const char *value );
+int slapi_rdn_remove_index( Slapi_RDN *rdn, int atindex );
+int slapi_rdn_remove( Slapi_RDN *rdn, const char *type, const char *value, size_t length );
+int slapi_rdn_remove_attr( Slapi_RDN *rdn, const char *type );
+int slapi_rdn_isempty( const Slapi_RDN *rdn );
+int slapi_rdn_get_num_components( Slapi_RDN *rdn );
+int slapi_rdn_compare( Slapi_RDN *rdn1, Slapi_RDN *rdn2 );
+const char *slapi_rdn_get_rdn( const Slapi_RDN *rdn );
+const char *slapi_rdn_get_nrdn( const Slapi_RDN *rdn );
+Slapi_DN *slapi_sdn_add_rdn( Slapi_DN *sdn, const Slapi_RDN *rdn );
+
+/* locks and synchronization */
+typedef struct slapi_mutex Slapi_Mutex;
+typedef struct slapi_condvar Slapi_CondVar;
+Slapi_Mutex *slapi_new_mutex( void );
+void slapi_destroy_mutex( Slapi_Mutex *mutex );
+void slapi_lock_mutex( Slapi_Mutex *mutex );
+int slapi_unlock_mutex( Slapi_Mutex *mutex );
+Slapi_CondVar *slapi_new_condvar( Slapi_Mutex *mutex );
+void slapi_destroy_condvar( Slapi_CondVar *cvar );
+int slapi_wait_condvar( Slapi_CondVar *cvar, struct timeval *timeout );
+int slapi_notify_condvar( Slapi_CondVar *cvar, int notify_all );
+
+/* thread-safe LDAP connections */
+LDAP *slapi_ldap_init( char *ldaphost, int ldapport, int secure, int shared );
+void slapi_ldap_unbind( LDAP *ld );
+
+char *slapi_ch_malloc( unsigned long size );
+void slapi_ch_free( void **ptr );
+void slapi_ch_free_string( char **ptr );
+char *slapi_ch_calloc( unsigned long nelem, unsigned long size );
+char *slapi_ch_realloc( char *block, unsigned long size );
+char *slapi_ch_strdup( const char *s );
+void slapi_ch_array_free( char **arrayp );
+struct berval *slapi_ch_bvdup(const struct berval *v);
+struct berval **slapi_ch_bvecdup(const struct berval **v);
+
+/* LDAP V3 routines */
+int slapi_control_present( LDAPControl **controls, char *oid,
+ struct berval **val, int *iscritical);
+void slapi_register_supported_control(char *controloid,
+ unsigned long controlops);
+#define SLAPI_OPERATION_BIND 0x00000001L
+#define SLAPI_OPERATION_UNBIND 0x00000002L
+#define SLAPI_OPERATION_SEARCH 0x00000004L
+#define SLAPI_OPERATION_MODIFY 0x00000008L
+#define SLAPI_OPERATION_ADD 0x00000010L
+#define SLAPI_OPERATION_DELETE 0x00000020L
+#define SLAPI_OPERATION_MODDN 0x00000040L
+#define SLAPI_OPERATION_MODRDN SLAPI_OPERATION_MODDN
+#define SLAPI_OPERATION_COMPARE 0x00000080L
+#define SLAPI_OPERATION_ABANDON 0x00000100L
+#define SLAPI_OPERATION_EXTENDED 0x00000200L
+#define SLAPI_OPERATION_ANY 0xFFFFFFFFL
+#define SLAPI_OPERATION_NONE 0x00000000L
+int slapi_get_supported_controls(char ***ctrloidsp, unsigned long **ctrlopsp);
+LDAPControl *slapi_dup_control(LDAPControl *ctrl);
+void slapi_register_supported_saslmechanism(char *mechanism);
+char **slapi_get_supported_saslmechanisms();
+char **slapi_get_supported_extended_ops(void);
+
+/* operation */
+int slapi_op_abandoned( Slapi_PBlock *pb );
+unsigned long slapi_op_get_type(Slapi_Operation * op);
+void slapi_operation_set_flag(Slapi_Operation *op, unsigned long flag);
+void slapi_operation_clear_flag(Slapi_Operation *op, unsigned long flag);
+int slapi_operation_is_flag_set(Slapi_Operation *op, unsigned long flag);
+char *slapi_op_type_to_string(unsigned long type);
+
+/* send ldap result back */
+void slapi_send_ldap_result( Slapi_PBlock *pb, int err, char *matched,
+ char *text, int nentries, struct berval **urls );
+int slapi_send_ldap_search_entry( Slapi_PBlock *pb, Slapi_Entry *e,
+ LDAPControl **ectrls, char **attrs, int attrsonly );
+int slapi_send_ldap_search_reference( Slapi_PBlock *pb, Slapi_Entry *e,
+ struct berval **urls, LDAPControl **ectrls, struct berval **v2refs );
+
+/* filter routines */
+Slapi_Filter *slapi_str2filter( char *str );
+Slapi_Filter *slapi_filter_dup( Slapi_Filter *f );
+void slapi_filter_free( Slapi_Filter *f, int recurse );
+int slapi_filter_get_choice( Slapi_Filter *f);
+int slapi_filter_get_ava( Slapi_Filter *f, char **type, struct berval **bval );
+Slapi_Filter *slapi_filter_list_first( Slapi_Filter *f );
+Slapi_Filter *slapi_filter_list_next( Slapi_Filter *f, Slapi_Filter *fprev );
+int slapi_filter_get_attribute_type( Slapi_Filter *f, char **type );
+int slapi_x_filter_set_attribute_type( Slapi_Filter *f, const char *type );
+int slapi_filter_get_subfilt( Slapi_Filter *f, char **type, char **initial,
+ char ***any, char **final );
+Slapi_Filter *slapi_filter_join( int ftype, Slapi_Filter *f1, Slapi_Filter *f2);
+int slapi_x_filter_append( int choice, Slapi_Filter **pContainingFilter,
+ Slapi_Filter **pNextFilter, Slapi_Filter *filterToAppend );
+int slapi_filter_test( Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Filter *f,
+ int verify_access );
+int slapi_filter_test_simple( Slapi_Entry *e, Slapi_Filter *f );
+typedef int (*FILTER_APPLY_FN)( Slapi_Filter *f, void *arg );
+int slapi_filter_apply( Slapi_Filter *f, FILTER_APPLY_FN fn, void *arg, int *error_code );
+#define SLAPI_FILTER_SCAN_STOP -1 /* set by callback */
+#define SLAPI_FILTER_SCAN_ERROR -2 /* set by callback */
+#define SLAPI_FILTER_SCAN_NOMORE 0 /* set by callback */
+#define SLAPI_FILTER_SCAN_CONTINUE 1 /* set by callback */
+#define SLAPI_FILTER_UNKNOWN_FILTER_TYPE 2 /* set by slapi_filter_apply() */
+
+/* internal add/delete/search/modify routines */
+Slapi_PBlock *slapi_search_internal( char *base, int scope, char *filter,
+ LDAPControl **controls, char **attrs, int attrsonly );
+Slapi_PBlock *slapi_modify_internal( char *dn, LDAPMod **mods,
+ LDAPControl **controls, int log_change );
+Slapi_PBlock *slapi_add_internal( char * dn, LDAPMod **attrs,
+ LDAPControl **controls, int log_changes );
+Slapi_PBlock *slapi_add_entry_internal( Slapi_Entry * e,
+ LDAPControl **controls, int log_change );
+Slapi_PBlock *slapi_delete_internal( char * dn, LDAPControl **controls,
+ int log_change );
+Slapi_PBlock *slapi_modrdn_internal( char * olddn, char * newrdn,
+ int deloldrdn, LDAPControl **controls,
+ int log_change );
+Slapi_PBlock *slapi_rename_internal( const char * olddn, const char *newrdn,
+ const char *newsuperior, int delolrdn,
+ LDAPControl **controls, int log_change );
+void slapi_free_search_results_internal(Slapi_PBlock *pb);
+
+/* new internal add/delete/search/modify routines */
+typedef void (*plugin_result_callback)( int rc, void *callback_data );
+typedef int (*plugin_referral_entry_callback)( char * referral,
+ void *callback_data );
+typedef int (*plugin_search_entry_callback)( Slapi_Entry *e,
+ void *callback_data );
+void slapi_free_search_results_internal( Slapi_PBlock *pb );
+
+#define SLAPI_OP_FLAG_NEVER_CHAIN 0x0800
+
+int slapi_search_internal_pb( Slapi_PBlock *pb );
+int slapi_search_internal_callback_pb( Slapi_PBlock *pb, void *callback_data,
+ plugin_result_callback prc, plugin_search_entry_callback psec,
+ plugin_referral_entry_callback prec );
+int slapi_add_internal_pb( Slapi_PBlock *pb );
+int slapi_modify_internal_pb( Slapi_PBlock *pb );
+int slapi_modrdn_internal_pb( Slapi_PBlock *pb );
+int slapi_delete_internal_pb( Slapi_PBlock *pb );
+
+int slapi_seq_internal_callback_pb(Slapi_PBlock *pb, void *callback_data,
+ plugin_result_callback res_callback,
+ plugin_search_entry_callback srch_callback,
+ plugin_referral_entry_callback ref_callback);
+
+void slapi_search_internal_set_pb( Slapi_PBlock *pb, const char *base,
+ int scope, const char *filter, char **attrs, int attrsonly,
+ LDAPControl **controls, const char *uniqueid,
+ Slapi_ComponentId *plugin_identity, int operation_flags );
+void slapi_add_entry_internal_set_pb( Slapi_PBlock *pb, Slapi_Entry *e,
+ LDAPControl **controls, Slapi_ComponentId *plugin_identity,
+ int operation_flags );
+int slapi_add_internal_set_pb( Slapi_PBlock *pb, const char *dn,
+ LDAPMod **attrs, LDAPControl **controls,
+ Slapi_ComponentId *plugin_identity, int operation_flags );
+void slapi_modify_internal_set_pb( Slapi_PBlock *pb, const char *dn,
+ LDAPMod **mods, LDAPControl **controls, const char *uniqueid,
+ Slapi_ComponentId *plugin_identity, int operation_flags );
+void slapi_rename_internal_set_pb( Slapi_PBlock *pb, const char *olddn,
+ const char *newrdn, const char *newsuperior, int deloldrdn,
+ LDAPControl **controls, const char *uniqueid,
+ Slapi_ComponentId *plugin_identity, int operation_flags );
+void slapi_delete_internal_set_pb( Slapi_PBlock *pb, const char *dn,
+ LDAPControl **controls, const char *uniqueid,
+ Slapi_ComponentId *plugin_identity, int operation_flags );
+void slapi_seq_internal_set_pb( Slapi_PBlock *pb, char *ibase, int type,
+ char *attrname, char *val, char **attrs, int attrsonly,
+ LDAPControl **controls, Slapi_ComponentId *plugin_identity,
+ int operation_flags );
+
+/* connection related routines */
+int slapi_is_connection_ssl(Slapi_PBlock *pPB, int *isSSL);
+int slapi_get_client_port(Slapi_PBlock *pPB, int *fromPort);
+
+/* computed attributes */
+typedef struct _computed_attr_context computed_attr_context;
+typedef int (*slapi_compute_output_t)(computed_attr_context *c, Slapi_Attr *a, Slapi_Entry *e);
+typedef int (*slapi_compute_callback_t)(computed_attr_context *c, char *type, Slapi_Entry *e, slapi_compute_output_t outputfn);
+typedef int (*slapi_search_rewrite_callback_t)(Slapi_PBlock *pb);
+int slapi_compute_add_evaluator(slapi_compute_callback_t function);
+int slapi_compute_add_search_rewriter(slapi_search_rewrite_callback_t function);
+int compute_rewrite_search_filter(Slapi_PBlock *pb);
+int compute_evaluator(computed_attr_context *c, char *type, Slapi_Entry *e, slapi_compute_output_t outputfn);
+int slapi_x_compute_get_pblock(computed_attr_context *c, Slapi_PBlock **pb);
+
+/* backend routines */
+void slapi_be_set_readonly( Slapi_Backend *be, int readonly );
+int slapi_be_get_readonly( Slapi_Backend *be );
+const char *slapi_x_be_get_updatedn( Slapi_Backend *be );
+Slapi_Backend *slapi_be_select( const Slapi_DN *sdn );
+
+/* ACL plugins; only SLAPI_PLUGIN_ACL_ALLOW_ACCESS supported now */
+typedef int (*slapi_acl_callback_t)(Slapi_PBlock *pb,
+ Slapi_Entry *e,
+ const char *attr,
+ struct berval *berval,
+ int access,
+ void *state);
+
+/* object extensions */
+typedef void *(*slapi_extension_constructor_fnptr)(void *object, void *parent);
+
+typedef void (*slapi_extension_destructor_fnptr)(void *extension,
+ void *object, void *parent);
+
+int slapi_register_object_extension( const char *pluginname,
+ const char *objectname, slapi_extension_constructor_fnptr constructor,
+ slapi_extension_destructor_fnptr destructor, int *objecttype,
+ int *extensionhandle);
+
+#define SLAPI_EXT_CONNECTION "Connection"
+#define SLAPI_EXT_OPERATION "Operation"
+#define SLAPI_EXT_ENTRY "Entry"
+#define SLAPI_EXT_MTNODE "Mapping Tree Node"
+
+void *slapi_get_object_extension(int objecttype, void *object,
+ int extensionhandle);
+void slapi_set_object_extension(int objecttype, void *object,
+ int extensionhandle, void *extension);
+
+int slapi_x_backend_get_flags( const Slapi_Backend *be, unsigned long *flags );
+
+/* parameters currently supported */
+
+/*
+ * Attribute flags returned by slapi_attr_get_flags()
+ */
+#define SLAPI_ATTR_FLAG_SINGLE 0x0001
+#define SLAPI_ATTR_FLAG_OPATTR 0x0002
+#define SLAPI_ATTR_FLAG_READONLY 0x0004
+#define SLAPI_ATTR_FLAG_STD_ATTR SLAPI_ATTR_FLAG_READONLY
+#define SLAPI_ATTR_FLAG_OBSOLETE 0x0040
+#define SLAPI_ATTR_FLAG_COLLECTIVE 0x0080
+#define SLAPI_ATTR_FLAG_NOUSERMOD 0x0100
+
+/*
+ * Backend flags returned by slapi_x_backend_get_flags()
+ */
+#define SLAPI_BACKEND_FLAG_NOLASTMOD 0x0001U
+#define SLAPI_BACKEND_FLAG_NO_SCHEMA_CHECK 0x0002U
+#define SLAPI_BACKEND_FLAG_GLUE_INSTANCE 0x0010U /* a glue backend */
+#define SLAPI_BACKEND_FLAG_GLUE_SUBORDINATE 0x0020U /* child of a glue hierarchy */
+#define SLAPI_BACKEND_FLAG_GLUE_LINKED 0x0040U /* child is connected to parent */
+#define SLAPI_BACKEND_FLAG_OVERLAY 0x0080U /* this db struct is an overlay */
+#define SLAPI_BACKEND_FLAG_GLOBAL_OVERLAY 0x0100U /* this db struct is a global overlay */
+#define SLAPI_BACKEND_FLAG_SHADOW 0x8000U /* a shadow */
+#define SLAPI_BACKEND_FLAG_SYNC_SHADOW 0x1000U /* a sync shadow */
+#define SLAPI_BACKEND_FLAG_SLURP_SHADOW 0x2000U /* a slurp shadow */
+
+/*
+ * ACL levels
+ */
+#define SLAPI_ACL_COMPARE 0x01
+#define SLAPI_ACL_SEARCH 0x02
+#define SLAPI_ACL_READ 0x04
+#define SLAPI_ACL_WRITE 0x08
+#define SLAPI_ACL_DELETE 0x10
+#define SLAPI_ACL_ADD 0x20
+#define SLAPI_ACL_SELF 0x40
+#define SLAPI_ACL_PROXY 0x80
+#define SLAPI_ACL_ALL 0x7f
+
+/* plugin types supported */
+
+#define SLAPI_PLUGIN_DATABASE 1
+#define SLAPI_PLUGIN_EXTENDEDOP 2
+#define SLAPI_PLUGIN_PREOPERATION 3
+#define SLAPI_PLUGIN_POSTOPERATION 4
+#define SLAPI_PLUGIN_MATCHINGRULE 5
+#define SLAPI_PLUGIN_SYNTAX 6
+#define SLAPI_PLUGIN_AUDIT 7
+
+/* misc params */
+
+#define SLAPI_BACKEND 130
+#define SLAPI_CONNECTION 131
+#define SLAPI_OPERATION 132
+#define SLAPI_REQUESTOR_ISROOT 133
+#define SLAPI_BE_MONITORDN 134
+#define SLAPI_BE_TYPE 135
+#define SLAPI_BE_READONLY 136
+#define SLAPI_BE_LASTMOD 137
+#define SLAPI_CONN_ID 139
+
+/* operation params */
+#define SLAPI_OPINITIATED_TIME 140
+#define SLAPI_REQUESTOR_DN 141
+#define SLAPI_IS_REPLICATED_OPERATION 142
+#define SLAPI_REQUESTOR_ISUPDATEDN SLAPI_IS_REPLICATED_OPERATION
+
+/* connection structure params*/
+#define SLAPI_CONN_DN 143
+#define SLAPI_CONN_AUTHTYPE 144
+#define SLAPI_CONN_CLIENTIP 145
+#define SLAPI_CONN_SERVERIP 146
+/* OpenLDAP extensions */
+#define SLAPI_X_CONN_CLIENTPATH 1300
+#define SLAPI_X_CONN_SERVERPATH 1301
+#define SLAPI_X_CONN_IS_UDP 1302
+#define SLAPI_X_CONN_SSF 1303
+#define SLAPI_X_CONN_SASL_CONTEXT 1304
+#define SLAPI_X_OPERATION_DELETE_GLUE_PARENT 1305
+#define SLAPI_X_RELAX 1306
+#define SLAPI_X_MANAGEDIT SLAPI_X_RELAX
+#define SLAPI_X_OPERATION_NO_SCHEMA_CHECK 1307
+#define SLAPI_X_ADD_STRUCTURAL_CLASS 1308
+#define SLAPI_X_OPERATION_NO_SUBORDINATE_GLUE 1309
+
+/* Authentication types */
+#define SLAPD_AUTH_NONE "none"
+#define SLAPD_AUTH_SIMPLE "simple"
+#define SLAPD_AUTH_SSL "SSL"
+#define SLAPD_AUTH_SASL "SASL "
+
+/* plugin configuration parmams */
+#define SLAPI_PLUGIN 3
+#define SLAPI_PLUGIN_PRIVATE 4
+#define SLAPI_PLUGIN_TYPE 5
+#define SLAPI_PLUGIN_ARGV 6
+#define SLAPI_PLUGIN_ARGC 7
+#define SLAPI_PLUGIN_VERSION 8
+#define SLAPI_PLUGIN_OPRETURN 9
+#define SLAPI_PLUGIN_OBJECT 10
+#define SLAPI_PLUGIN_DESTROY_FN 11
+#define SLAPI_PLUGIN_DESCRIPTION 12
+#define SLAPI_PLUGIN_IDENTITY 13
+
+/* internal opreations params */
+#define SLAPI_PLUGIN_INTOP_RESULT 15
+#define SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES 16
+#define SLAPI_PLUGIN_INTOP_SEARCH_REFERRALS 17
+
+/* transaction arguments */
+#define SLAPI_PARENT_TXN 190
+#define SLAPI_TXN 191
+
+/* function pointer params for backends */
+#define SLAPI_PLUGIN_DB_BIND_FN 200
+#define SLAPI_PLUGIN_DB_UNBIND_FN 201
+#define SLAPI_PLUGIN_DB_SEARCH_FN 202
+#define SLAPI_PLUGIN_DB_COMPARE_FN 203
+#define SLAPI_PLUGIN_DB_MODIFY_FN 204
+#define SLAPI_PLUGIN_DB_MODRDN_FN 205
+#define SLAPI_PLUGIN_DB_ADD_FN 206
+#define SLAPI_PLUGIN_DB_DELETE_FN 207
+#define SLAPI_PLUGIN_DB_ABANDON_FN 208
+#define SLAPI_PLUGIN_DB_CONFIG_FN 209
+#define SLAPI_PLUGIN_CLOSE_FN 210
+#define SLAPI_PLUGIN_DB_FLUSH_FN 211
+#define SLAPI_PLUGIN_START_FN 212
+#define SLAPI_PLUGIN_DB_SEQ_FN 213
+#define SLAPI_PLUGIN_DB_ENTRY_FN 214
+#define SLAPI_PLUGIN_DB_REFERRAL_FN 215
+#define SLAPI_PLUGIN_DB_RESULT_FN 216
+#define SLAPI_PLUGIN_DB_LDIF2DB_FN 217
+#define SLAPI_PLUGIN_DB_DB2LDIF_FN 218
+#define SLAPI_PLUGIN_DB_BEGIN_FN 219
+#define SLAPI_PLUGIN_DB_COMMIT_FN 220
+#define SLAPI_PLUGIN_DB_ABORT_FN 221
+#define SLAPI_PLUGIN_DB_ARCHIVE2DB_FN 222
+#define SLAPI_PLUGIN_DB_DB2ARCHIVE_FN 223
+#define SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN 224
+#define SLAPI_PLUGIN_DB_FREE_RESULT_SET_FN 225
+#define SLAPI_PLUGIN_DB_SIZE_FN 226
+#define SLAPI_PLUGIN_DB_TEST_FN 227
+
+
+/* functions pointers for LDAP V3 extended ops */
+#define SLAPI_PLUGIN_EXT_OP_FN 300
+#define SLAPI_PLUGIN_EXT_OP_OIDLIST 301
+
+/* preoperation */
+#define SLAPI_PLUGIN_PRE_BIND_FN 401
+#define SLAPI_PLUGIN_PRE_UNBIND_FN 402
+#define SLAPI_PLUGIN_PRE_SEARCH_FN 403
+#define SLAPI_PLUGIN_PRE_COMPARE_FN 404
+#define SLAPI_PLUGIN_PRE_MODIFY_FN 405
+#define SLAPI_PLUGIN_PRE_MODRDN_FN 406
+#define SLAPI_PLUGIN_PRE_ADD_FN 407
+#define SLAPI_PLUGIN_PRE_DELETE_FN 408
+#define SLAPI_PLUGIN_PRE_ABANDON_FN 409
+#define SLAPI_PLUGIN_PRE_ENTRY_FN 410
+#define SLAPI_PLUGIN_PRE_REFERRAL_FN 411
+#define SLAPI_PLUGIN_PRE_RESULT_FN 412
+
+/* internal preoperation */
+#define SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN 420
+#define SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN 421
+#define SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN 422
+#define SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN 423
+
+/* backend preoperation */
+#define SLAPI_PLUGIN_BE_PRE_ADD_FN 450
+#define SLAPI_PLUGIN_BE_PRE_MODIFY_FN 451
+#define SLAPI_PLUGIN_BE_PRE_MODRDN_FN 452
+#define SLAPI_PLUGIN_BE_PRE_DELETE_FN 453
+
+/* postoperation */
+#define SLAPI_PLUGIN_POST_BIND_FN 501
+#define SLAPI_PLUGIN_POST_UNBIND_FN 502
+#define SLAPI_PLUGIN_POST_SEARCH_FN 503
+#define SLAPI_PLUGIN_POST_COMPARE_FN 504
+#define SLAPI_PLUGIN_POST_MODIFY_FN 505
+#define SLAPI_PLUGIN_POST_MODRDN_FN 506
+#define SLAPI_PLUGIN_POST_ADD_FN 507
+#define SLAPI_PLUGIN_POST_DELETE_FN 508
+#define SLAPI_PLUGIN_POST_ABANDON_FN 509
+#define SLAPI_PLUGIN_POST_ENTRY_FN 510
+#define SLAPI_PLUGIN_POST_REFERRAL_FN 511
+#define SLAPI_PLUGIN_POST_RESULT_FN 512
+
+/* internal postoperation */
+#define SLAPI_PLUGIN_INTERNAL_POST_ADD_FN 520
+#define SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN 521
+#define SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN 522
+#define SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN 523
+
+/* backend postoperation */
+#define SLAPI_PLUGIN_BE_POST_ADD_FN 550
+#define SLAPI_PLUGIN_BE_POST_MODIFY_FN 551
+#define SLAPI_PLUGIN_BE_POST_MODRDN_FN 552
+#define SLAPI_PLUGIN_BE_POST_DELETE_FN 553
+
+#define SLAPI_OPERATION_TYPE 590
+#define SLAPI_OPERATION_MSGID 591
+
+#define SLAPI_PLUGIN_MR_FILTER_CREATE_FN 600
+#define SLAPI_PLUGIN_MR_INDEXER_CREATE_FN 601
+#define SLAPI_PLUGIN_MR_FILTER_MATCH_FN 602
+#define SLAPI_PLUGIN_MR_FILTER_INDEX_FN 603
+#define SLAPI_PLUGIN_MR_FILTER_RESET_FN 604
+#define SLAPI_PLUGIN_MR_INDEX_FN 605
+#define SLAPI_PLUGIN_MR_OID 610
+#define SLAPI_PLUGIN_MR_TYPE 611
+#define SLAPI_PLUGIN_MR_VALUE 612
+#define SLAPI_PLUGIN_MR_VALUES 613
+#define SLAPI_PLUGIN_MR_KEYS 614
+#define SLAPI_PLUGIN_MR_FILTER_REUSABLE 615
+#define SLAPI_PLUGIN_MR_QUERY_OPERATOR 616
+#define SLAPI_PLUGIN_MR_USAGE 617
+
+#define SLAPI_MATCHINGRULE_NAME 1
+#define SLAPI_MATCHINGRULE_OID 2
+#define SLAPI_MATCHINGRULE_DESC 3
+#define SLAPI_MATCHINGRULE_SYNTAX 4
+#define SLAPI_MATCHINGRULE_OBSOLETE 5
+
+#define SLAPI_OP_LESS 1
+#define SLAPI_OP_LESS_OR_EQUAL 2
+#define SLAPI_OP_EQUAL 3
+#define SLAPI_OP_GREATER_OR_EQUAL 4
+#define SLAPI_OP_GREATER 5
+#define SLAPI_OP_SUBSTRING 6
+
+#define SLAPI_PLUGIN_MR_USAGE_INDEX 0
+#define SLAPI_PLUGIN_MR_USAGE_SORT 1
+
+#define SLAPI_PLUGIN_SYNTAX_FILTER_AVA 700
+#define SLAPI_PLUGIN_SYNTAX_FILTER_SUB 701
+#define SLAPI_PLUGIN_SYNTAX_VALUES2KEYS 702
+#define SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA 703
+#define SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB 704
+#define SLAPI_PLUGIN_SYNTAX_NAMES 705
+#define SLAPI_PLUGIN_SYNTAX_OID 706
+#define SLAPI_PLUGIN_SYNTAX_FLAGS 707
+#define SLAPI_PLUGIN_SYNTAX_COMPARE 708
+
+#define SLAPI_PLUGIN_SYNTAX_FLAG_ORKEYS 1
+#define SLAPI_PLUGIN_SYNTAX_FLAG_ORDERING 2
+
+#define SLAPI_PLUGIN_ACL_INIT 730
+#define SLAPI_PLUGIN_ACL_SYNTAX_CHECK 731
+#define SLAPI_PLUGIN_ACL_ALLOW_ACCESS 732
+#define SLAPI_PLUGIN_ACL_MODS_ALLOWED 733
+#define SLAPI_PLUGIN_ACL_MODS_UPDATE 734
+
+#define SLAPI_OPERATION_AUTHTYPE 741
+#define SLAPI_OPERATION_ID 742
+#define SLAPI_CONN_CERT 743
+#define SLAPI_CONN_AUTHMETHOD 746
+#define SLAPI_IS_INTERNAL_OPERATION 748
+
+#define SLAPI_RESULT_CODE 881
+#define SLAPI_RESULT_TEXT 882
+#define SLAPI_RESULT_MATCHED 883
+
+/* managedsait control */
+#define SLAPI_MANAGEDSAIT 1000
+
+/* audit plugin defines */
+#define SLAPI_PLUGIN_AUDIT_DATA 1100
+#define SLAPI_PLUGIN_AUDIT_FN 1101
+
+/* backend_group extension */
+#define SLAPI_X_PLUGIN_PRE_GROUP_FN 1202
+#define SLAPI_X_PLUGIN_POST_GROUP_FN 1203
+
+#define SLAPI_X_GROUP_ENTRY 1250 /* group entry */
+#define SLAPI_X_GROUP_ATTRIBUTE 1251 /* member attribute */
+#define SLAPI_X_GROUP_OPERATION_DN 1252 /* asserted value */
+#define SLAPI_X_GROUP_TARGET_ENTRY 1253 /* target entry */
+
+/* internal preoperation extensions */
+#define SLAPI_PLUGIN_INTERNAL_PRE_BIND_FN 1260
+#define SLAPI_PLUGIN_INTERNAL_PRE_UNBIND_FN 1261
+#define SLAPI_PLUGIN_INTERNAL_PRE_SEARCH_FN 1262
+#define SLAPI_PLUGIN_INTERNAL_PRE_COMPARE_FN 1263
+#define SLAPI_PLUGIN_INTERNAL_PRE_ABANDON_FN 1264
+
+/* internal postoperation extensions */
+#define SLAPI_PLUGIN_INTERNAL_POST_BIND_FN 1270
+#define SLAPI_PLUGIN_INTERNAL_POST_UNBIND_FN 1271
+#define SLAPI_PLUGIN_INTERNAL_POST_SEARCH_FN 1272
+#define SLAPI_PLUGIN_INTERNAL_POST_COMPARE_FN 1273
+#define SLAPI_PLUGIN_INTERNAL_POST_ABANDON_FN 1274
+
+/* config stuff */
+#define SLAPI_CONFIG_FILENAME 40
+#define SLAPI_CONFIG_LINENO 41
+#define SLAPI_CONFIG_ARGC 42
+#define SLAPI_CONFIG_ARGV 43
+
+/* operational params */
+#define SLAPI_TARGET_ADDRESS 48
+#define SLAPI_TARGET_UNIQUEID 49
+#define SLAPI_TARGET_DN 50
+
+/* server LDAPv3 controls */
+#define SLAPI_REQCONTROLS 51
+#define SLAPI_RESCONTROLS 55
+#define SLAPI_ADD_RESCONTROL 56
+#define SLAPI_CONTROLS_ARG 58
+
+/* add params */
+#define SLAPI_ADD_TARGET SLAPI_TARGET_DN
+#define SLAPI_ADD_ENTRY 60
+#define SLAPI_ADD_EXISTING_DN_ENTRY 61
+#define SLAPI_ADD_PARENT_ENTRY 62
+#define SLAPI_ADD_PARENT_UNIQUEID 63
+#define SLAPI_ADD_EXISTING_UNIQUEID_ENTRY 64
+
+/* bind params */
+#define SLAPI_BIND_TARGET SLAPI_TARGET_DN
+#define SLAPI_BIND_METHOD 70
+#define SLAPI_BIND_CREDENTIALS 71
+#define SLAPI_BIND_SASLMECHANISM 72
+#define SLAPI_BIND_RET_SASLCREDS 73
+
+/* compare params */
+#define SLAPI_COMPARE_TARGET SLAPI_TARGET_DN
+#define SLAPI_COMPARE_TYPE 80
+#define SLAPI_COMPARE_VALUE 81
+
+/* delete params */
+#define SLAPI_DELETE_TARGET SLAPI_TARGET_DN
+#define SLAPI_DELETE_EXISTING_ENTRY SLAPI_ADD_EXISTING_DN_ENTRY
+
+/* modify params */
+#define SLAPI_MODIFY_TARGET SLAPI_TARGET_DN
+#define SLAPI_MODIFY_MODS 90
+#define SLAPI_MODIFY_EXISTING_ENTRY SLAPI_ADD_EXISTING_DN_ENTRY
+
+/* modrdn params */
+#define SLAPI_MODRDN_TARGET SLAPI_TARGET_DN
+#define SLAPI_MODRDN_NEWRDN 100
+#define SLAPI_MODRDN_DELOLDRDN 101
+#define SLAPI_MODRDN_NEWSUPERIOR 102 /* v3 only */
+#define SLAPI_MODRDN_EXISTING_ENTRY SLAPI_ADD_EXISTING_DN_ENTRY
+#define SLAPI_MODRDN_PARENT_ENTRY 104
+#define SLAPI_MODRDN_NEWPARENT_ENTRY 105
+#define SLAPI_MODRDN_TARGET_ENTRY 106
+#define SLAPI_MODRDN_NEWSUPERIOR_ADDRESS 107
+
+/* search params */
+#define SLAPI_SEARCH_TARGET SLAPI_TARGET_DN
+#define SLAPI_SEARCH_SCOPE 110
+#define SLAPI_SEARCH_DEREF 111
+#define SLAPI_SEARCH_SIZELIMIT 112
+#define SLAPI_SEARCH_TIMELIMIT 113
+#define SLAPI_SEARCH_FILTER 114
+#define SLAPI_SEARCH_STRFILTER 115
+#define SLAPI_SEARCH_ATTRS 116
+#define SLAPI_SEARCH_ATTRSONLY 117
+
+/* abandon params */
+#define SLAPI_ABANDON_MSGID 120
+
+/* extended operation params */
+#define SLAPI_EXT_OP_REQ_OID 160
+#define SLAPI_EXT_OP_REQ_VALUE 161
+
+/* extended operation return codes */
+#define SLAPI_EXT_OP_RET_OID 162
+#define SLAPI_EXT_OP_RET_VALUE 163
+
+#define SLAPI_PLUGIN_EXTENDED_SENT_RESULT -1
+
+#define SLAPI_FAIL_DISKFULL -2
+#define SLAPI_FAIL_GENERAL -1
+#define SLAPI_PLUGIN_EXTENDED_NOT_HANDLED -2
+#define SLAPI_BIND_SUCCESS 0
+#define SLAPI_BIND_FAIL 2
+#define SLAPI_BIND_ANONYMOUS 3
+
+/* Search result params */
+#define SLAPI_SEARCH_RESULT_SET 193
+#define SLAPI_SEARCH_RESULT_ENTRY 194
+#define SLAPI_NENTRIES 195
+#define SLAPI_SEARCH_REFERRALS 196
+
+/* filter types */
+#ifndef LDAP_FILTER_AND
+#define LDAP_FILTER_AND 0xa0L
+#endif
+#ifndef LDAP_FILTER_OR
+#define LDAP_FILTER_OR 0xa1L
+#endif
+#ifndef LDAP_FILTER_NOT
+#define LDAP_FILTER_NOT 0xa2L
+#endif
+#ifndef LDAP_FILTER_EQUALITY
+#define LDAP_FILTER_EQUALITY 0xa3L
+#endif
+#ifndef LDAP_FILTER_SUBSTRINGS
+#define LDAP_FILTER_SUBSTRINGS 0xa4L
+#endif
+#ifndef LDAP_FILTER_GE
+#define LDAP_FILTER_GE 0xa5L
+#endif
+#ifndef LDAP_FILTER_LE
+#define LDAP_FILTER_LE 0xa6L
+#endif
+#ifndef LDAP_FILTER_PRESENT
+#define LDAP_FILTER_PRESENT 0x87L
+#endif
+#ifndef LDAP_FILTER_APPROX
+#define LDAP_FILTER_APPROX 0xa8L
+#endif
+#ifndef LDAP_FILTER_EXT_MATCH
+#define LDAP_FILTER_EXT_MATCH 0xa9L
+#endif
+
+int slapi_log_error( int severity, char *subsystem, char *fmt, ... );
+#define SLAPI_LOG_FATAL 0
+#define SLAPI_LOG_TRACE 1
+#define SLAPI_LOG_PACKETS 2
+#define SLAPI_LOG_ARGS 3
+#define SLAPI_LOG_CONNS 4
+#define SLAPI_LOG_BER 5
+#define SLAPI_LOG_FILTER 6
+#define SLAPI_LOG_CONFIG 7
+#define SLAPI_LOG_ACL 8
+#define SLAPI_LOG_SHELL 9
+#define SLAPI_LOG_PARSE 10
+#define SLAPI_LOG_HOUSE 11
+#define SLAPI_LOG_REPL 12
+#define SLAPI_LOG_CACHE 13
+#define SLAPI_LOG_PLUGIN 14
+#define SLAPI_LOG_TIMING 15
+
+#define SLAPI_PLUGIN_DESCRIPTION 12
+typedef struct slapi_plugindesc {
+ char *spd_id;
+ char *spd_vendor;
+ char *spd_version;
+ char *spd_description;
+} Slapi_PluginDesc;
+
+#define SLAPI_PLUGIN_VERSION_01 "01"
+#define SLAPI_PLUGIN_VERSION_02 "02"
+#define SLAPI_PLUGIN_VERSION_03 "03"
+#define SLAPI_PLUGIN_CURRENT_VERSION SLAPI_PLUGIN_VERSION_03
+
+#endif /* _SLAPI_PLUGIN_H */
+
diff --git a/plugin/Makefile.am b/plugin/Makefile.am
new file mode 100644
index 0000000..d1887a3
--- /dev/null
+++ b/plugin/Makefile.am
@@ -0,0 +1,13 @@
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ -DSTRSET_CUSTMEM=1 \
+ -DDNS_NOTIFY_PATH=\"$(bindir)/notify-slaves\"
+
+lib_LTLIBRARIES = slapi-dnsnotify.la
+
+slapi_dnsnotify_la_SOURCES = \
+ slapi-dnsnotify.c
+
+slapi_dnsnotify_la_LDFLAGS = -module -avoid-version \
+ -export-symbols-regex 'plugin_init'
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;
+}
+
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..5372107
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,29 @@
+
+UNIT_TESTS =
+
+INCLUDES= \
+ -I$(top_srcdir) \
+ -I$(top_builddir)
+
+noinst_PROGRAMS= \
+ run-tests
+
+run-tests.c: $(UNIT_TESTS) prep-tests.sh Makefile.am
+ sh prep-tests.sh -b run-tests $(UNIT_TESTS)
+
+run_tests_SOURCES = \
+ run-tests.c \
+ run-tests.h \
+ $(UNIT_TESTS)
+
+EXTRA_DIST = \
+ prep-tests.sh \
+ cu-test \
+ test-helpers.c \
+ test-helpers.h
+
+# ------------------------------------------------------------------------------
+# Run the tests
+
+run: $(noinst_PROGRAMS)
+ ./run-tests
diff --git a/tests/cu-test/AllTests.c b/tests/cu-test/AllTests.c
new file mode 100644
index 0000000..5c849ef
--- /dev/null
+++ b/tests/cu-test/AllTests.c
@@ -0,0 +1,25 @@
+#include <stdio.h>
+
+#include "CuTest.h"
+
+CuSuite* CuGetSuite();
+CuSuite* CuStringGetSuite();
+
+void RunAllTests(void)
+{
+ CuString *output = CuStringNew();
+ CuSuite* suite = CuSuiteNew();
+
+ CuSuiteAddSuite(suite, CuGetSuite());
+ CuSuiteAddSuite(suite, CuStringGetSuite());
+
+ CuSuiteRun(suite);
+ CuSuiteSummary(suite, output);
+ CuSuiteDetails(suite, output);
+ printf("%s\n", output->buffer);
+}
+
+int main(void)
+{
+ RunAllTests();
+}
diff --git a/tests/cu-test/CuTest.c b/tests/cu-test/CuTest.c
new file mode 100644
index 0000000..86fa3ea
--- /dev/null
+++ b/tests/cu-test/CuTest.c
@@ -0,0 +1,309 @@
+#include <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "CuTest.h"
+
+/*-------------------------------------------------------------------------*
+ * CuStr
+ *-------------------------------------------------------------------------*/
+
+char* CuStrAlloc(int size)
+{
+ char* newStr = (char*) malloc( sizeof(char) * (size) );
+ return newStr;
+}
+
+char* CuStrCopy(const char* old)
+{
+ int len = strlen(old);
+ char* newStr = CuStrAlloc(len + 1);
+ strcpy(newStr, old);
+ return newStr;
+}
+
+/*-------------------------------------------------------------------------*
+ * CuString
+ *-------------------------------------------------------------------------*/
+
+void CuStringInit(CuString* str)
+{
+ str->length = 0;
+ str->size = STRING_MAX;
+ str->buffer = (char*) malloc(sizeof(char) * str->size);
+ str->buffer[0] = '\0';
+}
+
+CuString* CuStringNew(void)
+{
+ CuString* str = (CuString*) malloc(sizeof(CuString));
+ str->length = 0;
+ str->size = STRING_MAX;
+ str->buffer = (char*) malloc(sizeof(char) * str->size);
+ str->buffer[0] = '\0';
+ return str;
+}
+
+void CuStringResize(CuString* str, int newSize)
+{
+ str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize);
+ str->size = newSize;
+}
+
+void CuStringAppend(CuString* str, const char* text)
+{
+ int length;
+
+ if (text == NULL) {
+ text = "NULL";
+ }
+
+ length = strlen(text);
+ if (str->length + length + 1 >= str->size)
+ CuStringResize(str, str->length + length + 1 + STRING_INC);
+ str->length += length;
+ strcat(str->buffer, text);
+}
+
+void CuStringAppendChar(CuString* str, char ch)
+{
+ char text[2];
+ text[0] = ch;
+ text[1] = '\0';
+ CuStringAppend(str, text);
+}
+
+void CuStringAppendFormat(CuString* str, const char* format, ...)
+{
+ va_list argp;
+ char buf[HUGE_STRING_LEN];
+ va_start(argp, format);
+ vsprintf(buf, format, argp);
+ va_end(argp);
+ CuStringAppend(str, buf);
+}
+
+void CuStringInsert(CuString* str, const char* text, int pos)
+{
+ int length = strlen(text);
+ if (pos > str->length)
+ pos = str->length;
+ if (str->length + length + 1 >= str->size)
+ CuStringResize(str, str->length + length + 1 + STRING_INC);
+ memmove(str->buffer + pos + length, str->buffer + pos, (str->length - pos) + 1);
+ str->length += length;
+ memcpy(str->buffer + pos, text, length);
+}
+
+/*-------------------------------------------------------------------------*
+ * CuTest
+ *-------------------------------------------------------------------------*/
+
+void CuTestInit(CuTest* t, const char* name, TestFunction function)
+{
+ t->name = CuStrCopy(name);
+ t->failed = 0;
+ t->ran = 0;
+ t->message = NULL;
+ t->function = function;
+ t->jumpBuf = NULL;
+}
+
+CuTest* CuTestNew(const char* name, TestFunction function)
+{
+ CuTest* tc = CU_ALLOC(CuTest);
+ CuTestInit(tc, name, function);
+ return tc;
+}
+
+void CuTestRun(CuTest* tc)
+{
+ jmp_buf buf;
+ tc->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ tc->ran = 1;
+ (tc->function)(tc);
+ }
+ tc->jumpBuf = 0;
+}
+
+static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string)
+{
+ char buf[HUGE_STRING_LEN];
+
+ sprintf(buf, "%s:%d: ", file, line);
+ CuStringInsert(string, buf, 0);
+
+ tc->failed = 1;
+ tc->message = string->buffer;
+ if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
+}
+
+void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message)
+{
+ CuString string;
+
+ CuStringInit(&string);
+ if (message2 != NULL)
+ {
+ CuStringAppend(&string, message2);
+ CuStringAppend(&string, ": ");
+ }
+ CuStringAppend(&string, message);
+ CuFailInternal(tc, file, line, &string);
+}
+
+void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition)
+{
+ if (condition) return;
+ CuFail_Line(tc, file, line, NULL, message);
+}
+
+void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ const char* expected, const char* actual)
+{
+ CuString string;
+ if ((expected == NULL && actual == NULL) ||
+ (expected != NULL && actual != NULL &&
+ strcmp(expected, actual) == 0))
+ {
+ return;
+ }
+
+ CuStringInit(&string);
+ if (message != NULL)
+ {
+ CuStringAppend(&string, message);
+ CuStringAppend(&string, ": ");
+ }
+ CuStringAppend(&string, "expected <");
+ CuStringAppend(&string, expected);
+ CuStringAppend(&string, "> but was <");
+ CuStringAppend(&string, actual);
+ CuStringAppend(&string, ">");
+ CuFailInternal(tc, file, line, &string);
+}
+
+void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ int expected, int actual)
+{
+ char buf[STRING_MAX];
+ if (expected == actual) return;
+ sprintf(buf, "expected <%d> but was <%d>", expected, actual);
+ CuFail_Line(tc, file, line, message, buf);
+}
+
+void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ double expected, double actual, double delta)
+{
+ char buf[STRING_MAX];
+ if (fabs(expected - actual) <= delta) return;
+ sprintf(buf, "expected <%lf> but was <%lf>", expected, actual);
+ CuFail_Line(tc, file, line, message, buf);
+}
+
+void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ void* expected, void* actual)
+{
+ char buf[STRING_MAX];
+ if (expected == actual) return;
+ sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual);
+ CuFail_Line(tc, file, line, message, buf);
+}
+
+
+/*-------------------------------------------------------------------------*
+ * CuSuite
+ *-------------------------------------------------------------------------*/
+
+void CuSuiteInit(CuSuite* testSuite)
+{
+ testSuite->count = 0;
+ testSuite->failCount = 0;
+}
+
+CuSuite* CuSuiteNew(void)
+{
+ CuSuite* testSuite = CU_ALLOC(CuSuite);
+ CuSuiteInit(testSuite);
+ return testSuite;
+}
+
+void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase)
+{
+ assert(testSuite->count < MAX_TEST_CASES);
+ testSuite->list[testSuite->count] = testCase;
+ testSuite->count++;
+}
+
+void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2)
+{
+ int i;
+ for (i = 0 ; i < testSuite2->count ; ++i)
+ {
+ CuTest* testCase = testSuite2->list[i];
+ CuSuiteAdd(testSuite, testCase);
+ }
+}
+
+void CuSuiteRun(CuSuite* testSuite)
+{
+ int i;
+ for (i = 0 ; i < testSuite->count ; ++i)
+ {
+ CuTest* testCase = testSuite->list[i];
+ CuTestRun(testCase);
+ if (testCase->failed) { testSuite->failCount += 1; }
+ }
+}
+
+void CuSuiteSummary(CuSuite* testSuite, CuString* summary)
+{
+ int i;
+ for (i = 0 ; i < testSuite->count ; ++i)
+ {
+ CuTest* testCase = testSuite->list[i];
+ CuStringAppend(summary, testCase->failed ? "F" : ".");
+ }
+ CuStringAppend(summary, "\n\n");
+}
+
+void CuSuiteDetails(CuSuite* testSuite, CuString* details)
+{
+ int i;
+ int failCount = 0;
+
+ if (testSuite->failCount == 0)
+ {
+ int passCount = testSuite->count - testSuite->failCount;
+ const char* testWord = passCount == 1 ? "test" : "tests";
+ CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord);
+ }
+ else
+ {
+ if (testSuite->failCount == 1)
+ CuStringAppend(details, "There was 1 failure:\n");
+ else
+ CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount);
+
+ for (i = 0 ; i < testSuite->count ; ++i)
+ {
+ CuTest* testCase = testSuite->list[i];
+ if (testCase->failed)
+ {
+ failCount++;
+ CuStringAppendFormat(details, "%d) %s: %s\n",
+ failCount, testCase->name, testCase->message);
+ }
+ }
+ CuStringAppend(details, "\n!!!FAILURES!!!\n");
+
+ CuStringAppendFormat(details, "Runs: %d ", testSuite->count);
+ CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount);
+ CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount);
+ }
+}
diff --git a/tests/cu-test/CuTest.h b/tests/cu-test/CuTest.h
new file mode 100644
index 0000000..7cd2a4f
--- /dev/null
+++ b/tests/cu-test/CuTest.h
@@ -0,0 +1,111 @@
+#ifndef CU_TEST_H
+#define CU_TEST_H
+
+#include <setjmp.h>
+#include <stdarg.h>
+
+/* CuString */
+
+char* CuStrAlloc(int size);
+char* CuStrCopy(const char* old);
+
+#define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE)))
+
+#define HUGE_STRING_LEN 8192
+#define STRING_MAX 256
+#define STRING_INC 256
+
+typedef struct
+{
+ int length;
+ int size;
+ char* buffer;
+} CuString;
+
+void CuStringInit(CuString* str);
+CuString* CuStringNew(void);
+void CuStringRead(CuString* str, const char* path);
+void CuStringAppend(CuString* str, const char* text);
+void CuStringAppendChar(CuString* str, char ch);
+void CuStringAppendFormat(CuString* str, const char* format, ...);
+void CuStringInsert(CuString* str, const char* text, int pos);
+void CuStringResize(CuString* str, int newSize);
+
+/* CuTest */
+
+typedef struct CuTest CuTest;
+
+typedef void (*TestFunction)(CuTest *);
+
+struct CuTest
+{
+ const char* name;
+ TestFunction function;
+ int failed;
+ int ran;
+ const char* message;
+ jmp_buf *jumpBuf;
+};
+
+void CuTestInit(CuTest* t, const char* name, TestFunction function);
+CuTest* CuTestNew(const char* name, TestFunction function);
+void CuTestRun(CuTest* tc);
+
+/* Internal versions of assert functions -- use the public versions */
+void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message);
+void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition);
+void CuAssertStrEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ const char* expected, const char* actual);
+void CuAssertIntEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ int expected, int actual);
+void CuAssertDblEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ double expected, double actual, double delta);
+void CuAssertPtrEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ void* expected, void* actual);
+
+/* public assert functions */
+
+#define CuFail(tc, ms) CuFail_Line( (tc), __FILE__, __LINE__, NULL, (ms))
+#define CuAssert(tc, ms, cond) CuAssert_Line((tc), __FILE__, __LINE__, (ms), (cond))
+#define CuAssertTrue(tc, cond) CuAssert_Line((tc), __FILE__, __LINE__, "assert failed", (cond))
+
+#define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
+#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
+#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
+#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
+#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl))
+#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl))
+#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
+#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
+
+#define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",(p != NULL))
+#define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),(p != NULL))
+
+/* CuSuite */
+
+#define MAX_TEST_CASES 1024
+
+#define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST))
+
+typedef struct
+{
+ int count;
+ CuTest* list[MAX_TEST_CASES];
+ int failCount;
+
+} CuSuite;
+
+
+void CuSuiteInit(CuSuite* testSuite);
+CuSuite* CuSuiteNew(void);
+void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase);
+void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2);
+void CuSuiteRun(CuSuite* testSuite);
+void CuSuiteSummary(CuSuite* testSuite, CuString* summary);
+void CuSuiteDetails(CuSuite* testSuite, CuString* details);
+
+#endif /* CU_TEST_H */
diff --git a/tests/cu-test/CuTestTest.c b/tests/cu-test/CuTestTest.c
new file mode 100644
index 0000000..547f119
--- /dev/null
+++ b/tests/cu-test/CuTestTest.c
@@ -0,0 +1,709 @@
+#include <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "CuTest.h"
+
+/*-------------------------------------------------------------------------*
+ * Helper functions
+ *-------------------------------------------------------------------------*/
+
+#define CompareAsserts(tc, message, expected, actual) X_CompareAsserts((tc), __FILE__, __LINE__, (message), (expected), (actual))
+
+static void X_CompareAsserts(CuTest* tc, const char *file, int line, const char* message, const char* expected, const char* actual)
+{
+ int mismatch;
+ if (expected == NULL || actual == NULL) {
+ mismatch = (expected != NULL || actual != NULL);
+ } else {
+ const char *front = __FILE__ ":";
+ const size_t frontLen = strlen(front);
+ const size_t expectedLen = strlen(expected);
+
+ const char *matchStr = actual;
+
+ mismatch = (strncmp(matchStr, front, frontLen) != 0);
+ if (!mismatch) {
+ matchStr = strchr(matchStr + frontLen, ':');
+ mismatch |= (matchStr == NULL || strncmp(matchStr, ": ", 2));
+ if (!mismatch) {
+ matchStr += 2;
+ mismatch |= (strncmp(matchStr, expected, expectedLen) != 0);
+ }
+ }
+ }
+
+ CuAssert_Line(tc, file, line, message, !mismatch);
+}
+
+/*-------------------------------------------------------------------------*
+ * CuString Test
+ *-------------------------------------------------------------------------*/
+
+void TestCuStringNew(CuTest* tc)
+{
+ CuString* str = CuStringNew();
+ CuAssertTrue(tc, 0 == str->length);
+ CuAssertTrue(tc, 0 != str->size);
+ CuAssertStrEquals(tc, "", str->buffer);
+}
+
+
+void TestCuStringAppend(CuTest* tc)
+{
+ CuString* str = CuStringNew();
+ CuStringAppend(str, "hello");
+ CuAssertIntEquals(tc, 5, str->length);
+ CuAssertStrEquals(tc, "hello", str->buffer);
+ CuStringAppend(str, " world");
+ CuAssertIntEquals(tc, 11, str->length);
+ CuAssertStrEquals(tc, "hello world", str->buffer);
+}
+
+
+void TestCuStringAppendNULL(CuTest* tc)
+{
+ CuString* str = CuStringNew();
+ CuStringAppend(str, NULL);
+ CuAssertIntEquals(tc, 4, str->length);
+ CuAssertStrEquals(tc, "NULL", str->buffer);
+}
+
+
+void TestCuStringAppendChar(CuTest* tc)
+{
+ CuString* str = CuStringNew();
+ CuStringAppendChar(str, 'a');
+ CuStringAppendChar(str, 'b');
+ CuStringAppendChar(str, 'c');
+ CuStringAppendChar(str, 'd');
+ CuAssertIntEquals(tc, 4, str->length);
+ CuAssertStrEquals(tc, "abcd", str->buffer);
+}
+
+
+void TestCuStringInserts(CuTest* tc)
+{
+ CuString* str = CuStringNew();
+ CuStringAppend(str, "world");
+ CuAssertIntEquals(tc, 5, str->length);
+ CuAssertStrEquals(tc, "world", str->buffer);
+ CuStringInsert(str, "hell", 0);
+ CuAssertIntEquals(tc, 9, str->length);
+ CuAssertStrEquals(tc, "hellworld", str->buffer);
+ CuStringInsert(str, "o ", 4);
+ CuAssertIntEquals(tc, 11, str->length);
+ CuAssertStrEquals(tc, "hello world", str->buffer);
+ CuStringInsert(str, "!", 11);
+ CuAssertIntEquals(tc, 12, str->length);
+ CuAssertStrEquals(tc, "hello world!", str->buffer);
+}
+
+
+void TestCuStringResizes(CuTest* tc)
+{
+ CuString* str = CuStringNew();
+ int i;
+ for(i = 0 ; i < STRING_MAX ; ++i)
+ {
+ CuStringAppend(str, "aa");
+ }
+ CuAssertTrue(tc, STRING_MAX * 2 == str->length);
+ CuAssertTrue(tc, STRING_MAX * 2 <= str->size);
+}
+
+CuSuite* CuStringGetSuite(void)
+{
+ CuSuite* suite = CuSuiteNew();
+
+ SUITE_ADD_TEST(suite, TestCuStringNew);
+ SUITE_ADD_TEST(suite, TestCuStringAppend);
+ SUITE_ADD_TEST(suite, TestCuStringAppendNULL);
+ SUITE_ADD_TEST(suite, TestCuStringAppendChar);
+ SUITE_ADD_TEST(suite, TestCuStringInserts);
+ SUITE_ADD_TEST(suite, TestCuStringResizes);
+
+ return suite;
+}
+
+/*-------------------------------------------------------------------------*
+ * CuTest Test
+ *-------------------------------------------------------------------------*/
+
+void TestPasses(CuTest* tc)
+{
+ CuAssert(tc, "test should pass", 1 == 0 + 1);
+}
+
+void zTestFails(CuTest* tc)
+{
+ CuAssert(tc, "test should fail", 1 == 1 + 1);
+}
+
+
+void TestCuTestNew(CuTest* tc)
+{
+ CuTest* tc2 = CuTestNew("MyTest", TestPasses);
+ CuAssertStrEquals(tc, "MyTest", tc2->name);
+ CuAssertTrue(tc, !tc2->failed);
+ CuAssertTrue(tc, tc2->message == NULL);
+ CuAssertTrue(tc, tc2->function == TestPasses);
+ CuAssertTrue(tc, tc2->ran == 0);
+ CuAssertTrue(tc, tc2->jumpBuf == NULL);
+}
+
+
+void TestCuTestInit(CuTest *tc)
+{
+ CuTest tc2;
+ CuTestInit(&tc2, "MyTest", TestPasses);
+ CuAssertStrEquals(tc, "MyTest", tc2.name);
+ CuAssertTrue(tc, !tc2.failed);
+ CuAssertTrue(tc, tc2.message == NULL);
+ CuAssertTrue(tc, tc2.function == TestPasses);
+ CuAssertTrue(tc, tc2.ran == 0);
+ CuAssertTrue(tc, tc2.jumpBuf == NULL);
+}
+
+void TestCuAssert(CuTest* tc)
+{
+ CuTest tc2;
+ CuTestInit(&tc2, "MyTest", TestPasses);
+
+ CuAssert(&tc2, "test 1", 5 == 4 + 1);
+ CuAssertTrue(tc, !tc2.failed);
+ CuAssertTrue(tc, tc2.message == NULL);
+
+ CuAssert(&tc2, "test 2", 0);
+ CuAssertTrue(tc, tc2.failed);
+ CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message);
+
+ CuAssert(&tc2, "test 3", 1);
+ CuAssertTrue(tc, tc2.failed);
+ CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message);
+
+ CuAssert(&tc2, "test 4", 0);
+ CuAssertTrue(tc, tc2.failed);
+ CompareAsserts(tc, "CuAssert didn't fail", "test 4", tc2.message);
+
+}
+
+void TestCuAssertPtrEquals_Success(CuTest* tc)
+{
+ CuTest tc2;
+ int x;
+
+ CuTestInit(&tc2, "MyTest", TestPasses);
+
+ /* test success case */
+ CuAssertPtrEquals(&tc2, &x, &x);
+ CuAssertTrue(tc, ! tc2.failed);
+ CuAssertTrue(tc, NULL == tc2.message);
+}
+
+void TestCuAssertPtrEquals_Failure(CuTest* tc)
+{
+ CuTest tc2;
+ int x;
+ int* nullPtr = NULL;
+ char expected_message[STRING_MAX];
+
+ CuTestInit(&tc2, "MyTest", TestPasses);
+
+ /* test failing case */
+ sprintf(expected_message, "expected pointer <0x%p> but was <0x%p>", nullPtr, &x);
+ CuAssertPtrEquals(&tc2, NULL, &x);
+ CuAssertTrue(tc, tc2.failed);
+ CompareAsserts(tc, "CuAssertPtrEquals failed", expected_message, tc2.message);
+}
+
+void TestCuAssertPtrNotNull_Success(CuTest* tc)
+{
+ CuTest tc2;
+ int x;
+
+ CuTestInit(&tc2, "MyTest", TestPasses);
+
+ /* test success case */
+ CuAssertPtrNotNull(&tc2, &x);
+ CuAssertTrue(tc, ! tc2.failed);
+ CuAssertTrue(tc, NULL == tc2.message);
+}
+
+void TestCuAssertPtrNotNull_Failure(CuTest* tc)
+{
+ CuTest tc2;
+
+ CuTestInit(&tc2, "MyTest", TestPasses);
+
+ /* test failing case */
+ CuAssertPtrNotNull(&tc2, NULL);
+ CuAssertTrue(tc, tc2.failed);
+ CompareAsserts(tc, "CuAssertPtrNotNull failed", "null pointer unexpected", tc2.message);
+}
+
+void TestCuTestRun(CuTest* tc)
+{
+ CuTest tc2;
+ CuTestInit(&tc2, "MyTest", zTestFails);
+ CuTestRun(&tc2);
+
+ CuAssertStrEquals(tc, "MyTest", tc2.name);
+ CuAssertTrue(tc, tc2.failed);
+ CuAssertTrue(tc, tc2.ran);
+ CompareAsserts(tc, "TestRun failed", "test should fail", tc2.message);
+}
+
+/*-------------------------------------------------------------------------*
+ * CuSuite Test
+ *-------------------------------------------------------------------------*/
+
+void TestCuSuiteInit(CuTest* tc)
+{
+ CuSuite ts;
+ CuSuiteInit(&ts);
+ CuAssertTrue(tc, ts.count == 0);
+ CuAssertTrue(tc, ts.failCount == 0);
+}
+
+void TestCuSuiteNew(CuTest* tc)
+{
+ CuSuite* ts = CuSuiteNew();
+ CuAssertTrue(tc, ts->count == 0);
+ CuAssertTrue(tc, ts->failCount == 0);
+}
+
+void TestCuSuiteAddTest(CuTest* tc)
+{
+ CuSuite ts;
+ CuTest tc2;
+
+ CuSuiteInit(&ts);
+ CuTestInit(&tc2, "MyTest", zTestFails);
+
+ CuSuiteAdd(&ts, &tc2);
+ CuAssertTrue(tc, ts.count == 1);
+
+ CuAssertStrEquals(tc, "MyTest", ts.list[0]->name);
+}
+
+void TestCuSuiteAddSuite(CuTest* tc)
+{
+ CuSuite* ts1 = CuSuiteNew();
+ CuSuite* ts2 = CuSuiteNew();
+
+ CuSuiteAdd(ts1, CuTestNew("TestFails1", zTestFails));
+ CuSuiteAdd(ts1, CuTestNew("TestFails2", zTestFails));
+
+ CuSuiteAdd(ts2, CuTestNew("TestFails3", zTestFails));
+ CuSuiteAdd(ts2, CuTestNew("TestFails4", zTestFails));
+
+ CuSuiteAddSuite(ts1, ts2);
+ CuAssertIntEquals(tc, 4, ts1->count);
+
+ CuAssertStrEquals(tc, "TestFails1", ts1->list[0]->name);
+ CuAssertStrEquals(tc, "TestFails2", ts1->list[1]->name);
+ CuAssertStrEquals(tc, "TestFails3", ts1->list[2]->name);
+ CuAssertStrEquals(tc, "TestFails4", ts1->list[3]->name);
+}
+
+void TestCuSuiteRun(CuTest* tc)
+{
+ CuSuite ts;
+ CuTest tc1, tc2, tc3, tc4;
+
+ CuSuiteInit(&ts);
+ CuTestInit(&tc1, "TestPasses", TestPasses);
+ CuTestInit(&tc2, "TestPasses", TestPasses);
+ CuTestInit(&tc3, "TestFails", zTestFails);
+ CuTestInit(&tc4, "TestFails", zTestFails);
+
+ CuSuiteAdd(&ts, &tc1);
+ CuSuiteAdd(&ts, &tc2);
+ CuSuiteAdd(&ts, &tc3);
+ CuSuiteAdd(&ts, &tc4);
+ CuAssertTrue(tc, ts.count == 4);
+
+ CuSuiteRun(&ts);
+ CuAssertTrue(tc, ts.count - ts.failCount == 2);
+ CuAssertTrue(tc, ts.failCount == 2);
+}
+
+void TestCuSuiteSummary(CuTest* tc)
+{
+ CuSuite ts;
+ CuTest tc1, tc2;
+ CuString summary;
+
+ CuSuiteInit(&ts);
+ CuTestInit(&tc1, "TestPasses", TestPasses);
+ CuTestInit(&tc2, "TestFails", zTestFails);
+ CuStringInit(&summary);
+
+ CuSuiteAdd(&ts, &tc1);
+ CuSuiteAdd(&ts, &tc2);
+ CuSuiteRun(&ts);
+
+ CuSuiteSummary(&ts, &summary);
+
+ CuAssertTrue(tc, ts.count == 2);
+ CuAssertTrue(tc, ts.failCount == 1);
+ CuAssertStrEquals(tc, ".F\n\n", summary.buffer);
+}
+
+
+void TestCuSuiteDetails_SingleFail(CuTest* tc)
+{
+ CuSuite ts;
+ CuTest tc1, tc2;
+ CuString details;
+ const char* front;
+ const char* back;
+
+ CuSuiteInit(&ts);
+ CuTestInit(&tc1, "TestPasses", TestPasses);
+ CuTestInit(&tc2, "TestFails", zTestFails);
+ CuStringInit(&details);
+
+ CuSuiteAdd(&ts, &tc1);
+ CuSuiteAdd(&ts, &tc2);
+ CuSuiteRun(&ts);
+
+ CuSuiteDetails(&ts, &details);
+
+ CuAssertTrue(tc, ts.count == 2);
+ CuAssertTrue(tc, ts.failCount == 1);
+
+ front = "There was 1 failure:\n"
+ "1) TestFails: ";
+ back = "test should fail\n"
+ "\n!!!FAILURES!!!\n"
+ "Runs: 2 Passes: 1 Fails: 1\n";
+
+ CuAssertStrEquals(tc, back, details.buffer + strlen(details.buffer) - strlen(back));
+ details.buffer[strlen(front)] = 0;
+ CuAssertStrEquals(tc, front, details.buffer);
+}
+
+
+void TestCuSuiteDetails_SinglePass(CuTest* tc)
+{
+ CuSuite ts;
+ CuTest tc1;
+ CuString details;
+ const char* expected;
+
+ CuSuiteInit(&ts);
+ CuTestInit(&tc1, "TestPasses", TestPasses);
+ CuStringInit(&details);
+
+ CuSuiteAdd(&ts, &tc1);
+ CuSuiteRun(&ts);
+
+ CuSuiteDetails(&ts, &details);
+
+ CuAssertTrue(tc, ts.count == 1);
+ CuAssertTrue(tc, ts.failCount == 0);
+
+ expected =
+ "OK (1 test)\n";
+
+ CuAssertStrEquals(tc, expected, details.buffer);
+}
+
+void TestCuSuiteDetails_MultiplePasses(CuTest* tc)
+{
+ CuSuite ts;
+ CuTest tc1, tc2;
+ CuString details;
+ const char* expected;
+
+ CuSuiteInit(&ts);
+ CuTestInit(&tc1, "TestPasses", TestPasses);
+ CuTestInit(&tc2, "TestPasses", TestPasses);
+ CuStringInit(&details);
+
+ CuSuiteAdd(&ts, &tc1);
+ CuSuiteAdd(&ts, &tc2);
+ CuSuiteRun(&ts);
+
+ CuSuiteDetails(&ts, &details);
+
+ CuAssertTrue(tc, ts.count == 2);
+ CuAssertTrue(tc, ts.failCount == 0);
+
+ expected =
+ "OK (2 tests)\n";
+
+ CuAssertStrEquals(tc, expected, details.buffer);
+}
+
+void TestCuSuiteDetails_MultipleFails(CuTest* tc)
+{
+ CuSuite ts;
+ CuTest tc1, tc2;
+ CuString details;
+ const char* front;
+ const char* mid;
+ const char* back;
+
+ CuSuiteInit(&ts);
+ CuTestInit(&tc1, "TestFails1", zTestFails);
+ CuTestInit(&tc2, "TestFails2", zTestFails);
+ CuStringInit(&details);
+
+ CuSuiteAdd(&ts, &tc1);
+ CuSuiteAdd(&ts, &tc2);
+ CuSuiteRun(&ts);
+
+ CuSuiteDetails(&ts, &details);
+
+ CuAssertTrue(tc, ts.count == 2);
+ CuAssertTrue(tc, ts.failCount == 2);
+
+ front =
+ "There were 2 failures:\n"
+ "1) TestFails1: ";
+ mid = "test should fail\n"
+ "2) TestFails2: ";
+ back = "test should fail\n"
+ "\n!!!FAILURES!!!\n"
+ "Runs: 2 Passes: 0 Fails: 2\n";
+
+ CuAssertStrEquals(tc, back, details.buffer + strlen(details.buffer) - strlen(back));
+ CuAssert(tc, "Couldn't find middle", strstr(details.buffer, mid) != NULL);
+ details.buffer[strlen(front)] = 0;
+ CuAssertStrEquals(tc, front, details.buffer);
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Misc Test
+ *-------------------------------------------------------------------------*/
+
+void TestCuStrCopy(CuTest* tc)
+{
+ const char* old = "hello world";
+ const char* newStr = CuStrCopy(old);
+ CuAssert(tc, "old is new", strcmp(old, newStr) == 0);
+}
+
+
+void TestCuStringAppendFormat(CuTest* tc)
+{
+ int i;
+ char* text = CuStrAlloc(301); /* long string */
+ CuString* str = CuStringNew();
+ for (i = 0 ; i < 300 ; ++i)
+ text[i] = 'a';
+ text[300] = '\0';
+ CuStringAppendFormat(str, "%s", text);
+
+ /* buffer limit raised to HUGE_STRING_LEN so no overflow */
+
+ CuAssert(tc, "length of str->buffer is 300", 300 == strlen(str->buffer));
+}
+
+void TestFail(CuTest* tc)
+{
+ jmp_buf buf;
+ int pointReached = 0;
+ CuTest* tc2 = CuTestNew("TestFails", zTestFails);
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuFail(tc2, "hello world");
+ pointReached = 1;
+ }
+ CuAssert(tc, "point was not reached", pointReached == 0);
+}
+
+void TestAssertStrEquals(CuTest* tc)
+{
+ jmp_buf buf;
+ CuTest *tc2 = CuTestNew("TestAssertStrEquals", zTestFails);
+
+ const char* expected = "expected <hello> but was <world>";
+ const char *expectedMsg = "some text: expected <hello> but was <world>";
+
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals(tc2, "hello", "world");
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals failed", expected, tc2->message);
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals_Msg(tc2, "some text", "hello", "world");
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals failed", expectedMsg, tc2->message);
+}
+
+void TestAssertStrEquals_NULL(CuTest* tc)
+{
+ jmp_buf buf;
+ CuTest *tc2 = CuTestNew("TestAssertStrEquals_NULL", zTestFails);
+
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals(tc2, NULL, NULL);
+ }
+ CuAssertTrue(tc, !tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals_NULL failed", NULL, tc2->message);
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals_Msg(tc2, "some text", NULL, NULL);
+ }
+ CuAssertTrue(tc, !tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals_NULL failed", NULL, tc2->message);
+}
+
+void TestAssertStrEquals_FailNULLStr(CuTest* tc)
+{
+ jmp_buf buf;
+ CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailNULLStr", zTestFails);
+
+ const char* expected = "expected <hello> but was <NULL>";
+ const char *expectedMsg = "some text: expected <hello> but was <NULL>";
+
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals(tc2, "hello", NULL);
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expected, tc2->message);
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals_Msg(tc2, "some text", "hello", NULL);
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expectedMsg, tc2->message);
+}
+
+void TestAssertStrEquals_FailStrNULL(CuTest* tc)
+{
+ jmp_buf buf;
+ CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailStrNULL", zTestFails);
+
+ const char* expected = "expected <NULL> but was <hello>";
+ const char *expectedMsg = "some text: expected <NULL> but was <hello>";
+
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals(tc2, NULL, "hello");
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expected, tc2->message);
+ if (setjmp(buf) == 0)
+ {
+ CuAssertStrEquals_Msg(tc2, "some text", NULL, "hello");
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expectedMsg, tc2->message);
+}
+
+void TestAssertIntEquals(CuTest* tc)
+{
+ jmp_buf buf;
+ CuTest *tc2 = CuTestNew("TestAssertIntEquals", zTestFails);
+ const char* expected = "expected <42> but was <32>";
+ const char* expectedMsg = "some text: expected <42> but was <32>";
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuAssertIntEquals(tc2, 42, 32);
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertIntEquals failed", expected, tc2->message);
+ if (setjmp(buf) == 0)
+ {
+ CuAssertIntEquals_Msg(tc2, "some text", 42, 32);
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertStrEquals failed", expectedMsg, tc2->message);
+}
+
+void TestAssertDblEquals(CuTest* tc)
+{
+ jmp_buf buf;
+ double x = 3.33;
+ double y = 10.0 / 3.0;
+ CuTest *tc2 = CuTestNew("TestAssertDblEquals", zTestFails);
+ char expected[STRING_MAX];
+ char expectedMsg[STRING_MAX];
+ sprintf(expected, "expected <%lf> but was <%lf>", x, y);
+ sprintf(expectedMsg, "some text: expected <%lf> but was <%lf>", x, y);
+
+ CuTestInit(tc2, "TestAssertDblEquals", TestPasses);
+
+ CuAssertDblEquals(tc2, x, x, 0.0);
+ CuAssertTrue(tc, ! tc2->failed);
+ CuAssertTrue(tc, NULL == tc2->message);
+
+ CuAssertDblEquals(tc2, x, y, 0.01);
+ CuAssertTrue(tc, ! tc2->failed);
+ CuAssertTrue(tc, NULL == tc2->message);
+
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuAssertDblEquals(tc2, x, y, 0.001);
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertDblEquals failed", expected, tc2->message);
+ tc2->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ CuAssertDblEquals_Msg(tc2, "some text", x, y, 0.001);
+ }
+ CuAssertTrue(tc, tc2->failed);
+ CompareAsserts(tc, "CuAssertDblEquals failed", expectedMsg, tc2->message);
+}
+
+/*-------------------------------------------------------------------------*
+ * main
+ *-------------------------------------------------------------------------*/
+
+CuSuite* CuGetSuite(void)
+{
+ CuSuite* suite = CuSuiteNew();
+
+ SUITE_ADD_TEST(suite, TestCuStringAppendFormat);
+ SUITE_ADD_TEST(suite, TestCuStrCopy);
+ SUITE_ADD_TEST(suite, TestFail);
+ SUITE_ADD_TEST(suite, TestAssertStrEquals);
+ SUITE_ADD_TEST(suite, TestAssertStrEquals_NULL);
+ SUITE_ADD_TEST(suite, TestAssertStrEquals_FailStrNULL);
+ SUITE_ADD_TEST(suite, TestAssertStrEquals_FailNULLStr);
+ SUITE_ADD_TEST(suite, TestAssertIntEquals);
+ SUITE_ADD_TEST(suite, TestAssertDblEquals);
+
+ SUITE_ADD_TEST(suite, TestCuTestNew);
+ SUITE_ADD_TEST(suite, TestCuTestInit);
+ SUITE_ADD_TEST(suite, TestCuAssert);
+ SUITE_ADD_TEST(suite, TestCuAssertPtrEquals_Success);
+ SUITE_ADD_TEST(suite, TestCuAssertPtrEquals_Failure);
+ SUITE_ADD_TEST(suite, TestCuAssertPtrNotNull_Success);
+ SUITE_ADD_TEST(suite, TestCuAssertPtrNotNull_Failure);
+ SUITE_ADD_TEST(suite, TestCuTestRun);
+
+ SUITE_ADD_TEST(suite, TestCuSuiteInit);
+ SUITE_ADD_TEST(suite, TestCuSuiteNew);
+ SUITE_ADD_TEST(suite, TestCuSuiteAddTest);
+ SUITE_ADD_TEST(suite, TestCuSuiteAddSuite);
+ SUITE_ADD_TEST(suite, TestCuSuiteRun);
+ SUITE_ADD_TEST(suite, TestCuSuiteSummary);
+ SUITE_ADD_TEST(suite, TestCuSuiteDetails_SingleFail);
+ SUITE_ADD_TEST(suite, TestCuSuiteDetails_SinglePass);
+ SUITE_ADD_TEST(suite, TestCuSuiteDetails_MultiplePasses);
+ SUITE_ADD_TEST(suite, TestCuSuiteDetails_MultipleFails);
+
+ return suite;
+}
diff --git a/tests/cu-test/README b/tests/cu-test/README
new file mode 100644
index 0000000..8bc1540
--- /dev/null
+++ b/tests/cu-test/README
@@ -0,0 +1,209 @@
+HOW TO USE
+
+You can use CuTest to create unit tests to drive your development
+in the style of Extreme Programming. You can also add unit tests to
+existing code to ensure that it works as you suspect.
+
+Your unit tests are an investment. They let you to change your
+code and add new features confidently without worrying about
+accidentally breaking earlier features.
+
+
+LICENSING
+
+For details on licensing see license.txt.
+
+
+GETTING STARTED
+
+To add unit testing to your C code the only files you need are
+CuTest.c and CuTest.h.
+
+CuTestTest.c and AllTests.c have been included to provide an
+example of how to write unit tests and then how to aggregate them
+into suites and into a single AllTests.c file. Suites allow you
+to put group tests into logical sets. AllTests.c combines all the
+suites and runs them.
+
+You should not have to look inside CuTest.c. Looking in
+CuTestTest.c and AllTests.c (for example usage) should be
+sufficient.
+
+After downloading the sources, run your compiler to create an
+executable called AllTests.exe. For example, if you are using
+Windows with the cl.exe compiler you would type:
+
+ cl.exe AllTests.c CuTest.c CuTestTest.c
+ AllTests.exe
+
+This will run all the unit tests associated with CuTest and print
+the output on the console. You can replace cl.exe with gcc or
+your favorite compiler in the command above.
+
+
+DETAILED EXAMPLE
+
+Here is a more detailed example. We will work through a simple
+test first exercise. The goal is to create a library of string
+utilities. First, lets write a function that converts a
+null-terminated string to all upper case.
+
+Ensure that CuTest.c and CuTest.h are accessible from your C
+project. Next, create a file called StrUtil.c with these
+contents:
+
+ #include "CuTest.h"
+
+ char* StrToUpper(char* str) {
+ return str;
+ }
+
+ void TestStrToUpper(CuTest *tc) {
+ char* input = strdup("hello world");
+ char* actual = StrToUpper(input);
+ char* expected = "HELLO WORLD";
+ CuAssertStrEquals(tc, expected, actual);
+ }
+
+ CuSuite* StrUtilGetSuite() {
+ CuSuite* suite = CuSuiteNew();
+ SUITE_ADD_TEST(suite, TestStrToUpper);
+ return suite;
+ }
+
+Create another file called AllTests.c with these contents:
+
+ #include "CuTest.h"
+
+ CuSuite* StrUtilGetSuite();
+
+ void RunAllTests(void) {
+ CuString *output = CuStringNew();
+ CuSuite* suite = CuSuiteNew();
+
+ CuSuiteAddSuite(suite, StrUtilGetSuite());
+
+ CuSuiteRun(suite);
+ CuSuiteSummary(suite, output);
+ CuSuiteDetails(suite, output);
+ printf("%s\n", output->buffer);
+ }
+
+ int main(void) {
+ RunAllTests();
+ }
+
+Then type this on the command line:
+
+ gcc AllTests.c CuTest.c StrUtil.c
+
+to compile. You can replace gcc with your favorite compiler.
+CuTest should be portable enough to handle all Windows and Unix
+compilers. Then to run the tests type:
+
+ a.out
+
+This will print an error because we haven't implemented the
+StrToUpper function correctly. We are just returning the string
+without changing it to upper case.
+
+ char* StrToUpper(char* str) {
+ return str;
+ }
+
+Rewrite this as follows:
+
+ char* StrToUpper(char* str) {
+ char* p;
+ for (p = str ; *p ; ++p) *p = toupper(*p);
+ return str;
+ }
+
+Recompile and run the tests again. The test should pass this
+time.
+
+
+WHAT TO DO NEXT
+
+At this point you might want to write more tests for the
+StrToUpper function. Here are some ideas:
+
+TestStrToUpper_EmptyString : pass in ""
+TestStrToUpper_UpperCase : pass in "HELLO WORLD"
+TestStrToUpper_MixedCase : pass in "HELLO world"
+TestStrToUpper_Numbers : pass in "1234 hello"
+
+As you write each one of these tests add it to StrUtilGetSuite
+function. If you don't the tests won't be run. Later as you write
+other functions and write tests for them be sure to include those
+in StrUtilGetSuite also. The StrUtilGetSuite function should
+include all the tests in StrUtil.c
+
+Over time you will create another file called FunkyStuff.c
+containing other functions unrelated to StrUtil. Follow the same
+pattern. Create a FunkyStuffGetSuite function in FunkyStuff.c.
+And add FunkyStuffGetSuite to AllTests.c.
+
+The framework is designed in the way it is so that it is easy to
+organize a lot of tests.
+
+THE BIG PICTURE
+
+Each individual test corresponds to a CuTest. These are grouped
+to form a CuSuite. CuSuites can hold CuTests or other CuSuites.
+AllTests.c collects all the CuSuites in the program into a single
+CuSuite which it then runs as a single CuSuite.
+
+The project is open source so feel free to take a peek under the
+hood at the CuTest.c file to see how it works. CuTestTest.c
+contains tests for CuTest.c. So CuTest tests itself.
+
+Since AllTests.c has a main() you will need to exclude this when
+you are building your product. Here is a nicer way to do this if
+you want to avoid messing with multiple builds. Remove the main()
+in AllTests.c. Note that it just calls RunAllTests(). Instead
+we'll call this directly from the main program.
+
+Now in the main() of the actual program check to see if the
+command line option "--test" was passed. If it was then I call
+RunAllTests() from AllTests.c. Otherwise run the real program.
+
+Shipping the tests with the code can be useful. If you customers
+complain about a problem you can ask them to run the unit tests
+and send you the output. This can help you to quickly isolate the
+piece of your system that is malfunctioning in the customer's
+environment.
+
+CuTest offers a rich set of CuAssert functions. Here is a list:
+
+void CuAssert(CuTest* tc, char* message, int condition);
+void CuAssertTrue(CuTest* tc, int condition);
+void CuAssertStrEquals(CuTest* tc, char* expected, char* actual);
+void CuAssertIntEquals(CuTest* tc, int expected, int actual);
+void CuAssertPtrEquals(CuTest* tc, void* expected, void* actual);
+void CuAssertPtrNotNull(CuTest* tc, void* pointer);
+
+The project is open source and so you can add other more powerful
+asserts to make your tests easier to write and more concise.
+Please feel free to send me changes you make so that I can
+incorporate them into future releases.
+
+If you see any errors in this document please contact me at
+asimjalis@peakprogramming.com.
+
+
+AUTOMATING TEST SUITE GENERATION
+
+make-tests.sh will grep through all the .c files in the current
+directory and generate the code to run all the tests contained in
+them. Using this script you don't have to worry about writing
+AllTests.c or dealing with any of the other suite code.
+
+
+CREDITS
+
+[02.23.2003] Dave Glowacki <dglo@hyde.ssec.wisc.edu> has added
+(1) file name and line numbers to the error messages, (2)
+AssertDblEquals for doubles, (3) Assert<X>Equals_Msg version of
+all the Assert<X>Equals to pass in optional message which is
+printed out on assert failure.
diff --git a/tests/cu-test/license.txt b/tests/cu-test/license.txt
new file mode 100644
index 0000000..fd81689
--- /dev/null
+++ b/tests/cu-test/license.txt
@@ -0,0 +1,38 @@
+NOTE
+
+The license is based on the zlib/libpng license. For more details see
+http://www.opensource.org/licenses/zlib-license.html. The intent of the
+license is to:
+
+- keep the license as simple as possible
+- encourage the use of CuTest in both free and commercial applications
+ and libraries
+- keep the source code together
+- give credit to the CuTest contributors for their work
+
+If you ship CuTest in source form with your source distribution, the
+following license document must be included with it in unaltered form.
+If you find CuTest useful we would like to hear about it.
+
+LICENSE
+
+Copyright (c) 2003 Asim Jalis
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software in
+a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not
+be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
diff --git a/tests/cu-test/make-tests.sh b/tests/cu-test/make-tests.sh
new file mode 100755
index 0000000..c651033
--- /dev/null
+++ b/tests/cu-test/make-tests.sh
@@ -0,0 +1,51 @@
+#!/usr/local/bin/bash
+
+# Auto generate single AllTests file for CuTest.
+# Searches through all *.c files in the current directory.
+# Prints to stdout.
+# Author: Asim Jalis
+# Date: 01/08/2003
+
+if test $# -eq 0 ; then FILES=*.c ; else FILES=$* ; fi
+
+echo '
+
+/* This is auto-generated code. Edit at your own peril. */
+
+#include "CuTest.h"
+
+'
+
+cat $FILES | grep '^unit_test_' |
+ sed -e 's/(.*$//' \
+ -e 's/$/(CuTest*);/' \
+ -e 's/^/extern /'
+
+echo \
+'
+
+void RunAllTests(void)
+{
+ CuString *output = CuStringNew();
+ CuSuite* suite = CuSuiteNew();
+
+'
+cat $FILES | grep '^void Test' |
+ sed -e 's/^void //' \
+ -e 's/(.*$//' \
+ -e 's/^/ SUITE_ADD_TEST(suite, /' \
+ -e 's/$/);/'
+
+echo \
+'
+ CuSuiteRun(suite);
+ CuSuiteSummary(suite, output);
+ CuSuiteDetails(suite, output);
+ printf("%s\n", output->buffer);
+}
+
+int main(void)
+{
+ RunAllTests();
+}
+'
diff --git a/tests/prep-tests.sh b/tests/prep-tests.sh
new file mode 100755
index 0000000..bdceb11
--- /dev/null
+++ b/tests/prep-tests.sh
@@ -0,0 +1,114 @@
+#!/bin/sh -e
+
+set -e
+
+# --------------------------------------------------------------------
+# FUNCTIONS
+
+usage()
+{
+ echo "usage: prep-tests.sh -b base-name files.c ..." >&2
+ exit 2
+}
+
+# --------------------------------------------------------------------
+# ARGUMENT PARSING
+
+BASE=unit-test
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -b)
+ BASE="$2"
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+FILES=$*
+
+# --------------------------------------------------------------------
+# HEADER FILE
+
+(
+
+# HEADER TOP
+cat << END
+/* This is auto-generated code. Edit at your own peril. */
+#include "tests/cu-test/CuTest.h"
+#include "tests/test-helpers.h"
+#include <stdio.h>
+
+END
+
+# DECLARATIONS
+
+ if [ -n "$FILES" ]; then
+ cat $FILES | grep '^void unit_setup_' | sed -e 's/$/;/'
+ cat $FILES | grep '^void unit_test_' | sed -e 's/$/;/'
+ cat $FILES | grep '^void unit_teardown_' | sed -e 's/$/;/'
+ fi
+
+) > $BASE.h
+
+# --------------------------------------------------------------------
+# SOURCE FILE
+
+(
+# START RUNNER FUNCTION
+cat << END
+/* This is auto-generated code. Edit at your own peril. */
+#include "$BASE.h"
+
+static int RunAllTests(void)
+{
+ CuString *output = CuStringNew();
+ CuSuite* suite = CuSuiteNew();
+
+END
+
+ if [ -n "$FILES" ]; then
+ cat $FILES | grep '^void unit_setup_' | \
+ sed -e 's/^void //' -e 's/(.*$//' -e 's/$/();/'
+ cat $FILES | grep '^void unit_test_' | \
+ sed -e 's/^void //' -e 's/(.*$//' \
+ -e 's/^/SUITE_ADD_TEST(suite, /' -e 's/$/);/'
+ fi
+
+# MIDDLE RUNNER FUNCTION
+cat << END
+ CuSuiteRun(suite);
+ CuSuiteSummary(suite, output);
+ CuSuiteDetails(suite, output);
+ printf("%s\\n", output->buffer);
+END
+
+ if [ -n "$FILES" ]; then
+
+ cat $FILES | grep '^void unit_teardown_' | \
+ sed -e 's/^void //' -e 's/(.*$//' -e 's/$/();/'
+
+ fi
+
+# END RUNNER FUNCTION
+cat << END
+
+ return suite->failCount;
+}
+
+#include "tests/test-helpers.c"
+#include "tests/cu-test/CuTest.c"
+END
+) > $BASE.c
+
diff --git a/tests/test-helpers.c b/tests/test-helpers.c
new file mode 100644
index 0000000..d8f5307
--- /dev/null
+++ b/tests/test-helpers.c
@@ -0,0 +1,51 @@
+/*
+ * 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 <stef@memberwebs.com>
+ *
+ */
+
+/* This file is included into the main .c file for each unit-test program */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "test-helpers.h"
+
+int
+main (int argc, char* argv[])
+{
+ return RunAllTests();
+}
diff --git a/tests/test-helpers.h b/tests/test-helpers.h
new file mode 100644
index 0000000..8cb5a2c
--- /dev/null
+++ b/tests/test-helpers.h
@@ -0,0 +1,42 @@
+/*
+ * 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 <stef@memberwebs.com>
+ *
+ */
+
+#ifndef TEST_HELPERS_H_
+#define TEST_HELPERS_H_
+
+#endif /*TEST_HELPERS_H_*/
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..19bbc50
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,17 @@
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(PTHREAD_CFLAGS)
+
+bin_PROGRAMS = \
+ notify-slaves
+
+notify_slaves_SOURCES = \
+ notify-slaves.c \
+ ../common/async-resolver.c \
+ ../common/server-mainloop.c \
+ ../common/sock-any.c
+
+# TODO: Somehow PTHREAD_LIBS isn't setup on linux gcc
+notify_slaves_LDFLAGS = -pthread
+notify_slaves_LIBS = $(PTHREAD_LIBS) \ No newline at end of file
diff --git a/tools/notify-slaves.c b/tools/notify-slaves.c
new file mode 100644
index 0000000..ebd2262
--- /dev/null
+++ b/tools/notify-slaves.c
@@ -0,0 +1,813 @@
+
+/*
+ * Based on zonenotify by Morettoni Luca
+ */
+
+/*
+ * Copyright (c) 2004 Morettoni Luca <luca@morettoni.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $Id: zonenotify.c,v 1.4 2004/07/19 12:37:04 luca Exp $
+ */
+
+#include "config.h"
+
+#include "common/async-resolver.h"
+#include "common/server-mainloop.h"
+#include "common/sock-any.h"
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <err.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <netinet/in.h>
+#ifdef HAVE_INET6
+#include <netinet6/in6.h>
+#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <arpa/nameser.h>
+#ifdef _BSD
+#include <arpa/nameser_compat.h>
+#endif
+
+/* ------------------------------------------------------------------------------
+ * DECLARATIONS
+ */
+
+static const char *DNS_ERRORS[] = {
+ "No error",
+ "Format error",
+ "Server failure",
+ "Non-existent domain",
+ "Not implemented",
+ "Query refused",
+ "Name exists when it should not",
+ "RR Set exists when it should not",
+ "RR Set that should exist does not",
+ "Server not authoritative for zone",
+ "Name not contained in zone",
+ "", "", "", "", "", /* available for assignment */
+ "Bad OPT version",
+ "TSIG signature failure",
+ "Key not recognized",
+ "Signature out of time window",
+ "Bad TKEY mode",
+ "Duplicate key name",
+ "Algorithm not supported"
+};
+
+#define N_DNS_ERRORS (sizeof (DNS_ERRORS) / sizeof (DNS_ERRORS[0]))
+
+#define MAX_NAME 128 /* Maximum length of a zone or server name */
+#define BIND_TRIES 16 /* Number of times we try to get a port */
+#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 LINE_LENGTH 1023 /* Maximum length of buffer on stdin */
+
+static int the_socket4 = -1;
+static int the_socket6 = -1;
+
+static char stdin_buffer[LINE_LENGTH + 1];
+static size_t stdin_offset = 0;
+
+static int stdin_closed = 0;
+static int processing_active = 0;
+
+static unsigned int unique_identifier = 1;
+
+static int is_helper = 0;
+static int debug_level = LOG_WARNING;
+
+typedef struct _notification {
+ struct _notification *next;
+ int unique;
+
+ int resolved;
+ char zone[MAX_NAME];
+ char server[MAX_NAME];
+ struct sockaddr_any address;
+
+ unsigned char packet[PACKETSZ];
+ size_t packet_len;
+
+ uint64_t start;
+ uint64_t timeout;
+ uint64_t retry;
+ int retries;
+} notification;
+
+static notification *the_notifications = NULL;
+
+#define WHITESPACE " \t\r\n\v"
+
+/* --------------------------------------------------------------------------------
+ * WARNINGS AND LOGGING
+ */
+
+static void
+vmessage(int level, int erno, const char* msg, va_list ap)
+{
+ #define MAX_MSGLEN 1024
+ char buf[MAX_MSGLEN];
+ size_t len;
+
+ if(debug_level < level)
+ return;
+
+ assert (msg);
+
+ strncpy (buf, msg, MAX_MSGLEN);
+ buf[MAX_MSGLEN - 1] = 0;
+
+ if (erno) {
+ len = strlen (buf);
+ strncpy (buf + len, ": ", MAX_MSGLEN - len);
+ buf[MAX_MSGLEN - 1] = 0;
+ len = strlen (buf);
+ strncpy (buf + len, strerror (erno), MAX_MSGLEN - len);
+ buf[MAX_MSGLEN - 1] = 0;
+ }
+
+ /* Either to syslog or stderr */
+ if (is_helper && level < LOG_DEBUG)
+ vsyslog (level, buf, ap);
+ vwarnx(buf, ap);
+}
+
+static void
+debug (const char *msg, ...)
+{
+ va_list va;
+ va_start (va, msg);
+ vmessage (LOG_DEBUG, 0, msg, va);
+ va_end (va);
+}
+
+static void
+info (const char *msg, ...)
+{
+ va_list va;
+ va_start (va, msg);
+ vmessage (LOG_INFO, 0, msg, va);
+ va_end (va);
+}
+
+static void
+warningx (const char *msg, ...)
+{
+ va_list va;
+ va_start (va, msg);
+ vmessage (LOG_WARNING, 0, msg, va);
+ va_end (va);
+}
+
+static void
+warning (const char *msg, ...)
+{
+ va_list va;
+ va_start (va, msg);
+ vmessage (LOG_WARNING, errno, msg, va);
+ va_end (va);
+}
+
+static void
+fatalx (int ret, const char *msg, ...)
+{
+ va_list va;
+ va_start (va, msg);
+ vmessage (LOG_ERR, 0, msg, va);
+ va_end (va);
+ exit (1);
+}
+
+void
+fatal (int ret, const char *msg, ...)
+{
+ va_list va;
+ va_start (va, msg);
+ vmessage (LOG_ERR, errno, msg, va);
+ va_end (va);
+ exit (1);
+}
+
+/* --------------------------------------------------------------------------------
+ * HELPERS
+ */
+
+static const char*
+ltrim (const char *data)
+{
+ while (*data && strchr (WHITESPACE, *data))
+ ++data;
+ return data;
+}
+
+static void
+rtrim (char *data)
+{
+ char *t = data + strlen (data);
+ while (t > data && strchr (WHITESPACE, *(t - 1))) {
+ t--;
+ *t = 0;
+ }
+}
+
+static char*
+trim (char *data)
+{
+ data = (char*)ltrim (data);
+ rtrim (data);
+ return data;
+}
+
+/* --------------------------------------------------------------------------------
+ * FUNCTIONALITY
+ */
+
+static void
+socket_callback (int fd, int type, void *arg)
+{
+ unsigned char packet[PACKETSZ];
+ notification **not, *notif;
+ struct sockaddr_any sany;
+ HEADER *hdr;
+ ssize_t num;
+
+ SANY_LEN (sany) = sizeof (sany);
+ num = recvfrom (fd, packet, sizeof (packet), 0, &SANY_ADDR (sany), &SANY_LEN (sany));
+ if (num < 0) {
+ warning ("couldn't receive packet");
+ return;
+ }
+
+ if (num < 4) {
+ warningx ("received response packet that is too short (%d bytes)", num);
+ return;
+ }
+
+ hdr = (HEADER*)packet;
+
+ /* Find the notification that this refers to */
+ for (not = &the_notifications; *not; not = &(*not)->next) {
+ notif = *not;
+ if (notif->unique == hdr->id) {
+
+ /* Report any errors */
+ if (hdr->qr && hdr->rcode) {
+ if (hdr->rcode < N_DNS_ERRORS)
+ warningx ("received error for server: %s: %s", notif->server, DNS_ERRORS[hdr->rcode]);
+ else
+ warningx ("received errer for server: %s: %d", notif->server, (int)hdr->rcode);
+ } else {
+ debug ("received successful response for server: %s", notif->server);
+ }
+
+ /* Remove from notification queue */
+ *not = notif->next;
+ free (notif);
+
+ break;
+ }
+ }
+}
+
+static int
+process_all_packets (uint64_t when, void *unused)
+{
+ notification **not, *notif;
+ int rc;
+
+ if (!processing_active) {
+ if (server_timer (RETRY_INTERVAL / 2, process_all_packets, NULL) < 0)
+ warning ("couldn't setup timer to process packets");
+ processing_active = 1;
+ debug ("starting processing");
+ }
+
+ for (not = &the_notifications; *not; ) {
+ notif = *not;
+
+ /* Is it ready? */
+ if (notif->start < when && notif->resolved) {
+
+ /* Timed out? */
+ if (notif->timeout <= when) {
+ warningx ("notification to server timed out: %s", notif->server);
+ *not = notif->next;
+ free (notif);
+ continue;
+ }
+
+ /* See if we should send */
+ if (notif->retry <= when) {
+
+ /* Calculate next retry */
+ if (notif->retries) {
+ notif->retry = when + RETRY_INTERVAL;
+ --notif->retries;
+ } else {
+ /* Some time in the distant future */
+ notif->retry = when + (TIMEOUT_INTERVAL * 1000);
+ notif->retries = 0;
+ }
+
+ info ("sending notify for zone %s to %s", notif->zone, notif->server);
+
+ if (SANY_TYPE (notif->address) == AF_INET)
+ rc = sendto (the_socket4, notif->packet, notif->packet_len, 0,
+ &SANY_ADDR (notif->address), SANY_LEN (notif->address));
+ else if (SANY_TYPE (notif->address) == AF_INET6)
+ rc = sendto (the_socket6, notif->packet, notif->packet_len, 0,
+ &SANY_ADDR (notif->address), SANY_LEN (notif->address));
+ else {
+ warningx ("unsupported address type: %d", SANY_TYPE (notif->address));
+ *not = notif->next;
+ free (notif);
+ continue;
+ }
+
+ if (rc < 0)
+ warning ("couldn't send packet to server: %s", notif->server);
+ }
+ }
+
+ not = &(*not)->next;
+ }
+
+ /* Continue processing? */
+ if (the_notifications)
+ return 1;
+
+ if (stdin_closed) {
+ debug ("processing done, and no more input, stopping");
+ server_stop ();
+ } else {
+ debug ("processing done for now");
+ processing_active = 0;
+ }
+
+ return 0;
+}
+
+static void
+prepare_and_process (notification *notif, uint64_t when)
+{
+ assert (notif);
+ assert (SANY_TYPE (notif->address));
+ assert (notif->resolved);
+ assert (notif->start);
+
+ debug ("preparing notification to: %s", notif->server);
+ notif->timeout = notif->start + TIMEOUT_INTERVAL;
+ notif->retries = RETRIES;
+ notif->retry = when;
+
+ process_all_packets (when, NULL);
+}
+
+static void
+address_resolved (int ecode, struct addrinfo *ai, void *arg)
+{
+ notification **not, *notif;
+
+ for (not = &the_notifications; *not; not = &(*not)->next) {
+ notif = *not;
+
+ /* We search for the notification, in case it has been freed */
+ if (notif != arg)
+ continue;
+
+ /* A bummer resolve */
+ if (ecode) {
+ warningx ("couldn't resolve server: %s: %s",
+ notif->server, gai_strerror (ecode));
+ *not = notif->next;
+ free (notif);
+
+ /* A successful resolve */
+ } else {
+ debug ("resolved address for: %s", notif->server);
+ memcpy (&SANY_ADDR (notif->address), ai->ai_addr, ai->ai_addrlen);
+ SANY_LEN (notif->address) = ai->ai_addrlen;
+ notif->resolved = 1;
+ prepare_and_process (notif, server_get_time ());
+ }
+
+ break;
+ }
+}
+
+/* encode name string in ns query format */
+static int
+ns_encode (const char *str, char *buff)
+{
+ char *pos;
+ int size;
+ int len = 0;
+
+ for (;;) {
+ pos = (char *) strchr (str, '.');
+ if (!pos)
+ break;
+
+ size = pos - str;
+ *buff++ = size;
+
+ strncpy (buff, str, size);
+ buff += size;
+
+ len += size + 1;
+ str = pos + 1;
+ }
+
+ size = strlen (str);
+ if (size) {
+ *buff++ = size;
+ strncpy (buff, str, size);
+ buff += size;
+ len += size + 1;
+ }
+
+ *buff = 0;
+
+ return len;
+}
+
+static int
+process_notify (const char *zone, const char *server, uint64_t delay)
+{
+ notification *notif, **not;
+ char name[MAX_NAME * 2];
+ HEADER *hdr;
+ size_t reqlen;
+ u_int16_t val;
+ uint64_t when;
+ int rc;
+
+ assert (zone && zone[0]);
+ assert (server && server[0]);
+
+ if (strlen (zone) > MAX_NAME) {
+ warningx ("zone name too long, could not fit in packet");
+ return -1;
+ }
+ if (strlen (server) > MAX_NAME) {
+ warningx ("server name too long, could not fit in packet");
+ return -1;
+ }
+
+ /* Search for this name in the list */
+ for (not = &the_notifications; *not; not = &(*not)->next) {
+ notif = *not;
+ /* Find a match, just ignore this request */
+ if (strcmp (zone, notif->zone) == 0 &&
+ strcmp (server, notif->server) == 0) {
+ debug ("already have notification packet for %s to %s", zone, server);
+ return 0;
+ }
+ }
+
+ debug ("building notification packet for %s to %s", zone, server);
+
+ assert (MAX_NAME + sizeof (HEADER) <= PACKETSZ);
+ notif = calloc (1, sizeof (notification));
+ if (!notif) {
+ warningx ("out of memory");
+ return -1;
+ }
+
+ hdr = (HEADER*)notif->packet;
+ hdr->qr = 0;
+ hdr->opcode = NS_NOTIFY_OP;
+ hdr->aa = 1;
+ hdr->tc = 0;
+ hdr->rd = 0;
+ hdr->ra = 0;
+ hdr->unused = 0;
+ hdr->rcode = 0;
+ hdr->qdcount = htons (1);
+ hdr->ancount = 0;
+ hdr->nscount = 0;
+ hdr->arcount = 0;
+ hdr->id = htons (unique_identifier++);
+
+ /* the 0x00 at the end must be copied! */
+ reqlen = ns_encode (zone, name) + 1;
+ assert (reqlen < MAX_NAME);
+ memcpy (notif->packet + sizeof (HEADER), name, reqlen);
+
+ /* query type */
+ val = htons (T_SOA);
+ memcpy (notif->packet + sizeof (HEADER) + reqlen, &val, 2);
+ reqlen += 2;
+
+ /* query class */
+ val = htons (C_IN);
+ memcpy (notif->packet + sizeof (HEADER) + reqlen, &val, 2);
+ reqlen += 2;
+
+ notif->unique = hdr->id;
+ notif->packet_len = sizeof (HEADER) + reqlen;
+
+ /* Copy the address in */
+ strncpy (notif->zone, zone, sizeof (notif->zone));
+ strncpy (notif->server, server, sizeof (notif->server));
+ notif->server[sizeof (notif->server) - 1] = 0;
+
+ /* Try and resolve the domain name */
+ rc = sock_any_pton (notif->server, &notif->address, SANY_OPT_DEFPORT(53) | SANY_OPT_NORESOLV);
+ if (rc < 0) {
+ warning ("could not parse server name: %s", notif->server);
+ free (notif);
+ return -1;
+ }
+
+ /* Add it to the queue */
+ notif->next = the_notifications;
+ the_notifications = notif;
+
+ /* Delay before processing */
+ when = server_get_time ();
+ notif->start = when + delay;
+
+ /* Needs resolving */
+ if (rc == SANY_AF_DNS) {
+ SANY_TYPE (notif->address) = 0;
+ debug ("resolving address: %s", notif->server);
+ async_resolver_queue (notif->server, "53", NULL, address_resolved, notif);
+ } else {
+ notif->resolved = 1;
+ prepare_and_process (notif, when);
+ }
+
+ return 0;
+}
+
+static void
+process_stdin_line (char *line)
+{
+ char *zone, *server, *next;
+ size_t len;
+
+ debug ("received line: %s", line);
+
+ /* Ignore blank lines and comment lines */
+ line = trim (line);
+ if (!*line || *line == '#')
+ return;
+
+ next = strchr (line, ':');
+ if (!next) {
+ warningx ("received invalid line: %s", line);
+ return;
+ }
+
+ *next = 0;
+ ++next;
+
+ /* Figure out what command it is */
+ line = trim (line);
+ if (strcmp (line, "NOTIFY") != 0) {
+ warningx ("received invalid command: %s", line);
+ return;
+ }
+
+ /* Figure out the zone */
+ zone = trim (next);
+ len = strcspn (zone, WHITESPACE);
+ if (len == 0 || zone[len] == 0) {
+ warningx ("missing arguments to NOTIFY command");
+ return;
+ }
+ zone[len] = 0;
+
+ /* Figure out the server */
+ server = trim (zone + len + 1);
+ len = strcspn (server, WHITESPACE);
+ if (len == 0) {
+ warningx ("missing arguments to NOTIFY command");
+ return;
+ }
+ server[len] = 0;
+
+ process_notify (zone, server, DELAY_INTERVAL);
+}
+
+static void
+stdin_callback (int fd, int type, void *arg)
+{
+ char *line;
+ int num;
+
+ assert (fd == 0);
+
+ num = read (fd, stdin_buffer, LINE_LENGTH - stdin_offset);
+ if (num < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return;
+ warningx ("couldn't read from stdin");
+ server_unwatch (fd);
+ stdin_closed = 1;
+ return;
+ } else if (num == 0) {
+ stdin_closed = 1;
+ server_unwatch (fd);
+ }
+
+ stdin_offset += num;
+
+ for (;;) {
+ line = strchr (stdin_buffer, '\n');
+
+ if (!line && stdin_offset >= LINE_LENGTH) {
+ warningx ("input line too long");
+ line = stdin_buffer + LINE_LENGTH;
+ }
+
+ if (!line && stdin_closed)
+ line = stdin_buffer + stdin_offset;
+
+ if (!line) /* Wait for more data */
+ break;
+
+ *line = 0;
+ num = (line + 1) - stdin_buffer;
+
+ process_stdin_line (stdin_buffer);
+ stdin_offset = LINE_LENGTH - num;
+ memmove (stdin_buffer, stdin_buffer + num, stdin_offset);
+
+ if (stdin_closed)
+ break;
+ }
+}
+
+static void
+make_sockets (void)
+{
+ int i, rand, isbound;
+ time_t now;
+ struct sockaddr_in addr4;
+#ifdef HAVE_INET6
+ struct sockaddr_in6 addr6;
+#endif
+
+ time (&now);
+ srandom ((long) now);
+
+ the_socket4 = socket (AF_INET, SOCK_DGRAM, 0);
+#ifdef HAVE_INET6
+ the_socket6 = socket (AF_INET6, SOCK_DGRAM, 0);
+#endif
+ if (the_socket4 < 0 && the_socket6 < 0)
+ fatal (1, "couldn't create socket for communication");
+
+ if (the_socket4 >= 0) {
+ isbound = 0;
+
+ /* local port: random */
+ memset (&addr4, 0, sizeof (addr4));
+ addr4.sin_family = AF_INET;
+ for (i = 0; i < BIND_TRIES && !isbound; i++) {
+ rand = 1025 + (random () % 15000);
+ addr4.sin_port = htons (rand);
+ isbound = (bind (the_socket4, (struct sockaddr*) &addr4, sizeof (addr4)) >= 0);
+ }
+
+ if (!isbound)
+ fatal (1, "couldn't bind to local port");
+
+ if (server_watch (the_socket4, SERVER_READ, socket_callback, NULL) < 0)
+ fatal (1, "couldn't watch socket");
+ }
+
+#ifdef HAVE_INET6
+ if (the_socket6 >= 0) {
+ isbound = 0;
+
+ /* local port: random */
+ memset (&addr6, 0, sizeof (addr6));
+ addr6.sin_family = AF_INET6;
+ for (i = 0; i < BIND_TRIES && !isbound; i++) {
+ rand = 1025 + (random () % 15000);
+ addr6.sin6_port = htons (rand);
+ isbound = (bind (the_socket6, (struct sockaddr*) &addr6, sizeof (addr6)) == 0);
+ }
+
+ if (!isbound)
+ fatal_error (1, "couldn't bind to local port");
+
+ if (server_watch (the_socket6, SERVER_READ, socket_callback, NULL) < 0)
+ fatal_error (1, "couldn't watch socket");
+ }
+#endif
+}
+
+static void
+usage()
+{
+ fprintf (stderr, "usage: slapi-dnsnotify-helper -s [-d level]\n");
+ fprintf (stderr, "usage: slapi-dnsnotify-helper [-d level] zone server ...\n");
+ exit (2);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *str;
+ int ch = 0, i;
+
+ while ((ch = getopt (argc, argv, "d:s")) != -1) {
+ switch (ch)
+ {
+ case 'd':
+ debug_level = strtol(optarg, &str, 10);
+ if (*str || debug_level > 4)
+ fatalx (1, "invalid debug log level: %s", optarg);
+ debug_level += LOG_ERR;
+ break;
+ case 's':
+ is_helper = 1;
+ break;
+ case '?':
+ default:
+ usage ();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (!is_helper && argc < 2)
+ usage ();
+ if (is_helper && argc > 0)
+ usage ();
+
+ server_init ();
+ make_sockets ();
+
+ if (async_resolver_init () < 0)
+ fatal (1, "couldn't initialize DNS resolver");
+
+ if (is_helper) {
+ stdin_closed = 0;
+
+ /* 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 {
+ stdin_closed = 1;
+ str = argv[0];
+ for (i = 1; i < argc; ++i)
+ process_notify (str, argv[i], 0);
+ }
+
+ if (server_run () < 0)
+ fatal (1, "couldn't run server");
+
+ if (the_socket4 >= 0)
+ close (the_socket4);
+ if (the_socket6 >= 0)
+ close (the_socket6);
+
+ async_resolver_uninit ();
+ server_uninit ();
+
+ return 0;
+}