diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | COPYING | 31 | ||||
-rw-r--r-- | ChangeLog | 2 | ||||
l--------- | INSTALL | 1 | ||||
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | README | 3 | ||||
-rw-r--r-- | acsite.m4 | 227 | ||||
-rwxr-xr-x | autogen.sh | 11 | ||||
-rw-r--r-- | common/async-resolver.c | 366 | ||||
-rw-r--r-- | common/async-resolver.h | 48 | ||||
-rw-r--r-- | common/server-mainloop.c | 434 | ||||
-rw-r--r-- | common/server-mainloop.h | 56 | ||||
-rw-r--r-- | common/sock-any.c | 406 | ||||
-rw-r--r-- | common/sock-any.h | 103 | ||||
-rw-r--r-- | configure.in | 92 | ||||
-rw-r--r-- | include/slapi-plugin.h | 903 | ||||
-rw-r--r-- | plugin/Makefile.am | 13 | ||||
-rw-r--r-- | plugin/slapi-dnsnotify.c | 491 | ||||
-rw-r--r-- | tests/Makefile.am | 29 | ||||
-rw-r--r-- | tests/cu-test/AllTests.c | 25 | ||||
-rw-r--r-- | tests/cu-test/CuTest.c | 309 | ||||
-rw-r--r-- | tests/cu-test/CuTest.h | 111 | ||||
-rw-r--r-- | tests/cu-test/CuTestTest.c | 709 | ||||
-rw-r--r-- | tests/cu-test/README | 209 | ||||
-rw-r--r-- | tests/cu-test/license.txt | 38 | ||||
-rwxr-xr-x | tests/cu-test/make-tests.sh | 51 | ||||
-rwxr-xr-x | tests/prep-tests.sh | 114 | ||||
-rw-r--r-- | tests/test-helpers.c | 51 | ||||
-rw-r--r-- | tests/test-helpers.h | 42 | ||||
-rw-r--r-- | tools/Makefile.am | 17 | ||||
-rw-r--r-- | tools/notify-slaves.c | 813 |
32 files changed, 5713 insertions, 0 deletions
@@ -0,0 +1 @@ +stef@memberwebs.com @@ -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 @@ -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` + @@ -0,0 +1 @@ +See ChangeLog
\ No newline at end of file @@ -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 (¤t, 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 (¤t, &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), ¤t) <= 0) + memcpy (&(timcb->at), ¤t, 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, ¤t); + } + + 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, ¬if->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; +} |