From 1b41dfbce0730bb82fe44e315313d0b35079a7dd Mon Sep 17 00:00:00 2001 From: Kurt Zeilenga Date: Tue, 3 Aug 1999 19:27:22 +0000 Subject: [PATCH] LDAP_OPT_NETWORK_TIMEOUT feature (ITS#239) from Lars Uffmann. Needs a bit more work. - global net/api timeouts are not inherited on session creation. - need configure check for inet_aton() (coming soon) - ioctl/fcntl portability issues (should share implementation with lber routines) --- include/ldap.h | 1 + libraries/libldap/init.c | 5 +- libraries/libldap/ldap-int.h | 9 +- libraries/libldap/open.c | 4 +- libraries/libldap/options.c | 115 +++++++--- libraries/libldap/os-ip.c | 397 +++++++++++++++++++++++++---------- libraries/libldap/unbind.c | 10 + 7 files changed, 395 insertions(+), 146 deletions(-) diff --git a/include/ldap.h b/include/ldap.h index 70ab7318fe..4602377270 100644 --- a/include/ldap.h +++ b/include/ldap.h @@ -112,6 +112,7 @@ LDAP_BEGIN_DECL #define LDAP_OPT_TIMEOUT 0x5002 /* default timeout */ #define LDAP_OPT_REFHOPLIMIT 0x5003 /* ref hop limit */ #define LDAP_OPT_MATCHED_DN 0x5004 /* should have been in draft */ +#define LDAP_OPT_NETWORK_TIMEOUT 0x5005 /* socket level timeout */ /* TLS options */ #define LDAP_OPT_X_TLS_CACERTFILE 0x6001 diff --git a/libraries/libldap/init.c b/libraries/libldap/init.c index 5906bf78ae..c49b372ae9 100644 --- a/libraries/libldap/init.c +++ b/libraries/libldap/init.c @@ -315,12 +315,15 @@ void ldap_int_initialize( void ) if ( ldap_int_tblsize == 0 ) ldap_int_ip_init(); + gopts.ldo_debug = 0; + gopts.ldo_version = LDAP_VERSION2; gopts.ldo_deref = LDAP_DEREF_NEVER; gopts.ldo_timelimit = LDAP_NO_LIMIT; gopts.ldo_sizelimit = LDAP_NO_LIMIT; - gopts.ldo_debug = 0; + gopts.ldo_tm_api = (struct timeval *)NULL; + gopts.ldo_tm_net = (struct timeval *)NULL; gopts.ldo_defhost = LDAP_STRDUP("localhost"); gopts.ldo_defport = LDAP_PORT; diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h index 6b1ca1a027..e9146db127 100644 --- a/libraries/libldap/ldap-int.h +++ b/libraries/libldap/ldap-int.h @@ -90,6 +90,9 @@ struct ldapoptions { #define LDAP_VALID_SESSION 0x2 int ldo_debug; + /* per API call timeout */ + struct timeval *ldo_tm_api; + struct timeval *ldo_tm_net; ber_int_t ldo_version; /* version to connect at */ ber_int_t ldo_deref; @@ -119,6 +122,7 @@ struct ldapoptions { LDAP_BOOLEANS ldo_booleans; /* boolean options */ }; + /* * structure for tracking LDAP server host, ports, DNs, etc. */ @@ -353,9 +357,10 @@ int open_ldap_connection( LDAP *ld, Sockbuf *sb, const char *host, int defport, * in os-ip.c */ extern int ldap_int_tblsize; +int ldap_int_timeval_dup( struct timeval **dest, const struct timeval *tm ); +int ldap_connect_to_host( LDAP *ld, Sockbuf *sb, const char *host, + unsigned long address, int port, int async ); -int ldap_connect_to_host( Sockbuf *sb, const char *host, unsigned long address, int port, - int async ); void ldap_close_connection( Sockbuf *sb ); #ifdef HAVE_KERBEROS diff --git a/libraries/libldap/open.c b/libraries/libldap/open.c index 419b4277e0..93019b0915 100644 --- a/libraries/libldap/open.c +++ b/libraries/libldap/open.c @@ -236,13 +236,13 @@ open_ldap_connection( LDAP *ld, Sockbuf *sb, const char *host, int defport, port = defport; } - if (( rc = ldap_connect_to_host( sb, curhost, 0L, + if (( rc = ldap_connect_to_host( ld, sb, curhost, 0L, port, async )) != -1 ) { break; } } } else { - rc = ldap_connect_to_host( sb, NULL, htonl( INADDR_LOOPBACK ), + rc = ldap_connect_to_host( ld, sb, 0, htonl( INADDR_LOOPBACK ), defport, async ); } diff --git a/libraries/libldap/options.c b/libraries/libldap/options.c index b3dd1b4d58..c66ac0ab1f 100644 --- a/libraries/libldap/options.c +++ b/libraries/libldap/options.c @@ -11,6 +11,7 @@ #include #include +#include #include "ldap-int.h" @@ -155,6 +156,22 @@ ldap_get_option( * (ber_socket_t *) outvalue = ber_pvt_sb_get_desc( &(ld->ld_sb) ); return LDAP_OPT_SUCCESS; + case LDAP_OPT_TIMEOUT: + /* the caller has to free outvalue ! */ + if ( ldap_int_timeval_dup( outvalue, lo->ldo_tm_api) != 0 ) + { + return LDAP_OPT_ERROR; + } + return LDAP_OPT_SUCCESS; + + case LDAP_OPT_NETWORK_TIMEOUT: + /* the caller has to free outvalue ! */ + if ( ldap_int_timeval_dup( outvalue, lo->ldo_tm_net ) != 0 ) + { + return LDAP_OPT_ERROR; + } + return LDAP_OPT_SUCCESS; + case LDAP_OPT_DEREF: * (int *) outvalue = lo->ldo_deref; return LDAP_OPT_SUCCESS; @@ -322,38 +339,8 @@ ldap_set_option( return LDAP_OPT_SUCCESS; } - if(invalue == NULL) { - /* no place to set from */ - return LDAP_OPT_ERROR; - } - - switch(option) { - case LDAP_OPT_API_INFO: - case LDAP_OPT_DESC: - /* READ ONLY */ - break; - - case LDAP_OPT_DEREF: - lo->ldo_deref = * (const int *) invalue; - return LDAP_OPT_SUCCESS; - - case LDAP_OPT_SIZELIMIT: - lo->ldo_sizelimit = * (const int *) invalue; - return LDAP_OPT_SUCCESS; - - case LDAP_OPT_TIMELIMIT: - lo->ldo_timelimit = * (const int *) invalue; - return LDAP_OPT_SUCCESS; - - case LDAP_OPT_PROTOCOL_VERSION: { - int vers = * (const int *) invalue; - if (vers < LDAP_VERSION_MIN || vers > LDAP_VERSION_MAX) { - /* not supported */ - break; - } - ld->ld_version = vers; - } return LDAP_OPT_SUCCESS; - + /* options which can withstand invalue == NULL */ + switch ( option ) { case LDAP_OPT_SERVER_CONTROLS: { LDAPControl *const *controls = (LDAPControl *const *) invalue; @@ -392,6 +379,70 @@ ldap_set_option( } } return LDAP_OPT_SUCCESS; + case LDAP_OPT_TIMEOUT: { + const struct timeval *tv = + (struct timeval *) invalue; + + if ( lo->ldo_tm_api != NULL ) { + LDAP_FREE( lo->ldo_tm_api ); + lo->ldo_tm_api = NULL; + } + + if ( ldap_int_timeval_dup( &lo->ldo_tm_api, tv ) != 0 ) { + return LDAP_OPT_ERROR; + } + } return LDAP_OPT_SUCCESS; + + case LDAP_OPT_NETWORK_TIMEOUT: { + const struct timeval *tv = + (struct timeval *) invalue; + + if ( lo->ldo_tm_net != NULL ) { + LDAP_FREE( lo->ldo_tm_net ); + lo->ldo_tm_net = NULL; + } + + if ( ldap_int_timeval_dup( &lo->ldo_tm_net, tv ) != 0 ) { + return LDAP_OPT_ERROR; + } + } return LDAP_OPT_SUCCESS; + } + + if(invalue == NULL) { + /* no place to set from */ + return LDAP_OPT_ERROR; + } + + /* options which cannot withstand invalue == NULL */ + + switch(option) { + case LDAP_OPT_API_INFO: + case LDAP_OPT_DESC: + /* READ ONLY */ + break; + + case LDAP_OPT_DEREF: + lo->ldo_deref = * (const int *) invalue; + return LDAP_OPT_SUCCESS; + + case LDAP_OPT_SIZELIMIT: + lo->ldo_sizelimit = * (const int *) invalue; + return LDAP_OPT_SUCCESS; + + case LDAP_OPT_TIMELIMIT: + lo->ldo_timelimit = * (const int *) invalue; + return LDAP_OPT_SUCCESS; + + case LDAP_OPT_PROTOCOL_VERSION: { + int vers = * (const int *) invalue; + if (vers < LDAP_VERSION_MIN || vers > LDAP_VERSION_MAX) { + /* not supported */ + break; + } + ld->ld_version = vers; + } return LDAP_OPT_SUCCESS; + + case LDAP_OPT_HOST_NAME: { const char *host = (const char *) invalue; diff --git a/libraries/libldap/os-ip.c b/libraries/libldap/os-ip.c index 729c5d7902..140f333e74 100644 --- a/libraries/libldap/os-ip.c +++ b/libraries/libldap/os-ip.c @@ -25,6 +25,13 @@ #include #endif /* HAVE_IO_H */ +#if defined( HAVE_FCNTL_H ) +#include +#ifndef O_NONBLOCK +#define O_NONBLOCK O_NDELAY +#endif +#endif /* HAVE_FCNTL_H */ + #if defined( HAVE_SYS_FILIO_H ) #include #elif defined( HAVE_SYS_IOCTL_H ) @@ -35,143 +42,315 @@ int ldap_int_tblsize = 0; - -int -ldap_connect_to_host( Sockbuf *sb, const char *host, unsigned long address, - int port, int async ) /* - * if host == NULL, connect using address - * "address" and "port" must be in network byte order - * zero is returned upon success, -1 if fatal error, -2 EINPROGRESS - * async is only used ifdef LDAP_API_FEATURE_X_OPENLDAP_V2_REFERRALS (non-0 means don't wait for connect) - * XXX async is not used yet! + * nonblock connect code + * written by Lars Uffmann, . + * + * Copyright 1999, Lars Uffmann, All rights reserved. + * This software is not subject to any license of my employer + * mediaWays GmbH. + * + * OpenLDAP COPYING RESTRICTIONS APPLY, see COPYRIGHT file + * + * Read about the rationale in ldap_connect_timeout: + * ftp://koobera.math.uic.edu/www/docs/connect.html. */ + +#define osip_debug(ld,fmt,arg1,arg2,arg3) \ +do { \ + ldap_log_printf(ld, LDAP_DEBUG_TRACE, fmt, arg1, arg2, arg3); \ +} while(0) + +static void +ldap_pvt_set_errno(int err) { - int rc, i; - ber_socket_t s = AC_SOCKET_INVALID; - int connected, use_hp; - struct sockaddr_in sin; - struct hostent *hp = NULL; + errno = err; +} + +int +ldap_int_timeval_dup( struct timeval **dest, const struct timeval *src ) +{ + struct timeval *new; + + assert( dest != NULL ); + + if (src == NULL) { + *dest = NULL; + return 0; + } + + new = (struct timeval *) malloc(sizeof(struct timeval)); + + if( new == NULL ) { + *dest = NULL; + return 1; + } + + SAFEMEMCPY( (char *) new, (char *) src, sizeof(struct timeval)); + + *dest = new; + return 0; +} + +static int +ldap_pvt_ndelay_on(LDAP *ld, int fd) +{ + osip_debug(ld, "ldap_ndelay_on: %d\n",fd,0,0); #ifdef notyet - ioctl_t status; /* for ioctl call */ -#endif /* notyet */ - - /* buffers for ldap_pvt_gethostbyname_a */ - struct hostent he_buf; - int local_h_errno; - char *ha_buf=NULL; -#define DO_RETURN(x) if (ha_buf) LDAP_FREE(ha_buf); return (x); +/* #if defined( HAVE_FCNTL_H ) */ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) | O_NONBLOCK); +#else +{ + ioctl_t status = 1; + return ioctl( fd, FIONBIO, (caddr_t)&status ); +} +#endif + return 0; +} - Debug( LDAP_DEBUG_TRACE, "ldap_connect_to_host: %s:%d\n", - ( host == NULL ) ? "(by address)" : host, (int) ntohs( (short) port ), 0 ); - - connected = use_hp = 0; - - if ( host != NULL ) { - address = inet_addr( host ); - /* This was just a test for -1 until OSF1 let inet_addr return - unsigned int, which is narrower than 'unsigned long address' */ - if ( address == 0xffffffff || address == (unsigned long) -1 ) { - if ( ( ldap_pvt_gethostbyname_a( host, &he_buf, &ha_buf, - &hp, &local_h_errno) < 0) || (hp==NULL)) - { -#ifdef HAVE_WINSOCK - errno = WSAGetLastError(); +static int +ldap_pvt_ndelay_off(LDAP *ld, int fd) +{ + osip_debug(ld, "ldap_ndelay_off: %d\n",fd,0,0); +#ifdef notyet +/* #if defined( HAVE_FCNTL_H ) */ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) & ~O_NONBLOCK); #else - errno = EHOSTUNREACH; /* not exactly right, but... */ +{ + ioctl_t status = 0; + return ioctl( fd, FIONBIO, (caddr_t)&status ); +} #endif - DO_RETURN( -1 ); - } - use_hp = 1; - } - } +} + +static ber_socket_t +ldap_pvt_socket(LDAP *ld) +{ + ber_socket_t s = socket(AF_INET, SOCK_STREAM, 0); + osip_debug(ld, "ldap_new_socket: %d\n",s,0,0); + return ( s ); +} + +static int +ldap_pvt_close_socket(LDAP *ld, int s) +{ + osip_debug(ld, "ldap_close_socket: %d\n",s,0,0); + return tcp_close(s); +} + +static int +ldap_pvt_prepare_socket(LDAP *ld, int fd) +{ + osip_debug(ld, "ldap_prepare_socket: %d\n",fd,0,0); - rc = -1; - for ( i = 0; !use_hp || ( hp->h_addr_list[ i ] != 0 ); i++ ) { - if (( s = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) { - DO_RETURN( -1 ); - } #ifdef TCP_NODELAY - { - int tmp = 1; - if( setsockopt( s, IPPROTO_TCP, TCP_NODELAY, - (char *) &tmp, sizeof(tmp) ) == -1 ) - { - Debug( LDAP_DEBUG_ANY, - "setsockopt(TCP_NODELAY failed on %d\n", - s, 0, 0 ); - } - } +{ + int dummy = 1; + if ( setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,&dummy,sizeof(dummy)) == -1 ) + return -1; +} #endif + return 0; +} + +/* + * check the socket for errors after select returned. + */ +static int +ldap_pvt_is_socket_ready(LDAP *ld, int s) +{ + osip_debug(ld, "ldap_is_sock_ready: %d\n",s,0,0); + +#define TRACE \ +{ \ + osip_debug(ld, \ + "ldap_is_socket_ready: errror on socket %d: errno: %d (%s)\n", \ + s, \ + errno, \ + strerror(errno) ); \ +} + #ifdef notyet - status = 1; - if ( async && ioctl( s, FIONBIO, (caddr_t)&status ) == -1 ) { - Debug( LDAP_DEBUG_ANY, "FIONBIO ioctl failed on %d\n", - s, 0, 0 ); +/* #ifdef SO_ERROR */ +{ + int so_errno; + int dummy = sizeof(so_errno); + if ( getsockopt(s,SOL_SOCKET,SO_ERROR,&so_errno,&dummy) == -1 ) + return -1; + if ( so_errno ) { + ldap_pvt_set_errno(so_errno); + TRACE; + return -1; + } + return 0; +} +#else +{ + /* error slippery */ + struct sockaddr_in sin; + char ch; + int dummy = sizeof(sin); + if ( getpeername(s, (struct sockaddr *) &sin, &dummy) == -1 ) { + read(s, &ch, 1); +#ifdef HAVE_WINSOCK + ldap_pvt_set_errno( WSAGetLastError() ); +#endif + TRACE; + return -1; } -#endif /* notyet */ - (void)memset( (char *)&sin, 0, sizeof( struct sockaddr_in )); - sin.sin_family = AF_INET; - sin.sin_port = port; - SAFEMEMCPY( (char *) &sin.sin_addr.s_addr, - ( use_hp ? (char *) hp->h_addr_list[ i ] : - (char *) &address ), sizeof( sin.sin_addr.s_addr) ); - - if ( connect( s, (struct sockaddr *)&sin, - sizeof( struct sockaddr_in )) >= 0 ) { - connected = 1; - rc = 0; - break; - } else { + return 0; +} +#endif + return -1; +#undef TRACE +} + +static int +ldap_pvt_connect(LDAP *ld, int s, struct sockaddr_in *sin, int async) +{ + struct timeval tv, *opt_tv=NULL; + fd_set wfds, *z=NULL; + + if ( (opt_tv = ld->ld_options.ldo_tm_net) != NULL ) { + tv.tv_usec = opt_tv->tv_usec; + tv.tv_sec = opt_tv->tv_sec; + } + + osip_debug(ld, "ldap_connect_timeout: fd: %d tm: %d async: %d\n", + s, opt_tv ? tv.tv_sec : -1, async); + + if ( ldap_pvt_ndelay_on(ld, s) == -1 ) + return ( -1 ); + + if ( connect(s, (struct sockaddr *) sin, sizeof(struct sockaddr_in)) == 0 ) + { + if ( ldap_pvt_ndelay_off(ld, s) == -1 ) + return ( -1 ); + return ( 0 ); + } + #ifdef HAVE_WINSOCK - errno = WSAGetLastError(); + ldap_pvt_set_errno( WSAGetLastError() ); #endif + + if ( (errno != EINPROGRESS) && (errno != EWOULDBLOCK) ) + return ( -1 ); + #ifdef notyet -#ifdef EAGAIN - if ( errno == EINPROGRESS || errno == EAGAIN ) { -#else /* EAGAIN */ - if ( errno == EINPROGRESS ) { -#endif /* EAGAIN */ - Debug( LDAP_DEBUG_TRACE, - "connect would block...\n", 0, 0, 0 ); - rc = -2; - break; - } -#endif /* notyet */ + if ( async ) return ( -2 ); +#endif -#ifdef LDAP_DEBUG - if ( ldap_debug & LDAP_DEBUG_TRACE ) { - perror( (char *)inet_ntoa( sin.sin_addr )); - } + FD_ZERO(&wfds); FD_SET(s, &wfds ); + + if ( select(s + 1, z, &wfds, z, opt_tv ? &tv : NULL) == -1) + return ( -1 ); + + if ( FD_ISSET(s, &wfds) ) { + if ( ldap_pvt_is_socket_ready(ld, s) == -1 ) + return ( -1 ); + if ( ldap_pvt_ndelay_off(ld, s) == -1 ) + return ( -1 ); + return ( 0 ); + } + osip_debug(ld, "ldap_connect_timeout: timed out\n",0,0,0); + ldap_pvt_set_errno( ETIMEDOUT ); + return ( -1 ); +} + +static int +ldap_pvt_inet_aton( LDAP *ld, const char *host, struct in_addr *in) +{ +#ifdef notyet +/* #ifdef HAVE_INET_ATON */ + return inet_aton( host, in ); +#else +{ + unsigned long u = inet_addr( host ); + if ( u != 0xffffffff || u != (unsigned long) -1 ) { + in->s_addr = u; + return 1; + } +} +#endif + return 0; +} + + +int +ldap_connect_to_host(LDAP *ld, Sockbuf *sb, const char *host, + unsigned long address, int port, int async) +{ + struct sockaddr_in sin; + struct in_addr in; + ber_socket_t s = AC_SOCKET_INVALID; + int rc, i, use_hp = 0; + struct hostent *hp, he_buf; + int local_h_errno; + char *ha_buf=NULL, *p, *q; + + osip_debug(ld, "ldap_connect_to_host\n",0,0,0); + + if (host != NULL) { + if (! ldap_pvt_inet_aton( ld, host, &in) ) { + rc = ldap_pvt_gethostbyname_a(host, &he_buf, &ha_buf, + &hp, &local_h_errno); + + if ( rc < 0 ) + ; /*XXX NO MEMORY? */ + + if ( (rc < 0) || (hp == NULL) ) { +#ifdef HAVE_WINSOCK + ldap_pvt_set_errno( WSAGetLastError() ); +#else + /* not exactly right, but... */ + ldap_pvt_set_errno( EHOSTUNREACH ); #endif - tcp_close( s ); - if ( !use_hp ) { - break; + if (ha_buf) LDAP_FREE(ha_buf); + return -1; } + use_hp = 1; } + address = in.s_addr; } - ber_pvt_sb_set_desc( sb, s ); + rc = s = -1; + for ( i = 0; !use_hp || (hp->h_addr_list[i] != 0); ++i, rc = -1 ) { - if ( connected ) { + if ( (s = ldap_pvt_socket( ld )) == -1 ) + /* use_hp ? continue : break; */ + break; -#ifdef notyet - status = 0; - if ( !async && ioctl( s, FIONBIO, (caddr_t)&on ) == -1 ) { - Debug( LDAP_DEBUG_ANY, "FIONBIO ioctl failed on %d\n", - s, 0, 0 ); + if ( ldap_pvt_prepare_socket(ld, s) == -1 ) { + ldap_pvt_close_socket(ld, s); + /* use_hp ? continue : break; */ + break; } -#endif /* notyet */ - Debug( LDAP_DEBUG_TRACE, "sd %d connected to: %s\n", - s, (char *) inet_ntoa( sin.sin_addr ), 0 ); - } + (void)memset((char *)&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = port; + p = (char *)&sin.sin_addr.s_addr; + q = use_hp ? (char *)hp->h_addr_list[i] : (char *)&address; + SAFEMEMCPY(p, q, sizeof(p) ); - DO_RETURN( rc ); -} + osip_debug(ld, "ldap_connect_to_host: Trying %s:%d\n", + inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),0); + + rc = ldap_pvt_connect(ld, s, &sin, async); -#undef DO_RETURN + if ( (rc == 0) || (rc == -2) ) { + ber_pvt_sb_set_desc( sb, s ); + break; + } + ldap_pvt_close_socket(ld, s); + + if (!use_hp) + break; + } + if (ha_buf) LDAP_FREE(ha_buf); + return rc; +} void ldap_close_connection( Sockbuf *sb ) diff --git a/libraries/libldap/unbind.c b/libraries/libldap/unbind.c index 39b3263e4c..93ce360d75 100644 --- a/libraries/libldap/unbind.c +++ b/libraries/libldap/unbind.c @@ -133,6 +133,16 @@ ldap_ld_free( ld->ld_options.ldo_defhost = NULL; } + if ( ld->ld_options.ldo_tm_api != NULL ) { + LDAP_FREE( ld->ld_options.ldo_tm_api ); + ld->ld_options.ldo_tm_api = NULL; + } + + if ( ld->ld_options.ldo_tm_net != NULL ) { + LDAP_FREE( ld->ld_options.ldo_tm_net ); + ld->ld_options.ldo_tm_net = NULL; + } + ber_pvt_sb_destroy( &(ld->ld_sb) ); LDAP_FREE( (char *) ld ); -- 2.39.5