WRONG patch: Implement surrogate parent for back-shell (ITS#1815) A patch to delete the surrogate parent code again is at ftp://ftp.openldap.org/incoming/Hallvard-Furuseth-021010.txt ================ Written by Hallvard B. Furuseth and placed into the public domain. This software is not subject to any license of the University of Oslo. ================ A surrogate parent is supposed to keep back-shell children from deadlocking due to resources locked by a threading parent. Implementation note: The surrogate parent closes all unused file descriptors, so it logs errors to stderr instead of via Debug() and uses relloc() instead of ch_realloc(). Also close a file descriptor leak if fork() fails in fork.c. Hallvard B. Furuseth , May 2002. diff -u2 -r configure.in~ configure.in --- configure.in~ Wed May 8 08:58:07 2002 +++ configure.in Sat May 11 21:46:53 2002 @@ -2429,4 +2429,5 @@ recv \ recvfrom \ + recvmsg \ setpwfile \ setgid \ diff -u2 -r include/portable.h.in~ include/portable.h.in --- include/portable.h.in~ Mon Apr 8 09:43:23 2002 +++ include/portable.h.in Sat May 11 21:48:14 2002 @@ -225,4 +225,7 @@ #undef HAVE_RECVFROM +/* Define if you have the recvmsg function. */ +#undef HAVE_RECVMSG + /* Define if you have the sched_yield function. */ #undef HAVE_SCHED_YIELD diff -u2 -r include/portable.nt~ include/portable.nt --- include/portable.nt~ Mon Apr 8 09:43:23 2002 +++ include/portable.nt Sat May 11 21:48:44 2002 @@ -244,4 +244,7 @@ /* #undef HAVE_RECVFROM */ +/* Define if you have the recvmsg function. */ +/* #undef HAVE_RECVMSG */ + /* Define if you have the sched_yield function. */ /* #undef HAVE_SCHED_YIELD */ diff -u2 -r servers/slapd/back-shell/abandon.c~ servers/slapd/back-shell/abandon.c --- servers/slapd/back-shell/abandon.c~ Fri May 10 08:20:34 2002 +++ servers/slapd/back-shell/abandon.c Sun May 12 05:41:01 2002 @@ -30,5 +30,5 @@ /* no abandon command defined - just kill the process handling it */ - if ( si->si_abandon == NULL ) { + if ( IS_NULLCMD( si->si_abandon ) ) { ldap_pvt_thread_mutex_lock( &conn->c_mutex ); pid = -1; diff -u2 -r servers/slapd/back-shell/add.c~ servers/slapd/back-shell/add.c --- servers/slapd/back-shell/add.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/add.c Sun May 12 05:41:56 2002 @@ -28,5 +28,5 @@ int len; - if ( si->si_add == NULL ) { + if ( IS_NULLCMD( si->si_add ) ) { send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL, "add not implemented", NULL, NULL ); diff -u2 -r servers/slapd/back-shell/bind.c~ servers/slapd/back-shell/bind.c --- servers/slapd/back-shell/bind.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/bind.c Sun May 12 05:41:49 2002 @@ -32,5 +32,5 @@ int rc; - if ( si->si_bind == NULL ) { + if ( IS_NULLCMD( si->si_bind ) ) { send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL, "bind not implemented", NULL, NULL ); diff -u2 -r servers/slapd/back-shell/compare.c~ servers/slapd/back-shell/compare.c --- servers/slapd/back-shell/compare.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/compare.c Sun May 12 05:41:44 2002 @@ -29,5 +29,5 @@ FILE *rfp, *wfp; - if ( si->si_compare == NULL ) { + if ( IS_NULLCMD( si->si_compare ) ) { send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL, "compare not implemented", NULL, NULL ); diff -u2 -r servers/slapd/back-shell/config.c~ servers/slapd/back-shell/config.c --- servers/slapd/back-shell/config.c~ Fri Jan 4 21:17:54 2002 +++ servers/slapd/back-shell/config.c Sun May 12 16:35:58 2002 @@ -16,4 +16,26 @@ #include "shell.h" +#ifdef SHELL_SURROGATE_PARENT + +static struct berval make_cmd_info( + char **args +) +{ + struct berval ret = { 0, 0 }; + int i; + ber_len_t offset; + for( i = 0; args[i] != NULL; i++ ) + ret.bv_len += strlen( args[i] ) + 1; + ret.bv_val = ch_malloc( ret.bv_len ); + offset = 0; + for( i = 0; args[i] != NULL; i++ ) { + strcpy( ret.bv_val + offset, args[i] ); + offset += strlen( args[i] ) + 1; + } + return ret; +} + +#endif /* SHELL_SURROGATE_PARENT */ + int shell_back_db_config( @@ -41,5 +63,5 @@ return( 1 ); } - si->si_bind = charray_dup( &argv[1] ); + si->si_bind = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for unbinds */ @@ -51,5 +73,5 @@ return( 1 ); } - si->si_unbind = charray_dup( &argv[1] ); + si->si_unbind = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for searches */ @@ -61,5 +83,5 @@ return( 1 ); } - si->si_search = charray_dup( &argv[1] ); + si->si_search = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for compares */ @@ -71,5 +93,5 @@ return( 1 ); } - si->si_compare = charray_dup( &argv[1] ); + si->si_compare = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for modifies */ @@ -81,5 +103,5 @@ return( 1 ); } - si->si_modify = charray_dup( &argv[1] ); + si->si_modify = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for modrdn */ @@ -91,5 +113,5 @@ return( 1 ); } - si->si_modrdn = charray_dup( &argv[1] ); + si->si_modrdn = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for add */ @@ -101,5 +123,5 @@ return( 1 ); } - si->si_add = charray_dup( &argv[1] ); + si->si_add = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for delete */ @@ -111,5 +133,5 @@ return( 1 ); } - si->si_delete = charray_dup( &argv[1] ); + si->si_delete = MAKE_CMD_INFO( &argv[1] ); /* command + args to exec for abandon */ @@ -121,5 +143,5 @@ return( 1 ); } - si->si_abandon = charray_dup( &argv[1] ); + si->si_abandon = MAKE_CMD_INFO( &argv[1] ); /* anything else */ diff -u2 -r servers/slapd/back-shell/delete.c~ servers/slapd/back-shell/delete.c --- servers/slapd/back-shell/delete.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/delete.c Sun May 12 05:41:36 2002 @@ -28,5 +28,5 @@ FILE *rfp, *wfp; - if ( si->si_delete == NULL ) { + if ( IS_NULLCMD( si->si_delete ) ) { send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL, "delete not implemented", NULL, NULL ); diff -u2 -r servers/slapd/back-shell/fork.c~ servers/slapd/back-shell/fork.c --- servers/slapd/back-shell/fork.c~ Fri Jan 4 21:17:54 2002 +++ servers/slapd/back-shell/fork.c Mon May 13 07:16:41 2002 @@ -10,4 +10,5 @@ #include +#include #include #include @@ -17,16 +18,224 @@ #include "shell.h" +#ifdef SHELL_SURROGATE_PARENT + +#include + +/* Use several socketpairs to the surrogate parent, because * + * a single communication channel to it could be a bottleneck */ +ldap_pvt_thread_mutex_t shell_surrogate_fd_mutex[2]; +int shell_surrogate_fd[2] = { -1, -1 }; +/* Index to shell_surrogate_fd, and its mutex */ +ldap_pvt_thread_mutex_t shell_surrogate_index_mutex; +static int shell_surrogate_index = 1; + +pid_t shell_surrogate_pid = -1; + +#define nread( fd, buf, len ) n_rw( 0, fd, buf, len ) +#define nwrite( fd, buf, len ) n_rw( 1, fd, buf, len ) + +static int +n_rw( + int do_write, + int fd, + void *buf, + int len +) +{ + int ret = 0, i; + while( len ) { + for(;;) { + i = (do_write + ? write( fd, buf, len ) + : read( fd, buf, len )); + if( i < 0 ) { + if( errno == EINTR ) + continue; + if( ret == 0 ) + ret = -1; + } + break; + } + if( i <= 0 ) + break; + ret += i; + buf = (char *)buf + i; + len -= i; + } + return ret; +} + +void +make_surrogate_parent( void ) +{ + int pair[2][2], io[2], i, j, p, argc; + ber_len_t len, buflen, offset; + char *buf, **argv; + pid_t pid; + + if( socketpair( AF_LOCAL, SOCK_STREAM, 0, pair[0] ) < 0 || + socketpair( AF_LOCAL, SOCK_STREAM, 0, pair[1] ) < 0 ) { + Debug( LDAP_DEBUG_ANY, "socketpair failed\n", 0, 0, 0 ); + exit( EXIT_FAILURE ); + } + fflush( NULL ); + switch( fork() ) { + case -1: + Debug( LDAP_DEBUG_ANY, "fork failed\n", 0, 0, 0 ); + exit( EXIT_FAILURE ); + case 0: + break; + default: + shell_surrogate_fd[0] = pair[0][0]; + shell_surrogate_fd[1] = pair[1][0]; + close( pair[0][1] ); + close( pair[1][1] ); + return; + } + + /* Close unused file descriptors */ + for( i = 3, j = 32; j && i < 1024; i++ ) + if( i != pair[0][1] && i != pair[1][1] && close( i ) < 0 ) + --j; + else if( j < 32 ) + j = 32; + + /* Surrogate parent running */ + + buflen = 0; + buf = NULL; + argc = 0; + argv = NULL; + p = 0; + + for(;;) { + /* Read file descriptors io[] from socket */ + static char dummy; + static struct iovec iov = { &dummy, 1 }; + struct msghdr msg; +# ifdef CMSG_SPACE + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(io))]; + } control_un; + struct cmsghdr *cmptr; + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); +# else + msg.msg_accrights = (caddr_t) io; + msg.msg_accrightslen = sizeof(io); +# endif + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + switch( recvmsg( pair[p][1], &msg, MSG_WAITALL ) ) { + case -1: + if( errno == EINTR ) + continue; + _exit( EXIT_FAILURE ); + case 0: + _exit( EXIT_SUCCESS ); + } +# ifdef CMSG_SPACE + if( (cmptr = CMSG_FIRSTHDR(&msg)) == NULL || + cmptr->cmsg_len != CMSG_LEN(sizeof(io)) || + cmptr->cmsg_level != SOL_SOCKET || + cmptr->cmsg_type != SCM_RIGHTS ) { + fputs( "bad descriptor message received\n", stderr ); + exit( EXIT_FAILURE ); + } + memcpy( io, CMSG_DATA( cmptr ), sizeof(io) ); +# else + if( msg.msg_accrightslen != sizeof(io) ) { + fputs( "bad descriptor message received\n", stderr ); + exit( EXIT_FAILURE ); + } +# endif + + /* Read length of arguments and then arguments from socket */ + if( nread( pair[p][1], &len, sizeof(len) ) != sizeof(len) ) { + fputs( "bad descriptor message received\n", stderr ); + exit( EXIT_FAILURE ); + } + if( buflen < len ) { + buf = realloc( buf, buflen = len ); + if( buf == NULL ) { + fputs( "realloc failed\n", stderr ); + exit( EXIT_FAILURE ); + } + } + if( nread( pair[p][1], buf, len ) != len ) { + fputs( "bad descriptor message received\n", stderr ); + exit( EXIT_FAILURE ); + } + i = 0; + offset = 0; + while( offset < len ) { + if( i >= argc-1 ) { + argc += i + 10; + argv = realloc( argv, argc * sizeof(*argv) ); + if( argv == NULL ) { + fputs( "realloc failed\n", stderr ); + exit( EXIT_FAILURE ); + } + } + argv[i++] = buf + offset; + offset += strlen( buf + offset ) + 1; + } + argv[i] = NULL; + + /* Run program */ + pid = fork(); + switch( pid ) + { + case 0: /* child */ + if( dup2( io[0], 0 ) == -1 || dup2( io[1], 1 ) == -1 ) { + fputs( "dup2 failed\n", stderr ); + exit( EXIT_FAILURE ); + } + close( io[0] ); + close( io[1] ); + close( pair[0][1] ); + close( pair[1][1] ); + execv( argv[0], argv ); + + fputs( "execv failed\n", stderr ); + exit( EXIT_FAILURE ); + + case -1: /* trouble */ + fputs( "fork failed\n", stderr ); + break; + + default: /* parent */ + close( io[0] ); + close( io[1] ); + break; + } + if( nwrite( pair[p][1], &pid, + sizeof(pid_t) ) != sizeof(pid_t) ) { + fputs( "could not send pid\n", stderr ); + exit( EXIT_FAILURE ); + } + p ^= 1; + } +} +#endif /* SHELL_SURROGATE_PARENT */ + pid_t forkandexec( - char **args, + Cmd_info args, FILE **rfp, FILE **wfp ) { - int p2c[2], c2p[2]; + int p2c[2] = { -1, -1 }, c2p[2]; pid_t pid; if ( pipe( p2c ) != 0 || pipe( c2p ) != 0 ) { Debug( LDAP_DEBUG_ANY, "pipe failed\n", 0, 0, 0 ); + close( p2c[0] ); + close( p2c[1] ); return( -1 ); } @@ -38,25 +247,87 @@ */ -#ifdef HAVE_THR - switch ( (pid = fork1()) ) -#else - switch ( (pid = fork()) ) -#endif +#ifdef SHELL_SURROGATE_PARENT + { - case 0: /* child */ + int io[2] = { p2c[0], c2p[1] }, i, c; + static char dummy = '\0'; + static struct iovec iov = { &dummy, 1 }; + struct msghdr msg; +# ifdef CMSG_SPACE + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(io))]; + } control_un; + struct cmsghdr *cmptr; + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); + cmptr = CMSG_FIRSTHDR(&msg); + cmptr->cmsg_len = CMSG_LEN(sizeof(io)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + memcpy( CMSG_DATA(cmptr), io, sizeof(io) ); +# else + msg.msg_accrights = (caddr_t) io; + msg.msg_accrightslen = sizeof(io); +# endif + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + ldap_pvt_thread_mutex_lock( &shell_surrogate_index_mutex ); + i = shell_surrogate_index ^= 1; + ldap_pvt_thread_mutex_unlock( &shell_surrogate_index_mutex ); + ldap_pvt_thread_mutex_lock( &shell_surrogate_fd_mutex[i] ); + c = (sendmsg( shell_surrogate_fd[i], &msg, 0 ) == 1 && + nwrite( shell_surrogate_fd[i], &args.bv_len, + sizeof(args.bv_len) ) == sizeof(args.bv_len) && + nwrite( shell_surrogate_fd[i], args.bv_val, + args.bv_len ) == args.bv_len && + nread( shell_surrogate_fd[i], &pid, + sizeof(pid) ) == sizeof(pid)); + ldap_pvt_thread_mutex_unlock( &shell_surrogate_fd_mutex[i] ); + close( p2c[0] ); + close( c2p[1] ); + if ( !c ) { + Debug( LDAP_DEBUG_ANY, "process creation failed\n", 0, 0, 0 ); + close( p2c[1] ); + close( c2p[0] ); + close( shell_surrogate_fd[0] ); + close( shell_surrogate_fd[1] ); + shell_surrogate_fd[0] = + shell_surrogate_fd[1] = -1; + return( -1 ); + } + } + +#else /* !SHELL_SURROGATE_PARENT */ + + fflush( NULL ); +# ifdef HAVE_THR + pid = fork1(); +# else + pid = fork(); +# endif + if ( pid == 0 ) { /* child */ /* * child could deadlock here due to resources locked * by our parent * - * If so, configure --without-threads or implement forking - * via a surrogate parent. + * If so, configure --without-threads. */ - close( p2c[1] ); - close( c2p[0] ); if ( dup2( p2c[0], 0 ) == -1 || dup2( c2p[1], 1 ) == -1 ) { Debug( LDAP_DEBUG_ANY, "dup2 failed\n", 0, 0, 0 ); exit( EXIT_FAILURE ); } - + } + close( p2c[0] ); + close( c2p[1] ); + if ( pid <= 0 ) { + close( p2c[1] ); + close( c2p[0] ); + } + switch ( pid ) { + case 0: execv( args[0], args ); @@ -67,11 +338,9 @@ Debug( LDAP_DEBUG_ANY, "fork failed\n", 0, 0, 0 ); return( -1 ); - - default: /* parent */ - close( p2c[0] ); - close( c2p[1] ); - break; } +#endif /* SHELL_SURROGATE_PARENT */ + + /* parent */ if ( (*rfp = fdopen( c2p[0], "r" )) == NULL || (*wfp = fdopen( p2c[1], "w" )) == NULL ) { diff -u2 -r servers/slapd/back-shell/init.c~ servers/slapd/back-shell/init.c --- servers/slapd/back-shell/init.c~ Fri Jan 4 21:17:54 2002 +++ servers/slapd/back-shell/init.c Mon May 13 06:34:47 2002 @@ -11,4 +11,5 @@ #include +#include #include "slap.h" @@ -38,5 +39,5 @@ bi->bi_config = 0; bi->bi_close = 0; - bi->bi_destroy = 0; + bi->bi_destroy = shell_back_destroy; bi->bi_db_init = shell_back_db_init; @@ -65,4 +66,30 @@ bi->bi_connection_destroy = 0; +#ifdef SHELL_SURROGATE_PARENT + ldap_pvt_thread_mutex_init( &shell_surrogate_index_mutex ); + ldap_pvt_thread_mutex_init( &shell_surrogate_fd_mutex[0] ); + ldap_pvt_thread_mutex_init( &shell_surrogate_fd_mutex[1] ); +#endif + + return 0; +} + +int +shell_back_destroy( + BackendInfo *bi +) +{ +#ifdef SHELL_SURROGATE_PARENT + ldap_pvt_thread_mutex_destroy( &shell_surrogate_index_mutex ); + ldap_pvt_thread_mutex_destroy( &shell_surrogate_fd_mutex[0] ); + ldap_pvt_thread_mutex_destroy( &shell_surrogate_fd_mutex[1] ); + if ( shell_surrogate_fd[0] >= 0 ) { + close( shell_surrogate_fd[0] ); + close( shell_surrogate_fd[1] ); + } + if ( shell_surrogate_pid >= 0 ) + kill( shell_surrogate_pid, SIGTERM ); +#endif + return 0; } @@ -74,4 +101,9 @@ { struct shellinfo *si; + +#ifdef SHELL_SURROGATE_PARENT + if ( shell_surrogate_fd[0] < 0 ) + make_surrogate_parent(); +#endif si = (struct shellinfo *) ch_calloc( 1, sizeof(struct shellinfo) ); diff -u2 -r servers/slapd/back-shell/modify.c~ servers/slapd/back-shell/modify.c --- servers/slapd/back-shell/modify.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/modify.c Sun May 12 05:41:26 2002 @@ -31,5 +31,5 @@ int i; - if ( si->si_modify == NULL ) { + if ( IS_NULLCMD( si->si_modify ) ) { send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL, "modify not implemented", NULL, NULL ); diff -u2 -r servers/slapd/back-shell/modrdn.c~ servers/slapd/back-shell/modrdn.c --- servers/slapd/back-shell/modrdn.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/modrdn.c Sun May 12 05:41:20 2002 @@ -46,5 +46,5 @@ FILE *rfp, *wfp; - if ( si->si_modrdn == NULL ) { + if ( IS_NULLCMD( si->si_modrdn ) ) { send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL, "modrdn not implemented", NULL, NULL ); diff -u2 -r servers/slapd/back-shell/search.c~ servers/slapd/back-shell/search.c --- servers/slapd/back-shell/search.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/search.c Sun May 12 05:50:04 2002 @@ -34,9 +34,8 @@ { struct shellinfo *si = (struct shellinfo *) be->be_private; - int i; FILE *rfp, *wfp; AttributeName *an; - if ( si->si_search == NULL ) { + if ( IS_NULLCMD( si->si_search ) ) { send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL, "search not implemented", NULL, NULL ); diff -u2 -r servers/slapd/back-shell/shell.h~ servers/slapd/back-shell/shell.h --- servers/slapd/back-shell/shell.h~ Fri Jan 4 21:17:55 2002 +++ servers/slapd/back-shell/shell.h Mon May 13 07:19:02 2002 @@ -13,14 +13,39 @@ LDAP_BEGIN_DECL +#if defined(HAVE_RECVMSG) && !defined(NO_THREADS) +# define SHELL_SURROGATE_PARENT +#endif + +#ifdef SHELL_SURROGATE_PARENT + +extern ldap_pvt_thread_mutex_t shell_surrogate_index_mutex; +extern ldap_pvt_thread_mutex_t shell_surrogate_fd_mutex[2]; +extern int shell_surrogate_fd[2]; +extern pid_t shell_surrogate_pid; + +typedef struct berval Cmd_info; +#define MAKE_CMD_INFO(args) make_cmd_info( args ) +#define IS_NULLCMD(cmd) ((cmd).bv_val == NULL) + +extern void make_surrogate_parent LDAP_P(( void )); + +#else /* !SHELL_SURROGATE_PARENT */ + +typedef char **Cmd_info; +#define MAKE_CMD_INFO(args) charray_dup( args ) +#define IS_NULLCMD(cmd) ((cmd) == NULL) + +#endif /* SHELL_SURROGATE_PARENT */ + struct shellinfo { - char **si_bind; /* cmd + args to exec for bind */ - char **si_unbind; /* cmd + args to exec for unbind */ - char **si_search; /* cmd + args to exec for search */ - char **si_compare; /* cmd + args to exec for compare */ - char **si_modify; /* cmd + args to exec for modify */ - char **si_modrdn; /* cmd + args to exec for modrdn */ - char **si_add; /* cmd + args to exec for add */ - char **si_delete; /* cmd + args to exec for delete */ - char **si_abandon; /* cmd + args to exec for abandon */ + Cmd_info si_bind; /* cmd + args to exec for bind */ + Cmd_info si_unbind; /* cmd + args to exec for unbind */ + Cmd_info si_search; /* cmd + args to exec for search */ + Cmd_info si_compare; /* cmd + args to exec for compare */ + Cmd_info si_modify; /* cmd + args to exec for modify */ + Cmd_info si_modrdn; /* cmd + args to exec for modrdn */ + Cmd_info si_add; /* cmd + args to exec for add */ + Cmd_info si_delete; /* cmd + args to exec for delete */ + Cmd_info si_abandon; /* cmd + args to exec for abandon */ }; @@ -30,5 +55,5 @@ extern pid_t forkandexec LDAP_P(( - char **args, + Cmd_info args, FILE **rfp, FILE **wfp)); diff -u2 -r servers/slapd/back-shell/unbind.c~ servers/slapd/back-shell/unbind.c --- servers/slapd/back-shell/unbind.c~ Fri May 10 08:20:35 2002 +++ servers/slapd/back-shell/unbind.c Sun May 12 05:41:10 2002 @@ -26,5 +26,5 @@ FILE *rfp, *wfp; - if ( si->si_unbind == NULL ) { + if ( IS_NULLCMD( si->si_unbind ) ) { return 0; }