Commit 4e51bba2 authored by Kurt Zeilenga's avatar Kurt Zeilenga
Browse files

Patch: Implement surrogate parent for back-shell (ITS#1815)

			================
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 <h.b.furuseth@usit.uio.no>, May 2002.
parent d8388e46
#! /bin/sh
# $OpenLDAP$
# from OpenLDAP: pkg/ldap/configure.in,v 1.417 2002/05/13 16:48:37 kurt Exp
# from OpenLDAP: pkg/ldap/configure.in,v 1.418 2002/05/13 18:16:47 kurt Exp
 
# Copyright 1998-2002 The OpenLDAP Foundation. All Rights Reserved.
#
......@@ -19622,7 +19622,7 @@ else
#if SASL_VERSION_MAJOR == 1 && SASL_VERSION_MINOR >= 5
char *__sasl_compat = "1.5.x okay";
#elif SASL_VERSION_MAJOR == 2 && SASL_VERSION_MINOR > 1
__sasl_compat "2.2+ or better okay";
__sasl_compat "2.2+ or better okay (we guess)";
#elif SASL_VERSION_MAJOR == 2 && SASL_VERSION_MINOR == 1 \
&& SASL_VERSION_STEP >=3
__sasl_compat = "2.1.3+ or better okay";
......@@ -21790,6 +21790,7 @@ for ac_func in \
read \
recv \
recvfrom \
recvmsg \
setpwfile \
setgid \
setegid \
......@@ -21818,12 +21819,12 @@ for ac_func in \
 
do
echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
echo "configure:21822: checking for $ac_func" >&5
echo "configure:21823: checking for $ac_func" >&5
if eval "test \"\${ac_cv_func_$ac_func+set}\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
#line 21827 "configure"
#line 21828 "configure"
#include "confdefs.h"
/* System header to define __stub macros and hopefully few prototypes,
which can conflict with char $ac_func(); below. */
......@@ -21847,7 +21848,7 @@ f = $ac_func;
 
; return 0; }
EOF
if { (eval echo configure:21851: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
if { (eval echo configure:21852: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
rm -rf conftest*
eval "ac_cv_func_$ac_func=yes"
else
......@@ -21875,12 +21876,12 @@ done
for ac_func in getopt
do
echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
echo "configure:21879: checking for $ac_func" >&5
echo "configure:21880: checking for $ac_func" >&5
if eval "test \"\${ac_cv_func_$ac_func+set}\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
#line 21884 "configure"
#line 21885 "configure"
#include "confdefs.h"
/* System header to define __stub macros and hopefully few prototypes,
which can conflict with char $ac_func(); below. */
......@@ -21904,7 +21905,7 @@ f = $ac_func;
 
; return 0; }
EOF
if { (eval echo configure:21908: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
if { (eval echo configure:21909: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
rm -rf conftest*
eval "ac_cv_func_$ac_func=yes"
else
......@@ -21937,13 +21938,13 @@ fi
 
# Check Configuration
echo $ac_n "checking declaration of sys_errlist""... $ac_c" 1>&6
echo "configure:21941: checking declaration of sys_errlist" >&5
echo "configure:21942: checking declaration of sys_errlist" >&5
if eval "test \"\${ol_cv_dcl_sys_errlist+set}\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
#line 21947 "configure"
#line 21948 "configure"
#include "confdefs.h"
 
#include <stdio.h>
......@@ -21956,7 +21957,7 @@ int main() {
char *c = (char *) *sys_errlist
; return 0; }
EOF
if { (eval echo configure:21960: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
if { (eval echo configure:21961: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
rm -rf conftest*
ol_cv_dcl_sys_errlist=yes
ol_cv_have_sys_errlist=yes
......@@ -21979,20 +21980,20 @@ EOF
 
 
echo $ac_n "checking existence of sys_errlist""... $ac_c" 1>&6
echo "configure:21983: checking existence of sys_errlist" >&5
echo "configure:21984: checking existence of sys_errlist" >&5
if eval "test \"\${ol_cv_have_sys_errlist+set}\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
#line 21989 "configure"
#line 21990 "configure"
#include "confdefs.h"
#include <errno.h>
int main() {
char *c = (char *) *sys_errlist
; return 0; }
EOF
if { (eval echo configure:21996: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
if { (eval echo configure:21997: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
rm -rf conftest*
ol_cv_have_sys_errlist=yes
else
......
......@@ -2426,6 +2426,7 @@ AC_CHECK_FUNCS( \
read \
recv \
recvfrom \
recvmsg \
setpwfile \
setgid \
setegid \
......
......@@ -224,6 +224,9 @@
/* Define if you have the recvfrom function. */
#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
......
......@@ -243,6 +243,9 @@
/* Define if you have the recvfrom function. */
/* #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 */
......
......@@ -29,7 +29,7 @@ shell_back_abandon(
Operation *o;
/* 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;
LDAP_STAILQ_FOREACH( o, &conn->c_ops, o_next ) {
......
......@@ -27,7 +27,7 @@ shell_back_add(
FILE *rfp, *wfp;
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 );
return( -1 );
......
......@@ -31,7 +31,7 @@ shell_back_bind(
FILE *rfp, *wfp;
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 );
return( -1 );
......
......@@ -28,7 +28,7 @@ shell_back_compare(
struct shellinfo *si = (struct shellinfo *) be->be_private;
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 );
return( -1 );
......
......@@ -15,6 +15,28 @@
#include "slap.h"
#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(
BackendDB *be,
......@@ -40,7 +62,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_bind = charray_dup( &argv[1] );
si->si_bind = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for unbinds */
} else if ( strcasecmp( argv[0], "unbind" ) == 0 ) {
......@@ -50,7 +72,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_unbind = charray_dup( &argv[1] );
si->si_unbind = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for searches */
} else if ( strcasecmp( argv[0], "search" ) == 0 ) {
......@@ -60,7 +82,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_search = charray_dup( &argv[1] );
si->si_search = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for compares */
} else if ( strcasecmp( argv[0], "compare" ) == 0 ) {
......@@ -70,7 +92,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_compare = charray_dup( &argv[1] );
si->si_compare = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for modifies */
} else if ( strcasecmp( argv[0], "modify" ) == 0 ) {
......@@ -80,7 +102,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_modify = charray_dup( &argv[1] );
si->si_modify = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for modrdn */
} else if ( strcasecmp( argv[0], "modrdn" ) == 0 ) {
......@@ -90,7 +112,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_modrdn = charray_dup( &argv[1] );
si->si_modrdn = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for add */
} else if ( strcasecmp( argv[0], "add" ) == 0 ) {
......@@ -100,7 +122,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_add = charray_dup( &argv[1] );
si->si_add = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for delete */
} else if ( strcasecmp( argv[0], "delete" ) == 0 ) {
......@@ -110,7 +132,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_delete = charray_dup( &argv[1] );
si->si_delete = MAKE_CMD_INFO( &argv[1] );
/* command + args to exec for abandon */
} else if ( strcasecmp( argv[0], "abandon" ) == 0 ) {
......@@ -120,7 +142,7 @@ shell_back_db_config(
fname, lineno );
return( 1 );
}
si->si_abandon = charray_dup( &argv[1] );
si->si_abandon = MAKE_CMD_INFO( &argv[1] );
/* anything else */
} else {
......
......@@ -27,7 +27,7 @@ shell_back_delete(
struct shellinfo *si = (struct shellinfo *) be->be_private;
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 );
return( -1 );
......
......@@ -9,6 +9,7 @@
#include <stdio.h>
#include <ac/errno.h>
#include <ac/string.h>
#include <ac/socket.h>
#include <ac/unistd.h>
......@@ -16,18 +17,226 @@
#include "slap.h"
#include "shell.h"
#ifdef SHELL_SURROGATE_PARENT
#include <sys/uio.h>
/* 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 );
}
......@@ -37,27 +246,89 @@ forkandexec(
* parent *rfp <- c2p[0] | c2p[1] <- stdout child
*/
#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
*