diff --git a/doc/man/man3/ldap_get_option.3 b/doc/man/man3/ldap_get_option.3 index 28d0227316..023ef4be49 100644 --- a/doc/man/man3/ldap_get_option.3 +++ b/doc/man/man3/ldap_get_option.3 @@ -376,6 +376,21 @@ must be a This is a read-only, handle-specific option. This option is OpenLDAP specific. .TP +.B LDAP_OPT_SOCKET_BIND_ADDRESSES +Sets/gets a space-separated list of IP Addresses used as binding interface +to remote server when trying to establish a connection. Only one valid IPv4 +address and/or one valid IPv6 address are allowed in the list. +.BR outvalue +must be a +.BR "char **", +and the caller is responsible of freeing the returned string by calling +.BR ldap_memfree (3), +while +.BR invalue +must be a +.BR "const char *" ; +the library duplicates the corresponding string. +.TP .B LDAP_OPT_TIMELIMIT Sets/gets the value that defines the time limit after which a search operation should be terminated by the server. diff --git a/doc/man/man5/ldap.conf.5 b/doc/man/man5/ldap.conf.5 index f77fcf18a1..17b7154991 100644 --- a/doc/man/man5/ldap.conf.5 +++ b/doc/man/man5/ldap.conf.5 @@ -213,6 +213,11 @@ specifies a request for unlimited search size. Please note that the server may still apply any server-side limit on the amount of entries that can be returned by a search operation. .TP +.B SOCKET_BIND_ADDRESSES +Specifies the source bind IP to be used for connecting to target LDAP server. +Multiple IP addresses must be space separated. Only one valid IPv4 +address and/or one valid IPv6 address are allowed in the list. +.TP .B TIMELIMIT Specifies a time limit (in seconds) to use when performing searches. The number should be a non-negative integer. \fITIMELIMIT\fP of zero (0) diff --git a/include/ldap.h b/include/ldap.h index 8f45144e11..aca5790761 100644 --- a/include/ldap.h +++ b/include/ldap.h @@ -132,6 +132,7 @@ LDAP_BEGIN_DECL #define LDAP_OPT_CONNECT_CB 0x5011 /* connection callbacks */ #define LDAP_OPT_SESSION_REFCNT 0x5012 /* session reference count */ #define LDAP_OPT_KEEPCONN 0x5013 /* keep the connection on read error or NoD */ +#define LDAP_OPT_SOCKET_BIND_ADDRESSES 0x5014 /* user configured bind IPs */ /* OpenLDAP TLS options */ #define LDAP_OPT_X_TLS 0x6000 diff --git a/libraries/libldap/init.c b/libraries/libldap/init.c index f2e3e50384..ff9e5d49c2 100644 --- a/libraries/libldap/init.c +++ b/libraries/libldap/init.c @@ -38,6 +38,7 @@ struct ldapoptions ldap_int_global_options = { LDAP_UNINITIALIZED, LDAP_DEBUG_NONE LDAP_LDO_NULLARG + LDAP_LDO_SOURCEIP_NULLARG LDAP_LDO_CONNECTIONLESS_NULLARG LDAP_LDO_TLS_NULLARG LDAP_LDO_SASL_NULLARG @@ -93,6 +94,7 @@ static const struct ol_attribute { offsetof(struct ldapoptions, ldo_defport)}, {0, ATTR_OPTION, "HOST", NULL, LDAP_OPT_HOST_NAME}, /* deprecated */ {0, ATTR_OPTION, "URI", NULL, LDAP_OPT_URI}, /* replaces HOST/PORT */ + {0, ATTR_OPTION, "SOCKET_BIND_ADDRESSES", NULL, LDAP_OPT_SOCKET_BIND_ADDRESSES}, {0, ATTR_BOOL, "REFERRALS", NULL, LDAP_BOOL_REFERRALS}, {0, ATTR_INT, "KEEPALIVE_IDLE", NULL, LDAP_OPT_X_KEEPALIVE_IDLE}, {0, ATTR_INT, "KEEPALIVE_PROBES", NULL, LDAP_OPT_X_KEEPALIVE_PROBES}, @@ -142,7 +144,7 @@ static const struct ol_attribute { {0, ATTR_NONE, NULL, NULL, 0} }; -#define MAX_LDAP_ATTR_LEN sizeof("TLS_CIPHER_SUITE") +#define MAX_LDAP_ATTR_LEN sizeof("SOCKET_BIND_ADDRESSES") #define MAX_LDAP_ENV_PREFIX_LEN 8 static int @@ -519,6 +521,12 @@ ldap_int_destroy_global_options(void) ldap_free_urllist( gopts->ldo_defludp ); gopts->ldo_defludp = NULL; } + + if ( gopts->ldo_local_ip_addrs.local_ip_addrs ) { + LDAP_FREE( gopts->ldo_local_ip_addrs.local_ip_addrs ); + gopts->ldo_local_ip_addrs.local_ip_addrs = NULL; + } + #if defined(HAVE_WINSOCK) || defined(HAVE_WINSOCK2) WSACleanup( ); #endif @@ -558,6 +566,9 @@ void ldap_int_initialize_global_options( struct ldapoptions *gopts, int *dbglvl gopts->ldo_tm_api.tv_sec = -1; gopts->ldo_tm_net.tv_sec = -1; + memset( &gopts->ldo_local_ip_addrs, 0, + sizeof( gopts->ldo_local_ip_addrs ) ); + /* ldo_defludp will be freed by the termination handler */ ldap_url_parselist(&gopts->ldo_defludp, "ldap://localhost/"); diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h index 32ef666922..e40518b32d 100644 --- a/libraries/libldap/ldap-int.h +++ b/libraries/libldap/ldap-int.h @@ -45,9 +45,7 @@ /* for struct timeval */ #include -#ifdef _WIN32 #include -#endif #undef TV2MILLISEC #define TV2MILLISEC(tv) (((tv)->tv_sec * 1000) + ((tv)->tv_usec/1000)) @@ -199,6 +197,19 @@ typedef struct ldaplist { void *ll_data; } ldaplist; +/* + * LDAP Client Source IP structure + */ +typedef struct ldapsourceip { + char *local_ip_addrs; + struct in_addr ip4_addr; + unsigned short has_ipv4; +#ifdef LDAP_PF_INET6 + struct in6_addr ip6_addr; + unsigned short has_ipv6; +#endif +} ldapsourceip; + /* * structure representing get/set'able options * which have global defaults. @@ -256,6 +267,15 @@ struct ldapoptions { #define LDAP_LDO_NULLARG ,0,0,0,0 ,{0},{0} ,0,0,0,0, 0,0,0,0, 0,0, 0,0,0,0,0,0, 0, 0 + /* LDAP user configured bind IPs */ + struct ldapsourceip ldo_local_ip_addrs; + +#ifdef LDAP_PF_INET6 +#define LDAP_LDO_SOURCEIP_NULLARG ,{0,0,0,0,0} +#else +#define LDAP_LDO_SOURCEIP_NULLARG ,{0,0,0} +#endif + #ifdef LDAP_CONNECTIONLESS #define LDAP_IS_UDP(ld) ((ld)->ld_options.ldo_is_udp) void* ldo_peer; /* struct sockaddr* */ @@ -727,6 +747,9 @@ LDAP_F (void) ldap_clear_select_write( LDAP *ld, Sockbuf *sb ); LDAP_F (int) ldap_is_read_ready( LDAP *ld, Sockbuf *sb ); LDAP_F (int) ldap_is_write_ready( LDAP *ld, Sockbuf *sb ); +LDAP_F (int) ldap_validate_and_fill_sourceip ( char** source_ip_lst, + ldapsourceip* temp_source_ip ); + LDAP_F (int) ldap_int_connect_cbs( LDAP *ld, Sockbuf *sb, ber_socket_t *s, LDAPURLDesc *srv, struct sockaddr *addr ); diff --git a/libraries/libldap/open.c b/libraries/libldap/open.c index bdf24d12cc..fdded08149 100644 --- a/libraries/libldap/open.c +++ b/libraries/libldap/open.c @@ -207,6 +207,14 @@ ldap_create( LDAP **ldp ) if (( ld->ld_selectinfo = ldap_new_select_info()) == NULL ) goto nomem; + ld->ld_options.ldo_local_ip_addrs.local_ip_addrs = NULL; + if( gopts->ldo_local_ip_addrs.local_ip_addrs ) { + ld->ld_options.ldo_local_ip_addrs.local_ip_addrs = + LDAP_STRDUP( gopts->ldo_local_ip_addrs.local_ip_addrs ); + if ( ld->ld_options.ldo_local_ip_addrs.local_ip_addrs == NULL ) + goto nomem; + } + ld->ld_lberoptions = LBER_USE_DER; ld->ld_sb = ber_sockbuf_alloc( ); diff --git a/libraries/libldap/options.c b/libraries/libldap/options.c index c6ec0adc66..e07a4ce60b 100644 --- a/libraries/libldap/options.c +++ b/libraries/libldap/options.c @@ -258,6 +258,17 @@ ldap_get_option( rc = LDAP_OPT_SUCCESS; break; + case LDAP_OPT_SOCKET_BIND_ADDRESSES: + if ( lo->ldo_local_ip_addrs.local_ip_addrs == NULL ) { + * (void **) outvalue = NULL; + } + else { + * (char **) outvalue = + LDAP_STRDUP( lo->ldo_local_ip_addrs.local_ip_addrs ); + } + rc = LDAP_OPT_SUCCESS; + break; + case LDAP_OPT_URI: * (char **) outvalue = ldap_url_list2urls(lo->ldo_defludp); rc = LDAP_OPT_SUCCESS; @@ -594,6 +605,43 @@ ldap_set_option( break; } + case LDAP_OPT_SOCKET_BIND_ADDRESSES: { + const char *source_ip = (const char *) invalue; + char **source_ip_lst = NULL; + + ldapsourceip temp_source_ip; + memset( &temp_source_ip, 0, sizeof( ldapsourceip ) ); + rc = LDAP_OPT_SUCCESS; + if( source_ip == NULL ) { + if ( ld->ld_options.ldo_local_ip_addrs.local_ip_addrs ) { + LDAP_FREE( ld->ld_options.ldo_local_ip_addrs.local_ip_addrs ); + memset( &ld->ld_options.ldo_local_ip_addrs, 0, + sizeof( ldapsourceip ) ); + } + } + else { + source_ip_lst = ldap_str2charray( source_ip, " " ); + + if ( source_ip_lst == NULL ) + rc = LDAP_NO_MEMORY; + + if( rc == LDAP_OPT_SUCCESS ) { + rc = ldap_validate_and_fill_sourceip ( source_ip_lst, + &temp_source_ip ); + ldap_charray_free( source_ip_lst ); + } + if ( rc == LDAP_OPT_SUCCESS ) { + if ( lo->ldo_local_ip_addrs.local_ip_addrs != NULL ) { + LDAP_FREE( lo->ldo_local_ip_addrs.local_ip_addrs ); + lo->ldo_local_ip_addrs.local_ip_addrs = NULL; + } + lo->ldo_local_ip_addrs = temp_source_ip; + lo->ldo_local_ip_addrs.local_ip_addrs = LDAP_STRDUP( source_ip ); + } + } + break; + } + case LDAP_OPT_URI: { const char *urls = (const char *) invalue; LDAPURLDesc *ludlist = NULL; diff --git a/libraries/libldap/os-ip.c b/libraries/libldap/os-ip.c index bae97c57a2..14899cc007 100644 --- a/libraries/libldap/os-ip.c +++ b/libraries/libldap/os-ip.c @@ -475,6 +475,43 @@ ldap_pvt_inet_aton( const char *host, struct in_addr *in) } #endif +int +ldap_validate_and_fill_sourceip (char** source_ip_lst, ldapsourceip* temp_source_ip ) +{ + int i = 0; + int rc = LDAP_PARAM_ERROR; + + for ( i = 0; source_ip_lst[i] != NULL; i++ ) { + Debug1( LDAP_DEBUG_TRACE, + "ldap_validate_and_fill_sourceip(%s)\n", + source_ip_lst[i] ); + + if ( !temp_source_ip->has_ipv4 ) { + if ( inet_aton( source_ip_lst[i], &temp_source_ip->ip4_addr ) ) { + temp_source_ip->has_ipv4 = 1; + rc = LDAP_OPT_SUCCESS; + continue; + } + } +#ifdef LDAP_PF_INET6 + if ( !temp_source_ip->has_ipv6 ) { + if ( inet_pton( AF_INET6, source_ip_lst[i], + & temp_source_ip->ip6_addr ) ) { + temp_source_ip->has_ipv6 = 1; + rc = LDAP_OPT_SUCCESS; + continue; + } + } +#endif + memset( temp_source_ip, 0, sizeof( * (temp_source_ip ) ) ); + Debug1( LDAP_DEBUG_TRACE, + "ldap_validate_and_fill_sourceip: validation failed for (%s)\n", + source_ip_lst[i] ); + break; + } + return rc; +} + int ldap_int_connect_cbs(LDAP *ld, Sockbuf *sb, ber_socket_t *s, LDAPURLDesc *srv, struct sockaddr *addr) { @@ -607,6 +644,7 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb, rc = -1; for( sai=res; sai != NULL; sai=sai->ai_next) { + unsigned short bind_success = 1; if( sai->ai_addr == NULL ) { Debug0(LDAP_DEBUG_TRACE, "ldap_connect_to_host: getaddrinfo " @@ -638,6 +676,24 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb, Debug2(LDAP_DEBUG_TRACE, "ldap_connect_to_host: Trying %s %s\n", addr, serv ); + if( ld->ld_options.ldo_local_ip_addrs.has_ipv6 ) { + struct sockaddr_in6 ip6addr; + char bind_addr[INET6_ADDRSTRLEN]; + ip6addr.sin6_family = AF_INET6; + ip6addr.sin6_addr = ld->ld_options.ldo_local_ip_addrs.ip6_addr; + inet_ntop( AF_INET6, + &(ip6addr.sin6_addr), + bind_addr, sizeof bind_addr ); + Debug1( LDAP_DEBUG_TRACE, + "ldap_connect_to_host: From source address %s\n", + bind_addr ); + if ( bind( s, ( struct sockaddr* ) &ip6addr, sizeof ip6addr ) != 0 ) { + Debug1( LDAP_DEBUG_TRACE, + "ldap_connect_to_host: Failed to bind source address %s\n", + bind_addr ); + bind_success = 0; + } + } } break; #endif case AF_INET: { @@ -648,17 +704,36 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb, Debug2(LDAP_DEBUG_TRACE, "ldap_connect_to_host: Trying %s:%s\n", addr, serv ); + if( ld->ld_options.ldo_local_ip_addrs.has_ipv4 ) { + struct sockaddr_in ip4addr; + char bind_addr[INET_ADDRSTRLEN]; + ip4addr.sin_family = AF_INET; + ip4addr.sin_addr = ld->ld_options.ldo_local_ip_addrs.ip4_addr; + inet_ntop( AF_INET, + &(ip4addr.sin_addr), + bind_addr, sizeof bind_addr ); + Debug1( LDAP_DEBUG_TRACE, + "ldap_connect_to_host: From source address %s\n", + bind_addr ); + if ( bind(s, ( struct sockaddr* )&ip4addr, sizeof ip4addr ) != 0 ) { + Debug1( LDAP_DEBUG_TRACE, + "ldap_connect_to_host: Failed to bind source address %s\n", + bind_addr ); + bind_success = 0; + } + } } break; } - - rc = ldap_pvt_connect( ld, s, - sai->ai_addr, sai->ai_addrlen, async ); - if ( rc == 0 || rc == -2 ) { - err = ldap_int_connect_cbs( ld, sb, &s, srv, sai->ai_addr ); - if ( err ) - rc = err; - else - break; + if ( bind_success ) { + rc = ldap_pvt_connect( ld, s, + sai->ai_addr, sai->ai_addrlen, async ); + if ( rc == 0 || rc == -2 ) { + err = ldap_int_connect_cbs( ld, sb, &s, srv, sai->ai_addr ); + if ( err ) + rc = err; + else + break; + } } ldap_pvt_close_socket(ld, s); } @@ -687,7 +762,14 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb, rc = s = -1; for ( i = 0; !use_hp || (hp->h_addr_list[i] != 0); ++i, rc = -1 ) { struct sockaddr_in sin; - + unsigned short bind_success = 1; +#ifdef HAVE_INET_NTOA_B + char address[INET_ADDR_LEN]; + char bind_addr[INET_ADDR_LEN]; +#else + char *address; + char *bind_addr; +#endif s = ldap_int_socket( ld, PF_INET, socktype ); if ( s == AC_SOCKET_INVALID ) { /* use_hp ? continue : break; */ @@ -712,30 +794,45 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb, } #ifdef HAVE_INET_NTOA_B - { - /* for VxWorks */ - char address[INET_ADDR_LEN]; - inet_ntoa_b(sin.sin_address, address); - Debug2(LDAP_DEBUG_TRACE, - "ldap_connect_to_host: Trying %s:%d\n", - address, port ); - } + /* for VxWorks */ + inet_ntoa_b( sin.sin_address, address ); #else - Debug2(LDAP_DEBUG_TRACE, - "ldap_connect_to_host: Trying %s:%d\n", - inet_ntoa(sin.sin_addr), port ); + address = inet_ntoa( sin.sin_addr ); #endif + Debug2( LDAP_DEBUG_TRACE, + "ldap_connect_to_host: Trying %s:%d\n", + address, port ); + if( ld->ld_options.ldo_local_ip_addrs.has_ipv4 ) { + struct sockaddr_in ip4addr; + ip4addr.sin_family = AF_INET; + ip4addr.sin_addr = ld->ld_options.ldo_local_ip_addrs.ip4_addr; +#ifdef HAVE_INET_NTOA_B + inet_ntoa_b( ip4addr.sin_address, bind_addr ); +#else + bind_addr = inet_ntoa( ip4addr.sin_addr ); +#endif + Debug1( LDAP_DEBUG_TRACE, + "ldap_connect_to_host: From source address %s\n", + bind_addr ); + if ( bind( s, (struct sockaddr*)&ip4addr, sizeof ip4addr ) != 0 ) { + Debug1( LDAP_DEBUG_TRACE, + "ldap_connect_to_host: Failed to bind source address %s\n", + bind_addr ); + bind_success = 0; + } + } + if ( bind_success ) { + rc = ldap_pvt_connect(ld, s, + (struct sockaddr *)&sin, sizeof(sin), + async); - rc = ldap_pvt_connect(ld, s, - (struct sockaddr *)&sin, sizeof(sin), - async); - - if ( (rc == 0) || (rc == -2) ) { - int err = ldap_int_connect_cbs( ld, sb, &s, srv, (struct sockaddr *)&sin ); - if ( err ) - rc = err; - else - break; + if ( (rc == 0) || (rc == -2) ) { + int err = ldap_int_connect_cbs( ld, sb, &s, srv, (struct sockaddr *)&sin ); + if ( err ) + rc = err; + else + break; + } } ldap_pvt_close_socket(ld, s); diff --git a/libraries/libldap/unbind.c b/libraries/libldap/unbind.c index 754ea4d7b7..51f85b8162 100644 --- a/libraries/libldap/unbind.c +++ b/libraries/libldap/unbind.c @@ -175,6 +175,12 @@ ldap_ld_free( ld->ld_options.ldo_defludp = NULL; } + if ( ld->ld_options.ldo_local_ip_addrs.local_ip_addrs ) { + LDAP_FREE( ld->ld_options.ldo_local_ip_addrs.local_ip_addrs ); + memset( & ld->ld_options.ldo_local_ip_addrs, 0, + sizeof( ldapsourceip ) ); + } + #ifdef LDAP_CONNECTIONLESS if ( ld->ld_options.ldo_peer != NULL ) { LDAP_FREE( ld->ld_options.ldo_peer ); diff --git a/tests/progs/Makefile.in b/tests/progs/Makefile.in index d947e8db44..13f1e8be2c 100644 --- a/tests/progs/Makefile.in +++ b/tests/progs/Makefile.in @@ -57,7 +57,7 @@ slapd-bind: slapd-bind.o $(OBJS) $(XLIBS) $(LTLINK) -o $@ slapd-bind.o $(OBJS) $(LIBS) ldif-filter: ldif-filter.o $(XLIBS) - $(LTLINK) -o $@ ldif-filter.o $(LIBS) + $(LTLINK) -o $@ ldif-filter.o $(OBJS) $(LIBS) slapd-mtread: slapd-mtread.o $(OBJS) $(XLIBS) $(LTLINK) -o $@ slapd-mtread.o $(OBJS) $(LIBS)