From abb82291887b6784a13a7fcf719fa1d463781007 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 7 Jun 2008 20:36:27 +0000 Subject: Initial import --- AUTHORS | 1 + COPYING | 31 ++ ChangeLog | 2 + INSTALL | 1 + Makefile.am | 6 + NEWS | 1 + README | 3 + acsite.m4 | 227 +++++++++++ autogen.sh | 11 + common/async-resolver.c | 366 ++++++++++++++++++ common/async-resolver.h | 48 +++ common/server-mainloop.c | 434 +++++++++++++++++++++ common/server-mainloop.h | 56 +++ common/sock-any.c | 406 ++++++++++++++++++++ common/sock-any.h | 103 +++++ configure.in | 92 +++++ include/slapi-plugin.h | 903 ++++++++++++++++++++++++++++++++++++++++++++ plugin/Makefile.am | 13 + plugin/slapi-dnsnotify.c | 491 ++++++++++++++++++++++++ tests/Makefile.am | 29 ++ tests/cu-test/AllTests.c | 25 ++ tests/cu-test/CuTest.c | 309 +++++++++++++++ tests/cu-test/CuTest.h | 111 ++++++ tests/cu-test/CuTestTest.c | 709 ++++++++++++++++++++++++++++++++++ tests/cu-test/README | 209 ++++++++++ tests/cu-test/license.txt | 38 ++ tests/cu-test/make-tests.sh | 51 +++ tests/prep-tests.sh | 114 ++++++ tests/test-helpers.c | 51 +++ tests/test-helpers.h | 42 +++ tools/Makefile.am | 17 + tools/notify-slaves.c | 813 +++++++++++++++++++++++++++++++++++++++ 32 files changed, 5713 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 120000 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 acsite.m4 create mode 100755 autogen.sh create mode 100644 common/async-resolver.c create mode 100644 common/async-resolver.h create mode 100644 common/server-mainloop.c create mode 100644 common/server-mainloop.h create mode 100644 common/sock-any.c create mode 100644 common/sock-any.h create mode 100644 configure.in create mode 100644 include/slapi-plugin.h create mode 100644 plugin/Makefile.am create mode 100644 plugin/slapi-dnsnotify.c create mode 100644 tests/Makefile.am create mode 100644 tests/cu-test/AllTests.c create mode 100644 tests/cu-test/CuTest.c create mode 100644 tests/cu-test/CuTest.h create mode 100644 tests/cu-test/CuTestTest.c create mode 100644 tests/cu-test/README create mode 100644 tests/cu-test/license.txt create mode 100755 tests/cu-test/make-tests.sh create mode 100755 tests/prep-tests.sh create mode 100644 tests/test-helpers.c create mode 100644 tests/test-helpers.h create mode 100644 tools/Makefile.am create mode 100644 tools/notify-slaves.c diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..0dc24e0 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +stef@memberwebs.com diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..12d603c --- /dev/null +++ b/COPYING @@ -0,0 +1,31 @@ + +Copyright (c) 2008, Stef Walter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + * Redistributions in binary form must reproduce the + above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or + other materials provided with the distribution. + * The names of contributors to this software may not be + used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..883c9fc --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2 @@ +Version 0.1 + - Initial version diff --git a/INSTALL b/INSTALL new file mode 120000 index 0000000..5bb6e7b --- /dev/null +++ b/INSTALL @@ -0,0 +1 @@ +/usr/share/automake-1.10/INSTALL \ No newline at end of file diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..5cc07b6 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = common include +SUBDIRS = plugin tools tests + +dist-hook: + rm -rf `find $(distdir)/ -name .svn` + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..c7ab92a --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See ChangeLog \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..d1d8a13 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ +================================================================= + SLAPI-DNSNOTIFY 0.1 README + diff --git a/acsite.m4 b/acsite.m4 new file mode 100644 index 0000000..c28ff75 --- /dev/null +++ b/acsite.m4 @@ -0,0 +1,227 @@ +dnl Available from the GNU Autoconf Macro Archive at: +dnl http://www.gnu.org/software/ac-archive/htmldoc/acx_pthread.html +dnl +AC_DEFUN([ACX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthread or + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthread -pthreads pthread -mt $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) + if test x"$acx_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_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 ], + [int attr=PTHREAD_CREATE_JOINABLE;], + ok=PTHREAD_CREATE_JOINABLE, ok=unknown) + if test x"$ok" = xunknown; then + AC_TRY_LINK([#include ], + [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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +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 + +#include +#include +#include +#include +#include + +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 + +#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 + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sock-any.h" + +#include + +#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 + * + */ + +#ifndef __SOCK_ANY_H__ +#define __SOCK_ANY_H__ + +#include +#include +#include + +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 +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 . + * + * 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 + * . + */ + +/* + * 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 + +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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_SLAPI_PLUGIN_H +#include +#else +#include "include/slapi-plugin.h" +#endif + +#define PLUGIN_NAME "slapi-dnsnotify" + +/*---------------------------------------------------------------------------------- + * DECLARATIONS + */ + +static const char *dnsnotify_soa_attribute = "sOARecord"; +static const char *dnsnotify_ns_attribute = "nSRecord"; + +static Slapi_Mutex *dnsnotify_mutex = NULL; +static int dnsnotify_pipe = -1; +static pid_t dnsnotify_pid = -1; + +/* --------------------------------------------------------------------- + * LOGGING/TRACING + */ + +static void +log_msg_va (int level, const char* msg, va_list ap) +{ + size_t len; + char buf[1024]; + + assert (level >= 0); + assert (msg != NULL); + + vsnprintf (buf, sizeof (buf) - 2, msg, ap); + + len = strlen (buf); + buf[len] = '\n'; + buf[len + 1] = '\0'; + +#if _DEBUG + fprintf (stderr, "%s", buf); +#endif + slapi_log_error (level, PLUGIN_NAME, buf); +} + +static void +log_msg (int level, const char* msg, ...) +{ + va_list ap; + va_start (ap, msg); + log_msg_va (level, msg, ap); + va_end (ap); +} + +#if _DEBUG +static void +log_trace (const char *where, int line, const char *msg) +{ + log_msg (SLAPI_LOG_TRACE, "*** %s *** %s:%d %s%s", + PLUGIN_NAME, where, line, msg ? ": " : "", msg ? msg : ""); +} +#endif + +static void +log_plugin (const char* msg, ...) +{ + va_list ap; + va_start (ap, msg); + log_msg_va (SLAPI_LOG_PLUGIN, msg, ap); + va_end (ap); +} + +#define return_val_if_fail(expr, ret) \ + do { if (expr) { } else { \ + log_plugin ("*** %s *** check failed: '%s' at %s:%d", PLUGIN_NAME, #expr, __PRETTY_FUNCTION__, __LINE__); \ + return (ret); \ + } } while (0) + +#if _DEBUG +#define trace(x) log_trace (__PRETTY_FUNCTION__, __LINE__, (x)) +#else +#define trace(x) +#endif + +/* --------------------------------------------------------------------------------- + * OPERATIONS + */ + +static int +load_soa_ns_attributes (const char *dn, char **soa_result, char ***ns_result) +{ + Slapi_Entry **entries; + struct berval **values, **v; + Slapi_PBlock *pb; + Slapi_Attr *attr; + LDAPControl *ctrl; + char *attrs[2]; + char *soa, **ns; + int rc, code, num, i; + + trace (dn); + assert (dn && dn[0]); + assert (dnsnotify_soa_attribute); + assert (dnsnotify_ns_attribute); + + ctrl = NULL; /* No controls */ + attrs[0] = (char*)dnsnotify_soa_attribute; + attrs[1] = NULL; + + trace ("performing internal search"); + + /* Do the actual search */ + pb = slapi_search_internal ((char*)dn, LDAP_SCOPE_BASE, "(objectClass=*)", &ctrl, attrs, 0); + return_val_if_fail (pb, 0); + + /* Was it successful? */ + code = -1; + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &code); + return_val_if_fail (rc >= 0, 0); + if (code != LDAP_SUCCESS) { + log_plugin ("error loading attribute %s from %s (code %d)", dnsnotify_soa_attribute, dn, code); + slapi_pblock_destroy (pb); + trace ("failure"); + return 0; + } + + /* Dig out all the entries */ + entries = NULL; + slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + return_val_if_fail (entries, 0); + + soa = NULL; + if (entries[0]) { + if (slapi_entry_attr_find (entries[0], (char*)dnsnotify_soa_attribute, &attr) >= 0 && attr && + slapi_attr_get_values (attr, &values) >= 0 && values && values[0]) { + + /* Convert the berval into a string */ + soa = slapi_ch_malloc (values[0]->bv_len + 1); + if (values[0]->bv_len) + memcpy (soa, values[0]->bv_val, values[0]->bv_len); + soa[values[0]->bv_len] = 0; + } + + if (slapi_entry_attr_find (entries[0], (char*)dnsnotify_ns_attribute, &attr) >= 0 && attr && + slapi_attr_get_values (attr, &values) >= 0 && values) { + + num = 0; + for (v = values; *v; ++v) + ++num; + + ns = (char**)slapi_ch_calloc (num, sizeof (char*)); + for (i = 0; i < num; ++i) { + ns[i] = slapi_ch_malloc (values[i]->bv_len + 1); + if (values[i]->bv_len) + memcpy (ns[i], values[i]->bv_val, values[i]->bv_len); + ns[i][values[i]->bv_len] = 0; + } + } + } + + slapi_pblock_destroy (pb); + + /* Only proceed if we have all the data we need */ + if (soa && soa[0] && ns && ns[0] && ns[0]) { + *soa_result = soa; + *ns_result = ns; + return 1; + } + + slapi_ch_free_string (&soa); + slapi_ch_array_free (ns); + + return 0; +} + +static int +fork_notify_process (void) +{ + pid_t pid; + int status; + int commpipe[2]; + char *args[3]; + + if (pipe (commpipe) < 0) { + log_plugin ("couldn't create communication pipe for child process: %s", strerror (errno)); + return -1; + } + + pid = fork(); + switch (pid) { + case -1: + log_plugin ("couldn't fork for child notify process: %s", strerror (errno)); + close (commpipe[0]); + close (commpipe[1]); + return -1; + + /* The child, continues below */ + case 0: + break; + + /* The parent */ + default: + + /* close read end */ + close (commpipe[0]); + + /* hang onto write end */ + if (dnsnotify_pipe != -1) + close (dnsnotify_pipe); + dnsnotify_pipe = commpipe[1]; + + if (dnsnotify_pid != -1) { + if (waitpid (dnsnotify_pid, &status, WNOHANG) == 0) { + /* We can play rough, because we know process can take it */ + if (kill (dnsnotify_pid, SIGKILL) >= 0) + waitpid (dnsnotify_pid, &status, 0); + } + } + dnsnotify_pid = pid; + + return 0; + }; + + /* The child process, here */ + close (commpipe[1]); + + /* Dup the pipe into stdin of the process */ + if (dup2 (commpipe[0], 0) < 0) + log_plugin ("couldn't setup stdin on notify child process: %s", strerror (errno)); + + args[0] = DNS_NOTIFY_PATH; + args[1] = "-s"; + args[2] = NULL; + + execv (args[0], args); + log_plugin ("couldn't launch '%s' process: %s", DNS_NOTIFY_PATH, strerror (errno)); + _exit (1); +} + +static ssize_t +write_all (int fd, const void *b, size_t len) +{ + const unsigned char *buf = (const unsigned char*)b; + ssize_t l, left = len; + + while (left > 0) { + l = write (fd, buf, len); + if (l < 0) { + if (errno == EINTR && errno == EAGAIN) + continue; + return -1; + } else { + left -= l; + buf += l; + } + } + + return len; +} + +static char* +prep_soa_domain (char *soa) +{ + size_t at; + + assert (soa && soa[0]); + + /* Find the first space in the SOA and cut it off there */ + while (soa[0] && isspace (soa[0])) + ++soa; + at = strspn (soa, " \t\n\r\v"); + if (at == 0) { + log_plugin ("invalid SOA present"); + return NULL; + } + + soa[at] = 0; + trace (soa); + return soa; +} + +static void +notify_dns_slaves (char *soa, char **ns) +{ + int i, complete = 1; + char *n; + + trace (soa); + + assert (ns); + assert (soa && soa[0]); + + if (!*ns) + return; + + soa = prep_soa_domain (soa); + if (!soa) + return; + + slapi_lock_mutex (dnsnotify_mutex); + + /* Try this twice in case things have closed up shop */ + for (i = 0; i < 2; ++i) { + + if (dnsnotify_pipe < 0 || i > 0) + if (fork_notify_process () < 0) + break; + + assert (dnsnotify_pipe >= 0); + + for (; *ns; ++ns) { + n = *ns; + + /* An empty string ? */ + while (n[0] && isspace (n[0])) + ++n; + if (!n[0]) + continue; + + if (write_all (dnsnotify_pipe, "NOTIFY: ", 5) < 0 || + write_all (dnsnotify_pipe, soa, strlen (soa)) < 0 || + write_all (dnsnotify_pipe, " ", 1) < 0 || + write_all (dnsnotify_pipe, *ns, strlen (soa)) < 0 || + write_all (dnsnotify_pipe, "\n", 2) < 0) { + if (errno == EPIPE) { + complete = 0; + break; + } + log_plugin ("couldn't write data to child notify process: %s", strerror (errno)); + } + } + + if (complete) + break; + } + + slapi_unlock_mutex (dnsnotify_mutex); +} + +static int +slapi_dnsnotify_modify (Slapi_PBlock *pb) +{ + LDAPMod **mods, **m, *mod; + char *dn, *soa, **ns; + int rc, code; + + return_val_if_fail (pb, -1); + + /* Make sure it was successful, don't process errors */ + rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); + return_val_if_fail (rc >= 0, -1); + if (code != LDAP_SUCCESS) + return 0; + + /* Get out the DN and normalize it */ + rc = slapi_pblock_get (pb, SLAPI_MODIFY_TARGET, &dn); + return_val_if_fail (rc >= 0 && dn, -1); + dn = slapi_ch_strdup (dn); + slapi_dn_normalize_case (dn); + + rc = slapi_pblock_get (pb, SLAPI_MODIFY_MODS, &mods); + return_val_if_fail (rc >= 0 && mods, -1); + + for (m = mods; *m; ++m) { + mod = *m; + if (strcmp (mod->mod_type, dnsnotify_soa_attribute) == 0) { + if (load_soa_ns_attributes (dn, &soa, &ns)) { + notify_dns_slaves (soa, ns); + slapi_ch_array_free (ns); + slapi_ch_free_string (&soa); + } + break; + } + } + + slapi_ch_free_string (&dn); + return 0; +} + +static int +slapi_dnsnotify_add (Slapi_PBlock *pb) +{ + char *dn, *soa, **ns; + int rc, code, ret; + + return_val_if_fail (pb, -1); + + /* Make sure it was successful, don't process errors */ + rc = slapi_pblock_get (pb, SLAPI_RESULT_CODE, &code); + return_val_if_fail (rc >= 0, -1); + if (code != LDAP_SUCCESS) + return 0; + + /* Get out the DN and normalize it */ + rc = slapi_pblock_get (pb, SLAPI_ADD_TARGET, &dn); + return_val_if_fail (rc >= 0 && dn, -1); + dn = slapi_ch_strdup (dn); + slapi_dn_normalize_case (dn); + + /* A DN that is an SOA? */ + if (load_soa_ns_attributes (dn, &soa, &ns)) { + notify_dns_slaves (soa, ns); + slapi_ch_free_string (&soa); + slapi_ch_array_free (ns); + } + + slapi_ch_free_string (&dn); + return ret; +} + +static Slapi_PluginDesc plugin_description = { + PLUGIN_NAME, /* plug-in identifier */ + "stef@memberwebs.com", /* vendor name */ + VERSION, /* plug-in revision number */ + "Notify's DNS slaves when SOA change is made" /* plug-in description */ +}; + +int +plugin_init (Slapi_PBlock* pb) +{ + char **argv = NULL; + int argc = 0; + int rc; + + return_val_if_fail (pb, -1); + + rc = slapi_pblock_get (pb, SLAPI_PLUGIN_ARGV, &argv); + return_val_if_fail (rc >= 0, -1); + slapi_pblock_get (pb, SLAPI_PLUGIN_ARGC, &argc); + return_val_if_fail (rc >= 0, -1); + + if (argc >= 1) + dnsnotify_soa_attribute = argv[0]; + if (argc >= 2) + dnsnotify_ns_attribute = argv[1]; + if (argc > 2) + log_plugin ("extra arguments present"); + + if (!dnsnotify_soa_attribute || !dnsnotify_soa_attribute[0]) { + log_plugin ("DNS SOA attribute specified for plugin is empty"); + return -1; + } + + if (!dnsnotify_ns_attribute || !dnsnotify_ns_attribute[0]) { + log_plugin ("DNS SOA attribute specified for plugin is empty"); + return -1; + } + + if (slapi_pblock_set (pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03) != 0 || + slapi_pblock_set (pb, SLAPI_PLUGIN_DESCRIPTION, &plugin_description) != 0) { + log_plugin ("error registring plugin"); + return -1; + } + + /* + * TODO: Add support for changing the default attributes and search uri + * Accept an argument for this. + */ + + /* Setup the entry add/mobify functions */ + if (slapi_pblock_set (pb, SLAPI_PLUGIN_POST_ADD_FN, (void*)slapi_dnsnotify_add) != 0 || + slapi_pblock_set (pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void*)slapi_dnsnotify_modify)) { + log_plugin ("error registering plugin hooks"); + return -1; + } + + assert (!dnsnotify_mutex); + dnsnotify_mutex = slapi_new_mutex (); + return_val_if_fail (dnsnotify_mutex, -1); + + log_plugin ("%s initialized", PLUGIN_NAME); + return 0; +} + 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 + +#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 +#include +#include +#include +#include +#include + +#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 +#include + +/* 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 +#include +#include +#include +#include + +#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 but was "; + const char *expectedMsg = "some text: expected but was "; + + 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 but was "; + const char *expectedMsg = "some text: expected but was "; + + 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 but was "; + const char *expectedMsg = "some text: expected but was "; + + 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 has added +(1) file name and line numbers to the error messages, (2) +AssertDblEquals for doubles, (3) AssertEquals_Msg version of +all the AssertEquals 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 + +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 + * + */ + +/* This file is included into the main .c file for each unit-test program */ + +#include +#include +#include + +#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 + * + */ + +#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 + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef HAVE_INET6 +#include +#endif +#include +#include + +#include +#ifdef _BSD +#include +#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; +} -- cgit v1.2.3