diff options
Diffstat (limited to 'libraries/libldap')
72 files changed, 42541 insertions, 0 deletions
diff --git a/libraries/libldap/Makefile.in b/libraries/libldap/Makefile.in new file mode 100644 index 0000000..58d9cc7 --- /dev/null +++ b/libraries/libldap/Makefile.in @@ -0,0 +1,84 @@ +# Makefile.in for LDAP -lldap +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2018 The OpenLDAP Foundation. +## 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 the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +LIBRARY = libldap.la + +PROGRAMS = apitest dntest ftest ltest urltest + +SRCS = bind.c open.c result.c error.c compare.c search.c \ + controls.c messages.c references.c extended.c cyrus.c \ + modify.c add.c modrdn.c delete.c abandon.c \ + sasl.c gssapi.c sbind.c unbind.c cancel.c \ + filter.c free.c sort.c passwd.c whoami.c \ + getdn.c getentry.c getattr.c getvalues.c addentry.c \ + request.c os-ip.c url.c pagectrl.c sortctrl.c vlvctrl.c \ + init.c options.c print.c string.c util-int.c schema.c \ + charray.c os-local.c dnssrv.c utf-8.c utf-8-conv.c \ + tls2.c tls_o.c tls_g.c tls_m.c \ + turn.c ppolicy.c dds.c txn.c ldap_sync.c stctrl.c \ + assertion.c deref.c ldif.c fetch.c + +OBJS = bind.lo open.lo result.lo error.lo compare.lo search.lo \ + controls.lo messages.lo references.lo extended.lo cyrus.lo \ + modify.lo add.lo modrdn.lo delete.lo abandon.lo \ + sasl.lo gssapi.lo sbind.lo unbind.lo cancel.lo \ + filter.lo free.lo sort.lo passwd.lo whoami.lo \ + getdn.lo getentry.lo getattr.lo getvalues.lo addentry.lo \ + request.lo os-ip.lo url.lo pagectrl.lo sortctrl.lo vlvctrl.lo \ + init.lo options.lo print.lo string.lo util-int.lo schema.lo \ + charray.lo os-local.lo dnssrv.lo utf-8.lo utf-8-conv.lo \ + tls2.lo tls_o.lo tls_g.lo tls_m.lo \ + turn.lo ppolicy.lo dds.lo txn.lo ldap_sync.lo stctrl.lo \ + assertion.lo deref.lo ldif.lo fetch.lo + +LDAP_INCDIR= ../../include +LDAP_LIBDIR= ../../libraries + +LIB_DEFS = -DLDAP_LIBRARY + +XLIBS = $(LIBRARY) $(LDAP_LIBLBER_LA) $(LDAP_LIBLUTIL_A) +XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS) +NT_LINK_LIBS = $(LDAP_LIBLBER_LA) $(AC_LIBS) $(SECURITY_LIBS) +UNIX_LINK_LIBS = $(LDAP_LIBLBER_LA) $(AC_LIBS) $(SECURITY_LIBS) + +apitest: $(XLIBS) apitest.o + $(LTLINK) -o $@ apitest.o $(LIBS) +dntest: $(XLIBS) dntest.o + $(LTLINK) -o $@ dntest.o $(LIBS) +ftest: $(XLIBS) ftest.o + $(LTLINK) -o $@ ftest.o $(LIBS) +ltest: $(XLIBS) test.o + $(LTLINK) -o $@ test.o $(LIBS) +urltest: $(XLIBS) urltest.o + $(LTLINK) -o $@ urltest.o $(LIBS) + +CFFILES=ldap.conf + +install-local: $(CFFILES) FORCE + -$(MKDIR) $(DESTDIR)$(libdir) + $(LTINSTALL) $(INSTALLFLAGS) -m 644 $(LIBRARY) $(DESTDIR)$(libdir) + $(LTFINISH) $(DESTDIR)$(libdir) + -$(MKDIR) $(DESTDIR)$(sysconfdir) + @for i in $(CFFILES); do \ + if test ! -f $(DESTDIR)$(sysconfdir)/$$i; then \ + echo "installing $$i in $(sysconfdir)"; \ + echo "$(INSTALL) $(INSTALLFLAGS) -m 644 $(srcdir)/$$i $(DESTDIR)$(sysconfdir)/$$i"; \ + $(INSTALL) $(INSTALLFLAGS) -m 644 $(srcdir)/$$i $(DESTDIR)$(sysconfdir)/$$i; \ + else \ + echo "PRESERVING EXISTING CONFIGURATION FILE $(sysconfdir)/$$i" ; \ + fi; \ + $(INSTALL) $(INSTALLFLAGS) -m 644 $(srcdir)/$$i $(DESTDIR)$(sysconfdir)/$$i.default; \ + done + diff --git a/libraries/libldap/abandon.c b/libraries/libldap/abandon.c new file mode 100644 index 0000000..e6b4f5a --- /dev/null +++ b/libraries/libldap/abandon.c @@ -0,0 +1,460 @@ +/* abandon.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * An abandon request looks like this: + * AbandonRequest ::= [APPLICATION 16] MessageID + * and has no response. (Source: RFC 4511) + */ +#include "lutil.h" + +static int +do_abandon( + LDAP *ld, + ber_int_t origid, + ber_int_t msgid, + LDAPControl **sctrls, + int sendabandon ); + +/* + * ldap_abandon_ext - perform an ldap extended abandon operation. + * + * Parameters: + * ld LDAP descriptor + * msgid The message id of the operation to abandon + * scntrls Server Controls + * ccntrls Client Controls + * + * ldap_abandon_ext returns a LDAP error code. + * (LDAP_SUCCESS if everything went ok) + * + * Example: + * ldap_abandon_ext( ld, msgid, scntrls, ccntrls ); + */ +int +ldap_abandon_ext( + LDAP *ld, + int msgid, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int rc; + + Debug( LDAP_DEBUG_TRACE, "ldap_abandon_ext %d\n", msgid, 0, 0 ); + + /* check client controls */ + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + + rc = ldap_int_client_controls( ld, cctrls ); + if ( rc == LDAP_SUCCESS ) { + rc = do_abandon( ld, msgid, msgid, sctrls, 1 ); + } + + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + + return rc; +} + + +/* + * ldap_abandon - perform an ldap abandon operation. Parameters: + * + * ld LDAP descriptor + * msgid The message id of the operation to abandon + * + * ldap_abandon returns 0 if everything went ok, -1 otherwise. + * + * Example: + * ldap_abandon( ld, msgid ); + */ +int +ldap_abandon( LDAP *ld, int msgid ) +{ + Debug( LDAP_DEBUG_TRACE, "ldap_abandon %d\n", msgid, 0, 0 ); + return ldap_abandon_ext( ld, msgid, NULL, NULL ) == LDAP_SUCCESS + ? 0 : -1; +} + + +int +ldap_pvt_discard( + LDAP *ld, + ber_int_t msgid ) +{ + int rc; + + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + rc = do_abandon( ld, msgid, msgid, NULL, 0 ); + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + return rc; +} + +static int +do_abandon( + LDAP *ld, + ber_int_t origid, + ber_int_t msgid, + LDAPControl **sctrls, + int sendabandon ) +{ + BerElement *ber; + int i, err; + Sockbuf *sb; + LDAPRequest *lr; + + Debug( LDAP_DEBUG_TRACE, "do_abandon origid %d, msgid %d\n", + origid, msgid, 0 ); + + /* find the request that we are abandoning */ +start_again:; + lr = ld->ld_requests; + while ( lr != NULL ) { + /* this message */ + if ( lr->lr_msgid == msgid ) { + break; + } + + /* child: abandon it */ + if ( lr->lr_origid == msgid && !lr->lr_abandoned ) { + (void)do_abandon( ld, lr->lr_origid, lr->lr_msgid, + sctrls, sendabandon ); + + /* restart, as lr may now be dangling... */ + goto start_again; + } + + lr = lr->lr_next; + } + + if ( lr != NULL ) { + if ( origid == msgid && lr->lr_parent != NULL ) { + /* don't let caller abandon child requests! */ + ld->ld_errno = LDAP_PARAM_ERROR; + return( LDAP_PARAM_ERROR ); + } + if ( lr->lr_status != LDAP_REQST_INPROGRESS ) { + /* no need to send abandon message */ + sendabandon = 0; + } + } + + /* ldap_msgdelete locks the res_mutex. Give up the req_mutex + * while we're in there. + */ + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + err = ldap_msgdelete( ld, msgid ); + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + if ( err == 0 ) { + ld->ld_errno = LDAP_SUCCESS; + return LDAP_SUCCESS; + } + + /* fetch again the request that we are abandoning */ + if ( lr != NULL ) { + for ( lr = ld->ld_requests; lr != NULL; lr = lr->lr_next ) { + /* this message */ + if ( lr->lr_msgid == msgid ) { + break; + } + } + } + + err = 0; + if ( sendabandon ) { + if ( ber_sockbuf_ctrl( ld->ld_sb, LBER_SB_OPT_GET_FD, NULL ) == -1 ) { + /* not connected */ + err = -1; + ld->ld_errno = LDAP_SERVER_DOWN; + + } else if ( ( ber = ldap_alloc_ber_with_options( ld ) ) == NULL ) { + /* BER element allocation failed */ + err = -1; + ld->ld_errno = LDAP_NO_MEMORY; + + } else { + /* + * We already have the mutex in LDAP_R_COMPILE, so + * don't try to get it again. + * LDAP_NEXT_MSGID(ld, i); + */ + + LDAP_NEXT_MSGID(ld, i); +#ifdef LDAP_CONNECTIONLESS + if ( LDAP_IS_UDP(ld) ) { + struct sockaddr_storage sa = {0}; + /* dummy, filled with ldo_peer in request.c */ + err = ber_write( ber, (char *) &sa, sizeof(sa), 0 ); + } + if ( LDAP_IS_UDP(ld) && ld->ld_options.ldo_version == + LDAP_VERSION2 ) + { + char *dn; + LDAP_MUTEX_LOCK( &ld->ld_options.ldo_mutex ); + dn = ld->ld_options.ldo_cldapdn; + if (!dn) dn = ""; + err = ber_printf( ber, "{isti", /* '}' */ + i, dn, + LDAP_REQ_ABANDON, msgid ); + LDAP_MUTEX_UNLOCK( &ld->ld_options.ldo_mutex ); + } else +#endif + { + /* create a message to send */ + err = ber_printf( ber, "{iti", /* '}' */ + i, + LDAP_REQ_ABANDON, msgid ); + } + + if ( err == -1 ) { + /* encoding error */ + ld->ld_errno = LDAP_ENCODING_ERROR; + + } else { + /* Put Server Controls */ + if ( ldap_int_put_controls( ld, sctrls, ber ) + != LDAP_SUCCESS ) + { + err = -1; + + } else { + /* close '{' */ + err = ber_printf( ber, /*{*/ "N}" ); + + if ( err == -1 ) { + /* encoding error */ + ld->ld_errno = LDAP_ENCODING_ERROR; + } + } + } + + if ( err == -1 ) { + ber_free( ber, 1 ); + + } else { + /* send the message */ + if ( lr != NULL ) { + assert( lr->lr_conn != NULL ); + sb = lr->lr_conn->lconn_sb; + } else { + sb = ld->ld_sb; + } + + if ( ber_flush2( sb, ber, LBER_FLUSH_FREE_ALWAYS ) != 0 ) { + ld->ld_errno = LDAP_SERVER_DOWN; + err = -1; + } else { + err = 0; + } + } + } + } + + if ( lr != NULL ) { + LDAPConn *lc; + int freeconn = 0; + if ( sendabandon || lr->lr_status == LDAP_REQST_WRITING ) { + freeconn = 1; + lc = lr->lr_conn; + } + if ( origid == msgid ) { + ldap_free_request( ld, lr ); + + } else { + lr->lr_abandoned = 1; + } + + if ( freeconn ) { + /* release ld_req_mutex while grabbing ld_conn_mutex to + * prevent deadlock. + */ + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + ldap_free_connection( ld, lc, 0, 1 ); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + } + } + + LDAP_MUTEX_LOCK( &ld->ld_abandon_mutex ); + + /* use bisection */ + i = 0; + if ( ld->ld_nabandoned == 0 || + ldap_int_bisect_find( ld->ld_abandoned, ld->ld_nabandoned, msgid, &i ) == 0 ) + { + ldap_int_bisect_insert( &ld->ld_abandoned, &ld->ld_nabandoned, msgid, i ); + } + + if ( err != -1 ) { + ld->ld_errno = LDAP_SUCCESS; + } + + LDAP_MUTEX_UNLOCK( &ld->ld_abandon_mutex ); + return( ld->ld_errno ); +} + +/* + * ldap_int_bisect_find + * + * args: + * v: array of length n (in) + * n: length of array v (in) + * id: value to look for (in) + * idxp: pointer to location of value/insert point + * + * return: + * 0: not found + * 1: found + * -1: error + */ +int +ldap_int_bisect_find( ber_int_t *v, ber_len_t n, ber_int_t id, int *idxp ) +{ + int begin, + end, + rc = 0; + + assert( id >= 0 ); + + begin = 0; + end = n - 1; + + if ( n <= 0 || id < v[ begin ] ) { + *idxp = 0; + + } else if ( id > v[ end ] ) { + *idxp = n; + + } else { + int pos; + ber_int_t curid; + + do { + pos = (begin + end)/2; + curid = v[ pos ]; + + if ( id < curid ) { + end = pos - 1; + + } else if ( id > curid ) { + begin = ++pos; + + } else { + /* already abandoned? */ + rc = 1; + break; + } + } while ( end >= begin ); + + *idxp = pos; + } + + return rc; +} + +/* + * ldap_int_bisect_insert + * + * args: + * vp: pointer to array of length *np (in/out) + * np: pointer to length of array *vp (in/out) + * id: value to insert (in) + * idx: location of insert point (as computed by ldap_int_bisect_find()) + * + * return: + * 0: inserted + * -1: error + */ +int +ldap_int_bisect_insert( ber_int_t **vp, ber_len_t *np, int id, int idx ) +{ + ber_int_t *v; + ber_len_t n; + int i; + + assert( vp != NULL ); + assert( np != NULL ); + assert( idx >= 0 ); + assert( (unsigned) idx <= *np ); + + n = *np; + + v = ber_memrealloc( *vp, sizeof( ber_int_t ) * ( n + 1 ) ); + if ( v == NULL ) { + return -1; + } + *vp = v; + + for ( i = n; i > idx; i-- ) { + v[ i ] = v[ i - 1 ]; + } + v[ idx ] = id; + ++(*np); + + return 0; +} + +/* + * ldap_int_bisect_delete + * + * args: + * vp: pointer to array of length *np (in/out) + * np: pointer to length of array *vp (in/out) + * id: value to delete (in) + * idx: location of value to delete (as computed by ldap_int_bisect_find()) + * + * return: + * 0: deleted + */ +int +ldap_int_bisect_delete( ber_int_t **vp, ber_len_t *np, int id, int idx ) +{ + ber_int_t *v; + ber_len_t i, n; + + assert( vp != NULL ); + assert( np != NULL ); + assert( idx >= 0 ); + assert( (unsigned) idx < *np ); + + v = *vp; + + assert( v[ idx ] == id ); + + --(*np); + n = *np; + + for ( i = idx; i < n; i++ ) { + v[ i ] = v[ i + 1 ]; + } + + return 0; +} diff --git a/libraries/libldap/add.c b/libraries/libldap/add.c new file mode 100644 index 0000000..c3c0541 --- /dev/null +++ b/libraries/libldap/add.c @@ -0,0 +1,263 @@ +/* add.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* An LDAP Add Request/Response looks like this: + * AddRequest ::= [APPLICATION 8] SEQUENCE { + * entry LDAPDN, + * attributes AttributeList } + * + * AttributeList ::= SEQUENCE OF attribute Attribute + * + * Attribute ::= PartialAttribute(WITH COMPONENTS { + * ..., + * vals (SIZE(1..MAX))}) + * + * PartialAttribute ::= SEQUENCE { + * type AttributeDescription, + * vals SET OF value AttributeValue } + * + * AttributeDescription ::= LDAPString + * -- Constrained to <attributedescription> [RFC4512] + * + * AttributeValue ::= OCTET STRING + * + * AddResponse ::= [APPLICATION 9] LDAPResult + * (Source: RFC 4511) + */ + +/* + * ldap_add - initiate an ldap add operation. Parameters: + * + * ld LDAP descriptor + * dn DN of the entry to add + * mods List of attributes for the entry. This is a null- + * terminated array of pointers to LDAPMod structures. + * only the type and values in the structures need be + * filled in. + * + * Example: + * LDAPMod *attrs[] = { + * { 0, "cn", { "babs jensen", "babs", 0 } }, + * { 0, "sn", { "jensen", 0 } }, + * { 0, "objectClass", { "person", 0 } }, + * 0 + * } + * msgid = ldap_add( ld, dn, attrs ); + */ +int +ldap_add( LDAP *ld, LDAP_CONST char *dn, LDAPMod **attrs ) +{ + int rc; + int msgid; + + rc = ldap_add_ext( ld, dn, attrs, NULL, NULL, &msgid ); + + if ( rc != LDAP_SUCCESS ) + return -1; + + return msgid; +} + + +BerElement * +ldap_build_add_req( + LDAP *ld, + const char *dn, + LDAPMod **attrs, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp ) +{ + BerElement *ber; + int i, rc; + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + LDAP_NEXT_MSGID(ld, *msgidp); + rc = ber_printf( ber, "{it{s{", /* '}}}' */ + *msgidp, LDAP_REQ_ADD, dn ); + + if ( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* allow attrs to be NULL ("touch"; should fail...) */ + if ( attrs ) { + /* for each attribute in the entry... */ + for ( i = 0; attrs[i] != NULL; i++ ) { + if ( ( attrs[i]->mod_op & LDAP_MOD_BVALUES) != 0 ) { + int j; + + if ( attrs[i]->mod_bvalues == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + for ( j = 0; attrs[i]->mod_bvalues[ j ] != NULL; j++ ) { + if ( attrs[i]->mod_bvalues[ j ]->bv_val == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + } + + rc = ber_printf( ber, "{s[V]N}", attrs[i]->mod_type, + attrs[i]->mod_bvalues ); + + } else { + if ( attrs[i]->mod_values == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + rc = ber_printf( ber, "{s[v]N}", attrs[i]->mod_type, + attrs[i]->mod_values ); + } + if ( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + } + } + + if ( ber_printf( ber, /*{{*/ "N}N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +/* + * ldap_add_ext - initiate an ldap extended add operation. Parameters: + * + * ld LDAP descriptor + * dn DN of the entry to add + * mods List of attributes for the entry. This is a null- + * terminated array of pointers to LDAPMod structures. + * only the type and values in the structures need be + * filled in. + * sctrl Server Controls + * cctrl Client Controls + * msgidp Message ID pointer + * + * Example: + * LDAPMod *attrs[] = { + * { 0, "cn", { "babs jensen", "babs", 0 } }, + * { 0, "sn", { "jensen", 0 } }, + * { 0, "objectClass", { "person", 0 } }, + * 0 + * } + * rc = ldap_add_ext( ld, dn, attrs, NULL, NULL, &msgid ); + */ +int +ldap_add_ext( + LDAP *ld, + LDAP_CONST char *dn, + LDAPMod **attrs, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *ber; + int rc; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_add_ext\n", 0, 0, 0 ); + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( dn != NULL ); + assert( msgidp != NULL ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + ber = ldap_build_add_req( ld, dn, attrs, sctrls, cctrls, &id ); + if( !ber ) + return ld->ld_errno; + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_ADD, dn, ber, id ); + + if(*msgidp < 0) + return ld->ld_errno; + + return LDAP_SUCCESS; +} + +int +ldap_add_ext_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAPMod **attrs, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int msgid, rc; + LDAPMessage *res; + + rc = ldap_add_ext( ld, dn, attrs, sctrls, cctrls, &msgid ); + + if ( rc != LDAP_SUCCESS ) + return( rc ); + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, &res ) == -1 || !res ) + return( ld->ld_errno ); + + return( ldap_result2error( ld, res, 1 ) ); +} + +int +ldap_add_s( LDAP *ld, LDAP_CONST char *dn, LDAPMod **attrs ) +{ + return ldap_add_ext_s( ld, dn, attrs, NULL, NULL ); +} + diff --git a/libraries/libldap/addentry.c b/libraries/libldap/addentry.c new file mode 100644 index 0000000..37acb74 --- /dev/null +++ b/libraries/libldap/addentry.c @@ -0,0 +1,72 @@ +/* addentry.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +LDAPMessage * +ldap_delete_result_entry( LDAPMessage **list, LDAPMessage *e ) +{ + LDAPMessage *tmp, *prev = NULL; + + assert( list != NULL ); + assert( e != NULL ); + + for ( tmp = *list; tmp != NULL && tmp != e; tmp = tmp->lm_chain ) + prev = tmp; + + if ( tmp == NULL ) + return( NULL ); + + if ( prev == NULL ) { + if ( tmp->lm_chain ) + tmp->lm_chain->lm_chain_tail = (*list)->lm_chain_tail; + *list = tmp->lm_chain; + } else { + prev->lm_chain = tmp->lm_chain; + if ( prev->lm_chain == NULL ) + (*list)->lm_chain_tail = prev; + } + tmp->lm_chain = NULL; + + return( tmp ); +} + +void +ldap_add_result_entry( LDAPMessage **list, LDAPMessage *e ) +{ + assert( list != NULL ); + assert( e != NULL ); + + e->lm_chain = *list; + if ( *list ) + e->lm_chain_tail = (*list)->lm_chain_tail; + else + e->lm_chain_tail = e; + *list = e; +} diff --git a/libraries/libldap/apitest.c b/libraries/libldap/apitest.c new file mode 100644 index 0000000..ceef0b8 --- /dev/null +++ b/libraries/libldap/apitest.c @@ -0,0 +1,241 @@ +/* apitest.c -- OpenLDAP API Test Program */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This program was orignally developed by Kurt D. Zeilenga for inclusion in + * OpenLDAP Software. + */ +#include "portable.h" + +#include <ac/stdlib.h> + +#include <stdio.h> + +#include <ldap.h> + +int +main(int argc, char **argv) +{ + LDAPAPIInfo api; + int ival; + char *sval; + + printf("Compile time API Information\n"); + +#ifdef LDAP_API_INFO_VERSION + api.ldapai_info_version = LDAP_API_INFO_VERSION; + printf(" API Info version: %d\n", (int) api.ldapai_info_version); +#else + api.ldapai_info_version = 1; + printf(" API Info version: unknown\n"); +#endif + +#ifdef LDAP_FEATURE_INFO_VERSION + printf(" Feature Info version: %d\n", (int) LDAP_FEATURE_INFO_VERSION); +#else + printf(" Feature Info version: unknown\n"); + api.ldapai_info_version = 1; +#endif + +#ifdef LDAP_API_VERSION + printf(" API version: %d\n", (int) LDAP_API_VERSION); +#else + printf(" API version: unknown\n"); +#endif + +#ifdef LDAP_VERSION + printf(" Protocol Version: %d\n", (int) LDAP_VERSION); +#else + printf(" Protocol Version: unknown\n"); +#endif +#ifdef LDAP_VERSION_MIN + printf(" Protocol Min: %d\n", (int) LDAP_VERSION_MIN); +#else + printf(" Protocol Min: unknown\n"); +#endif +#ifdef LDAP_VERSION_MAX + printf(" Protocol Max: %d\n", (int) LDAP_VERSION_MAX); +#else + printf(" Protocol Max: unknown\n"); +#endif +#ifdef LDAP_VENDOR_NAME + printf(" Vendor Name: %s\n", LDAP_VENDOR_NAME); +#else + printf(" Vendor Name: unknown\n"); +#endif +#ifdef LDAP_VENDOR_VERSION + printf(" Vendor Version: %d\n", (int) LDAP_VENDOR_VERSION); +#else + printf(" Vendor Version: unknown\n"); +#endif + + if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(API_INFO) failed\n", argv[0]); + return EXIT_FAILURE; + } + + printf("\nExecution time API Information\n"); + printf(" API Info version: %d\n", api.ldapai_info_version); + + if (api.ldapai_info_version != LDAP_API_INFO_VERSION) { + printf(" API INFO version mismatch: got %d, expected %d\n", + api.ldapai_info_version, LDAP_API_INFO_VERSION); + return EXIT_FAILURE; + } + + printf(" API Version: %d\n", api.ldapai_api_version); + printf(" Protocol Max: %d\n", api.ldapai_protocol_version); + + if(api.ldapai_extensions == NULL) { + printf(" Extensions: none\n"); + + } else { + int i; + for(i=0; api.ldapai_extensions[i] != NULL; i++) /* empty */; + printf(" Extensions: %d\n", i); + for(i=0; api.ldapai_extensions[i] != NULL; i++) { +#ifdef LDAP_OPT_API_FEATURE_INFO + LDAPAPIFeatureInfo fi; + fi.ldapaif_info_version = LDAP_FEATURE_INFO_VERSION; + fi.ldapaif_name = api.ldapai_extensions[i]; + fi.ldapaif_version = 0; + + if( ldap_get_option(NULL, LDAP_OPT_API_FEATURE_INFO, &fi) == LDAP_SUCCESS ) { + if(fi.ldapaif_info_version != LDAP_FEATURE_INFO_VERSION) { + printf(" %s feature info mismatch: got %d, expected %d\n", + api.ldapai_extensions[i], + LDAP_FEATURE_INFO_VERSION, + fi.ldapaif_info_version); + + } else { + printf(" %s: version %d\n", + fi.ldapaif_name, + fi.ldapaif_version); + } + + } else { + printf(" %s (NO FEATURE INFO)\n", + api.ldapai_extensions[i]); + } + +#else + printf(" %s\n", + api.ldapai_extensions[i]); +#endif + + ldap_memfree(api.ldapai_extensions[i]); + } + ldap_memfree(api.ldapai_extensions); + } + + printf(" Vendor Name: %s\n", api.ldapai_vendor_name); + ldap_memfree(api.ldapai_vendor_name); + + printf(" Vendor Version: %d\n", api.ldapai_vendor_version); + + printf("\nExecution time Default Options\n"); + + if(ldap_get_option(NULL, LDAP_OPT_DEREF, &ival) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(api) failed\n", argv[0]); + return EXIT_FAILURE; + } + printf(" DEREF: %d\n", ival); + + if(ldap_get_option(NULL, LDAP_OPT_SIZELIMIT, &ival) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(sizelimit) failed\n", argv[0]); + return EXIT_FAILURE; + } + printf(" SIZELIMIT: %d\n", ival); + + if(ldap_get_option(NULL, LDAP_OPT_TIMELIMIT, &ival) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(timelimit) failed\n", argv[0]); + return EXIT_FAILURE; + } + printf(" TIMELIMIT: %d\n", ival); + + if(ldap_get_option(NULL, LDAP_OPT_REFERRALS, &ival) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(referrals) failed\n", argv[0]); + return EXIT_FAILURE; + } + printf(" REFERRALS: %s\n", ival ? "on" : "off"); + + if(ldap_get_option(NULL, LDAP_OPT_RESTART, &ival) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(restart) failed\n", argv[0]); + return EXIT_FAILURE; + } + printf(" RESTART: %s\n", ival ? "on" : "off"); + + if(ldap_get_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &ival) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(protocol version) failed\n", argv[0]); + return EXIT_FAILURE; + } + printf(" PROTOCOL VERSION: %d\n", ival); + + if(ldap_get_option(NULL, LDAP_OPT_HOST_NAME, &sval) != LDAP_SUCCESS) { + fprintf(stderr, "%s: ldap_get_option(host name) failed\n", argv[0]); + return EXIT_FAILURE; + } + if( sval != NULL ) { + printf(" HOST NAME: %s\n", sval); + ldap_memfree(sval); + } else { + puts(" HOST NAME: <not set>"); + } + +#if 0 + /* API tests */ + { /* bindless unbind */ + LDAP *ld; + int rc; + + ld = ldap_init( "localhost", 389 ); + if( ld == NULL ) { + perror("ldap_init"); + return EXIT_FAILURE; + } + + rc = ldap_unbind( ld ); + if( rc != LDAP_SUCCESS ) { + perror("ldap_unbind"); + return EXIT_FAILURE; + } + } + { /* bindless unbind */ + LDAP *ld; + int rc; + + ld = ldap_init( "localhost", 389 ); + if( ld == NULL ) { + perror("ldap_init"); + return EXIT_FAILURE; + } + + rc = ldap_abandon_ext( ld, 0, NULL, NULL ); + if( rc != LDAP_SERVER_DOWN ) { + ldap_perror( ld, "ldap_abandon"); + return EXIT_FAILURE; + } + + rc = ldap_unbind( ld ); + if( rc != LDAP_SUCCESS ) { + perror("ldap_unbind"); + return EXIT_FAILURE; + } + } +#endif + + return EXIT_SUCCESS; +} diff --git a/libraries/libldap/assertion.c b/libraries/libldap/assertion.c new file mode 100644 index 0000000..2871469 --- /dev/null +++ b/libraries/libldap/assertion.c @@ -0,0 +1,98 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +int +ldap_create_assertion_control_value( + LDAP *ld, + char *assertion, + struct berval *value ) +{ + BerElement *ber = NULL; + int err; + + if ( assertion == NULL || assertion[ 0 ] == '\0' ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + if ( value == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + BER_BVZERO( value ); + + ber = ldap_alloc_ber_with_options( ld ); + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + err = ldap_pvt_put_filter( ber, assertion ); + if ( err < 0 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + + err = ber_flatten2( ber, value, 1 ); + if ( err < 0 ) { + ld->ld_errno = LDAP_NO_MEMORY; + goto done; + } + +done:; + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + return ld->ld_errno; +} + +int +ldap_create_assertion_control( + LDAP *ld, + char *assertion, + int iscritical, + LDAPControl **ctrlp ) +{ + struct berval value; + + if ( ctrlp == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + ld->ld_errno = ldap_create_assertion_control_value( ld, + assertion, &value ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = ldap_control_create( LDAP_CONTROL_ASSERT, + iscritical, &value, 0, ctrlp ); + if ( ld->ld_errno != LDAP_SUCCESS ) { + LDAP_FREE( value.bv_val ); + } + } + + return ld->ld_errno; +} + diff --git a/libraries/libldap/bind.c b/libraries/libldap/bind.c new file mode 100644 index 0000000..6ecef9d --- /dev/null +++ b/libraries/libldap/bind.c @@ -0,0 +1,127 @@ +/* bind.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +/* + * BindRequest ::= SEQUENCE { + * version INTEGER, + * name DistinguishedName, -- who + * authentication CHOICE { + * simple [0] OCTET STRING -- passwd + * krbv42ldap [1] OCTET STRING -- OBSOLETE + * krbv42dsa [2] OCTET STRING -- OBSOLETE + * sasl [3] SaslCredentials -- LDAPv3 + * } + * } + * + * BindResponse ::= SEQUENCE { + * COMPONENTS OF LDAPResult, + * serverSaslCreds OCTET STRING OPTIONAL -- LDAPv3 + * } + * + * (Source: RFC 2251) + */ + +/* + * ldap_bind - bind to the ldap server (and X.500). The dn and password + * of the entry to which to bind are supplied, along with the authentication + * method to use. The msgid of the bind request is returned on success, + * -1 if there's trouble. ldap_result() should be called to find out the + * outcome of the bind request. + * + * Example: + * ldap_bind( ld, "cn=manager, o=university of michigan, c=us", "secret", + * LDAP_AUTH_SIMPLE ) + */ + +int +ldap_bind( LDAP *ld, LDAP_CONST char *dn, LDAP_CONST char *passwd, int authmethod ) +{ + Debug( LDAP_DEBUG_TRACE, "ldap_bind\n", 0, 0, 0 ); + + switch ( authmethod ) { + case LDAP_AUTH_SIMPLE: + return( ldap_simple_bind( ld, dn, passwd ) ); + +#ifdef HAVE_GSSAPI + case LDAP_AUTH_NEGOTIATE: + return( ldap_gssapi_bind_s( ld, dn, passwd) ); +#endif + + case LDAP_AUTH_SASL: + /* user must use ldap_sasl_bind */ + /* FALL-THRU */ + + default: + ld->ld_errno = LDAP_AUTH_UNKNOWN; + return( -1 ); + } +} + +/* + * ldap_bind_s - bind to the ldap server (and X.500). The dn and password + * of the entry to which to bind are supplied, along with the authentication + * method to use. This routine just calls whichever bind routine is + * appropriate and returns the result of the bind (e.g. LDAP_SUCCESS or + * some other error indication). + * + * Examples: + * ldap_bind_s( ld, "cn=manager, o=university of michigan, c=us", + * "secret", LDAP_AUTH_SIMPLE ) + * ldap_bind_s( ld, "cn=manager, o=university of michigan, c=us", + * NULL, LDAP_AUTH_KRBV4 ) + */ +int +ldap_bind_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *passwd, + int authmethod ) +{ + Debug( LDAP_DEBUG_TRACE, "ldap_bind_s\n", 0, 0, 0 ); + + switch ( authmethod ) { + case LDAP_AUTH_SIMPLE: + return( ldap_simple_bind_s( ld, dn, passwd ) ); + +#ifdef HAVE_GSSAPI + case LDAP_AUTH_NEGOTIATE: + return( ldap_gssapi_bind_s( ld, dn, passwd) ); +#endif + + case LDAP_AUTH_SASL: + /* user must use ldap_sasl_bind */ + /* FALL-THRU */ + + default: + return( ld->ld_errno = LDAP_AUTH_UNKNOWN ); + } +} diff --git a/libraries/libldap/cancel.c b/libraries/libldap/cancel.c new file mode 100644 index 0000000..25de783 --- /dev/null +++ b/libraries/libldap/cancel.c @@ -0,0 +1,76 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This program was originally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +/* + * LDAPv3 Cancel Operation Request + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +int +ldap_cancel( + LDAP *ld, + int cancelid, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *cancelidber = NULL; + struct berval cancelidvalp = { 0, NULL }; + int rc; + + cancelidber = ber_alloc_t( LBER_USE_DER ); + ber_printf( cancelidber, "{i}", cancelid ); + ber_flatten2( cancelidber, &cancelidvalp, 0 ); + rc = ldap_extended_operation( ld, LDAP_EXOP_CANCEL, + &cancelidvalp, sctrls, cctrls, msgidp ); + ber_free( cancelidber, 1 ); + return rc; +} + +int +ldap_cancel_s( + LDAP *ld, + int cancelid, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + BerElement *cancelidber = NULL; + struct berval cancelidvalp = { 0, NULL }; + int rc; + + cancelidber = ber_alloc_t( LBER_USE_DER ); + ber_printf( cancelidber, "{i}", cancelid ); + ber_flatten2( cancelidber, &cancelidvalp, 0 ); + rc = ldap_extended_operation_s( ld, LDAP_EXOP_CANCEL, + &cancelidvalp, sctrls, cctrls, NULL, NULL ); + ber_free( cancelidber, 1 ); + return rc; +} + diff --git a/libraries/libldap/charray.c b/libraries/libldap/charray.c new file mode 100644 index 0000000..70c905b --- /dev/null +++ b/libraries/libldap/charray.c @@ -0,0 +1,275 @@ +/* charray.c - routines for dealing with char * arrays */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "ldap-int.h" + +int +ldap_charray_add( + char ***a, + const char *s +) +{ + int n; + + if ( *a == NULL ) { + *a = (char **) LDAP_MALLOC( 2 * sizeof(char *) ); + n = 0; + + if( *a == NULL ) { + return -1; + } + + } else { + char **new; + + for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { + ; /* NULL */ + } + + new = (char **) LDAP_REALLOC( (char *) *a, + (n + 2) * sizeof(char *) ); + + if( new == NULL ) { + /* caller is required to call ldap_charray_free(*a) */ + return -1; + } + + *a = new; + } + + (*a)[n] = LDAP_STRDUP(s); + + if( (*a)[n] == NULL ) { + return 1; + } + + (*a)[++n] = NULL; + + return 0; +} + +int +ldap_charray_merge( + char ***a, + char **s +) +{ + int i, n, nn; + char **aa; + + for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { + ; /* NULL */ + } + for ( nn = 0; s[nn] != NULL; nn++ ) { + ; /* NULL */ + } + + aa = (char **) LDAP_REALLOC( (char *) *a, (n + nn + 1) * sizeof(char *) ); + + if( aa == NULL ) { + return -1; + } + + *a = aa; + + for ( i = 0; i < nn; i++ ) { + (*a)[n + i] = LDAP_STRDUP(s[i]); + + if( (*a)[n + i] == NULL ) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( (*a)[n + i] ); + (*a)[n + i] = NULL; + } + return -1; + } + } + + (*a)[n + nn] = NULL; + return 0; +} + +void +ldap_charray_free( char **a ) +{ + char **p; + + if ( a == NULL ) { + return; + } + + for ( p = a; *p != NULL; p++ ) { + if ( *p != NULL ) { + LDAP_FREE( *p ); + } + } + + LDAP_FREE( (char *) a ); +} + +int +ldap_charray_inlist( + char **a, + const char *s +) +{ + int i; + + if( a == NULL ) return 0; + + for ( i=0; a[i] != NULL; i++ ) { + if ( strcasecmp( s, a[i] ) == 0 ) { + return 1; + } + } + + return 0; +} + +char ** +ldap_charray_dup( char **a ) +{ + int i; + char **new; + + for ( i = 0; a[i] != NULL; i++ ) + ; /* NULL */ + + new = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); + + if( new == NULL ) { + return NULL; + } + + for ( i = 0; a[i] != NULL; i++ ) { + new[i] = LDAP_STRDUP( a[i] ); + + if( new[i] == NULL ) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( new[i] ); + } + LDAP_FREE( new ); + return NULL; + } + } + new[i] = NULL; + + return( new ); +} + +char ** +ldap_str2charray( const char *str_in, const char *brkstr ) +{ + char **res; + char *str, *s; + char *lasts; + int i; + + /* protect the input string from strtok */ + str = LDAP_STRDUP( str_in ); + if( str == NULL ) { + return NULL; + } + + i = 1; + for ( s = str; ; LDAP_UTF8_INCR(s) ) { + s = ldap_utf8_strpbrk( s, brkstr ); + if ( !s ) break; + i++; + } + + res = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); + + if( res == NULL ) { + LDAP_FREE( str ); + return NULL; + } + + i = 0; + + for ( s = ldap_utf8_strtok( str, brkstr, &lasts ); + s != NULL; + s = ldap_utf8_strtok( NULL, brkstr, &lasts ) ) + { + res[i] = LDAP_STRDUP( s ); + + if(res[i] == NULL) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( res[i] ); + } + LDAP_FREE( res ); + LDAP_FREE( str ); + return NULL; + } + + i++; + } + + res[i] = NULL; + + LDAP_FREE( str ); + return( res ); +} + +char * ldap_charray2str( char **a, const char *sep ) +{ + char *s, **v, *p; + int len; + int slen; + + if( sep == NULL ) sep = " "; + + slen = strlen( sep ); + len = 0; + + for ( v = a; *v != NULL; v++ ) { + len += strlen( *v ) + slen; + } + + if ( len == 0 ) { + return NULL; + } + + /* trim extra sep len */ + len -= slen; + + s = LDAP_MALLOC ( len + 1 ); + + if ( s == NULL ) { + return NULL; + } + + p = s; + for ( v = a; *v != NULL; v++ ) { + if ( v != a ) { + strncpy( p, sep, slen ); + p += slen; + } + + len = strlen( *v ); + strncpy( p, *v, len ); + p += len; + } + + *p = '\0'; + return s; +} diff --git a/libraries/libldap/compare.c b/libraries/libldap/compare.c new file mode 100644 index 0000000..2ce7a78 --- /dev/null +++ b/libraries/libldap/compare.c @@ -0,0 +1,197 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +/* The compare request looks like this: + * CompareRequest ::= SEQUENCE { + * entry DistinguishedName, + * ava SEQUENCE { + * type AttributeType, + * value AttributeValue + * } + * } + */ + +BerElement * +ldap_build_compare_req( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *attr, + struct berval *bvalue, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *ber; + int rc; + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + LDAP_NEXT_MSGID(ld, *msgidp); + rc = ber_printf( ber, "{it{s{sON}N}", /* '}' */ + *msgidp, + LDAP_REQ_COMPARE, dn, attr, bvalue ); + if ( rc == -1 ) + { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + if( ber_printf( ber, /*{*/ "N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +/* + * ldap_compare_ext - perform an ldap extended compare operation. The dn + * of the entry to compare to and the attribute and value to compare (in + * attr and value) are supplied. The msgid of the response is returned. + * + * Example: + * struct berval bvalue = { "secret", sizeof("secret")-1 }; + * rc = ldap_compare( ld, "c=us@cn=bob", + * "userPassword", &bvalue, + * sctrl, cctrl, &msgid ) + */ +int +ldap_compare_ext( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *attr, + struct berval *bvalue, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + int rc; + BerElement *ber; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_compare\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( dn != NULL ); + assert( attr != NULL ); + assert( msgidp != NULL ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + ber = ldap_build_compare_req( + ld, dn, attr, bvalue, sctrls, cctrls, &id ); + if( !ber ) + return ld->ld_errno; + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_COMPARE, dn, ber, id ); + return ( *msgidp < 0 ? ld->ld_errno : LDAP_SUCCESS ); +} + +/* + * ldap_compare_ext - perform an ldap extended compare operation. The dn + * of the entry to compare to and the attribute and value to compare (in + * attr and value) are supplied. The msgid of the response is returned. + * + * Example: + * msgid = ldap_compare( ld, "c=us@cn=bob", "userPassword", "secret" ) + */ +int +ldap_compare( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *attr, + LDAP_CONST char *value ) +{ + int msgid; + struct berval bvalue; + + assert( value != NULL ); + + bvalue.bv_val = (char *) value; + bvalue.bv_len = (value == NULL) ? 0 : strlen( value ); + + return ldap_compare_ext( ld, dn, attr, &bvalue, NULL, NULL, &msgid ) == LDAP_SUCCESS + ? msgid : -1; +} + +int +ldap_compare_ext_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *attr, + struct berval *bvalue, + LDAPControl **sctrl, + LDAPControl **cctrl ) +{ + int rc; + int msgid; + LDAPMessage *res; + + rc = ldap_compare_ext( ld, dn, attr, bvalue, sctrl, cctrl, &msgid ); + + if ( rc != LDAP_SUCCESS ) + return( rc ); + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, &res ) == -1 || !res ) + return( ld->ld_errno ); + + return( ldap_result2error( ld, res, 1 ) ); +} + +int +ldap_compare_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *attr, + LDAP_CONST char *value ) +{ + struct berval bvalue; + + assert( value != NULL ); + + bvalue.bv_val = (char *) value; + bvalue.bv_len = (value == NULL) ? 0 : strlen( value ); + + return ldap_compare_ext_s( ld, dn, attr, &bvalue, NULL, NULL ); +} diff --git a/libraries/libldap/controls.c b/libraries/libldap/controls.c new file mode 100644 index 0000000..d5723f3 --- /dev/null +++ b/libraries/libldap/controls.c @@ -0,0 +1,552 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* This notice applies to changes, created by or for Novell, Inc., + * to preexisting works for which notices appear elsewhere in this file. + * + * Copyright (C) 1999, 2000 Novell, Inc. All Rights Reserved. + * + * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES. + * USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO VERSION + * 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS AVAILABLE AT + * HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" IN THE + * TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION OF THIS + * WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP PUBLIC + * LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT THE + * PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. + *--- + * Note: A verbatim copy of version 2.0.1 of the OpenLDAP Public License + * can be found in the file "build/LICENSE-2.0.1" in this distribution + * of OpenLDAP Software. + */ + +#include "portable.h" + +#include <ac/stdlib.h> + +#include <ac/time.h> +#include <ac/string.h> + +#include "ldap-int.h" + +/* LDAPv3 Controls (RFC 4511) + * + * Controls ::= SEQUENCE OF control Control + * + * Control ::= SEQUENCE { + * controlType LDAPOID, + * criticality BOOLEAN DEFAULT FALSE, + * controlValue OCTET STRING OPTIONAL + * } + */ + +int +ldap_pvt_put_control( + const LDAPControl *c, + BerElement *ber ) +{ + if ( ber_printf( ber, "{s" /*}*/, c->ldctl_oid ) == -1 ) { + return LDAP_ENCODING_ERROR; + } + + if ( c->ldctl_iscritical /* only if true */ + && ( ber_printf( ber, "b", + (ber_int_t) c->ldctl_iscritical ) == -1 ) ) + { + return LDAP_ENCODING_ERROR; + } + + if ( !BER_BVISNULL( &c->ldctl_value ) /* only if we have a value */ + && ( ber_printf( ber, "O", &c->ldctl_value ) == -1 ) ) + { + return LDAP_ENCODING_ERROR; + } + + if ( ber_printf( ber, /*{*/"N}" ) == -1 ) { + return LDAP_ENCODING_ERROR; + } + + return LDAP_SUCCESS; +} + + +/* + * ldap_int_put_controls + */ + +int +ldap_int_put_controls( + LDAP *ld, + LDAPControl *const *ctrls, + BerElement *ber ) +{ + LDAPControl *const *c; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( ber != NULL ); + + if( ctrls == NULL ) { + /* use default server controls */ + ctrls = ld->ld_sctrls; + } + + if( ctrls == NULL || *ctrls == NULL ) { + return LDAP_SUCCESS; + } + + if ( ld->ld_version < LDAP_VERSION3 ) { + /* LDAPv2 doesn't support controls, + * error if any control is critical + */ + for( c = ctrls ; *c != NULL; c++ ) { + if( (*c)->ldctl_iscritical ) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return ld->ld_errno; + } + } + + return LDAP_SUCCESS; + } + + /* Controls are encoded as a sequence of sequences */ + if( ber_printf( ber, "t{"/*}*/, LDAP_TAG_CONTROLS ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + return ld->ld_errno; + } + + for( c = ctrls ; *c != NULL; c++ ) { + ld->ld_errno = ldap_pvt_put_control( *c, ber ); + if ( ld->ld_errno != LDAP_SUCCESS ) { + return ld->ld_errno; + } + } + + + if( ber_printf( ber, /*{*/ "}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + return ld->ld_errno; + } + + return LDAP_SUCCESS; +} + +int ldap_pvt_get_controls( + BerElement *ber, + LDAPControl ***ctrls ) +{ + int nctrls; + ber_tag_t tag; + ber_len_t len; + char *opaque; + + assert( ber != NULL ); + + if( ctrls == NULL ) { + return LDAP_SUCCESS; + } + *ctrls = NULL; + + len = ber_pvt_ber_remaining( ber ); + + if( len == 0) { + /* no controls */ + return LDAP_SUCCESS; + } + + if(( tag = ber_peek_tag( ber, &len )) != LDAP_TAG_CONTROLS ) { + if( tag == LBER_ERROR ) { + /* decoding error */ + return LDAP_DECODING_ERROR; + } + + /* ignore unexpected input */ + return LDAP_SUCCESS; + } + + /* set through each element */ + nctrls = 0; + *ctrls = LDAP_MALLOC( 1 * sizeof(LDAPControl *) ); + + if( *ctrls == NULL ) { + return LDAP_NO_MEMORY; + } + + *ctrls[nctrls] = NULL; + + for( tag = ber_first_element( ber, &len, &opaque ); + tag != LBER_ERROR; + tag = ber_next_element( ber, &len, opaque ) ) + { + LDAPControl *tctrl; + LDAPControl **tctrls; + + tctrl = LDAP_CALLOC( 1, sizeof(LDAPControl) ); + + /* allocate pointer space for current controls (nctrls) + * + this control + extra NULL + */ + tctrls = (tctrl == NULL) ? NULL : + LDAP_REALLOC(*ctrls, (nctrls+2) * sizeof(LDAPControl *)); + + if( tctrls == NULL ) { + /* one of the above allocation failed */ + + if( tctrl != NULL ) { + LDAP_FREE( tctrl ); + } + + ldap_controls_free(*ctrls); + *ctrls = NULL; + + return LDAP_NO_MEMORY; + } + + + tctrls[nctrls++] = tctrl; + tctrls[nctrls] = NULL; + + tag = ber_scanf( ber, "{a" /*}*/, &tctrl->ldctl_oid ); + + if( tag == LBER_ERROR ) { + *ctrls = NULL; + ldap_controls_free( tctrls ); + return LDAP_DECODING_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + + if( tag == LBER_BOOLEAN ) { + ber_int_t crit; + tag = ber_scanf( ber, "b", &crit ); + tctrl->ldctl_iscritical = crit ? (char) 0 : (char) ~0; + tag = ber_peek_tag( ber, &len ); + } + + if( tag == LBER_OCTETSTRING ) { + tag = ber_scanf( ber, "o", &tctrl->ldctl_value ); + } else { + BER_BVZERO( &tctrl->ldctl_value ); + } + + *ctrls = tctrls; + } + + return LDAP_SUCCESS; +} + +/* + * Free a LDAPControl + */ +void +ldap_control_free( LDAPControl *c ) +{ + LDAP_MEMORY_DEBUG_ASSERT( c != NULL ); + + if ( c != NULL ) { + if( c->ldctl_oid != NULL) { + LDAP_FREE( c->ldctl_oid ); + } + + if( c->ldctl_value.bv_val != NULL ) { + LDAP_FREE( c->ldctl_value.bv_val ); + } + + LDAP_FREE( c ); + } +} + +/* + * Free an array of LDAPControl's + */ +void +ldap_controls_free( LDAPControl **controls ) +{ + LDAP_MEMORY_DEBUG_ASSERT( controls != NULL ); + + if ( controls != NULL ) { + int i; + + for( i=0; controls[i] != NULL; i++) { + ldap_control_free( controls[i] ); + } + + LDAP_FREE( controls ); + } +} + +/* + * Duplicate an array of LDAPControl + */ +LDAPControl ** +ldap_controls_dup( LDAPControl *const *controls ) +{ + LDAPControl **new; + int i; + + if ( controls == NULL ) { + return NULL; + } + + /* count the controls */ + for(i=0; controls[i] != NULL; i++) /* empty */ ; + + if( i < 1 ) { + /* no controls to duplicate */ + return NULL; + } + + new = (LDAPControl **) LDAP_MALLOC( (i+1) * sizeof(LDAPControl *) ); + + if( new == NULL ) { + /* memory allocation failure */ + return NULL; + } + + /* duplicate the controls */ + for(i=0; controls[i] != NULL; i++) { + new[i] = ldap_control_dup( controls[i] ); + + if( new[i] == NULL ) { + ldap_controls_free( new ); + return NULL; + } + } + + new[i] = NULL; + + return new; +} + +/* + * Duplicate a LDAPControl + */ +LDAPControl * +ldap_control_dup( const LDAPControl *c ) +{ + LDAPControl *new; + + if ( c == NULL || c->ldctl_oid == NULL ) { + return NULL; + } + + new = (LDAPControl *) LDAP_MALLOC( sizeof(LDAPControl) ); + + if( new == NULL ) { + return NULL; + } + + new->ldctl_oid = LDAP_STRDUP( c->ldctl_oid ); + + if(new->ldctl_oid == NULL) { + LDAP_FREE( new ); + return NULL; + } + + if( c->ldctl_value.bv_val != NULL ) { + new->ldctl_value.bv_val = + (char *) LDAP_MALLOC( c->ldctl_value.bv_len + 1 ); + + if(new->ldctl_value.bv_val == NULL) { + if(new->ldctl_oid != NULL) { + LDAP_FREE( new->ldctl_oid ); + } + LDAP_FREE( new ); + return NULL; + } + + new->ldctl_value.bv_len = c->ldctl_value.bv_len; + + AC_MEMCPY( new->ldctl_value.bv_val, c->ldctl_value.bv_val, + c->ldctl_value.bv_len ); + + new->ldctl_value.bv_val[new->ldctl_value.bv_len] = '\0'; + + } else { + new->ldctl_value.bv_len = 0; + new->ldctl_value.bv_val = NULL; + } + + new->ldctl_iscritical = c->ldctl_iscritical; + return new; +} + +/* + * Find a LDAPControl - deprecated + */ +LDAPControl * +ldap_find_control( + LDAP_CONST char *oid, + LDAPControl **ctrls ) +{ + if( ctrls == NULL || *ctrls == NULL ) { + return NULL; + } + + for( ; *ctrls != NULL; ctrls++ ) { + if( strcmp( (*ctrls)->ldctl_oid, oid ) == 0 ) { + return *ctrls; + } + } + + return NULL; +} + +/* + * Find a LDAPControl + */ +LDAPControl * +ldap_control_find( + LDAP_CONST char *oid, + LDAPControl **ctrls, + LDAPControl ***nextctrlp ) +{ + if ( oid == NULL || ctrls == NULL || *ctrls == NULL ) { + return NULL; + } + + for( ; *ctrls != NULL; ctrls++ ) { + if( strcmp( (*ctrls)->ldctl_oid, oid ) == 0 ) { + if ( nextctrlp != NULL ) { + *nextctrlp = ctrls + 1; + } + + return *ctrls; + } + } + + if ( nextctrlp != NULL ) { + *nextctrlp = NULL; + } + + return NULL; +} + +/* + * Create a LDAPControl, optionally from ber - deprecated + */ +int +ldap_create_control( + LDAP_CONST char *requestOID, + BerElement *ber, + int iscritical, + LDAPControl **ctrlp ) +{ + LDAPControl *ctrl; + + assert( requestOID != NULL ); + assert( ctrlp != NULL ); + + ctrl = (LDAPControl *) LDAP_MALLOC( sizeof(LDAPControl) ); + if ( ctrl == NULL ) { + return LDAP_NO_MEMORY; + } + + BER_BVZERO(&ctrl->ldctl_value); + if ( ber && ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 )) { + LDAP_FREE( ctrl ); + return LDAP_NO_MEMORY; + } + + ctrl->ldctl_oid = LDAP_STRDUP( requestOID ); + ctrl->ldctl_iscritical = iscritical; + + if ( requestOID != NULL && ctrl->ldctl_oid == NULL ) { + ldap_control_free( ctrl ); + return LDAP_NO_MEMORY; + } + + *ctrlp = ctrl; + return LDAP_SUCCESS; +} + +/* + * Create a LDAPControl, optionally from value + */ +int +ldap_control_create( + LDAP_CONST char *requestOID, + int iscritical, + struct berval *value, + int dupval, + LDAPControl **ctrlp ) +{ + LDAPControl *ctrl; + + assert( requestOID != NULL ); + assert( ctrlp != NULL ); + + ctrl = (LDAPControl *) LDAP_CALLOC( sizeof(LDAPControl), 1 ); + if ( ctrl == NULL ) { + return LDAP_NO_MEMORY; + } + + ctrl->ldctl_iscritical = iscritical; + if ( requestOID != NULL ) { + ctrl->ldctl_oid = LDAP_STRDUP( requestOID ); + if ( ctrl->ldctl_oid == NULL ) { + ldap_control_free( ctrl ); + return LDAP_NO_MEMORY; + } + } + + if ( value && !BER_BVISNULL( value ) ) { + if ( dupval ) { + ber_dupbv( &ctrl->ldctl_value, value ); + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + ldap_control_free( ctrl ); + return LDAP_NO_MEMORY; + } + + } else { + ctrl->ldctl_value = *value; + } + } + + *ctrlp = ctrl; + + return LDAP_SUCCESS; +} + +/* + * check for critical client controls and bitch if present + * if we ever support critical controls, we'll have to + * find a means for maintaining per API call control + * information. + */ +int ldap_int_client_controls( LDAP *ld, LDAPControl **ctrls ) +{ + LDAPControl *const *c; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + if( ctrls == NULL ) { + /* use default client controls */ + ctrls = ld->ld_cctrls; + } + + if( ctrls == NULL || *ctrls == NULL ) { + return LDAP_SUCCESS; + } + + for( c = ctrls ; *c != NULL; c++ ) { + if( (*c)->ldctl_iscritical ) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return ld->ld_errno; + } + } + + return LDAP_SUCCESS; +} diff --git a/libraries/libldap/cyrus.c b/libraries/libldap/cyrus.c new file mode 100644 index 0000000..8a496f9 --- /dev/null +++ b/libraries/libldap/cyrus.c @@ -0,0 +1,1218 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/errno.h> +#include <ac/ctype.h> +#include <ac/unistd.h> + +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include "ldap-int.h" + +#ifdef HAVE_CYRUS_SASL + +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#ifndef INT_MAX +#define INT_MAX 2147483647 /* 32 bit signed max */ +#endif + +#ifdef HAVE_SASL_SASL_H +#include <sasl/sasl.h> +#else +#include <sasl.h> +#endif + +#if SASL_VERSION_MAJOR >= 2 +#define SASL_CONST const +#else +#define SASL_CONST +#endif + +/* +* Various Cyrus SASL related stuff. +*/ + +static const sasl_callback_t client_callbacks[] = { +#ifdef SASL_CB_GETREALM + { SASL_CB_GETREALM, NULL, NULL }, +#endif + { SASL_CB_USER, NULL, NULL }, + { SASL_CB_AUTHNAME, NULL, NULL }, + { SASL_CB_PASS, NULL, NULL }, + { SASL_CB_ECHOPROMPT, NULL, NULL }, + { SASL_CB_NOECHOPROMPT, NULL, NULL }, + { SASL_CB_LIST_END, NULL, NULL } +}; + +/* + * ldap_int_initialize is responsible for calling this only once. + */ +int ldap_int_sasl_init( void ) +{ +#ifdef HAVE_SASL_VERSION + /* stringify the version number, sasl.h doesn't do it for us */ +#define VSTR0(maj, min, pat) #maj "." #min "." #pat +#define VSTR(maj, min, pat) VSTR0(maj, min, pat) +#define SASL_VERSION_STRING VSTR(SASL_VERSION_MAJOR, SASL_VERSION_MINOR, \ + SASL_VERSION_STEP) + { int rc; + sasl_version( NULL, &rc ); + if ( ((rc >> 16) != ((SASL_VERSION_MAJOR << 8)|SASL_VERSION_MINOR)) || + (rc & 0xffff) < SASL_VERSION_STEP) { + char version[sizeof("xxx.xxx.xxxxx")]; + sprintf( version, "%u.%d.%d", (unsigned)rc >> 24, (rc >> 16) & 0xff, + rc & 0xffff ); + + Debug( LDAP_DEBUG_ANY, + "ldap_int_sasl_init: SASL library version mismatch:" + " expected " SASL_VERSION_STRING "," + " got %s\n", version, 0, 0 ); + return -1; + } + } +#endif + +/* SASL 2 takes care of its own memory completely internally */ +#if SASL_VERSION_MAJOR < 2 && !defined(CSRIMALLOC) + sasl_set_alloc( + ber_memalloc, + ber_memcalloc, + ber_memrealloc, + ber_memfree ); +#endif /* CSRIMALLOC */ + +#ifdef LDAP_R_COMPILE + sasl_set_mutex( + ldap_pvt_sasl_mutex_new, + ldap_pvt_sasl_mutex_lock, + ldap_pvt_sasl_mutex_unlock, + ldap_pvt_sasl_mutex_dispose ); +#endif + + if ( sasl_client_init( NULL ) == SASL_OK ) { + return 0; + } + +#if SASL_VERSION_MAJOR < 2 + /* A no-op to make sure we link with Cyrus 1.5 */ + sasl_client_auth( NULL, NULL, NULL, 0, NULL, NULL ); +#endif + return -1; +} + +static void +sb_sasl_cyrus_init( + struct sb_sasl_generic_data *p, + ber_len_t *min_send, + ber_len_t *max_send, + ber_len_t *max_recv) +{ + sasl_conn_t *sasl_context = (sasl_conn_t *)p->ops_private; + ber_len_t maxbuf; + + sasl_getprop( sasl_context, SASL_MAXOUTBUF, + (SASL_CONST void **)(char *) &maxbuf ); + + *min_send = SASL_MIN_BUFF_SIZE; + *max_send = maxbuf; + *max_recv = SASL_MAX_BUFF_SIZE; +} + +static ber_int_t +sb_sasl_cyrus_encode( + struct sb_sasl_generic_data *p, + unsigned char *buf, + ber_len_t len, + Sockbuf_Buf *dst) +{ + sasl_conn_t *sasl_context = (sasl_conn_t *)p->ops_private; + ber_int_t ret; + unsigned tmpsize = dst->buf_size; + + ret = sasl_encode( sasl_context, (char *)buf, len, + (SASL_CONST char **)&dst->buf_base, + &tmpsize ); + + dst->buf_size = tmpsize; + dst->buf_end = dst->buf_size; + + if ( ret != SASL_OK ) { + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_cyrus_encode: failed to encode packet: %s\n", + sasl_errstring( ret, NULL, NULL ) ); + return -1; + } + + return 0; +} + +static ber_int_t +sb_sasl_cyrus_decode( + struct sb_sasl_generic_data *p, + const Sockbuf_Buf *src, + Sockbuf_Buf *dst) +{ + sasl_conn_t *sasl_context = (sasl_conn_t *)p->ops_private; + ber_int_t ret; + unsigned tmpsize = dst->buf_size; + + ret = sasl_decode( sasl_context, + src->buf_base, src->buf_end, + (SASL_CONST char **)&dst->buf_base, + (unsigned *)&tmpsize ); + + + dst->buf_size = tmpsize; + dst->buf_end = dst->buf_size; + + if ( ret != SASL_OK ) { + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_cyrus_decode: failed to decode packet: %s\n", + sasl_errstring( ret, NULL, NULL ) ); + return -1; + } + + return 0; +} + +static void +sb_sasl_cyrus_reset_buf( + struct sb_sasl_generic_data *p, + Sockbuf_Buf *buf) +{ +#if SASL_VERSION_MAJOR >= 2 + ber_pvt_sb_buf_init( buf ); +#else + ber_pvt_sb_buf_destroy( buf ); +#endif +} + +static void +sb_sasl_cyrus_fini( + struct sb_sasl_generic_data *p) +{ +#if SASL_VERSION_MAJOR >= 2 + /* + * SASLv2 encode/decode buffers are managed by + * libsasl2. Ensure they are not freed by liblber. + */ + p->buf_in.buf_base = NULL; + p->buf_out.buf_base = NULL; +#endif +} + +static const struct sb_sasl_generic_ops sb_sasl_cyrus_ops = { + sb_sasl_cyrus_init, + sb_sasl_cyrus_encode, + sb_sasl_cyrus_decode, + sb_sasl_cyrus_reset_buf, + sb_sasl_cyrus_fini + }; + +int ldap_pvt_sasl_install( Sockbuf *sb, void *ctx_arg ) +{ + struct sb_sasl_generic_install install_arg; + + install_arg.ops = &sb_sasl_cyrus_ops; + install_arg.ops_private = ctx_arg; + + return ldap_pvt_sasl_generic_install( sb, &install_arg ); +} + +void ldap_pvt_sasl_remove( Sockbuf *sb ) +{ + ldap_pvt_sasl_generic_remove( sb ); +} + +static int +sasl_err2ldap( int saslerr ) +{ + int rc; + + /* map SASL errors to LDAP API errors returned by: + * sasl_client_new() + * SASL_OK, SASL_NOMECH, SASL_NOMEM + * sasl_client_start() + * SASL_OK, SASL_NOMECH, SASL_NOMEM, SASL_INTERACT + * sasl_client_step() + * SASL_OK, SASL_INTERACT, SASL_BADPROT, SASL_BADSERV + */ + + switch (saslerr) { + case SASL_CONTINUE: + rc = LDAP_MORE_RESULTS_TO_RETURN; + break; + case SASL_INTERACT: + rc = LDAP_LOCAL_ERROR; + break; + case SASL_OK: + rc = LDAP_SUCCESS; + break; + case SASL_NOMEM: + rc = LDAP_NO_MEMORY; + break; + case SASL_NOMECH: + rc = LDAP_AUTH_UNKNOWN; + break; + case SASL_BADPROT: + rc = LDAP_DECODING_ERROR; + break; + case SASL_BADSERV: + rc = LDAP_AUTH_UNKNOWN; + break; + + /* other codes */ + case SASL_BADAUTH: + rc = LDAP_AUTH_UNKNOWN; + break; + case SASL_NOAUTHZ: + rc = LDAP_PARAM_ERROR; + break; + case SASL_FAIL: + rc = LDAP_LOCAL_ERROR; + break; + case SASL_TOOWEAK: + case SASL_ENCRYPT: + rc = LDAP_AUTH_UNKNOWN; + break; + default: + rc = LDAP_LOCAL_ERROR; + break; + } + + assert( rc == LDAP_SUCCESS || LDAP_API_ERROR( rc ) ); + return rc; +} + +int +ldap_int_sasl_open( + LDAP *ld, + LDAPConn *lc, + const char * host ) +{ + int rc; + sasl_conn_t *ctx; + + assert( lc->lconn_sasl_authctx == NULL ); + + if ( host == NULL ) { + ld->ld_errno = LDAP_LOCAL_ERROR; + return ld->ld_errno; + } + +#if SASL_VERSION_MAJOR >= 2 + rc = sasl_client_new( "ldap", host, NULL, NULL, + client_callbacks, 0, &ctx ); +#else + rc = sasl_client_new( "ldap", host, client_callbacks, + SASL_SECURITY_LAYER, &ctx ); +#endif + + if ( rc != SASL_OK ) { + ld->ld_errno = sasl_err2ldap( rc ); + return ld->ld_errno; + } + + Debug( LDAP_DEBUG_TRACE, "ldap_int_sasl_open: host=%s\n", + host, 0, 0 ); + + lc->lconn_sasl_authctx = ctx; + + return LDAP_SUCCESS; +} + +int ldap_int_sasl_close( LDAP *ld, LDAPConn *lc ) +{ + sasl_conn_t *ctx = lc->lconn_sasl_authctx; + + if( ctx != NULL ) { + sasl_dispose( &ctx ); + if ( lc->lconn_sasl_sockctx && + lc->lconn_sasl_authctx != lc->lconn_sasl_sockctx ) { + ctx = lc->lconn_sasl_sockctx; + sasl_dispose( &ctx ); + } + lc->lconn_sasl_sockctx = NULL; + lc->lconn_sasl_authctx = NULL; + } + + return LDAP_SUCCESS; +} + +int +ldap_int_sasl_bind( + LDAP *ld, + const char *dn, + const char *mechs, + LDAPControl **sctrls, + LDAPControl **cctrls, + unsigned flags, + LDAP_SASL_INTERACT_PROC *interact, + void *defaults, + LDAPMessage *result, + const char **rmech, + int *msgid ) +{ + const char *mech; + sasl_ssf_t *ssf; + sasl_conn_t *ctx; + sasl_interact_t *prompts = NULL; + struct berval ccred = BER_BVNULL; + int saslrc, rc; + unsigned credlen; + + Debug( LDAP_DEBUG_TRACE, "ldap_int_sasl_bind: %s\n", + mechs ? mechs : "<null>", 0, 0 ); + + /* do a quick !LDAPv3 check... ldap_sasl_bind will do the rest. */ + if (ld->ld_version < LDAP_VERSION3) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return ld->ld_errno; + } + + /* Starting a Bind */ + if ( !result ) { + const char *pmech = NULL; + sasl_conn_t *oldctx; + ber_socket_t sd; + void *ssl; + + rc = 0; + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + ber_sockbuf_ctrl( ld->ld_sb, LBER_SB_OPT_GET_FD, &sd ); + + if ( sd == AC_SOCKET_INVALID || !ld->ld_defconn ) { + /* not connected yet */ + + rc = ldap_open_defconn( ld ); + + if ( rc == 0 ) { + ber_sockbuf_ctrl( ld->ld_defconn->lconn_sb, + LBER_SB_OPT_GET_FD, &sd ); + + if( sd == AC_SOCKET_INVALID ) { + ld->ld_errno = LDAP_LOCAL_ERROR; + rc = ld->ld_errno; + } + } + } + if ( rc == 0 && ld->ld_defconn && + ld->ld_defconn->lconn_status == LDAP_CONNST_CONNECTING ) { + rc = ldap_int_check_async_open( ld, sd ); + } + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + if( rc != 0 ) return ld->ld_errno; + + oldctx = ld->ld_defconn->lconn_sasl_authctx; + + /* If we already have an authentication context, clear it out */ + if( oldctx ) { + if ( oldctx != ld->ld_defconn->lconn_sasl_sockctx ) { + sasl_dispose( &oldctx ); + } + ld->ld_defconn->lconn_sasl_authctx = NULL; + } + + { + char *saslhost; + int nocanon = (int)LDAP_BOOL_GET( &ld->ld_options, + LDAP_BOOL_SASL_NOCANON ); + + /* If we don't need to canonicalize just use the host + * from the LDAP URI. + */ + if ( nocanon ) + saslhost = ld->ld_defconn->lconn_server->lud_host; + else + saslhost = ldap_host_connected_to( ld->ld_defconn->lconn_sb, + "localhost" ); + rc = ldap_int_sasl_open( ld, ld->ld_defconn, saslhost ); + if ( !nocanon ) + LDAP_FREE( saslhost ); + } + + if ( rc != LDAP_SUCCESS ) return rc; + + ctx = ld->ld_defconn->lconn_sasl_authctx; + +#ifdef HAVE_TLS + /* Check for TLS */ + ssl = ldap_pvt_tls_sb_ctx( ld->ld_defconn->lconn_sb ); + if ( ssl ) { + struct berval authid = BER_BVNULL; + ber_len_t fac; + + fac = ldap_pvt_tls_get_strength( ssl ); + /* failure is OK, we just can't use SASL EXTERNAL */ + (void) ldap_pvt_tls_get_my_dn( ssl, &authid, NULL, 0 ); + + (void) ldap_int_sasl_external( ld, ld->ld_defconn, authid.bv_val, fac ); + LDAP_FREE( authid.bv_val ); + } +#endif + +#if !defined(_WIN32) + /* Check for local */ + if ( ldap_pvt_url_scheme2proto( + ld->ld_defconn->lconn_server->lud_scheme ) == LDAP_PROTO_IPC ) + { + char authid[sizeof("gidNumber=4294967295+uidNumber=4294967295," + "cn=peercred,cn=external,cn=auth")]; + sprintf( authid, "gidNumber=%u+uidNumber=%u," + "cn=peercred,cn=external,cn=auth", + getegid(), geteuid() ); + (void) ldap_int_sasl_external( ld, ld->ld_defconn, authid, + LDAP_PVT_SASL_LOCAL_SSF ); + } +#endif + + /* (re)set security properties */ + sasl_setprop( ctx, SASL_SEC_PROPS, + &ld->ld_options.ldo_sasl_secprops ); + + mech = NULL; + + do { + saslrc = sasl_client_start( ctx, + mechs, +#if SASL_VERSION_MAJOR < 2 + NULL, +#endif + &prompts, + (SASL_CONST char **)&ccred.bv_val, + &credlen, + &mech ); + + if( pmech == NULL && mech != NULL ) { + pmech = mech; + *rmech = mech; + + if( flags != LDAP_SASL_QUIET ) { + fprintf(stderr, + "SASL/%s authentication started\n", + pmech ); + } + } + + if( saslrc == SASL_INTERACT ) { + int res; + if( !interact ) break; + res = (interact)( ld, flags, defaults, prompts ); + + if( res != LDAP_SUCCESS ) break; + } + } while ( saslrc == SASL_INTERACT ); + rc = LDAP_SASL_BIND_IN_PROGRESS; + + } else { + /* continuing an in-progress Bind */ + struct berval *scred = NULL; + + ctx = ld->ld_defconn->lconn_sasl_authctx; + + rc = ldap_parse_sasl_bind_result( ld, result, &scred, 0 ); + if ( rc != LDAP_SUCCESS ) { + if ( scred ) + ber_bvfree( scred ); + goto done; + } + + rc = ldap_result2error( ld, result, 0 ); + if ( rc != LDAP_SUCCESS && rc != LDAP_SASL_BIND_IN_PROGRESS ) { + if( scred ) { + /* and server provided us with data? */ + Debug( LDAP_DEBUG_TRACE, + "ldap_int_sasl_bind: rc=%d len=%ld\n", + rc, scred ? (long) scred->bv_len : -1L, 0 ); + ber_bvfree( scred ); + scred = NULL; + } + goto done; + } + + mech = *rmech; + if ( rc == LDAP_SUCCESS && mech == NULL ) { + if ( scred ) + ber_bvfree( scred ); + goto success; + } + + do { + if( ! scred ) { + /* no data! */ + Debug( LDAP_DEBUG_TRACE, + "ldap_int_sasl_bind: no data in step!\n", + 0, 0, 0 ); + } + + saslrc = sasl_client_step( ctx, + (scred == NULL) ? NULL : scred->bv_val, + (scred == NULL) ? 0 : scred->bv_len, + &prompts, + (SASL_CONST char **)&ccred.bv_val, + &credlen ); + + Debug( LDAP_DEBUG_TRACE, "sasl_client_step: %d\n", + saslrc, 0, 0 ); + + if( saslrc == SASL_INTERACT ) { + int res; + if( !interact ) break; + res = (interact)( ld, flags, defaults, prompts ); + if( res != LDAP_SUCCESS ) break; + } + } while ( saslrc == SASL_INTERACT ); + + ber_bvfree( scred ); + } + + if ( (saslrc != SASL_OK) && (saslrc != SASL_CONTINUE) ) { + rc = ld->ld_errno = sasl_err2ldap( saslrc ); +#if SASL_VERSION_MAJOR >= 2 + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( sasl_errdetail( ctx ) ); +#endif + goto done; + } + + if ( saslrc == SASL_OK ) + *rmech = NULL; + + ccred.bv_len = credlen; + + if ( rc == LDAP_SASL_BIND_IN_PROGRESS ) { + rc = ldap_sasl_bind( ld, dn, mech, &ccred, sctrls, cctrls, msgid ); + + if ( ccred.bv_val != NULL ) { +#if SASL_VERSION_MAJOR < 2 + LDAP_FREE( ccred.bv_val ); +#endif + ccred.bv_val = NULL; + } + if ( rc == LDAP_SUCCESS ) + rc = LDAP_SASL_BIND_IN_PROGRESS; + goto done; + } + +success: + /* Conversation was completed successfully by now */ + if( flags != LDAP_SASL_QUIET ) { + char *data; + saslrc = sasl_getprop( ctx, SASL_USERNAME, + (SASL_CONST void **)(char *) &data ); + if( saslrc == SASL_OK && data && *data ) { + fprintf( stderr, "SASL username: %s\n", data ); + } + +#if SASL_VERSION_MAJOR < 2 + saslrc = sasl_getprop( ctx, SASL_REALM, + (SASL_CONST void **) &data ); + if( saslrc == SASL_OK && data && *data ) { + fprintf( stderr, "SASL realm: %s\n", data ); + } +#endif + } + + ssf = NULL; + saslrc = sasl_getprop( ctx, SASL_SSF, (SASL_CONST void **)(char *) &ssf ); + if( saslrc == SASL_OK ) { + if( flags != LDAP_SASL_QUIET ) { + fprintf( stderr, "SASL SSF: %lu\n", + (unsigned long) *ssf ); + } + + if( ssf && *ssf ) { + if ( ld->ld_defconn->lconn_sasl_sockctx ) { + sasl_conn_t *oldctx = ld->ld_defconn->lconn_sasl_sockctx; + sasl_dispose( &oldctx ); + ldap_pvt_sasl_remove( ld->ld_defconn->lconn_sb ); + } + ldap_pvt_sasl_install( ld->ld_defconn->lconn_sb, ctx ); + ld->ld_defconn->lconn_sasl_sockctx = ctx; + + if( flags != LDAP_SASL_QUIET ) { + fprintf( stderr, "SASL data security layer installed.\n" ); + } + } + } + ld->ld_defconn->lconn_sasl_authctx = ctx; + +done: + return rc; +} + +int +ldap_int_sasl_external( + LDAP *ld, + LDAPConn *conn, + const char * authid, + ber_len_t ssf ) +{ + int sc; + sasl_conn_t *ctx; +#if SASL_VERSION_MAJOR < 2 + sasl_external_properties_t extprops; +#else + sasl_ssf_t sasl_ssf = ssf; +#endif + + ctx = conn->lconn_sasl_authctx; + + if ( ctx == NULL ) { + return LDAP_LOCAL_ERROR; + } + +#if SASL_VERSION_MAJOR >= 2 + sc = sasl_setprop( ctx, SASL_SSF_EXTERNAL, &sasl_ssf ); + if ( sc == SASL_OK ) + sc = sasl_setprop( ctx, SASL_AUTH_EXTERNAL, authid ); +#else + memset( &extprops, '\0', sizeof(extprops) ); + extprops.ssf = ssf; + extprops.auth_id = (char *) authid; + + sc = sasl_setprop( ctx, SASL_SSF_EXTERNAL, + (void *) &extprops ); +#endif + + if ( sc != SASL_OK ) { + return LDAP_LOCAL_ERROR; + } + + return LDAP_SUCCESS; +} + + +#define GOT_MINSSF 1 +#define GOT_MAXSSF 2 +#define GOT_MAXBUF 4 + +static struct { + struct berval key; + int sflag; + int ival; + int idef; +} sprops[] = { + { BER_BVC("none"), 0, 0, 0 }, + { BER_BVC("nodict"), SASL_SEC_NODICTIONARY, 0, 0 }, + { BER_BVC("noplain"), SASL_SEC_NOPLAINTEXT, 0, 0 }, + { BER_BVC("noactive"), SASL_SEC_NOACTIVE, 0, 0 }, + { BER_BVC("passcred"), SASL_SEC_PASS_CREDENTIALS, 0, 0 }, + { BER_BVC("forwardsec"), SASL_SEC_FORWARD_SECRECY, 0, 0 }, + { BER_BVC("noanonymous"), SASL_SEC_NOANONYMOUS, 0, 0 }, + { BER_BVC("minssf="), 0, GOT_MINSSF, 0 }, + { BER_BVC("maxssf="), 0, GOT_MAXSSF, INT_MAX }, + { BER_BVC("maxbufsize="), 0, GOT_MAXBUF, 65536 }, + { BER_BVNULL, 0, 0, 0 } +}; + +void ldap_pvt_sasl_secprops_unparse( + sasl_security_properties_t *secprops, + struct berval *out ) +{ + int i, l = 0; + int comma; + char *ptr; + + if ( secprops == NULL || out == NULL ) { + return; + } + + comma = 0; + for ( i=0; !BER_BVISNULL( &sprops[i].key ); i++ ) { + if ( sprops[i].ival ) { + int v = 0; + + switch( sprops[i].ival ) { + case GOT_MINSSF: v = secprops->min_ssf; break; + case GOT_MAXSSF: v = secprops->max_ssf; break; + case GOT_MAXBUF: v = secprops->maxbufsize; break; + } + /* It is the default, ignore it */ + if ( v == sprops[i].idef ) continue; + + l += sprops[i].key.bv_len + 24; + } else if ( sprops[i].sflag ) { + if ( sprops[i].sflag & secprops->security_flags ) { + l += sprops[i].key.bv_len; + } + } else if ( secprops->security_flags == 0 ) { + l += sprops[i].key.bv_len; + } + if ( comma ) l++; + comma = 1; + } + l++; + + out->bv_val = LDAP_MALLOC( l ); + if ( out->bv_val == NULL ) { + out->bv_len = 0; + return; + } + + ptr = out->bv_val; + comma = 0; + for ( i=0; !BER_BVISNULL( &sprops[i].key ); i++ ) { + if ( sprops[i].ival ) { + int v = 0; + + switch( sprops[i].ival ) { + case GOT_MINSSF: v = secprops->min_ssf; break; + case GOT_MAXSSF: v = secprops->max_ssf; break; + case GOT_MAXBUF: v = secprops->maxbufsize; break; + } + /* It is the default, ignore it */ + if ( v == sprops[i].idef ) continue; + + if ( comma ) *ptr++ = ','; + ptr += sprintf(ptr, "%s%d", sprops[i].key.bv_val, v ); + comma = 1; + } else if ( sprops[i].sflag ) { + if ( sprops[i].sflag & secprops->security_flags ) { + if ( comma ) *ptr++ = ','; + ptr += sprintf(ptr, "%s", sprops[i].key.bv_val ); + comma = 1; + } + } else if ( secprops->security_flags == 0 ) { + if ( comma ) *ptr++ = ','; + ptr += sprintf(ptr, "%s", sprops[i].key.bv_val ); + comma = 1; + } + } + out->bv_len = ptr - out->bv_val; +} + +int ldap_pvt_sasl_secprops( + const char *in, + sasl_security_properties_t *secprops ) +{ + unsigned i, j, l; + char **props; + unsigned sflags = 0; + int got_sflags = 0; + sasl_ssf_t max_ssf = 0; + int got_max_ssf = 0; + sasl_ssf_t min_ssf = 0; + int got_min_ssf = 0; + unsigned maxbufsize = 0; + int got_maxbufsize = 0; + + if( secprops == NULL ) { + return LDAP_PARAM_ERROR; + } + props = ldap_str2charray( in, "," ); + if( props == NULL ) { + return LDAP_PARAM_ERROR; + } + + for( i=0; props[i]; i++ ) { + l = strlen( props[i] ); + for ( j=0; !BER_BVISNULL( &sprops[j].key ); j++ ) { + if ( l < sprops[j].key.bv_len ) continue; + if ( strncasecmp( props[i], sprops[j].key.bv_val, + sprops[j].key.bv_len )) continue; + if ( sprops[j].ival ) { + unsigned v; + char *next = NULL; + if ( !isdigit( (unsigned char)props[i][sprops[j].key.bv_len] )) + continue; + v = strtoul( &props[i][sprops[j].key.bv_len], &next, 10 ); + if ( next == &props[i][sprops[j].key.bv_len] || next[0] != '\0' ) continue; + switch( sprops[j].ival ) { + case GOT_MINSSF: + min_ssf = v; got_min_ssf++; break; + case GOT_MAXSSF: + max_ssf = v; got_max_ssf++; break; + case GOT_MAXBUF: + maxbufsize = v; got_maxbufsize++; break; + } + } else { + if ( props[i][sprops[j].key.bv_len] ) continue; + if ( sprops[j].sflag ) + sflags |= sprops[j].sflag; + else + sflags = 0; + got_sflags++; + } + break; + } + if ( BER_BVISNULL( &sprops[j].key )) { + ldap_charray_free( props ); + return LDAP_NOT_SUPPORTED; + } + } + + if(got_sflags) { + secprops->security_flags = sflags; + } + if(got_min_ssf) { + secprops->min_ssf = min_ssf; + } + if(got_max_ssf) { + secprops->max_ssf = max_ssf; + } + if(got_maxbufsize) { + secprops->maxbufsize = maxbufsize; + } + + ldap_charray_free( props ); + return LDAP_SUCCESS; +} + +int +ldap_int_sasl_config( struct ldapoptions *lo, int option, const char *arg ) +{ + int rc; + + switch( option ) { + case LDAP_OPT_X_SASL_SECPROPS: + rc = ldap_pvt_sasl_secprops( arg, &lo->ldo_sasl_secprops ); + if( rc == LDAP_SUCCESS ) return 0; + } + + return -1; +} + +int +ldap_int_sasl_get_option( LDAP *ld, int option, void *arg ) +{ + if ( option == LDAP_OPT_X_SASL_MECHLIST ) { + *(char ***)arg = (char **)sasl_global_listmech(); + return 0; + } + + if ( ld == NULL ) + return -1; + + switch ( option ) { + case LDAP_OPT_X_SASL_MECH: { + *(char **)arg = ld->ld_options.ldo_def_sasl_mech + ? LDAP_STRDUP( ld->ld_options.ldo_def_sasl_mech ) : NULL; + } break; + case LDAP_OPT_X_SASL_REALM: { + *(char **)arg = ld->ld_options.ldo_def_sasl_realm + ? LDAP_STRDUP( ld->ld_options.ldo_def_sasl_realm ) : NULL; + } break; + case LDAP_OPT_X_SASL_AUTHCID: { + *(char **)arg = ld->ld_options.ldo_def_sasl_authcid + ? LDAP_STRDUP( ld->ld_options.ldo_def_sasl_authcid ) : NULL; + } break; + case LDAP_OPT_X_SASL_AUTHZID: { + *(char **)arg = ld->ld_options.ldo_def_sasl_authzid + ? LDAP_STRDUP( ld->ld_options.ldo_def_sasl_authzid ) : NULL; + } break; + + case LDAP_OPT_X_SASL_SSF: { + int sc; + sasl_ssf_t *ssf; + sasl_conn_t *ctx; + + if( ld->ld_defconn == NULL ) { + return -1; + } + + ctx = ld->ld_defconn->lconn_sasl_sockctx; + + if ( ctx == NULL ) { + return -1; + } + + sc = sasl_getprop( ctx, SASL_SSF, + (SASL_CONST void **)(char *) &ssf ); + + if ( sc != SASL_OK ) { + return -1; + } + + *(ber_len_t *)arg = *ssf; + } break; + + case LDAP_OPT_X_SASL_SSF_EXTERNAL: + /* this option is write only */ + return -1; + + case LDAP_OPT_X_SASL_SSF_MIN: + *(ber_len_t *)arg = ld->ld_options.ldo_sasl_secprops.min_ssf; + break; + case LDAP_OPT_X_SASL_SSF_MAX: + *(ber_len_t *)arg = ld->ld_options.ldo_sasl_secprops.max_ssf; + break; + case LDAP_OPT_X_SASL_MAXBUFSIZE: + *(ber_len_t *)arg = ld->ld_options.ldo_sasl_secprops.maxbufsize; + break; + case LDAP_OPT_X_SASL_NOCANON: + *(int *)arg = (int) LDAP_BOOL_GET(&ld->ld_options, LDAP_BOOL_SASL_NOCANON ); + break; + + case LDAP_OPT_X_SASL_USERNAME: { + int sc; + char *username; + sasl_conn_t *ctx; + + if( ld->ld_defconn == NULL ) { + return -1; + } + + ctx = ld->ld_defconn->lconn_sasl_authctx; + + if ( ctx == NULL ) { + return -1; + } + + sc = sasl_getprop( ctx, SASL_USERNAME, + (SASL_CONST void **)(char **) &username ); + + if ( sc != SASL_OK ) { + return -1; + } + + *(char **)arg = username ? LDAP_STRDUP( username ) : NULL; + } break; + + case LDAP_OPT_X_SASL_SECPROPS: + /* this option is write only */ + return -1; + +#ifdef SASL_GSS_CREDS + case LDAP_OPT_X_SASL_GSS_CREDS: { + sasl_conn_t *ctx; + int sc; + + if ( ld->ld_defconn == NULL ) + return -1; + + ctx = ld->ld_defconn->lconn_sasl_authctx; + if ( ctx == NULL ) + return -1; + + sc = sasl_getprop( ctx, SASL_GSS_CREDS, arg ); + if ( sc != SASL_OK ) + return -1; + } + break; +#endif + + default: + return -1; + } + return 0; +} + +int +ldap_int_sasl_set_option( LDAP *ld, int option, void *arg ) +{ + if ( ld == NULL ) + return -1; + + if ( arg == NULL && option != LDAP_OPT_X_SASL_NOCANON ) + return -1; + + switch ( option ) { + case LDAP_OPT_X_SASL_SSF: + case LDAP_OPT_X_SASL_USERNAME: + /* This option is read-only */ + return -1; + + case LDAP_OPT_X_SASL_SSF_EXTERNAL: { + int sc; +#if SASL_VERSION_MAJOR < 2 + sasl_external_properties_t extprops; +#else + sasl_ssf_t sasl_ssf; +#endif + sasl_conn_t *ctx; + + if( ld->ld_defconn == NULL ) { + return -1; + } + + ctx = ld->ld_defconn->lconn_sasl_authctx; + + if ( ctx == NULL ) { + return -1; + } + +#if SASL_VERSION_MAJOR >= 2 + sasl_ssf = * (ber_len_t *)arg; + sc = sasl_setprop( ctx, SASL_SSF_EXTERNAL, &sasl_ssf); +#else + memset(&extprops, 0L, sizeof(extprops)); + + extprops.ssf = * (ber_len_t *) arg; + + sc = sasl_setprop( ctx, SASL_SSF_EXTERNAL, + (void *) &extprops ); +#endif + + if ( sc != SASL_OK ) { + return -1; + } + } break; + + case LDAP_OPT_X_SASL_SSF_MIN: + ld->ld_options.ldo_sasl_secprops.min_ssf = *(ber_len_t *)arg; + break; + case LDAP_OPT_X_SASL_SSF_MAX: + ld->ld_options.ldo_sasl_secprops.max_ssf = *(ber_len_t *)arg; + break; + case LDAP_OPT_X_SASL_MAXBUFSIZE: + ld->ld_options.ldo_sasl_secprops.maxbufsize = *(ber_len_t *)arg; + break; + case LDAP_OPT_X_SASL_NOCANON: + if ( arg == LDAP_OPT_OFF ) { + LDAP_BOOL_CLR(&ld->ld_options, LDAP_BOOL_SASL_NOCANON ); + } else { + LDAP_BOOL_SET(&ld->ld_options, LDAP_BOOL_SASL_NOCANON ); + } + break; + + case LDAP_OPT_X_SASL_SECPROPS: { + int sc; + sc = ldap_pvt_sasl_secprops( (char *) arg, + &ld->ld_options.ldo_sasl_secprops ); + + return sc == LDAP_SUCCESS ? 0 : -1; + } + +#ifdef SASL_GSS_CREDS + case LDAP_OPT_X_SASL_GSS_CREDS: { + sasl_conn_t *ctx; + int sc; + + if ( ld->ld_defconn == NULL ) + return -1; + + ctx = ld->ld_defconn->lconn_sasl_authctx; + if ( ctx == NULL ) + return -1; + + sc = sasl_setprop( ctx, SASL_GSS_CREDS, arg ); + if ( sc != SASL_OK ) + return -1; + } + break; +#endif + + default: + return -1; + } + return 0; +} + +#ifdef LDAP_R_COMPILE +#define LDAP_DEBUG_R_SASL +void *ldap_pvt_sasl_mutex_new(void) +{ + ldap_pvt_thread_mutex_t *mutex; + + mutex = (ldap_pvt_thread_mutex_t *) LDAP_CALLOC( 1, + sizeof(ldap_pvt_thread_mutex_t) ); + + if ( ldap_pvt_thread_mutex_init( mutex ) == 0 ) { + return mutex; + } + LDAP_FREE( mutex ); +#ifndef LDAP_DEBUG_R_SASL + assert( 0 ); +#endif /* !LDAP_DEBUG_R_SASL */ + return NULL; +} + +int ldap_pvt_sasl_mutex_lock(void *mutex) +{ +#ifdef LDAP_DEBUG_R_SASL + if ( mutex == NULL ) { + return SASL_OK; + } +#else /* !LDAP_DEBUG_R_SASL */ + assert( mutex != NULL ); +#endif /* !LDAP_DEBUG_R_SASL */ + return ldap_pvt_thread_mutex_lock( (ldap_pvt_thread_mutex_t *)mutex ) + ? SASL_FAIL : SASL_OK; +} + +int ldap_pvt_sasl_mutex_unlock(void *mutex) +{ +#ifdef LDAP_DEBUG_R_SASL + if ( mutex == NULL ) { + return SASL_OK; + } +#else /* !LDAP_DEBUG_R_SASL */ + assert( mutex != NULL ); +#endif /* !LDAP_DEBUG_R_SASL */ + return ldap_pvt_thread_mutex_unlock( (ldap_pvt_thread_mutex_t *)mutex ) + ? SASL_FAIL : SASL_OK; +} + +void ldap_pvt_sasl_mutex_dispose(void *mutex) +{ +#ifdef LDAP_DEBUG_R_SASL + if ( mutex == NULL ) { + return; + } +#else /* !LDAP_DEBUG_R_SASL */ + assert( mutex != NULL ); +#endif /* !LDAP_DEBUG_R_SASL */ + (void) ldap_pvt_thread_mutex_destroy( (ldap_pvt_thread_mutex_t *)mutex ); + LDAP_FREE( mutex ); +} +#endif + +#else +int ldap_int_sasl_init( void ) +{ return LDAP_SUCCESS; } + +int ldap_int_sasl_close( LDAP *ld, LDAPConn *lc ) +{ return LDAP_SUCCESS; } + +int +ldap_int_sasl_bind( + LDAP *ld, + const char *dn, + const char *mechs, + LDAPControl **sctrls, + LDAPControl **cctrls, + unsigned flags, + LDAP_SASL_INTERACT_PROC *interact, + void *defaults, + LDAPMessage *result, + const char **rmech, + int *msgid ) +{ return LDAP_NOT_SUPPORTED; } + +int +ldap_int_sasl_external( + LDAP *ld, + LDAPConn *conn, + const char * authid, + ber_len_t ssf ) +{ return LDAP_SUCCESS; } + +#endif /* HAVE_CYRUS_SASL */ diff --git a/libraries/libldap/dds.c b/libraries/libldap/dds.c new file mode 100644 index 0000000..1adff4e --- /dev/null +++ b/libraries/libldap/dds.c @@ -0,0 +1,156 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2018 The OpenLDAP Foundation. + * Portions Copyright 2005-2006 SysNet s.n.c. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +int +ldap_parse_refresh( LDAP *ld, LDAPMessage *res, ber_int_t *newttl ) +{ + int rc; + struct berval *retdata = NULL; + ber_tag_t tag; + BerElement *ber; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( res != NULL ); + assert( newttl != NULL ); + + *newttl = 0; + + rc = ldap_parse_extended_result( ld, res, NULL, &retdata, 0 ); + + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( ld->ld_errno != LDAP_SUCCESS ) { + return ld->ld_errno; + } + + if ( retdata == NULL ) { + rc = ld->ld_errno = LDAP_DECODING_ERROR; + return rc; + } + + ber = ber_init( retdata ); + if ( ber == NULL ) { + rc = ld->ld_errno = LDAP_NO_MEMORY; + goto done; + } + + /* check the tag */ + tag = ber_scanf( ber, "{i}", newttl ); + ber_free( ber, 1 ); + + if ( tag != LDAP_TAG_EXOP_REFRESH_RES_TTL ) { + *newttl = 0; + rc = ld->ld_errno = LDAP_DECODING_ERROR; + } + +done:; + if ( retdata ) { + ber_bvfree( retdata ); + } + + return rc; +} + +int +ldap_refresh( + LDAP *ld, + struct berval *dn, + ber_int_t ttl, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + struct berval bv = { 0, NULL }; + BerElement *ber = NULL; + int rc; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( dn != NULL ); + assert( msgidp != NULL ); + + *msgidp = -1; + + ber = ber_alloc_t( LBER_USE_DER ); + + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + ber_printf( ber, "{tOtiN}", + LDAP_TAG_EXOP_REFRESH_REQ_DN, dn, + LDAP_TAG_EXOP_REFRESH_REQ_TTL, ttl ); + + rc = ber_flatten2( ber, &bv, 0 ); + + if ( rc < 0 ) { + rc = ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + + rc = ldap_extended_operation( ld, LDAP_EXOP_REFRESH, &bv, + sctrls, cctrls, msgidp ); + +done:; + ber_free( ber, 1 ); + + return rc; +} + +int +ldap_refresh_s( + LDAP *ld, + struct berval *dn, + ber_int_t ttl, + ber_int_t *newttl, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int rc; + int msgid; + LDAPMessage *res; + + rc = ldap_refresh( ld, dn, ttl, sctrls, cctrls, &msgid ); + if ( rc != LDAP_SUCCESS ) return rc; + + rc = ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *)NULL, &res ); + if( rc == -1 || !res ) return ld->ld_errno; + + rc = ldap_parse_refresh( ld, res, newttl ); + if( rc != LDAP_SUCCESS ) { + ldap_msgfree( res ); + return rc; + } + + return ldap_result2error( ld, res, 1 ); +} + diff --git a/libraries/libldap/delete.c b/libraries/libldap/delete.c new file mode 100644 index 0000000..7171e9d --- /dev/null +++ b/libraries/libldap/delete.c @@ -0,0 +1,174 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * A delete request looks like this: + * DelRequet ::= DistinguishedName, + */ + +BerElement * +ldap_build_delete_req( + LDAP *ld, + LDAP_CONST char *dn, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *ber; + int rc; + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + LDAP_NEXT_MSGID( ld, *msgidp ); + rc = ber_printf( ber, "{its", /* '}' */ + *msgidp, LDAP_REQ_DELETE, dn ); + if ( rc == -1 ) + { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +/* + * ldap_delete_ext - initiate an ldap extended delete operation. Parameters: + * + * ld LDAP descriptor + * dn DN of the object to delete + * sctrls Server Controls + * cctrls Client Controls + * msgidp Message Id Pointer + * + * Example: + * rc = ldap_delete( ld, dn, sctrls, cctrls, msgidp ); + */ +int +ldap_delete_ext( + LDAP *ld, + LDAP_CONST char* dn, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + int rc; + BerElement *ber; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_delete_ext\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( dn != NULL ); + assert( msgidp != NULL ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + ber = ldap_build_delete_req( ld, dn, sctrls, cctrls, &id ); + if( !ber ) + return ld->ld_errno; + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_DELETE, dn, ber, id ); + + if(*msgidp < 0) + return ld->ld_errno; + + return LDAP_SUCCESS; +} + +int +ldap_delete_ext_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int msgid; + int rc; + LDAPMessage *res; + + rc = ldap_delete_ext( ld, dn, sctrls, cctrls, &msgid ); + + if( rc != LDAP_SUCCESS ) + return( ld->ld_errno ); + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, &res ) == -1 || !res ) + return( ld->ld_errno ); + + return( ldap_result2error( ld, res, 1 ) ); +} +/* + * ldap_delete - initiate an ldap (and X.500) delete operation. Parameters: + * + * ld LDAP descriptor + * dn DN of the object to delete + * + * Example: + * msgid = ldap_delete( ld, dn ); + */ +int +ldap_delete( LDAP *ld, LDAP_CONST char *dn ) +{ + int msgid; + + /* + * A delete request looks like this: + * DelRequet ::= DistinguishedName, + */ + + Debug( LDAP_DEBUG_TRACE, "ldap_delete\n", 0, 0, 0 ); + + return ldap_delete_ext( ld, dn, NULL, NULL, &msgid ) == LDAP_SUCCESS + ? msgid : -1 ; +} + + +int +ldap_delete_s( LDAP *ld, LDAP_CONST char *dn ) +{ + return ldap_delete_ext_s( ld, dn, NULL, NULL ); +} diff --git a/libraries/libldap/deref.c b/libraries/libldap/deref.c new file mode 100644 index 0000000..a20a49c --- /dev/null +++ b/libraries/libldap/deref.c @@ -0,0 +1,282 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * Portions Copyright 2008 Pierangelo Masarati. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati + * for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +int +ldap_create_deref_control_value( + LDAP *ld, + LDAPDerefSpec *ds, + struct berval *value ) +{ + BerElement *ber = NULL; + ber_tag_t tag; + int i; + + if ( ld == NULL || value == NULL || ds == NULL ) + { + if ( ld ) + ld->ld_errno = LDAP_PARAM_ERROR; + return LDAP_PARAM_ERROR; + } + + assert( LDAP_VALID( ld ) ); + + value->bv_val = NULL; + value->bv_len = 0; + ld->ld_errno = LDAP_SUCCESS; + + ber = ldap_alloc_ber_with_options( ld ); + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + tag = ber_printf( ber, "{" /*}*/ ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + + for ( i = 0; ds[i].derefAttr != NULL; i++ ) { + int j; + + tag = ber_printf( ber, "{s{" /*}}*/ , ds[i].derefAttr ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + + for ( j = 0; ds[i].attributes[j] != NULL; j++ ) { + tag = ber_printf( ber, "s", ds[i].attributes[ j ] ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + } + + tag = ber_printf( ber, /*{{*/ "}N}" ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + } + + tag = ber_printf( ber, /*{*/ "}" ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + + if ( ber_flatten2( ber, value, 1 ) == -1 ) { + ld->ld_errno = LDAP_NO_MEMORY; + } + +done:; + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + return ld->ld_errno; +} + +int +ldap_create_deref_control( + LDAP *ld, + LDAPDerefSpec *ds, + int iscritical, + LDAPControl **ctrlp ) +{ + struct berval value; + + if ( ctrlp == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + ld->ld_errno = ldap_create_deref_control_value( ld, ds, &value ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = ldap_control_create( LDAP_CONTROL_X_DEREF, + iscritical, &value, 0, ctrlp ); + if ( ld->ld_errno != LDAP_SUCCESS ) { + LDAP_FREE( value.bv_val ); + } + } + + return ld->ld_errno; +} + +void +ldap_derefresponse_free( LDAPDerefRes *dr ) +{ + for ( ; dr; ) { + LDAPDerefRes *drnext = dr->next; + LDAPDerefVal *dv; + + LDAP_FREE( dr->derefAttr ); + LDAP_FREE( dr->derefVal.bv_val ); + + for ( dv = dr->attrVals; dv; ) { + LDAPDerefVal *dvnext = dv->next; + LDAP_FREE( dv->type ); + ber_bvarray_free( dv->vals ); + LDAP_FREE( dv ); + dv = dvnext; + } + + LDAP_FREE( dr ); + + dr = drnext; + } +} + +int +ldap_parse_derefresponse_control( + LDAP *ld, + LDAPControl *ctrl, + LDAPDerefRes **drp2 ) +{ + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + char *last; + LDAPDerefRes *drhead = NULL, **drp; + + if ( ld == NULL || ctrl == NULL || drp2 == NULL ) { + if ( ld ) + ld->ld_errno = LDAP_PARAM_ERROR; + return LDAP_PARAM_ERROR; + } + + /* Create a BerElement from the berval returned in the control. */ + ber = ber_init( &ctrl->ldctl_value ); + + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + /* Extract the count and cookie from the control. */ + drp = &drhead; + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + LDAPDerefRes *dr; + LDAPDerefVal **dvp; + char *last2; + + dr = LDAP_CALLOC( 1, sizeof(LDAPDerefRes) ); + dvp = &dr->attrVals; + + tag = ber_scanf( ber, "{ao", &dr->derefAttr, &dr->derefVal ); + if ( tag == LBER_ERROR ) { + goto done; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == (LBER_CONSTRUCTED|LBER_CLASS_CONTEXT) ) { + for ( tag = ber_first_element( ber, &len, &last2 ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last2 ) ) + { + LDAPDerefVal *dv; + + dv = LDAP_CALLOC( 1, sizeof(LDAPDerefVal) ); + + tag = ber_scanf( ber, "{a[W]}", &dv->type, &dv->vals ); + if ( tag == LBER_ERROR ) { + goto done; + } + + *dvp = dv; + dvp = &dv->next; + } + } + + tag = ber_scanf( ber, "}" ); + if ( tag == LBER_ERROR ) { + goto done; + } + + *drp = dr; + drp = &dr->next; + } + + tag = 0; + +done:; + ber_free( ber, 1 ); + + if ( tag == LBER_ERROR ) { + if ( drhead != NULL ) { + ldap_derefresponse_free( drhead ); + } + + *drp2 = NULL; + ld->ld_errno = LDAP_DECODING_ERROR; + + } else { + *drp2 = drhead; + ld->ld_errno = LDAP_SUCCESS; + } + + return ld->ld_errno; +} + +int +ldap_parse_deref_control( + LDAP *ld, + LDAPControl **ctrls, + LDAPDerefRes **drp ) +{ + LDAPControl *c; + + if ( drp == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + *drp = NULL; + + if ( ctrls == NULL ) { + ld->ld_errno = LDAP_CONTROL_NOT_FOUND; + return ld->ld_errno; + } + + c = ldap_control_find( LDAP_CONTROL_X_DEREF, ctrls, NULL ); + if ( c == NULL ) { + /* No deref control was found. */ + ld->ld_errno = LDAP_CONTROL_NOT_FOUND; + return ld->ld_errno; + } + + ld->ld_errno = ldap_parse_derefresponse_control( ld, c, drp ); + + return ld->ld_errno; +} + diff --git a/libraries/libldap/dnssrv.c b/libraries/libldap/dnssrv.c new file mode 100644 index 0000000..1524a3a --- /dev/null +++ b/libraries/libldap/dnssrv.c @@ -0,0 +1,431 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * locate LDAP servers using DNS SRV records. + * Location code based on MIT Kerberos KDC location code. + */ +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/param.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +#ifdef HAVE_ARPA_NAMESER_H +#include <arpa/nameser.h> +#endif +#ifdef HAVE_RESOLV_H +#include <resolv.h> +#endif + +int ldap_dn2domain( + LDAP_CONST char *dn_in, + char **domainp) +{ + int i, j; + char *ndomain; + LDAPDN dn = NULL; + LDAPRDN rdn = NULL; + LDAPAVA *ava = NULL; + struct berval domain = BER_BVNULL; + static const struct berval DC = BER_BVC("DC"); + static const struct berval DCOID = BER_BVC("0.9.2342.19200300.100.1.25"); + + assert( dn_in != NULL ); + assert( domainp != NULL ); + + *domainp = NULL; + + if ( ldap_str2dn( dn_in, &dn, LDAP_DN_FORMAT_LDAP ) != LDAP_SUCCESS ) { + return -2; + } + + if( dn ) for( i=0; dn[i] != NULL; i++ ) { + rdn = dn[i]; + + for( j=0; rdn[j] != NULL; j++ ) { + ava = rdn[j]; + + if( rdn[j+1] == NULL && + (ava->la_flags & LDAP_AVA_STRING) && + ava->la_value.bv_len && + ( ber_bvstrcasecmp( &ava->la_attr, &DC ) == 0 + || ber_bvcmp( &ava->la_attr, &DCOID ) == 0 ) ) + { + if( domain.bv_len == 0 ) { + ndomain = LDAP_REALLOC( domain.bv_val, + ava->la_value.bv_len + 1); + + if( ndomain == NULL ) { + goto return_error; + } + + domain.bv_val = ndomain; + + AC_MEMCPY( domain.bv_val, ava->la_value.bv_val, + ava->la_value.bv_len ); + + domain.bv_len = ava->la_value.bv_len; + domain.bv_val[domain.bv_len] = '\0'; + + } else { + ndomain = LDAP_REALLOC( domain.bv_val, + ava->la_value.bv_len + sizeof(".") + domain.bv_len ); + + if( ndomain == NULL ) { + goto return_error; + } + + domain.bv_val = ndomain; + domain.bv_val[domain.bv_len++] = '.'; + AC_MEMCPY( &domain.bv_val[domain.bv_len], + ava->la_value.bv_val, ava->la_value.bv_len ); + domain.bv_len += ava->la_value.bv_len; + domain.bv_val[domain.bv_len] = '\0'; + } + } else { + domain.bv_len = 0; + } + } + } + + + if( domain.bv_len == 0 && domain.bv_val != NULL ) { + LDAP_FREE( domain.bv_val ); + domain.bv_val = NULL; + } + + ldap_dnfree( dn ); + *domainp = domain.bv_val; + return 0; + +return_error: + ldap_dnfree( dn ); + LDAP_FREE( domain.bv_val ); + return -1; +} + +int ldap_domain2dn( + LDAP_CONST char *domain_in, + char **dnp) +{ + char *domain, *s, *tok_r, *dn, *dntmp; + size_t loc; + + assert( domain_in != NULL ); + assert( dnp != NULL ); + + domain = LDAP_STRDUP(domain_in); + if (domain == NULL) { + return LDAP_NO_MEMORY; + } + dn = NULL; + loc = 0; + + for (s = ldap_pvt_strtok(domain, ".", &tok_r); + s != NULL; + s = ldap_pvt_strtok(NULL, ".", &tok_r)) + { + size_t len = strlen(s); + + dntmp = (char *) LDAP_REALLOC(dn, loc + sizeof(",dc=") + len ); + if (dntmp == NULL) { + if (dn != NULL) + LDAP_FREE(dn); + LDAP_FREE(domain); + return LDAP_NO_MEMORY; + } + + dn = dntmp; + + if (loc > 0) { + /* not first time. */ + strcpy(dn + loc, ","); + loc++; + } + strcpy(dn + loc, "dc="); + loc += sizeof("dc=")-1; + + strcpy(dn + loc, s); + loc += len; + } + + LDAP_FREE(domain); + *dnp = dn; + return LDAP_SUCCESS; +} + +#ifdef HAVE_RES_QUERY +#define DNSBUFSIZ (64*1024) +#define MAXHOST 254 /* RFC 1034, max length is 253 chars */ +typedef struct srv_record { + u_short priority; + u_short weight; + u_short port; + char hostname[MAXHOST]; +} srv_record; + +/* Linear Congruential Generator - we don't need + * high quality randomness, and we don't want to + * interfere with anyone else's use of srand(). + * + * The PRNG here cycles thru 941,955 numbers. + */ +static float srv_seed; + +static void srv_srand(int seed) { + srv_seed = (float)seed / (float)RAND_MAX; +} + +static float srv_rand() { + float val = 9821.0 * srv_seed + .211327; + srv_seed = val - (int)val; + return srv_seed; +} + +static int srv_cmp(const void *aa, const void *bb){ + srv_record *a=(srv_record *)aa; + srv_record *b=(srv_record *)bb; + int i = a->priority - b->priority; + if (i) return i; + return b->weight - a->weight; +} + +static void srv_shuffle(srv_record *a, int n) { + int i, j, total = 0, r, p; + + for (i=0; i<n; i++) + total += a[i].weight; + + /* all weights are zero, do a straight Fisher-Yates shuffle */ + if (!total) { + while (n) { + srv_record t; + i = srv_rand() * n--; + t = a[n]; + a[n] = a[i]; + a[i] = t; + } + return; + } + + /* Do a shuffle per RFC2782 Page 4 */ + p = n; + for (i=0; i<n-1; i++) { + r = srv_rand() * total; + for (j=0; j<p; j++) { + r -= a[j].weight; + if (r <= 0) { + if (j) { + srv_record t = a[0]; + a[0] = a[j]; + a[j] = t; + } + total -= a[0].weight; + a++; + p--; + break; + } + } + } +} +#endif /* HAVE_RES_QUERY */ + +/* + * Lookup and return LDAP servers for domain (using the DNS + * SRV record _ldap._tcp.domain). + */ +int ldap_domain2hostlist( + LDAP_CONST char *domain, + char **list ) +{ +#ifdef HAVE_RES_QUERY + char *request; + char *hostlist = NULL; + srv_record *hostent_head=NULL; + int i, j; + int rc, len, cur = 0; + unsigned char reply[DNSBUFSIZ]; + int hostent_count=0; + + assert( domain != NULL ); + assert( list != NULL ); + if( *domain == '\0' ) { + return LDAP_PARAM_ERROR; + } + + request = LDAP_MALLOC(strlen(domain) + sizeof("_ldap._tcp.")); + if (request == NULL) { + return LDAP_NO_MEMORY; + } + sprintf(request, "_ldap._tcp.%s", domain); + + LDAP_MUTEX_LOCK(&ldap_int_resolv_mutex); + + rc = LDAP_UNAVAILABLE; +#ifdef NS_HFIXEDSZ + /* Bind 8/9 interface */ + len = res_query(request, ns_c_in, ns_t_srv, reply, sizeof(reply)); +# ifndef T_SRV +# define T_SRV ns_t_srv +# endif +#else + /* Bind 4 interface */ +# ifndef T_SRV +# define T_SRV 33 +# endif + + len = res_query(request, C_IN, T_SRV, reply, sizeof(reply)); +#endif + if (len >= 0) { + unsigned char *p; + char host[DNSBUFSIZ]; + int status; + u_short port, priority, weight; + + /* Parse out query */ + p = reply; + +#ifdef NS_HFIXEDSZ + /* Bind 8/9 interface */ + p += NS_HFIXEDSZ; +#elif defined(HFIXEDSZ) + /* Bind 4 interface w/ HFIXEDSZ */ + p += HFIXEDSZ; +#else + /* Bind 4 interface w/o HFIXEDSZ */ + p += sizeof(HEADER); +#endif + + status = dn_expand(reply, reply + len, p, host, sizeof(host)); + if (status < 0) { + goto out; + } + p += status; + p += 4; + + while (p < reply + len) { + int type, class, ttl, size; + status = dn_expand(reply, reply + len, p, host, sizeof(host)); + if (status < 0) { + goto out; + } + p += status; + type = (p[0] << 8) | p[1]; + p += 2; + class = (p[0] << 8) | p[1]; + p += 2; + ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + p += 4; + size = (p[0] << 8) | p[1]; + p += 2; + if (type == T_SRV) { + status = dn_expand(reply, reply + len, p + 6, host, sizeof(host)); + if (status < 0) { + goto out; + } + + /* Get priority weight and port */ + priority = (p[0] << 8) | p[1]; + weight = (p[2] << 8) | p[3]; + port = (p[4] << 8) | p[5]; + + if ( port == 0 || host[ 0 ] == '\0' ) { + goto add_size; + } + + hostent_head = (srv_record *) LDAP_REALLOC(hostent_head, (hostent_count+1)*(sizeof(srv_record))); + if(hostent_head==NULL){ + rc=LDAP_NO_MEMORY; + goto out; + } + hostent_head[hostent_count].priority=priority; + hostent_head[hostent_count].weight=weight; + hostent_head[hostent_count].port=port; + strncpy(hostent_head[hostent_count].hostname, host, MAXHOST-1); + hostent_head[hostent_count].hostname[MAXHOST-1] = '\0'; + hostent_count++; + } +add_size:; + p += size; + } + if (!hostent_head) goto out; + qsort(hostent_head, hostent_count, sizeof(srv_record), srv_cmp); + + if (!srv_seed) + srv_srand(time(0L)); + + /* shuffle records of same priority */ + j = 0; + priority = hostent_head[0].priority; + for (i=1; i<hostent_count; i++) { + if (hostent_head[i].priority != priority) { + priority = hostent_head[i].priority; + if (i-j > 1) + srv_shuffle(hostent_head+j, i-j); + j = i; + } + } + if (i-j > 1) + srv_shuffle(hostent_head+j, i-j); + + for(i=0; i<hostent_count; i++){ + int buflen; + buflen = strlen(hostent_head[i].hostname) + STRLENOF(":65535 "); + hostlist = (char *) LDAP_REALLOC(hostlist, cur+buflen+1); + if (hostlist == NULL) { + rc = LDAP_NO_MEMORY; + goto out; + } + if(cur>0){ + hostlist[cur++]=' '; + } + cur += sprintf(&hostlist[cur], "%s:%hu", hostent_head[i].hostname, hostent_head[i].port); + } + } + + if (hostlist == NULL) { + /* No LDAP servers found in DNS. */ + rc = LDAP_UNAVAILABLE; + goto out; + } + + rc = LDAP_SUCCESS; + *list = hostlist; + + out: + LDAP_MUTEX_UNLOCK(&ldap_int_resolv_mutex); + + if (request != NULL) { + LDAP_FREE(request); + } + if (hostent_head != NULL) { + LDAP_FREE(hostent_head); + } + if (rc != LDAP_SUCCESS && hostlist != NULL) { + LDAP_FREE(hostlist); + } + return rc; +#else + return LDAP_NOT_SUPPORTED; +#endif /* HAVE_RES_QUERY */ +} diff --git a/libraries/libldap/dntest.c b/libraries/libldap/dntest.c new file mode 100644 index 0000000..c523318 --- /dev/null +++ b/libraries/libldap/dntest.c @@ -0,0 +1,296 @@ +/* dntest.c -- OpenLDAP DN API Test Program */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENT: + * This program was initially developed by Pierangelo Masarati <ando@OpenLDAP.org> + * for inclusion in OpenLDAP Software. + */ + +/* + * This program is designed to test the ldap_str2dn/ldap_dn2str + * functions + */ +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include <ldap.h> + +#include "ldap-int.h" + +#include "ldif.h" +#include "lutil.h" +#include "lutil_ldap.h" +#include "ldap_defaults.h" + +int +main( int argc, char *argv[] ) +{ + int rc, i, debug = 0, f2 = 0; + unsigned flags[ 2 ] = { 0U, 0 }; + char *strin, *str = NULL, buf[ 1024 ]; + LDAPDN dn, dn2 = NULL; + + while ( 1 ) { + int opt = getopt( argc, argv, "d:" ); + + if ( opt == EOF ) { + break; + } + + switch ( opt ) { + case 'd': + debug = atoi( optarg ); + break; + } + } + + optind--; + argc -= optind; + argv += optind; + + if ( argc < 2 ) { + fprintf( stderr, "usage: dntest <dn> [flags-in[,...]] [flags-out[,...]]\n\n" ); + fprintf( stderr, "\tflags-in: V3,V2,DCE,<flags>\n" ); + fprintf( stderr, "\tflags-out: V3,V2,UFN,DCE,AD,<flags>\n\n" ); + fprintf( stderr, "\t<flags>: PRETTY,PEDANTIC,NOSPACES,NOONESPACE\n\n" ); + return( 0 ); + } + + if ( ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &debug ) != LBER_OPT_SUCCESS ) { + fprintf( stderr, "Could not set LBER_OPT_DEBUG_LEVEL %d\n", debug ); + } + if ( ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &debug ) != LDAP_OPT_SUCCESS ) { + fprintf( stderr, "Could not set LDAP_OPT_DEBUG_LEVEL %d\n", debug ); + } + + if ( strcmp( argv[ 1 ], "-" ) == 0 ) { + size_t len = fgets( buf, sizeof( buf ), stdin ) ? strlen( buf ) : 0; + + if ( len == 0 || buf[ --len ] == '\n' ) { + buf[ len ] = '\0'; + } + strin = buf; + } else { + strin = argv[ 1 ]; + } + + if ( argc >= 3 ) { + for ( i = 0; i < argc - 2; i++ ) { + char *s, *e; + for ( s = argv[ 2 + i ]; s; s = e ) { + e = strchr( s, ',' ); + if ( e != NULL ) { + e[ 0 ] = '\0'; + e++; + } + + if ( !strcasecmp( s, "V3" ) ) { + flags[ i ] |= LDAP_DN_FORMAT_LDAPV3; + } else if ( !strcasecmp( s, "V2" ) ) { + flags[ i ] |= LDAP_DN_FORMAT_LDAPV2; + } else if ( !strcasecmp( s, "DCE" ) ) { + flags[ i ] |= LDAP_DN_FORMAT_DCE; + } else if ( !strcasecmp( s, "UFN" ) ) { + flags[ i ] |= LDAP_DN_FORMAT_UFN; + } else if ( !strcasecmp( s, "AD" ) ) { + flags[ i ] |= LDAP_DN_FORMAT_AD_CANONICAL; + } else if ( !strcasecmp( s, "PRETTY" ) ) { + flags[ i ] |= LDAP_DN_PRETTY; + } else if ( !strcasecmp( s, "PEDANTIC" ) ) { + flags[ i ] |= LDAP_DN_PEDANTIC; + } else if ( !strcasecmp( s, "NOSPACES" ) ) { + flags[ i ] |= LDAP_DN_P_NOLEADTRAILSPACES; + } else if ( !strcasecmp( s, "NOONESPACE" ) ) { + flags[ i ] |= LDAP_DN_P_NOSPACEAFTERRDN; + } + } + } + } + + if ( flags[ 1 ] == 0 ) + flags[ 1 ] = LDAP_DN_FORMAT_LDAPV3; + + f2 = 1; + + rc = ldap_str2dn( strin, &dn, flags[ 0 ] ); + + if ( rc == LDAP_SUCCESS ) { + int i; + if ( dn ) { + for ( i = 0; dn[ i ]; i++ ) { + LDAPRDN rdn = dn[ i ]; + char *rstr = NULL; + + if ( ldap_rdn2str( rdn, &rstr, flags[ f2 ] ) ) { + fprintf( stdout, "\tldap_rdn2str() failed\n" ); + continue; + } + + fprintf( stdout, "\tldap_rdn2str() = \"%s\"\n", rstr ); + ldap_memfree( rstr ); + } + } else { + fprintf( stdout, "\tempty DN\n" ); + } + } + + str = NULL; + if ( rc == LDAP_SUCCESS && + ldap_dn2str( dn, &str, flags[ f2 ] ) == LDAP_SUCCESS ) + { + char **values, *tmp, *tmp2, *str2 = NULL; + int n; + + fprintf( stdout, "\nldap_dn2str(ldap_str2dn(\"%s\"))\n" + "\t= \"%s\"\n", strin, str ); + + switch ( flags[ f2 ] & LDAP_DN_FORMAT_MASK ) { + case LDAP_DN_FORMAT_UFN: + case LDAP_DN_FORMAT_AD_CANONICAL: + return( 0 ); + + case LDAP_DN_FORMAT_LDAPV3: + case LDAP_DN_FORMAT_LDAPV2: + n = ldap_dn2domain( strin, &tmp ); + if ( n ) { + fprintf( stdout, "\nldap_dn2domain(\"%s\") FAILED\n", strin ); + } else { + fprintf( stdout, "\nldap_dn2domain(\"%s\")\n" + "\t= \"%s\"\n", strin, tmp ? tmp : "" ); + } + ldap_memfree( tmp ); + + tmp = ldap_dn2ufn( strin ); + fprintf( stdout, "\nldap_dn2ufn(\"%s\")\n" + "\t= \"%s\"\n", strin, tmp ? tmp : "" ); + ldap_memfree( tmp ); + + tmp = ldap_dn2dcedn( strin ); + fprintf( stdout, "\nldap_dn2dcedn(\"%s\")\n" + "\t= \"%s\"\n", strin, tmp ? tmp : "" ); + tmp2 = ldap_dcedn2dn( tmp ); + fprintf( stdout, "\nldap_dcedn2dn(\"%s\")\n" + "\t= \"%s\"\n", + tmp ? tmp : "", tmp2 ? tmp2 : "" ); + ldap_memfree( tmp ); + ldap_memfree( tmp2 ); + + tmp = ldap_dn2ad_canonical( strin ); + fprintf( stdout, "\nldap_dn2ad_canonical(\"%s\")\n" + "\t= \"%s\"\n", strin, tmp ? tmp : "" ); + ldap_memfree( tmp ); + + fprintf( stdout, "\nldap_explode_dn(\"%s\"):\n", str ); + values = ldap_explode_dn( str, 0 ); + for ( n = 0; values && values[ n ]; n++ ) { + char **vv; + int nn; + + fprintf( stdout, "\t\"%s\"\n", values[ n ] ); + + fprintf( stdout, "\tldap_explode_rdn(\"%s\")\n", + values[ n ] ); + vv = ldap_explode_rdn( values[ n ], 0 ); + for ( nn = 0; vv && vv[ nn ]; nn++ ) { + fprintf( stdout, "\t\t'%s'\n", + vv[ nn ] ); + } + LDAP_VFREE( vv ); + + fprintf( stdout, "\tldap_explode_rdn(\"%s\")" + " (no types)\n", values[ n ] ); + vv = ldap_explode_rdn( values[ n ], 1 ); + for ( nn = 0; vv && vv[ nn ]; nn++ ) { + fprintf( stdout, "\t\t\t\"%s\"\n", + vv[ nn ] ); + } + LDAP_VFREE( vv ); + + } + LDAP_VFREE( values ); + + fprintf( stdout, "\nldap_explode_dn(\"%s\")" + " (no types):\n", str ); + values = ldap_explode_dn( str, 1 ); + for ( n = 0; values && values[ n ]; n++ ) { + fprintf( stdout, "\t\"%s\"\n", values[ n ] ); + } + LDAP_VFREE( values ); + + break; + } + + dn2 = NULL; + rc = ldap_str2dn( str, &dn2, flags[ f2 ] ); + str2 = NULL; + if ( rc == LDAP_SUCCESS && + ldap_dn2str( dn2, &str2, flags[ f2 ] ) + == LDAP_SUCCESS ) { + int iRDN; + + fprintf( stdout, "\n\"%s\"\n\t == \"%s\" ? %s\n", + str, str2, + strcmp( str, str2 ) == 0 ? "yes" : "no" ); + + if( dn != NULL && dn2 == NULL ) { + fprintf( stdout, "dn mismatch\n" ); + } else if (( dn != NULL ) && (dn2 != NULL)) + for ( iRDN = 0; dn[ iRDN ] && dn2[ iRDN ]; iRDN++ ) + { + LDAPRDN r = dn[ iRDN ]; + LDAPRDN r2 = dn2[ iRDN ]; + int iAVA; + + for ( iAVA = 0; r[ iAVA ] && r2[ iAVA ]; iAVA++ ) { + LDAPAVA *a = r[ iAVA ]; + LDAPAVA *a2 = r2[ iAVA ]; + + if ( a->la_attr.bv_len != a2->la_attr.bv_len ) { + fprintf( stdout, "ava(%d), rdn(%d) attr len mismatch (%ld->%ld)\n", + iAVA + 1, iRDN + 1, + a->la_attr.bv_len, a2->la_attr.bv_len ); + } else if ( memcmp( a->la_attr.bv_val, a2->la_attr.bv_val, a->la_attr.bv_len ) ) { + fprintf( stdout, "ava(%d), rdn(%d) attr mismatch\n", + iAVA + 1, iRDN + 1 ); + } else if ( a->la_flags != a2->la_flags ) { + fprintf( stdout, "ava(%d), rdn(%d) flag mismatch (%x->%x)\n", + iAVA + 1, iRDN + 1, a->la_flags, a2->la_flags ); + } else if ( a->la_value.bv_len != a2->la_value.bv_len ) { + fprintf( stdout, "ava(%d), rdn(%d) value len mismatch (%ld->%ld)\n", + iAVA + 1, iRDN + 1, + a->la_value.bv_len, a2->la_value.bv_len ); + } else if ( memcmp( a->la_value.bv_val, a2->la_value.bv_val, a->la_value.bv_len ) ) { + fprintf( stdout, "ava(%d), rdn(%d) value mismatch\n", + iAVA + 1, iRDN + 1 ); + } + } + } + + ldap_dnfree( dn2 ); + ldap_memfree( str2 ); + } + ldap_memfree( str ); + } + ldap_dnfree( dn ); + + /* note: dn is not freed */ + + return( 0 ); +} diff --git a/libraries/libldap/error.c b/libraries/libldap/error.c new file mode 100644 index 0000000..7052d3a --- /dev/null +++ b/libraries/libldap/error.c @@ -0,0 +1,398 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +void ldap_int_error_init( void ) { +} + +char * +ldap_err2string( int err ) +{ + char *m; + + Debug( LDAP_DEBUG_TRACE, "ldap_err2string\n", 0, 0, 0 ); + + switch ( err ) { +# define C(code, message) case code: m = message; break + + /* LDAPv3 (RFC 4511) codes */ + C(LDAP_SUCCESS, N_("Success")); + C(LDAP_OPERATIONS_ERROR, N_("Operations error")); + C(LDAP_PROTOCOL_ERROR, N_("Protocol error")); + C(LDAP_TIMELIMIT_EXCEEDED, N_("Time limit exceeded")); + C(LDAP_SIZELIMIT_EXCEEDED, N_("Size limit exceeded")); + C(LDAP_COMPARE_FALSE, N_("Compare False")); + C(LDAP_COMPARE_TRUE, N_("Compare True")); + C(LDAP_STRONG_AUTH_NOT_SUPPORTED,N_("Authentication method not supported")); + C(LDAP_STRONG_AUTH_REQUIRED, N_("Strong(er) authentication required")); + + C(LDAP_REFERRAL, N_("Referral")); + C(LDAP_ADMINLIMIT_EXCEEDED, N_("Administrative limit exceeded")); + C(LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + N_("Critical extension is unavailable")); + C(LDAP_CONFIDENTIALITY_REQUIRED,N_("Confidentiality required")); + C(LDAP_SASL_BIND_IN_PROGRESS, N_("SASL bind in progress")); + + C(LDAP_NO_SUCH_ATTRIBUTE, N_("No such attribute")); + C(LDAP_UNDEFINED_TYPE, N_("Undefined attribute type")); + C(LDAP_INAPPROPRIATE_MATCHING, N_("Inappropriate matching")); + C(LDAP_CONSTRAINT_VIOLATION, N_("Constraint violation")); + C(LDAP_TYPE_OR_VALUE_EXISTS, N_("Type or value exists")); + C(LDAP_INVALID_SYNTAX, N_("Invalid syntax")); + + C(LDAP_NO_SUCH_OBJECT, N_("No such object")); + C(LDAP_ALIAS_PROBLEM, N_("Alias problem")); + C(LDAP_INVALID_DN_SYNTAX, N_("Invalid DN syntax")); + + C(LDAP_ALIAS_DEREF_PROBLEM, N_("Alias dereferencing problem")); + + C(LDAP_INAPPROPRIATE_AUTH, N_("Inappropriate authentication")); + C(LDAP_INVALID_CREDENTIALS, N_("Invalid credentials")); + C(LDAP_INSUFFICIENT_ACCESS, N_("Insufficient access")); + C(LDAP_BUSY, N_("Server is busy")); + C(LDAP_UNAVAILABLE, N_("Server is unavailable")); + C(LDAP_UNWILLING_TO_PERFORM, N_("Server is unwilling to perform")); + C(LDAP_LOOP_DETECT, N_("Loop detected")); + + C(LDAP_NAMING_VIOLATION, N_("Naming violation")); + C(LDAP_OBJECT_CLASS_VIOLATION, N_("Object class violation")); + C(LDAP_NOT_ALLOWED_ON_NONLEAF, N_("Operation not allowed on non-leaf")); + C(LDAP_NOT_ALLOWED_ON_RDN, N_("Operation not allowed on RDN")); + C(LDAP_ALREADY_EXISTS, N_("Already exists")); + C(LDAP_NO_OBJECT_CLASS_MODS, N_("Cannot modify object class")); + + C(LDAP_AFFECTS_MULTIPLE_DSAS, N_("Operation affects multiple DSAs")); + + /* Virtual List View draft */ + C(LDAP_VLV_ERROR, N_("Virtual List View error")); + + C(LDAP_OTHER, N_("Other (e.g., implementation specific) error")); + + /* LDAPv2 (RFC 1777) codes */ + C(LDAP_PARTIAL_RESULTS, N_("Partial results and referral received")); + C(LDAP_IS_LEAF, N_("Entry is a leaf")); + + /* Connection-less LDAP (CLDAP - RFC 1798) code */ + C(LDAP_RESULTS_TOO_LARGE, N_("Results too large")); + + /* Cancel Operation (RFC 3909) codes */ + C(LDAP_CANCELLED, N_("Cancelled")); + C(LDAP_NO_SUCH_OPERATION, N_("No Operation to Cancel")); + C(LDAP_TOO_LATE, N_("Too Late to Cancel")); + C(LDAP_CANNOT_CANCEL, N_("Cannot Cancel")); + + /* Assert Control (RFC 4528 and old internet-draft) codes */ + C(LDAP_ASSERTION_FAILED, N_("Assertion Failed")); + C(LDAP_X_ASSERTION_FAILED, N_("Assertion Failed (X)")); + + /* Proxied Authorization Control (RFC 4370 and I-D) codes */ + C(LDAP_PROXIED_AUTHORIZATION_DENIED, N_("Proxied Authorization Denied")); + C(LDAP_X_PROXY_AUTHZ_FAILURE, N_("Proxy Authorization Failure (X)")); + + /* Content Sync Operation (RFC 4533 and I-D) codes */ + C(LDAP_SYNC_REFRESH_REQUIRED, N_("Content Sync Refresh Required")); + C(LDAP_X_SYNC_REFRESH_REQUIRED, N_("Content Sync Refresh Required (X)")); + + /* No-Op Control (draft-zeilenga-ldap-noop) code */ + C(LDAP_X_NO_OPERATION, N_("No Operation (X)")); + + /* Client Update Protocol (RFC 3928) codes */ + C(LDAP_CUP_RESOURCES_EXHAUSTED, N_("LCUP Resources Exhausted")); + C(LDAP_CUP_SECURITY_VIOLATION, N_("LCUP Security Violation")); + C(LDAP_CUP_INVALID_DATA, N_("LCUP Invalid Data")); + C(LDAP_CUP_UNSUPPORTED_SCHEME, N_("LCUP Unsupported Scheme")); + C(LDAP_CUP_RELOAD_REQUIRED, N_("LCUP Reload Required")); + +#ifdef LDAP_X_TXN + /* Codes related to LDAP Transactions (draft-zeilenga-ldap-txn) */ + C(LDAP_X_TXN_SPECIFY_OKAY, N_("TXN specify okay")); + C(LDAP_X_TXN_ID_INVALID, N_("TXN ID is invalid")); +#endif + + /* API codes - renumbered since draft-ietf-ldapext-ldap-c-api */ + C(LDAP_SERVER_DOWN, N_("Can't contact LDAP server")); + C(LDAP_LOCAL_ERROR, N_("Local error")); + C(LDAP_ENCODING_ERROR, N_("Encoding error")); + C(LDAP_DECODING_ERROR, N_("Decoding error")); + C(LDAP_TIMEOUT, N_("Timed out")); + C(LDAP_AUTH_UNKNOWN, N_("Unknown authentication method")); + C(LDAP_FILTER_ERROR, N_("Bad search filter")); + C(LDAP_USER_CANCELLED, N_("User cancelled operation")); + C(LDAP_PARAM_ERROR, N_("Bad parameter to an ldap routine")); + C(LDAP_NO_MEMORY, N_("Out of memory")); + C(LDAP_CONNECT_ERROR, N_("Connect error")); + C(LDAP_NOT_SUPPORTED, N_("Not Supported")); + C(LDAP_CONTROL_NOT_FOUND, N_("Control not found")); + C(LDAP_NO_RESULTS_RETURNED, N_("No results returned")); + C(LDAP_MORE_RESULTS_TO_RETURN, N_("More results to return")); + C(LDAP_CLIENT_LOOP, N_("Client Loop")); + C(LDAP_REFERRAL_LIMIT_EXCEEDED, N_("Referral Limit Exceeded")); + C(LDAP_X_CONNECTING, N_("Connecting (X)")); +# undef C + + default: + m = (LDAP_API_ERROR(err) ? N_("Unknown API error") + : LDAP_E_ERROR(err) ? N_("Unknown (extension) error") + : LDAP_X_ERROR(err) ? N_("Unknown (private extension) error") + : N_("Unknown error")); + break; + } + + return _(m); +} + +/* deprecated */ +void +ldap_perror( LDAP *ld, LDAP_CONST char *str ) +{ + int i; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( str != NULL ); + + fprintf( stderr, "%s: %s (%d)\n", + str ? str : "ldap_perror", + ldap_err2string( ld->ld_errno ), + ld->ld_errno ); + + if ( ld->ld_matched != NULL && ld->ld_matched[0] != '\0' ) { + fprintf( stderr, _("\tmatched DN: %s\n"), ld->ld_matched ); + } + + if ( ld->ld_error != NULL && ld->ld_error[0] != '\0' ) { + fprintf( stderr, _("\tadditional info: %s\n"), ld->ld_error ); + } + + if ( ld->ld_referrals != NULL && ld->ld_referrals[0] != NULL) { + fprintf( stderr, _("\treferrals:\n") ); + for (i=0; ld->ld_referrals[i]; i++) { + fprintf( stderr, _("\t\t%s\n"), ld->ld_referrals[i] ); + } + } + + fflush( stderr ); +} + +/* deprecated */ +int +ldap_result2error( LDAP *ld, LDAPMessage *r, int freeit ) +{ + int rc, err; + + rc = ldap_parse_result( ld, r, &err, + NULL, NULL, NULL, NULL, freeit ); + + return err != LDAP_SUCCESS ? err : rc; +} + +/* + * Parse LDAPResult Messages: + * + * LDAPResult ::= SEQUENCE { + * resultCode ENUMERATED, + * matchedDN LDAPDN, + * errorMessage LDAPString, + * referral [3] Referral OPTIONAL } + * + * including Bind results: + * + * BindResponse ::= [APPLICATION 1] SEQUENCE { + * COMPONENTS OF LDAPResult, + * serverSaslCreds [7] OCTET STRING OPTIONAL } + * + * and ExtendedOp results: + * + * ExtendedResponse ::= [APPLICATION 24] SEQUENCE { + * COMPONENTS OF LDAPResult, + * responseName [10] LDAPOID OPTIONAL, + * response [11] OCTET STRING OPTIONAL } + * + */ +int +ldap_parse_result( + LDAP *ld, + LDAPMessage *r, + int *errcodep, + char **matcheddnp, + char **errmsgp, + char ***referralsp, + LDAPControl ***serverctrls, + int freeit ) +{ + LDAPMessage *lm; + ber_int_t errcode = LDAP_SUCCESS; + + ber_tag_t tag; + BerElement *ber; + + Debug( LDAP_DEBUG_TRACE, "ldap_parse_result\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( r != NULL ); + + if(errcodep != NULL) *errcodep = LDAP_SUCCESS; + if(matcheddnp != NULL) *matcheddnp = NULL; + if(errmsgp != NULL) *errmsgp = NULL; + if(referralsp != NULL) *referralsp = NULL; + if(serverctrls != NULL) *serverctrls = NULL; + + LDAP_MUTEX_LOCK( &ld->ld_res_mutex ); + /* Find the result, last msg in chain... */ + lm = r->lm_chain_tail; + /* FIXME: either this is not possible (assert?) + * or it should be handled */ + if ( lm != NULL ) { + switch ( lm->lm_msgtype ) { + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + case LDAP_RES_INTERMEDIATE: + lm = NULL; + break; + + default: + break; + } + } + + if( lm == NULL ) { + errcode = ld->ld_errno = LDAP_NO_RESULTS_RETURNED; + LDAP_MUTEX_UNLOCK( &ld->ld_res_mutex ); + goto done; + } + + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + ld->ld_error = NULL; + } + if ( ld->ld_matched ) { + LDAP_FREE( ld->ld_matched ); + ld->ld_matched = NULL; + } + if ( ld->ld_referrals ) { + LDAP_VFREE( ld->ld_referrals ); + ld->ld_referrals = NULL; + } + + /* parse results */ + + ber = ber_dup( lm->lm_ber ); + + if ( ld->ld_version < LDAP_VERSION2 ) { + tag = ber_scanf( ber, "{iA}", + &ld->ld_errno, &ld->ld_error ); + + } else { + ber_len_t len; + + tag = ber_scanf( ber, "{iAA" /*}*/, + &ld->ld_errno, &ld->ld_matched, &ld->ld_error ); + + if( tag != LBER_ERROR ) { + /* peek for referrals */ + if( ber_peek_tag(ber, &len) == LDAP_TAG_REFERRAL ) { + tag = ber_scanf( ber, "v", &ld->ld_referrals ); + } + } + + /* need to clean out misc items */ + if( tag != LBER_ERROR ) { + if( lm->lm_msgtype == LDAP_RES_BIND ) { + /* look for sasl result creditials */ + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SASL_RES_CREDS ) { + /* skip 'em */ + tag = ber_scanf( ber, "x" ); + } + + } else if( lm->lm_msgtype == LDAP_RES_EXTENDED ) { + /* look for exop result oid or value */ + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_EXOP_RES_OID ) { + /* skip 'em */ + tag = ber_scanf( ber, "x" ); + } + + if ( tag != LBER_ERROR && + ber_peek_tag( ber, &len ) == LDAP_TAG_EXOP_RES_VALUE ) + { + /* skip 'em */ + tag = ber_scanf( ber, "x" ); + } + } + } + + if( tag != LBER_ERROR ) { + int rc = ldap_pvt_get_controls( ber, serverctrls ); + + if( rc != LDAP_SUCCESS ) { + tag = LBER_ERROR; + } + } + + if( tag != LBER_ERROR ) { + tag = ber_scanf( ber, /*{*/"}" ); + } + } + + if ( tag == LBER_ERROR ) { + ld->ld_errno = errcode = LDAP_DECODING_ERROR; + } + + if( ber != NULL ) { + ber_free( ber, 0 ); + } + + /* return */ + if( errcodep != NULL ) { + *errcodep = ld->ld_errno; + } + if ( errcode == LDAP_SUCCESS ) { + if( matcheddnp != NULL ) { + if ( ld->ld_matched ) + { + *matcheddnp = LDAP_STRDUP( ld->ld_matched ); + } + } + if( errmsgp != NULL ) { + if ( ld->ld_error ) + { + *errmsgp = LDAP_STRDUP( ld->ld_error ); + } + } + + if( referralsp != NULL) { + *referralsp = ldap_value_dup( ld->ld_referrals ); + } + } + LDAP_MUTEX_UNLOCK( &ld->ld_res_mutex ); + +done: + if ( freeit ) { + ldap_msgfree( r ); + } + + return errcode; +} diff --git a/libraries/libldap/extended.c b/libraries/libldap/extended.c new file mode 100644 index 0000000..2002889 --- /dev/null +++ b/libraries/libldap/extended.c @@ -0,0 +1,419 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +BerElement * +ldap_build_extended_req( + LDAP *ld, + LDAP_CONST char *reqoid, + struct berval *reqdata, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp ) +{ + BerElement *ber; + int rc; + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + LDAP_NEXT_MSGID( ld, *msgidp ); + if ( reqdata != NULL ) { + rc = ber_printf( ber, "{it{tstON}", /* '}' */ + *msgidp, LDAP_REQ_EXTENDED, + LDAP_TAG_EXOP_REQ_OID, reqoid, + LDAP_TAG_EXOP_REQ_VALUE, reqdata ); + + } else { + rc = ber_printf( ber, "{it{tsN}", /* '}' */ + *msgidp, LDAP_REQ_EXTENDED, + LDAP_TAG_EXOP_REQ_OID, reqoid ); + } + + if( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +/* + * LDAPv3 Extended Operation Request + * ExtendedRequest ::= [APPLICATION 23] SEQUENCE { + * requestName [0] LDAPOID, + * requestValue [1] OCTET STRING OPTIONAL + * } + * + * LDAPv3 Extended Operation Response + * ExtendedResponse ::= [APPLICATION 24] SEQUENCE { + * COMPONENTS OF LDAPResult, + * responseName [10] LDAPOID OPTIONAL, + * response [11] OCTET STRING OPTIONAL + * } + * + * (Source RFC 4511) + */ + +int +ldap_extended_operation( + LDAP *ld, + LDAP_CONST char *reqoid, + struct berval *reqdata, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *ber; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_extended_operation\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( reqoid != NULL && *reqoid != '\0' ); + assert( msgidp != NULL ); + + /* must be version 3 (or greater) */ + if ( ld->ld_version < LDAP_VERSION3 ) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return( ld->ld_errno ); + } + + ber = ldap_build_extended_req( ld, reqoid, reqdata, + sctrls, cctrls, &id ); + if ( !ber ) + return( ld->ld_errno ); + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_EXTENDED, NULL, ber, id ); + + return( *msgidp < 0 ? ld->ld_errno : LDAP_SUCCESS ); +} + +int +ldap_extended_operation_s( + LDAP *ld, + LDAP_CONST char *reqoid, + struct berval *reqdata, + LDAPControl **sctrls, + LDAPControl **cctrls, + char **retoidp, + struct berval **retdatap ) +{ + int rc; + int msgid; + LDAPMessage *res; + + Debug( LDAP_DEBUG_TRACE, "ldap_extended_operation_s\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( reqoid != NULL && *reqoid != '\0' ); + + rc = ldap_extended_operation( ld, reqoid, reqdata, + sctrls, cctrls, &msgid ); + + if ( rc != LDAP_SUCCESS ) { + return( rc ); + } + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, &res ) == -1 || !res ) { + return( ld->ld_errno ); + } + + if ( retoidp != NULL ) *retoidp = NULL; + if ( retdatap != NULL ) *retdatap = NULL; + + rc = ldap_parse_extended_result( ld, res, retoidp, retdatap, 0 ); + + if( rc != LDAP_SUCCESS ) { + ldap_msgfree( res ); + return rc; + } + + return( ldap_result2error( ld, res, 1 ) ); +} + +/* Parse an extended result */ +int +ldap_parse_extended_result ( + LDAP *ld, + LDAPMessage *res, + char **retoidp, + struct berval **retdatap, + int freeit ) +{ + BerElement *ber; + ber_tag_t rc; + ber_tag_t tag; + ber_len_t len; + struct berval *resdata; + ber_int_t errcode; + char *resoid; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( res != NULL ); + + Debug( LDAP_DEBUG_TRACE, "ldap_parse_extended_result\n", 0, 0, 0 ); + + if( ld->ld_version < LDAP_VERSION3 ) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return ld->ld_errno; + } + + if( res->lm_msgtype != LDAP_RES_EXTENDED ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + if( retoidp != NULL ) *retoidp = NULL; + if( retdatap != NULL ) *retdatap = NULL; + + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + ld->ld_error = NULL; + } + + if ( ld->ld_matched ) { + LDAP_FREE( ld->ld_matched ); + ld->ld_matched = NULL; + } + + ber = ber_dup( res->lm_ber ); + + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + rc = ber_scanf( ber, "{eAA" /*}*/, &errcode, + &ld->ld_matched, &ld->ld_error ); + + if( rc == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return ld->ld_errno; + } + + resoid = NULL; + resdata = NULL; + + tag = ber_peek_tag( ber, &len ); + + if( tag == LDAP_TAG_REFERRAL ) { + /* skip over referral */ + if( ber_scanf( ber, "x" ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return ld->ld_errno; + } + + tag = ber_peek_tag( ber, &len ); + } + + if( tag == LDAP_TAG_EXOP_RES_OID ) { + /* we have a resoid */ + if( ber_scanf( ber, "a", &resoid ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return ld->ld_errno; + } + + assert( resoid[ 0 ] != '\0' ); + + tag = ber_peek_tag( ber, &len ); + } + + if( tag == LDAP_TAG_EXOP_RES_VALUE ) { + /* we have a resdata */ + if( ber_scanf( ber, "O", &resdata ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + if( resoid != NULL ) LDAP_FREE( resoid ); + return ld->ld_errno; + } + } + + ber_free( ber, 0 ); + + if( retoidp != NULL ) { + *retoidp = resoid; + } else { + LDAP_FREE( resoid ); + } + + if( retdatap != NULL ) { + *retdatap = resdata; + } else { + ber_bvfree( resdata ); + } + + ld->ld_errno = errcode; + + if( freeit ) { + ldap_msgfree( res ); + } + + return LDAP_SUCCESS; +} + + +/* Parse an extended partial */ +int +ldap_parse_intermediate ( + LDAP *ld, + LDAPMessage *res, + char **retoidp, + struct berval **retdatap, + LDAPControl ***serverctrls, + int freeit ) +{ + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + struct berval *resdata; + char *resoid; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( res != NULL ); + + Debug( LDAP_DEBUG_TRACE, "ldap_parse_intermediate\n", 0, 0, 0 ); + + if( ld->ld_version < LDAP_VERSION3 ) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return ld->ld_errno; + } + + if( res->lm_msgtype != LDAP_RES_INTERMEDIATE ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + if( retoidp != NULL ) *retoidp = NULL; + if( retdatap != NULL ) *retdatap = NULL; + if( serverctrls != NULL ) *serverctrls = NULL; + + ber = ber_dup( res->lm_ber ); + + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + tag = ber_scanf( ber, "{" /*}*/ ); + + if( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return ld->ld_errno; + } + + resoid = NULL; + resdata = NULL; + + tag = ber_peek_tag( ber, &len ); + + /* + * NOTE: accept intermediate and extended response tag values + * as older versions of slapd(8) incorrectly used extended + * response tags. + * Should be removed when 2.2 is moved to Historic. + */ + if( tag == LDAP_TAG_IM_RES_OID || tag == LDAP_TAG_EXOP_RES_OID ) { + /* we have a resoid */ + if( ber_scanf( ber, "a", &resoid ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return ld->ld_errno; + } + + assert( resoid[ 0 ] != '\0' ); + + tag = ber_peek_tag( ber, &len ); + } + + if( tag == LDAP_TAG_IM_RES_VALUE || tag == LDAP_TAG_EXOP_RES_VALUE ) { + /* we have a resdata */ + if( ber_scanf( ber, "O", &resdata ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + if( resoid != NULL ) LDAP_FREE( resoid ); + return ld->ld_errno; + } + } + + if ( serverctrls == NULL ) { + ld->ld_errno = LDAP_SUCCESS; + goto free_and_return; + } + + if ( ber_scanf( ber, /*{*/ "}" ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + goto free_and_return; + } + + ld->ld_errno = ldap_pvt_get_controls( ber, serverctrls ); + +free_and_return: + ber_free( ber, 0 ); + + if( retoidp != NULL ) { + *retoidp = resoid; + } else { + LDAP_FREE( resoid ); + } + + if( retdatap != NULL ) { + *retdatap = resdata; + } else { + ber_bvfree( resdata ); + } + + if( freeit ) { + ldap_msgfree( res ); + } + + return ld->ld_errno; +} + diff --git a/libraries/libldap/fetch.c b/libraries/libldap/fetch.c new file mode 100644 index 0000000..410fb80 --- /dev/null +++ b/libraries/libldap/fetch.c @@ -0,0 +1,146 @@ +/* fetch.c - routines for fetching data at URLs */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2018 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Kurt D. Zeilenga. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* This work was initially developed by Kurt D. Zeilenga for + * inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/time.h> + +#ifdef HAVE_FETCH +#include <fetch.h> +#endif + +#include "lber_pvt.h" +#include "ldap_pvt.h" +#include "ldap_config.h" +#include "ldif.h" + +FILE * +ldif_open_url( + LDAP_CONST char *urlstr ) +{ + FILE *url; + + if( strncasecmp( "file:", urlstr, sizeof("file:")-1 ) == 0 ) { + char *p; + urlstr += sizeof("file:")-1; + + /* we don't check for LDAP_DIRSEP since URLs should contain '/' */ + if ( urlstr[0] == '/' && urlstr[1] == '/' ) { + urlstr += 2; + /* path must be absolute if authority is present + * technically, file://hostname/path is also legal but we don't + * accept a non-empty hostname + */ + if ( urlstr[0] != '/' ) { +#ifdef _WIN32 + /* An absolute path in improper file://C:/foo/bar format */ + if ( urlstr[1] != ':' ) +#endif + return NULL; + } +#ifdef _WIN32 + /* An absolute path in proper file:///C:/foo/bar format */ + if ( urlstr[2] == ':' ) + urlstr++; +#endif + } + + p = ber_strdup( urlstr ); + + /* But we should convert to LDAP_DIRSEP before use */ + if ( LDAP_DIRSEP[0] != '/' ) { + char *s = p; + while (( s = strchr( s, '/' ))) + *s++ = LDAP_DIRSEP[0]; + } + + ldap_pvt_hex_unescape( p ); + + url = fopen( p, "rb" ); + + ber_memfree( p ); + } else { +#ifdef HAVE_FETCH + url = fetchGetURL( (char*) urlstr, "" ); +#else + url = NULL; +#endif + } + return url; +} + +int +ldif_fetch_url( + LDAP_CONST char *urlstr, + char **valuep, + ber_len_t *vlenp ) +{ + FILE *url; + char buffer[1024]; + char *p = NULL; + size_t total; + size_t bytes; + + *valuep = NULL; + *vlenp = 0; + + url = ldif_open_url( urlstr ); + + if( url == NULL ) { + return -1; + } + + total = 0; + + while( (bytes = fread( buffer, 1, sizeof(buffer), url )) != 0 ) { + char *newp = ber_memrealloc( p, total + bytes + 1 ); + if( newp == NULL ) { + ber_memfree( p ); + fclose( url ); + return -1; + } + p = newp; + AC_MEMCPY( &p[total], buffer, bytes ); + total += bytes; + } + + fclose( url ); + + if( total == 0 ) { + char *newp = ber_memrealloc( p, 1 ); + if( newp == NULL ) { + ber_memfree( p ); + return -1; + } + p = newp; + } + + p[total] = '\0'; + *valuep = p; + *vlenp = total; + + return 0; +} diff --git a/libraries/libldap/filter.c b/libraries/libldap/filter.c new file mode 100644 index 0000000..086bc00 --- /dev/null +++ b/libraries/libldap/filter.c @@ -0,0 +1,1124 @@ +/* search.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +static int put_simple_vrFilter LDAP_P(( + BerElement *ber, + char *str )); + +static int put_vrFilter_list LDAP_P(( + BerElement *ber, + char *str )); + +static char *put_complex_filter LDAP_P(( + BerElement *ber, + char *str, + ber_tag_t tag, + int not )); + +static int put_simple_filter LDAP_P(( + BerElement *ber, + char *str )); + +static int put_substring_filter LDAP_P(( + BerElement *ber, + char *type, + char *str, + char *nextstar )); + +static int put_filter_list LDAP_P(( + BerElement *ber, + char *str, + ber_tag_t tag )); + +static int ldap_is_oid ( const char *str ) +{ + int i; + + if( LDAP_ALPHA( str[0] )) { + for( i=1; str[i]; i++ ) { + if( !LDAP_LDH( str[i] )) { + return 0; + } + } + return 1; + + } else if LDAP_DIGIT( str[0] ) { + int dot=0; + for( i=1; str[i]; i++ ) { + if( LDAP_DIGIT( str[i] )) { + dot=0; + + } else if ( str[i] == '.' ) { + if( ++dot > 1 ) return 0; + + } else { + return 0; + } + } + return !dot; + } + + return 0; +} + +static int ldap_is_desc ( const char *str ) +{ + int i; + + if( LDAP_ALPHA( str[0] )) { + for( i=1; str[i]; i++ ) { + if( str[i] == ';' ) { + str = &str[i+1]; + goto options; + } + + if( !LDAP_LDH( str[i] )) { + return 0; + } + } + return 1; + + } else if LDAP_DIGIT( str[0] ) { + int dot=0; + for( i=1; str[i]; i++ ) { + if( str[i] == ';' ) { + if( dot ) return 0; + str = &str[i+1]; + goto options; + } + + if( LDAP_DIGIT( str[i] )) { + dot=0; + + } else if ( str[i] == '.' ) { + if( ++dot > 1 ) return 0; + + } else { + return 0; + } + } + return !dot; + } + + return 0; + +options: + if( !LDAP_LDH( str[0] )) { + return 0; + } + for( i=1; str[i]; i++ ) { + if( str[i] == ';' ) { + str = &str[i+1]; + goto options; + } + if( !LDAP_LDH( str[i] )) { + return 0; + } + } + return 1; +} + +static char * +find_right_paren( char *s ) +{ + int balance, escape; + + balance = 1; + escape = 0; + while ( *s && balance ) { + if ( !escape ) { + if ( *s == '(' ) { + balance++; + } else if ( *s == ')' ) { + balance--; + } + } + + escape = ( *s == '\\' && !escape ); + + if ( balance ) s++; + } + + return *s ? s : NULL; +} + +static int hex2value( int c ) +{ + if( c >= '0' && c <= '9' ) { + return c - '0'; + } + + if( c >= 'A' && c <= 'F' ) { + return c + (10 - (int) 'A'); + } + + if( c >= 'a' && c <= 'f' ) { + return c + (10 - (int) 'a'); + } + + return -1; +} + +char * +ldap_pvt_find_wildcard( const char *s ) +{ + for( ; *s; s++ ) { + switch( *s ) { + case '*': /* found wildcard */ + return (char *) s; + + case '(': + case ')': + return NULL; + + case '\\': + if( s[1] == '\0' ) return NULL; + + if( LDAP_HEX( s[1] ) && LDAP_HEX( s[2] ) ) { + s+=2; + + } else switch( s[1] ) { + default: + return NULL; + + /* allow RFC 1960 escapes */ + case '*': + case '(': + case ')': + case '\\': + s++; + } + } + } + + return (char *) s; +} + +/* unescape filter value */ +/* support both LDAP v2 and v3 escapes */ +/* output can include nul characters! */ +ber_slen_t +ldap_pvt_filter_value_unescape( char *fval ) +{ + ber_slen_t r, v; + int v1, v2; + + for( r=v=0; fval[v] != '\0'; v++ ) { + switch( fval[v] ) { + case '(': + case ')': + case '*': + return -1; + + case '\\': + /* escape */ + v++; + + if ( fval[v] == '\0' ) { + /* escape at end of string */ + return -1; + } + + if (( v1 = hex2value( fval[v] )) >= 0 ) { + /* LDAPv3 escape */ + if (( v2 = hex2value( fval[v+1] )) < 0 ) { + /* must be two digit code */ + return -1; + } + + fval[r++] = v1 * 16 + v2; + v++; + + } else { + /* LDAPv2 escape */ + switch( fval[v] ) { + case '(': + case ')': + case '*': + case '\\': + fval[r++] = fval[v]; + break; + default: + /* illegal escape */ + return -1; + } + } + break; + + default: + fval[r++] = fval[v]; + } + } + + fval[r] = '\0'; + return r; +} + +static char * +put_complex_filter( BerElement *ber, char *str, ber_tag_t tag, int not ) +{ + char *next; + + /* + * We have (x(filter)...) with str sitting on + * the x. We have to find the paren matching + * the one before the x and put the intervening + * filters by calling put_filter_list(). + */ + + /* put explicit tag */ + if ( ber_printf( ber, "t{" /*"}"*/, tag ) == -1 ) { + return NULL; + } + + str++; + if ( (next = find_right_paren( str )) == NULL ) { + return NULL; + } + + *next = '\0'; + if ( put_filter_list( ber, str, tag ) == -1 ) { + return NULL; + } + + /* close the '(' */ + *next++ = ')'; + + /* flush explicit tagged thang */ + if ( ber_printf( ber, /*"{"*/ "N}" ) == -1 ) { + return NULL; + } + + return next; +} + +int +ldap_pvt_put_filter( BerElement *ber, const char *str_in ) +{ + int rc; + char *freeme; + char *str; + char *next; + int parens, balance, escape; + + /* + * A Filter looks like this (RFC 4511 as extended by RFC 4526): + * Filter ::= CHOICE { + * and [0] SET SIZE (0..MAX) OF filter Filter, + * or [1] SET SIZE (0..MAX) OF filter Filter, + * not [2] Filter, + * equalityMatch [3] AttributeValueAssertion, + * substrings [4] SubstringFilter, + * greaterOrEqual [5] AttributeValueAssertion, + * lessOrEqual [6] AttributeValueAssertion, + * present [7] AttributeDescription, + * approxMatch [8] AttributeValueAssertion, + * extensibleMatch [9] MatchingRuleAssertion, + * ... } + * + * SubstringFilter ::= SEQUENCE { + * type AttributeDescription, + * substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE { + * initial [0] AssertionValue, -- only once + * any [1] AssertionValue, + * final [2] AssertionValue -- only once + * } + * } + * + * MatchingRuleAssertion ::= SEQUENCE { + * matchingRule [1] MatchingRuleId OPTIONAL, + * type [2] AttributeDescription OPTIONAL, + * matchValue [3] AssertionValue, + * dnAttributes [4] BOOLEAN DEFAULT FALSE } + * + * Note: tags in a CHOICE are always explicit + */ + + Debug( LDAP_DEBUG_TRACE, "put_filter: \"%s\"\n", str_in, 0, 0 ); + + freeme = LDAP_STRDUP( str_in ); + if( freeme == NULL ) return LDAP_NO_MEMORY; + str = freeme; + + parens = 0; + while ( *str ) { + switch ( *str ) { + case '(': /*')'*/ + str++; + parens++; + + /* skip spaces */ + while( LDAP_SPACE( *str ) ) str++; + + switch ( *str ) { + case '&': + Debug( LDAP_DEBUG_TRACE, "put_filter: AND\n", + 0, 0, 0 ); + + str = put_complex_filter( ber, str, + LDAP_FILTER_AND, 0 ); + if( str == NULL ) { + rc = -1; + goto done; + } + + parens--; + break; + + case '|': + Debug( LDAP_DEBUG_TRACE, "put_filter: OR\n", + 0, 0, 0 ); + + str = put_complex_filter( ber, str, + LDAP_FILTER_OR, 0 ); + if( str == NULL ) { + rc = -1; + goto done; + } + + parens--; + break; + + case '!': + Debug( LDAP_DEBUG_TRACE, "put_filter: NOT\n", + 0, 0, 0 ); + + str = put_complex_filter( ber, str, + LDAP_FILTER_NOT, 0 ); + if( str == NULL ) { + rc = -1; + goto done; + } + + parens--; + break; + + case '(': + rc = -1; + goto done; + + default: + Debug( LDAP_DEBUG_TRACE, "put_filter: simple\n", + 0, 0, 0 ); + + balance = 1; + escape = 0; + next = str; + + while ( *next && balance ) { + if ( escape == 0 ) { + if ( *next == '(' ) { + balance++; + } else if ( *next == ')' ) { + balance--; + } + } + + if ( *next == '\\' && ! escape ) { + escape = 1; + } else { + escape = 0; + } + + if ( balance ) next++; + } + + if ( balance != 0 ) { + rc = -1; + goto done; + } + + *next = '\0'; + + if ( put_simple_filter( ber, str ) == -1 ) { + rc = -1; + goto done; + } + + *next++ = /*'('*/ ')'; + + str = next; + parens--; + break; + } + break; + + case /*'('*/ ')': + Debug( LDAP_DEBUG_TRACE, "put_filter: end\n", + 0, 0, 0 ); + if ( ber_printf( ber, /*"["*/ "]" ) == -1 ) { + rc = -1; + goto done; + } + str++; + parens--; + break; + + case ' ': + str++; + break; + + default: /* assume it's a simple type=value filter */ + Debug( LDAP_DEBUG_TRACE, "put_filter: default\n", + 0, 0, 0 ); + next = strchr( str, '\0' ); + if ( put_simple_filter( ber, str ) == -1 ) { + rc = -1; + goto done; + } + str = next; + break; + } + if ( !parens ) + break; + } + + rc = ( parens || *str ) ? -1 : 0; + +done: + LDAP_FREE( freeme ); + return rc; +} + +/* + * Put a list of filters like this "(filter1)(filter2)..." + */ + +static int +put_filter_list( BerElement *ber, char *str, ber_tag_t tag ) +{ + char *next = NULL; + char save; + + Debug( LDAP_DEBUG_TRACE, "put_filter_list \"%s\"\n", + str, 0, 0 ); + + while ( *str ) { + while ( *str && LDAP_SPACE( (unsigned char) *str ) ) { + str++; + } + if ( *str == '\0' ) break; + + if ( (next = find_right_paren( str + 1 )) == NULL ) { + return -1; + } + save = *++next; + + /* now we have "(filter)" with str pointing to it */ + *next = '\0'; + if ( ldap_pvt_put_filter( ber, str ) == -1 ) return -1; + *next = save; + str = next; + + if( tag == LDAP_FILTER_NOT ) break; + } + + if( tag == LDAP_FILTER_NOT && ( next == NULL || *str )) { + return -1; + } + + return 0; +} + +static int +put_simple_filter( + BerElement *ber, + char *str ) +{ + char *s; + char *value; + ber_tag_t ftype; + int rc = -1; + + Debug( LDAP_DEBUG_TRACE, "put_simple_filter: \"%s\"\n", + str, 0, 0 ); + + str = LDAP_STRDUP( str ); + if( str == NULL ) return -1; + + if ( (s = strchr( str, '=' )) == NULL ) { + goto done; + } + + value = s + 1; + *s-- = '\0'; + + switch ( *s ) { + case '<': + ftype = LDAP_FILTER_LE; + *s = '\0'; + break; + + case '>': + ftype = LDAP_FILTER_GE; + *s = '\0'; + break; + + case '~': + ftype = LDAP_FILTER_APPROX; + *s = '\0'; + break; + + case ':': + /* RFC 4515 extensible filters are off the form: + * type [:dn] [:rule] := value + * or [:dn]:rule := value + */ + ftype = LDAP_FILTER_EXT; + *s = '\0'; + + { + char *dn = strchr( str, ':' ); + char *rule = NULL; + + if( dn != NULL ) { + *dn++ = '\0'; + rule = strchr( dn, ':' ); + + if( rule == NULL ) { + /* one colon */ + if ( strcasecmp(dn, "dn") == 0 ) { + /* must have attribute */ + if( !ldap_is_desc( str ) ) { + goto done; + } + + rule = ""; + + } else { + rule = dn; + dn = NULL; + } + + } else { + /* two colons */ + *rule++ = '\0'; + + if ( strcasecmp(dn, "dn") != 0 ) { + /* must have "dn" */ + goto done; + } + } + + } + + if ( *str == '\0' && ( !rule || *rule == '\0' ) ) { + /* must have either type or rule */ + goto done; + } + + if ( *str != '\0' && !ldap_is_desc( str ) ) { + goto done; + } + + if ( rule && *rule != '\0' && !ldap_is_oid( rule ) ) { + goto done; + } + + rc = ber_printf( ber, "t{" /*"}"*/, ftype ); + + if( rc != -1 && rule && *rule != '\0' ) { + rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_OID, rule ); + } + + if( rc != -1 && *str != '\0' ) { + rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_TYPE, str ); + } + + if( rc != -1 ) { + ber_slen_t len = ldap_pvt_filter_value_unescape( value ); + + if( len >= 0 ) { + rc = ber_printf( ber, "to", + LDAP_FILTER_EXT_VALUE, value, len ); + } else { + rc = -1; + } + } + + if( rc != -1 && dn ) { + rc = ber_printf( ber, "tb", + LDAP_FILTER_EXT_DNATTRS, (ber_int_t) 1 ); + } + + if( rc != -1 ) { + rc = ber_printf( ber, /*"{"*/ "N}" ); + } + } + goto done; + + default: + if( !ldap_is_desc( str ) ) { + goto done; + + } else { + char *nextstar = ldap_pvt_find_wildcard( value ); + + if ( nextstar == NULL ) { + goto done; + + } else if ( *nextstar == '\0' ) { + ftype = LDAP_FILTER_EQUALITY; + + } else if ( strcmp( value, "*" ) == 0 ) { + ftype = LDAP_FILTER_PRESENT; + + } else { + rc = put_substring_filter( ber, str, value, nextstar ); + goto done; + } + } break; + } + + if( !ldap_is_desc( str ) ) goto done; + + if ( ftype == LDAP_FILTER_PRESENT ) { + rc = ber_printf( ber, "ts", ftype, str ); + + } else { + ber_slen_t len = ldap_pvt_filter_value_unescape( value ); + + if( len >= 0 ) { + rc = ber_printf( ber, "t{soN}", + ftype, str, value, len ); + } + } + +done: + if( rc != -1 ) rc = 0; + LDAP_FREE( str ); + return rc; +} + +static int +put_substring_filter( BerElement *ber, char *type, char *val, char *nextstar ) +{ + int gotstar = 0; + ber_tag_t ftype = LDAP_FILTER_SUBSTRINGS; + + Debug( LDAP_DEBUG_TRACE, "put_substring_filter \"%s=%s\"\n", + type, val, 0 ); + + if ( ber_printf( ber, "t{s{" /*"}}"*/, ftype, type ) == -1 ) { + return -1; + } + + for( ; *val; val=nextstar ) { + if ( gotstar ) + nextstar = ldap_pvt_find_wildcard( val ); + + if ( nextstar == NULL ) { + return -1; + } + + if ( *nextstar == '\0' ) { + ftype = LDAP_SUBSTRING_FINAL; + } else { + *nextstar++ = '\0'; + if ( gotstar++ == 0 ) { + ftype = LDAP_SUBSTRING_INITIAL; + } else { + ftype = LDAP_SUBSTRING_ANY; + } + } + + if ( *val != '\0' || ftype == LDAP_SUBSTRING_ANY ) { + ber_slen_t len = ldap_pvt_filter_value_unescape( val ); + + if ( len <= 0 ) { + return -1; + } + + if ( ber_printf( ber, "to", ftype, val, len ) == -1 ) { + return -1; + } + } + } + + if ( ber_printf( ber, /*"{{"*/ "N}N}" ) == -1 ) { + return -1; + } + + return 0; +} + +static int +put_vrFilter( BerElement *ber, const char *str_in ) +{ + int rc; + char *freeme; + char *str; + char *next; + int parens, balance, escape; + + /* + * A ValuesReturnFilter looks like this: + * + * ValuesReturnFilter ::= SEQUENCE OF SimpleFilterItem + * SimpleFilterItem ::= CHOICE { + * equalityMatch [3] AttributeValueAssertion, + * substrings [4] SubstringFilter, + * greaterOrEqual [5] AttributeValueAssertion, + * lessOrEqual [6] AttributeValueAssertion, + * present [7] AttributeType, + * approxMatch [8] AttributeValueAssertion, + * extensibleMatch [9] SimpleMatchingAssertion -- LDAPv3 + * } + * + * SubstringFilter ::= SEQUENCE { + * type AttributeType, + * SEQUENCE OF CHOICE { + * initial [0] IA5String, + * any [1] IA5String, + * final [2] IA5String + * } + * } + * + * SimpleMatchingAssertion ::= SEQUENCE { -- LDAPv3 + * matchingRule [1] MatchingRuleId OPTIONAL, + * type [2] AttributeDescription OPTIONAL, + * matchValue [3] AssertionValue } + * + * (Source: RFC 3876) + */ + + Debug( LDAP_DEBUG_TRACE, "put_vrFilter: \"%s\"\n", str_in, 0, 0 ); + + freeme = LDAP_STRDUP( str_in ); + if( freeme == NULL ) return LDAP_NO_MEMORY; + str = freeme; + + parens = 0; + while ( *str ) { + switch ( *str ) { + case '(': /*')'*/ + str++; + parens++; + + /* skip spaces */ + while( LDAP_SPACE( *str ) ) str++; + + switch ( *str ) { + case '(': + if ( (next = find_right_paren( str )) == NULL ) { + rc = -1; + goto done; + } + + *next = '\0'; + + if ( put_vrFilter_list( ber, str ) == -1 ) { + rc = -1; + goto done; + } + + /* close the '(' */ + *next++ = ')'; + + str = next; + + parens--; + break; + + + default: + Debug( LDAP_DEBUG_TRACE, "put_vrFilter: simple\n", + 0, 0, 0 ); + + balance = 1; + escape = 0; + next = str; + + while ( *next && balance ) { + if ( escape == 0 ) { + if ( *next == '(' ) { + balance++; + } else if ( *next == ')' ) { + balance--; + } + } + + if ( *next == '\\' && ! escape ) { + escape = 1; + } else { + escape = 0; + } + + if ( balance ) next++; + } + + if ( balance != 0 ) { + rc = -1; + goto done; + } + + *next = '\0'; + + if ( put_simple_vrFilter( ber, str ) == -1 ) { + rc = -1; + goto done; + } + + *next++ = /*'('*/ ')'; + + str = next; + parens--; + break; + } + break; + + case /*'('*/ ')': + Debug( LDAP_DEBUG_TRACE, "put_vrFilter: end\n", + 0, 0, 0 ); + if ( ber_printf( ber, /*"["*/ "]" ) == -1 ) { + rc = -1; + goto done; + } + str++; + parens--; + break; + + case ' ': + str++; + break; + + default: /* assume it's a simple type=value filter */ + Debug( LDAP_DEBUG_TRACE, "put_vrFilter: default\n", + 0, 0, 0 ); + next = strchr( str, '\0' ); + if ( put_simple_vrFilter( ber, str ) == -1 ) { + rc = -1; + goto done; + } + str = next; + break; + } + } + + rc = parens ? -1 : 0; + +done: + LDAP_FREE( freeme ); + return rc; +} + +int +ldap_put_vrFilter( BerElement *ber, const char *str_in ) +{ + int rc =0; + + if ( ber_printf( ber, "{" /*"}"*/ ) == -1 ) { + return -1; + } + + rc = put_vrFilter( ber, str_in ); + + if ( ber_printf( ber, /*"{"*/ "N}" ) == -1 ) { + rc = -1; + } + + return rc; +} + +static int +put_vrFilter_list( BerElement *ber, char *str ) +{ + char *next = NULL; + char save; + + Debug( LDAP_DEBUG_TRACE, "put_vrFilter_list \"%s\"\n", + str, 0, 0 ); + + while ( *str ) { + while ( *str && LDAP_SPACE( (unsigned char) *str ) ) { + str++; + } + if ( *str == '\0' ) break; + + if ( (next = find_right_paren( str + 1 )) == NULL ) { + return -1; + } + save = *++next; + + /* now we have "(filter)" with str pointing to it */ + *next = '\0'; + if ( put_vrFilter( ber, str ) == -1 ) return -1; + *next = save; + str = next; + } + + return 0; +} + +static int +put_simple_vrFilter( + BerElement *ber, + char *str ) +{ + char *s; + char *value; + ber_tag_t ftype; + int rc = -1; + + Debug( LDAP_DEBUG_TRACE, "put_simple_vrFilter: \"%s\"\n", + str, 0, 0 ); + + str = LDAP_STRDUP( str ); + if( str == NULL ) return -1; + + if ( (s = strchr( str, '=' )) == NULL ) { + goto done; + } + + value = s + 1; + *s-- = '\0'; + + switch ( *s ) { + case '<': + ftype = LDAP_FILTER_LE; + *s = '\0'; + break; + + case '>': + ftype = LDAP_FILTER_GE; + *s = '\0'; + break; + + case '~': + ftype = LDAP_FILTER_APPROX; + *s = '\0'; + break; + + case ':': + /* According to ValuesReturnFilter control definition + * extensible filters are off the form: + * type [:rule] := value + * or :rule := value + */ + ftype = LDAP_FILTER_EXT; + *s = '\0'; + + { + char *rule = strchr( str, ':' ); + + if( rule == NULL ) { + /* must have attribute */ + if( !ldap_is_desc( str ) ) { + goto done; + } + rule = ""; + } else { + *rule++ = '\0'; + } + + if ( *str == '\0' && ( !rule || *rule == '\0' ) ) { + /* must have either type or rule */ + goto done; + } + + if ( *str != '\0' && !ldap_is_desc( str ) ) { + goto done; + } + + if ( rule && *rule != '\0' && !ldap_is_oid( rule ) ) { + goto done; + } + + rc = ber_printf( ber, "t{" /*"}"*/, ftype ); + + if( rc != -1 && rule && *rule != '\0' ) { + rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_OID, rule ); + } + + if( rc != -1 && *str != '\0' ) { + rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_TYPE, str ); + } + + if( rc != -1 ) { + ber_slen_t len = ldap_pvt_filter_value_unescape( value ); + + if( len >= 0 ) { + rc = ber_printf( ber, "to", + LDAP_FILTER_EXT_VALUE, value, len ); + } else { + rc = -1; + } + } + + if( rc != -1 ) { + rc = ber_printf( ber, /*"{"*/ "N}" ); + } + } + goto done; + + default: + if( !ldap_is_desc( str ) ) { + goto done; + + } else { + char *nextstar = ldap_pvt_find_wildcard( value ); + + if ( nextstar == NULL ) { + goto done; + + } else if ( *nextstar == '\0' ) { + ftype = LDAP_FILTER_EQUALITY; + + } else if ( strcmp( value, "*" ) == 0 ) { + ftype = LDAP_FILTER_PRESENT; + + } else { + rc = put_substring_filter( ber, str, value, nextstar ); + goto done; + } + } break; + } + + if( !ldap_is_desc( str ) ) goto done; + + if ( ftype == LDAP_FILTER_PRESENT ) { + rc = ber_printf( ber, "ts", ftype, str ); + + } else { + ber_slen_t len = ldap_pvt_filter_value_unescape( value ); + + if( len >= 0 ) { + rc = ber_printf( ber, "t{soN}", + ftype, str, value, len ); + } + } + +done: + if( rc != -1 ) rc = 0; + LDAP_FREE( str ); + return rc; +} + diff --git a/libraries/libldap/free.c b/libraries/libldap/free.c new file mode 100644 index 0000000..194d6bc --- /dev/null +++ b/libraries/libldap/free.c @@ -0,0 +1,107 @@ +/* free.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1994 The Regents of the University of Michigan. + * All rights reserved. + */ + +/* + * free.c - some free routines are included here to avoid having to + * link in lots of extra code when not using certain features + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * C-API deallocator + */ +void +ldap_memfree( void *p ) +{ + LDAP_FREE( p ); +} + +void +ldap_memvfree( void **v ) +{ + LDAP_VFREE( v ); +} + +void * +ldap_memalloc( ber_len_t s ) +{ + return LDAP_MALLOC( s ); +} + +void * +ldap_memcalloc( ber_len_t n, ber_len_t s ) +{ + return LDAP_CALLOC( n, s ); +} + +void * +ldap_memrealloc( void* p, ber_len_t s ) +{ + return LDAP_REALLOC( p, s ); +} + +char * +ldap_strdup( LDAP_CONST char *p ) +{ + return LDAP_STRDUP( p ); +} + +/* + * free a null-terminated array of pointers to mod structures. the + * structures are freed, not the array itself, unless the freemods + * flag is set. + */ + +void +ldap_mods_free( LDAPMod **mods, int freemods ) +{ + int i; + + if ( mods == NULL ) + return; + + for ( i = 0; mods[i] != NULL; i++ ) { + if ( mods[i]->mod_op & LDAP_MOD_BVALUES ) { + if( mods[i]->mod_bvalues != NULL ) + ber_bvecfree( mods[i]->mod_bvalues ); + + } else if( mods[i]->mod_values != NULL ) { + LDAP_VFREE( mods[i]->mod_values ); + } + + if ( mods[i]->mod_type != NULL ) { + LDAP_FREE( mods[i]->mod_type ); + } + + LDAP_FREE( (char *) mods[i] ); + } + + if ( freemods ) { + LDAP_FREE( (char *) mods ); + } +} diff --git a/libraries/libldap/ftest.c b/libraries/libldap/ftest.c new file mode 100644 index 0000000..5c014ab --- /dev/null +++ b/libraries/libldap/ftest.c @@ -0,0 +1,119 @@ +/* ftest.c -- OpenLDAP Filter API Test */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include <stdio.h> + +#include <ldap.h> + +#include "ldap_pvt.h" +#include "lber_pvt.h" + +#include "ldif.h" +#include "lutil.h" +#include "lutil_ldap.h" +#include "ldap_defaults.h" + +static int filter2ber( char *filter ); + +int usage() +{ + fprintf( stderr, "usage:\n" + " ftest [-d n] filter\n" + " filter - RFC 4515 string representation of an " + "LDAP search filter\n" ); + return EXIT_FAILURE; +} + +int +main( int argc, char *argv[] ) +{ + int c; + int debug=0; + + while( (c = getopt( argc, argv, "d:" )) != EOF ) { + switch ( c ) { + case 'd': + debug = atoi( optarg ); + break; + default: + fprintf( stderr, "ftest: unrecognized option -%c\n", + optopt ); + return usage(); + } + } + + if ( debug ) { + if ( ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &debug ) + != LBER_OPT_SUCCESS ) + { + fprintf( stderr, "Could not set LBER_OPT_DEBUG_LEVEL %d\n", + debug ); + } + if ( ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &debug ) + != LDAP_OPT_SUCCESS ) + { + fprintf( stderr, "Could not set LDAP_OPT_DEBUG_LEVEL %d\n", + debug ); + } + } + + if ( argc - optind != 1 ) { + return usage(); + } + + return filter2ber( strdup( argv[optind] ) ); +} + +static int filter2ber( char *filter ) +{ + int rc; + struct berval bv = BER_BVNULL; + BerElement *ber; + + printf( "Filter: %s\n", filter ); + + ber = ber_alloc_t( LBER_USE_DER ); + if( ber == NULL ) { + perror( "ber_alloc_t" ); + return EXIT_FAILURE; + } + + rc = ldap_pvt_put_filter( ber, filter ); + if( rc < 0 ) { + fprintf( stderr, "Filter error!\n"); + return EXIT_FAILURE; + } + + rc = ber_flatten2( ber, &bv, 0 ); + if( rc < 0 ) { + perror( "ber_flatten2" ); + return EXIT_FAILURE; + } + + printf( "BER encoding (len=%ld):\n", (long) bv.bv_len ); + ber_bprint( bv.bv_val, bv.bv_len ); + + ber_free( ber, 1 ); + + return EXIT_SUCCESS; +} + diff --git a/libraries/libldap/getattr.c b/libraries/libldap/getattr.c new file mode 100644 index 0000000..8bf130b --- /dev/null +++ b/libraries/libldap/getattr.c @@ -0,0 +1,157 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +char * +ldap_first_attribute( LDAP *ld, LDAPMessage *entry, BerElement **berout ) +{ + int rc; + ber_tag_t tag; + ber_len_t len = 0; + char *attr; + BerElement *ber; + + Debug( LDAP_DEBUG_TRACE, "ldap_first_attribute\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( entry != NULL ); + assert( berout != NULL ); + + *berout = NULL; + + ber = ldap_alloc_ber_with_options( ld ); + if( ber == NULL ) { + return NULL; + } + + *ber = *entry->lm_ber; + + /* + * Skip past the sequence, dn, sequence of sequence leaving + * us at the first attribute. + */ + + tag = ber_scanf( ber, "{xl{" /*}}*/, &len ); + if( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return NULL; + } + + /* set the length to avoid overrun */ + rc = ber_set_option( ber, LBER_OPT_REMAINING_BYTES, &len ); + if( rc != LBER_OPT_SUCCESS ) { + ld->ld_errno = LDAP_LOCAL_ERROR; + ber_free( ber, 0 ); + return NULL; + } + + if ( ber_pvt_ber_remaining( ber ) == 0 ) { + assert( len == 0 ); + ber_free( ber, 0 ); + return NULL; + } + assert( len != 0 ); + + /* snatch the first attribute */ + tag = ber_scanf( ber, "{ax}", &attr ); + if( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return NULL; + } + + *berout = ber; + return attr; +} + +/* ARGSUSED */ +char * +ldap_next_attribute( LDAP *ld, LDAPMessage *entry, BerElement *ber ) +{ + ber_tag_t tag; + char *attr; + + Debug( LDAP_DEBUG_TRACE, "ldap_next_attribute\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( entry != NULL ); + assert( ber != NULL ); + + if ( ber_pvt_ber_remaining( ber ) == 0 ) { + return NULL; + } + + /* skip sequence, snarf attribute type, skip values */ + tag = ber_scanf( ber, "{ax}", &attr ); + if( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return NULL; + } + + return attr; +} + +/* Fetch attribute type and optionally fetch values. The type + * and values are referenced in-place from the BerElement, they are + * not dup'd into malloc'd memory. + */ +/* ARGSUSED */ +int +ldap_get_attribute_ber( LDAP *ld, LDAPMessage *entry, BerElement *ber, + BerValue *attr, BerVarray *vals ) +{ + ber_tag_t tag; + int rc = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "ldap_get_attribute_ber\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( entry != NULL ); + assert( ber != NULL ); + assert( attr != NULL ); + + attr->bv_val = NULL; + attr->bv_len = 0; + + if ( ber_pvt_ber_remaining( ber ) ) { + ber_len_t siz = sizeof( BerValue ); + + /* skip sequence, snarf attribute type */ + tag = ber_scanf( ber, vals ? "{mM}" : "{mx}", attr, vals, + &siz, 0 ); + if( tag == LBER_ERROR ) { + rc = ld->ld_errno = LDAP_DECODING_ERROR; + } + } + + return rc; +} diff --git a/libraries/libldap/getdn.c b/libraries/libldap/getdn.c new file mode 100644 index 0000000..69ff62a --- /dev/null +++ b/libraries/libldap/getdn.c @@ -0,0 +1,3305 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1994 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_schema.h" +#include "ldif.h" + +/* extension to UFN that turns trailing "dc=value" rdns in DNS style, + * e.g. "ou=People,dc=openldap,dc=org" => "People, openldap.org" */ +#define DC_IN_UFN + +/* parsing/printing routines */ +static int str2strval( const char *str, ber_len_t stoplen, struct berval *val, + const char **next, unsigned flags, int *retFlags, void *ctx ); +static int DCE2strval( const char *str, struct berval *val, + const char **next, unsigned flags, void *ctx ); +static int IA52strval( const char *str, struct berval *val, + const char **next, unsigned flags, void *ctx ); +static int quotedIA52strval( const char *str, struct berval *val, + const char **next, unsigned flags, void *ctx ); +static int hexstr2binval( const char *str, struct berval *val, + const char **next, unsigned flags, void *ctx ); +static int hexstr2bin( const char *str, char *c ); +static int byte2hexpair( const char *val, char *pair ); +static int binval2hexstr( struct berval *val, char *str ); +static int strval2strlen( struct berval *val, unsigned flags, + ber_len_t *len ); +static int strval2str( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static int strval2IA5strlen( struct berval *val, unsigned flags, + ber_len_t *len ); +static int strval2IA5str( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static int strval2DCEstrlen( struct berval *val, unsigned flags, + ber_len_t *len ); +static int strval2DCEstr( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static int strval2ADstrlen( struct berval *val, unsigned flags, + ber_len_t *len ); +static int strval2ADstr( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static int dn2domain( LDAPDN dn, struct berval *bv, int pos, int *iRDN ); + +/* AVA helpers */ +static LDAPAVA * ldapava_new( + const struct berval *attr, const struct berval *val, unsigned flags, void *ctx ); + +/* Higher level helpers */ +static int rdn2strlen( LDAPRDN rdn, unsigned flags, ber_len_t *len, + int ( *s2l )( struct berval *, unsigned, ber_len_t * ) ); +static int rdn2str( LDAPRDN rdn, char *str, unsigned flags, ber_len_t *len, + int ( *s2s )( struct berval *, char *, unsigned, ber_len_t * )); +static int rdn2UFNstrlen( LDAPRDN rdn, unsigned flags, ber_len_t *len ); +static int rdn2UFNstr( LDAPRDN rdn, char *str, unsigned flags, ber_len_t *len ); +static int rdn2DCEstrlen( LDAPRDN rdn, unsigned flags, ber_len_t *len ); +static int rdn2DCEstr( LDAPRDN rdn, char *str, unsigned flag, ber_len_t *len, int first ); +static int rdn2ADstrlen( LDAPRDN rdn, unsigned flags, ber_len_t *len ); +static int rdn2ADstr( LDAPRDN rdn, char *str, unsigned flags, ber_len_t *len, int first ); + +/* + * RFC 1823 ldap_get_dn + */ +char * +ldap_get_dn( LDAP *ld, LDAPMessage *entry ) +{ + char *dn; + BerElement tmp; + + Debug( LDAP_DEBUG_TRACE, "ldap_get_dn\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID(ld) ); + assert( entry != NULL ); + + tmp = *entry->lm_ber; /* struct copy */ + if ( ber_scanf( &tmp, "{a" /*}*/, &dn ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + return( dn ); +} + +int +ldap_get_dn_ber( LDAP *ld, LDAPMessage *entry, BerElement **berout, + BerValue *dn ) +{ + BerElement tmp, *ber; + ber_len_t len = 0; + int rc = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "ldap_get_dn_ber\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID(ld) ); + assert( entry != NULL ); + assert( dn != NULL ); + + dn->bv_val = NULL; + dn->bv_len = 0; + + if ( berout ) { + *berout = NULL; + ber = ldap_alloc_ber_with_options( ld ); + if( ber == NULL ) { + return LDAP_NO_MEMORY; + } + *berout = ber; + } else { + ber = &tmp; + } + + *ber = *entry->lm_ber; /* struct copy */ + if ( ber_scanf( ber, "{ml{" /*}*/, dn, &len ) == LBER_ERROR ) { + rc = ld->ld_errno = LDAP_DECODING_ERROR; + } + if ( rc == LDAP_SUCCESS ) { + /* set the length to avoid overrun */ + rc = ber_set_option( ber, LBER_OPT_REMAINING_BYTES, &len ); + if( rc != LBER_OPT_SUCCESS ) { + rc = ld->ld_errno = LDAP_LOCAL_ERROR; + } + } + if ( rc != LDAP_SUCCESS && berout ) { + ber_free( ber, 0 ); + *berout = NULL; + } + return rc; +} + +/* + * RFC 1823 ldap_dn2ufn + */ +char * +ldap_dn2ufn( LDAP_CONST char *dn ) +{ + char *out = NULL; + + Debug( LDAP_DEBUG_TRACE, "ldap_dn2ufn\n", 0, 0, 0 ); + + ( void )ldap_dn_normalize( dn, LDAP_DN_FORMAT_LDAP, + &out, LDAP_DN_FORMAT_UFN ); + + return( out ); +} + +/* + * RFC 1823 ldap_explode_dn + */ +char ** +ldap_explode_dn( LDAP_CONST char *dn, int notypes ) +{ + LDAPDN tmpDN; + char **values = NULL; + int iRDN; + unsigned flag = notypes ? LDAP_DN_FORMAT_UFN : LDAP_DN_FORMAT_LDAPV3; + + Debug( LDAP_DEBUG_TRACE, "ldap_explode_dn\n", 0, 0, 0 ); + + if ( ldap_str2dn( dn, &tmpDN, LDAP_DN_FORMAT_LDAP ) + != LDAP_SUCCESS ) { + return NULL; + } + + if( tmpDN == NULL ) { + values = LDAP_MALLOC( sizeof( char * ) ); + if( values == NULL ) return NULL; + + values[0] = NULL; + return values; + } + + for ( iRDN = 0; tmpDN[ iRDN ]; iRDN++ ); + + values = LDAP_MALLOC( sizeof( char * ) * ( 1 + iRDN ) ); + if ( values == NULL ) { + ldap_dnfree( tmpDN ); + return NULL; + } + + for ( iRDN = 0; tmpDN[ iRDN ]; iRDN++ ) { + ldap_rdn2str( tmpDN[ iRDN ], &values[ iRDN ], flag ); + } + ldap_dnfree( tmpDN ); + values[ iRDN ] = NULL; + + return values; +} + +char ** +ldap_explode_rdn( LDAP_CONST char *rdn, int notypes ) +{ + LDAPRDN tmpRDN; + char **values = NULL; + const char *p; + int iAVA; + + Debug( LDAP_DEBUG_TRACE, "ldap_explode_rdn\n", 0, 0, 0 ); + + /* + * we only parse the first rdn + * FIXME: we prefer efficiency over checking if the _ENTIRE_ + * dn can be parsed + */ + if ( ldap_str2rdn( rdn, &tmpRDN, (char **) &p, LDAP_DN_FORMAT_LDAP ) + != LDAP_SUCCESS ) { + return( NULL ); + } + + for ( iAVA = 0; tmpRDN[ iAVA ]; iAVA++ ) ; + values = LDAP_MALLOC( sizeof( char * ) * ( 1 + iAVA ) ); + if ( values == NULL ) { + ldap_rdnfree( tmpRDN ); + return( NULL ); + } + + for ( iAVA = 0; tmpRDN[ iAVA ]; iAVA++ ) { + ber_len_t l = 0, vl, al = 0; + char *str; + LDAPAVA *ava = tmpRDN[ iAVA ]; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + vl = 1 + 2 * ava->la_value.bv_len; + + } else { + if ( strval2strlen( &ava->la_value, + ava->la_flags, &vl ) ) { + goto error_return; + } + } + + if ( !notypes ) { + al = ava->la_attr.bv_len; + l = vl + ava->la_attr.bv_len + 1; + + str = LDAP_MALLOC( l + 1 ); + AC_MEMCPY( str, ava->la_attr.bv_val, + ava->la_attr.bv_len ); + str[ al++ ] = '='; + + } else { + l = vl; + str = LDAP_MALLOC( l + 1 ); + } + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + str[ al++ ] = '#'; + if ( binval2hexstr( &ava->la_value, &str[ al ] ) ) { + goto error_return; + } + + } else { + if ( strval2str( &ava->la_value, &str[ al ], + ava->la_flags, &vl ) ) { + goto error_return; + } + } + + str[ l ] = '\0'; + values[ iAVA ] = str; + } + values[ iAVA ] = NULL; + + ldap_rdnfree( tmpRDN ); + + return( values ); + +error_return:; + LBER_VFREE( values ); + ldap_rdnfree( tmpRDN ); + return( NULL ); +} + +char * +ldap_dn2dcedn( LDAP_CONST char *dn ) +{ + char *out = NULL; + + Debug( LDAP_DEBUG_TRACE, "ldap_dn2dcedn\n", 0, 0, 0 ); + + ( void )ldap_dn_normalize( dn, LDAP_DN_FORMAT_LDAP, + &out, LDAP_DN_FORMAT_DCE ); + + return( out ); +} + +char * +ldap_dcedn2dn( LDAP_CONST char *dce ) +{ + char *out = NULL; + + Debug( LDAP_DEBUG_TRACE, "ldap_dcedn2dn\n", 0, 0, 0 ); + + ( void )ldap_dn_normalize( dce, LDAP_DN_FORMAT_DCE, &out, LDAP_DN_FORMAT_LDAPV3 ); + + return( out ); +} + +char * +ldap_dn2ad_canonical( LDAP_CONST char *dn ) +{ + char *out = NULL; + + Debug( LDAP_DEBUG_TRACE, "ldap_dn2ad_canonical\n", 0, 0, 0 ); + + ( void )ldap_dn_normalize( dn, LDAP_DN_FORMAT_LDAP, + &out, LDAP_DN_FORMAT_AD_CANONICAL ); + + return( out ); +} + +/* + * function that changes the string representation of dnin + * from ( fin & LDAP_DN_FORMAT_MASK ) to ( fout & LDAP_DN_FORMAT_MASK ) + * + * fin can be one of: + * LDAP_DN_FORMAT_LDAP (RFC 4514 liberal, plus some RFC 1779) + * LDAP_DN_FORMAT_LDAPV3 (RFC 4514) + * LDAP_DN_FORMAT_LDAPV2 (RFC 1779) + * LDAP_DN_FORMAT_DCE (?) + * + * fout can be any of the above except + * LDAP_DN_FORMAT_LDAP + * plus: + * LDAP_DN_FORMAT_UFN (RFC 1781, partial and with extensions) + * LDAP_DN_FORMAT_AD_CANONICAL (?) + */ +int +ldap_dn_normalize( LDAP_CONST char *dnin, + unsigned fin, char **dnout, unsigned fout ) +{ + int rc; + LDAPDN tmpDN = NULL; + + Debug( LDAP_DEBUG_TRACE, "ldap_dn_normalize\n", 0, 0, 0 ); + + assert( dnout != NULL ); + + *dnout = NULL; + + if ( dnin == NULL ) { + return( LDAP_SUCCESS ); + } + + rc = ldap_str2dn( dnin , &tmpDN, fin ); + if ( rc != LDAP_SUCCESS ) { + return( rc ); + } + + rc = ldap_dn2str( tmpDN, dnout, fout ); + + ldap_dnfree( tmpDN ); + + return( rc ); +} + +/* States */ +#define B4AVA 0x0000 + +/* #define B4ATTRTYPE 0x0001 */ +#define B4OIDATTRTYPE 0x0002 +#define B4STRINGATTRTYPE 0x0003 + +#define B4AVAEQUALS 0x0100 +#define B4AVASEP 0x0200 +#define B4RDNSEP 0x0300 +#define GOTAVA 0x0400 + +#define B4ATTRVALUE 0x0010 +#define B4STRINGVALUE 0x0020 +#define B4IA5VALUEQUOTED 0x0030 +#define B4IA5VALUE 0x0040 +#define B4BINARYVALUE 0x0050 + +/* + * Helpers (mostly from slap.h) + * c is assumed to Unicode in an ASCII compatible format (UTF-8) + * Macros assume "C" Locale (ASCII) + */ +#define LDAP_DN_ASCII_SPACE(c) \ + ( (c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' ) +#define LDAP_DN_ASCII_LOWER(c) LDAP_LOWER(c) +#define LDAP_DN_ASCII_UPPER(c) LDAP_UPPER(c) +#define LDAP_DN_ASCII_ALPHA(c) LDAP_ALPHA(c) + +#define LDAP_DN_ASCII_DIGIT(c) LDAP_DIGIT(c) +#define LDAP_DN_ASCII_LCASE_HEXALPHA(c) LDAP_HEXLOWER(c) +#define LDAP_DN_ASCII_UCASE_HEXALPHA(c) LDAP_HEXUPPER(c) +#define LDAP_DN_ASCII_HEXDIGIT(c) LDAP_HEX(c) +#define LDAP_DN_ASCII_ALNUM(c) LDAP_ALNUM(c) +#define LDAP_DN_ASCII_PRINTABLE(c) ( (c) >= ' ' && (c) <= '~' ) + +/* attribute type */ +#define LDAP_DN_OID_LEADCHAR(c) LDAP_DIGIT(c) +#define LDAP_DN_DESC_LEADCHAR(c) LDAP_ALPHA(c) +#define LDAP_DN_DESC_CHAR(c) LDAP_LDH(c) +#define LDAP_DN_LANG_SEP(c) ( (c) == ';' ) +#define LDAP_DN_ATTRDESC_CHAR(c) \ + ( LDAP_DN_DESC_CHAR(c) || LDAP_DN_LANG_SEP(c) ) + +/* special symbols */ +#define LDAP_DN_AVA_EQUALS(c) ( (c) == '=' ) +#define LDAP_DN_AVA_SEP(c) ( (c) == '+' ) +#define LDAP_DN_RDN_SEP(c) ( (c) == ',' ) +#define LDAP_DN_RDN_SEP_V2(c) ( LDAP_DN_RDN_SEP(c) || (c) == ';' ) +#define LDAP_DN_OCTOTHORPE(c) ( (c) == '#' ) +#define LDAP_DN_QUOTES(c) ( (c) == '\"' ) +#define LDAP_DN_ESCAPE(c) ( (c) == '\\' ) +#define LDAP_DN_VALUE_END(c) \ + ( LDAP_DN_RDN_SEP(c) || LDAP_DN_AVA_SEP(c) ) + +/* NOTE: according to RFC 4514, '=' can be escaped and treated as special, + * i.e. escaped both as "\<hexpair>" and * as "\=", but it is treated as + * a regular char, i.e. it can also appear as '='. + * + * As such, in 2.2 we used to allow reading unescaped '=', but we always + * produced escaped '\3D'; this changes since 2.3, if compatibility issues + * do not arise + */ +#define LDAP_DN_NE(c) \ + ( LDAP_DN_RDN_SEP_V2(c) || LDAP_DN_AVA_SEP(c) \ + || LDAP_DN_QUOTES(c) \ + || (c) == '<' || (c) == '>' ) +#define LDAP_DN_MAYESCAPE(c) \ + ( LDAP_DN_ESCAPE(c) || LDAP_DN_NE(c) \ + || LDAP_DN_AVA_EQUALS(c) \ + || LDAP_DN_ASCII_SPACE(c) || LDAP_DN_OCTOTHORPE(c) ) +#define LDAP_DN_SHOULDESCAPE(c) ( LDAP_DN_AVA_EQUALS(c) ) + +#define LDAP_DN_NEEDESCAPE(c) \ + ( LDAP_DN_ESCAPE(c) || LDAP_DN_NE(c) ) +#define LDAP_DN_NEEDESCAPE_LEAD(c) LDAP_DN_MAYESCAPE(c) +#define LDAP_DN_NEEDESCAPE_TRAIL(c) \ + ( LDAP_DN_ASCII_SPACE(c) || LDAP_DN_NEEDESCAPE(c) ) +#define LDAP_DN_WILLESCAPE_CHAR(c) \ + ( LDAP_DN_RDN_SEP(c) || LDAP_DN_AVA_SEP(c) || LDAP_DN_ESCAPE(c) ) +#define LDAP_DN_IS_PRETTY(f) ( (f) & LDAP_DN_PRETTY ) +#define LDAP_DN_WILLESCAPE_HEX(f, c) \ + ( ( !LDAP_DN_IS_PRETTY( f ) ) && LDAP_DN_WILLESCAPE_CHAR(c) ) + +/* LDAPv2 */ +#define LDAP_DN_VALUE_END_V2(c) \ + ( LDAP_DN_RDN_SEP_V2(c) || LDAP_DN_AVA_SEP(c) ) +/* RFC 1779 */ +#define LDAP_DN_V2_SPECIAL(c) \ + ( LDAP_DN_RDN_SEP_V2(c) || LDAP_DN_AVA_EQUALS(c) \ + || LDAP_DN_AVA_SEP(c) || (c) == '<' || (c) == '>' \ + || LDAP_DN_OCTOTHORPE(c) ) +#define LDAP_DN_V2_PAIR(c) \ + ( LDAP_DN_V2_SPECIAL(c) || LDAP_DN_ESCAPE(c) || LDAP_DN_QUOTES(c) ) + +/* + * DCE (mostly from Luke Howard and IBM implementation for AIX) + * + * From: "Application Development Guide - Directory Services" (FIXME: add link?) + * Here escapes and valid chars for GDS are considered; as soon as more + * specific info is found, the macros will be updated. + * + * Chars: 'a'-'z', 'A'-'Z', '0'-'9', + * '.', ':', ',', ''', '+', '-', '=', '(', ')', '?', '/', ' '. + * + * Metachars: '/', ',', '=', '\'. + * + * the '\' is used to escape other metachars. + * + * Assertion: '=' + * RDN separator: '/' + * AVA separator: ',' + * + * Attribute types must start with alphabetic chars and can contain + * alphabetic chars and digits (FIXME: no '-'?). OIDs are allowed. + */ +#define LDAP_DN_RDN_SEP_DCE(c) ( (c) == '/' ) +#define LDAP_DN_AVA_SEP_DCE(c) ( (c) == ',' ) +#define LDAP_DN_ESCAPE_DCE(c) ( LDAP_DN_ESCAPE(c) ) +#define LDAP_DN_VALUE_END_DCE(c) \ + ( LDAP_DN_RDN_SEP_DCE(c) || LDAP_DN_AVA_SEP_DCE(c) ) +#define LDAP_DN_NEEDESCAPE_DCE(c) \ + ( LDAP_DN_VALUE_END_DCE(c) || LDAP_DN_AVA_EQUALS(c) ) + +/* AD Canonical */ +#define LDAP_DN_RDN_SEP_AD(c) ( (c) == '/' ) +#define LDAP_DN_ESCAPE_AD(c) ( LDAP_DN_ESCAPE(c) ) +#define LDAP_DN_AVA_SEP_AD(c) ( (c) == ',' ) /* assume same as DCE */ +#define LDAP_DN_VALUE_END_AD(c) \ + ( LDAP_DN_RDN_SEP_AD(c) || LDAP_DN_AVA_SEP_AD(c) ) +#define LDAP_DN_NEEDESCAPE_AD(c) \ + ( LDAP_DN_VALUE_END_AD(c) || LDAP_DN_AVA_EQUALS(c) ) + +/* generics */ +#define LDAP_DN_HEXPAIR(s) \ + ( LDAP_DN_ASCII_HEXDIGIT((s)[0]) && LDAP_DN_ASCII_HEXDIGIT((s)[1]) ) +/* better look at the AttributeDescription? */ + +/* FIXME: no composite rdn or non-"dc" types, right? + * (what about "dc" in OID form?) */ +/* FIXME: we do not allow binary values in domain, right? */ +/* NOTE: use this macro only when ABSOLUTELY SURE rdn IS VALID! */ +/* NOTE: don't use strcasecmp() as it is locale specific! */ +#define LDAP_DC_ATTR "dc" +#define LDAP_DC_ATTRU "DC" +#define LDAP_DN_IS_RDN_DC( r ) \ + ( (r) && (r)[0] && !(r)[1] \ + && ((r)[0]->la_flags & LDAP_AVA_STRING) \ + && ((r)[0]->la_attr.bv_len == 2) \ + && (((r)[0]->la_attr.bv_val[0] == LDAP_DC_ATTR[0]) \ + || ((r)[0]->la_attr.bv_val[0] == LDAP_DC_ATTRU[0])) \ + && (((r)[0]->la_attr.bv_val[1] == LDAP_DC_ATTR[1]) \ + || ((r)[0]->la_attr.bv_val[1] == LDAP_DC_ATTRU[1]))) + +/* Composite rules */ +#define LDAP_DN_ALLOW_ONE_SPACE(f) \ + ( LDAP_DN_LDAPV2(f) \ + || !( (f) & LDAP_DN_P_NOSPACEAFTERRDN ) ) +#define LDAP_DN_ALLOW_SPACES(f) \ + ( LDAP_DN_LDAPV2(f) \ + || !( (f) & ( LDAP_DN_P_NOLEADTRAILSPACES | LDAP_DN_P_NOSPACEAFTERRDN ) ) ) +#define LDAP_DN_LDAP(f) \ + ( ( (f) & LDAP_DN_FORMAT_MASK ) == LDAP_DN_FORMAT_LDAP ) +#define LDAP_DN_LDAPV3(f) \ + ( ( (f) & LDAP_DN_FORMAT_MASK ) == LDAP_DN_FORMAT_LDAPV3 ) +#define LDAP_DN_LDAPV2(f) \ + ( ( (f) & LDAP_DN_FORMAT_MASK ) == LDAP_DN_FORMAT_LDAPV2 ) +#define LDAP_DN_DCE(f) \ + ( ( (f) & LDAP_DN_FORMAT_MASK ) == LDAP_DN_FORMAT_DCE ) +#define LDAP_DN_UFN(f) \ + ( ( (f) & LDAP_DN_FORMAT_MASK ) == LDAP_DN_FORMAT_UFN ) +#define LDAP_DN_ADC(f) \ + ( ( (f) & LDAP_DN_FORMAT_MASK ) == LDAP_DN_FORMAT_AD_CANONICAL ) +#define LDAP_DN_FORMAT(f) ( (f) & LDAP_DN_FORMAT_MASK ) + +/* + * LDAPAVA helpers (will become part of the API for operations + * on structural representations of DNs). + */ +static LDAPAVA * +ldapava_new( const struct berval *attr, const struct berval *val, + unsigned flags, void *ctx ) +{ + LDAPAVA *ava; + + assert( attr != NULL ); + assert( val != NULL ); + + ava = LDAP_MALLOCX( sizeof( LDAPAVA ) + attr->bv_len + 1, ctx ); + + if ( ava ) { + ava->la_attr.bv_len = attr->bv_len; + ava->la_attr.bv_val = (char *)(ava+1); + AC_MEMCPY( ava->la_attr.bv_val, attr->bv_val, attr->bv_len ); + ava->la_attr.bv_val[attr->bv_len] = '\0'; + + ava->la_value = *val; + ava->la_flags = flags | LDAP_AVA_FREE_VALUE; + + ava->la_private = NULL; + } + + return( ava ); +} + +static void +ldapava_free( LDAPAVA *ava, void *ctx ) +{ + assert( ava != NULL ); + +#if 0 + /* ava's private must be freed by caller + * (at present let's skip this check because la_private + * basically holds static data) */ + assert( ava->la_private == NULL ); +#endif + + if (ava->la_flags & LDAP_AVA_FREE_VALUE) + LDAP_FREEX( ava->la_value.bv_val, ctx ); + + LDAP_FREEX( ava, ctx ); +} + +void +ldap_rdnfree( LDAPRDN rdn ) +{ + ldap_rdnfree_x( rdn, NULL ); +} + +void +ldap_rdnfree_x( LDAPRDN rdn, void *ctx ) +{ + int iAVA; + + if ( rdn == NULL ) { + return; + } + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + ldapava_free( rdn[ iAVA ], ctx ); + } + + LDAP_FREEX( rdn, ctx ); +} + +void +ldap_dnfree( LDAPDN dn ) +{ + ldap_dnfree_x( dn, NULL ); +} + +void +ldap_dnfree_x( LDAPDN dn, void *ctx ) +{ + int iRDN; + + if ( dn == NULL ) { + return; + } + + for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) { + ldap_rdnfree_x( dn[ iRDN ], ctx ); + } + + LDAP_FREEX( dn, ctx ); +} + +/* + * Converts a string representation of a DN (in LDAPv3, LDAPv2 or DCE) + * into a structural representation of the DN, by separating attribute + * types and values encoded in the more appropriate form, which is + * string or OID for attribute types and binary form of the BER encoded + * value or Unicode string. Formats different from LDAPv3 are parsed + * according to their own rules and turned into the more appropriate + * form according to LDAPv3. + * + * NOTE: I realize the code is getting spaghettish; it is rather + * experimental and will hopefully turn into something more simple + * and readable as soon as it works as expected. + */ + +/* + * Default sizes of AVA and RDN static working arrays; if required + * the are dynamically resized. The values can be tuned in case + * of special requirements (e.g. very deep DN trees or high number + * of AVAs per RDN). + */ +#define TMP_AVA_SLOTS 8 +#define TMP_RDN_SLOTS 32 + +int +ldap_str2dn( LDAP_CONST char *str, LDAPDN *dn, unsigned flags ) +{ + struct berval bv; + + assert( str != NULL ); + + bv.bv_len = strlen( str ); + bv.bv_val = (char *) str; + + return ldap_bv2dn_x( &bv, dn, flags, NULL ); +} + +int +ldap_bv2dn( struct berval *bv, LDAPDN *dn, unsigned flags ) +{ + return ldap_bv2dn_x( bv, dn, flags, NULL ); +} + +int +ldap_bv2dn_x( struct berval *bvin, LDAPDN *dn, unsigned flags, void *ctx ) +{ + const char *p; + int rc = LDAP_DECODING_ERROR; + int nrdns = 0; + + LDAPDN newDN = NULL; + LDAPRDN newRDN = NULL, tmpDN_[TMP_RDN_SLOTS], *tmpDN = tmpDN_; + int num_slots = TMP_RDN_SLOTS; + char *str, *end; + struct berval bvtmp, *bv = &bvtmp; + + assert( bvin != NULL ); + assert( bvin->bv_val != NULL ); + assert( dn != NULL ); + + *bv = *bvin; + str = bv->bv_val; + end = str + bv->bv_len; + + Debug( LDAP_DEBUG_ARGS, "=> ldap_bv2dn(%s,%u)\n", str, flags, 0 ); + + *dn = NULL; + + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAP: + case LDAP_DN_FORMAT_LDAPV3: + case LDAP_DN_FORMAT_DCE: + break; + + /* allow DN enclosed in brackets */ + case LDAP_DN_FORMAT_LDAPV2: + if ( str[0] == '<' ) { + if ( bv->bv_len < 2 || end[ -1 ] != '>' ) { + rc = LDAP_DECODING_ERROR; + goto parsing_error; + } + bv->bv_val++; + bv->bv_len -= 2; + str++; + end--; + } + break; + + /* unsupported in str2dn */ + case LDAP_DN_FORMAT_UFN: + case LDAP_DN_FORMAT_AD_CANONICAL: + return LDAP_PARAM_ERROR; + + case LDAP_DN_FORMAT_LBER: + default: + return LDAP_PARAM_ERROR; + } + + if ( bv->bv_len == 0 ) { + return LDAP_SUCCESS; + } + + if( memchr( bv->bv_val, '\0', bv->bv_len ) != NULL ) { + /* value must have embedded NULs */ + return LDAP_DECODING_ERROR; + } + + p = str; + if ( LDAP_DN_DCE( flags ) ) { + + /* + * (from Luke Howard: thnx) A RDN separator is required + * at the beginning of an (absolute) DN. + */ + if ( !LDAP_DN_RDN_SEP_DCE( p[ 0 ] ) ) { + goto parsing_error; + } + p++; + + /* + * actually we do not want to accept by default the DCE form, + * we do not want to auto-detect it + */ +#if 0 + } else if ( LDAP_DN_LDAP( flags ) ) { + /* + * if dn starts with '/' let's make it a DCE dn + */ + if ( LDAP_DN_RDN_SEP_DCE( p[ 0 ] ) ) { + flags |= LDAP_DN_FORMAT_DCE; + p++; + } +#endif + } + + for ( ; p < end; p++ ) { + int err; + struct berval tmpbv; + tmpbv.bv_len = bv->bv_len - ( p - str ); + tmpbv.bv_val = (char *)p; + + err = ldap_bv2rdn_x( &tmpbv, &newRDN, (char **) &p, flags,ctx); + if ( err != LDAP_SUCCESS ) { + goto parsing_error; + } + + /* + * We expect a rdn separator + */ + if ( p < end && p[ 0 ] ) { + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAPV3: + if ( !LDAP_DN_RDN_SEP( p[ 0 ] ) ) { + rc = LDAP_DECODING_ERROR; + goto parsing_error; + } + break; + + case LDAP_DN_FORMAT_LDAP: + case LDAP_DN_FORMAT_LDAPV2: + if ( !LDAP_DN_RDN_SEP_V2( p[ 0 ] ) ) { + rc = LDAP_DECODING_ERROR; + goto parsing_error; + } + break; + + case LDAP_DN_FORMAT_DCE: + if ( !LDAP_DN_RDN_SEP_DCE( p[ 0 ] ) ) { + rc = LDAP_DECODING_ERROR; + goto parsing_error; + } + break; + } + } + + + tmpDN[nrdns++] = newRDN; + newRDN = NULL; + + /* + * make the static RDN array dynamically rescalable + */ + if ( nrdns == num_slots ) { + LDAPRDN *tmp; + + if ( tmpDN == tmpDN_ ) { + tmp = LDAP_MALLOCX( num_slots * 2 * sizeof( LDAPRDN * ), ctx ); + if ( tmp == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + AC_MEMCPY( tmp, tmpDN, num_slots * sizeof( LDAPRDN * ) ); + + } else { + tmp = LDAP_REALLOCX( tmpDN, num_slots * 2 * sizeof( LDAPRDN * ), ctx ); + if ( tmp == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + } + + tmpDN = tmp; + num_slots *= 2; + } + + if ( p >= end || p[ 0 ] == '\0' ) { + /* + * the DN is over, phew + */ + newDN = (LDAPDN)LDAP_MALLOCX( sizeof(LDAPRDN *) * (nrdns+1), ctx ); + if ( newDN == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } else { + int i; + + if ( LDAP_DN_DCE( flags ) ) { + /* add in reversed order */ + for ( i=0; i<nrdns; i++ ) + newDN[i] = tmpDN[nrdns-1-i]; + } else { + for ( i=0; i<nrdns; i++ ) + newDN[i] = tmpDN[i]; + } + newDN[nrdns] = NULL; + rc = LDAP_SUCCESS; + } + goto return_result; + } + } + +parsing_error:; + if ( newRDN ) { + ldap_rdnfree_x( newRDN, ctx ); + } + + for ( nrdns-- ;nrdns >= 0; nrdns-- ) { + ldap_rdnfree_x( tmpDN[nrdns], ctx ); + } + +return_result:; + + if ( tmpDN != tmpDN_ ) { + LDAP_FREEX( tmpDN, ctx ); + } + + Debug( LDAP_DEBUG_ARGS, "<= ldap_bv2dn(%s)=%d %s\n", str, rc, + rc ? ldap_err2string( rc ) : "" ); + *dn = newDN; + + return( rc ); +} + +/* + * ldap_str2rdn + * + * Parses a relative DN according to flags up to a rdn separator + * or to the end of str. + * Returns the rdn and a pointer to the string continuation, which + * corresponds to the rdn separator or to '\0' in case the string is over. + */ +int +ldap_str2rdn( LDAP_CONST char *str, LDAPRDN *rdn, + char **n_in, unsigned flags ) +{ + struct berval bv; + + assert( str != NULL ); + assert( str[ 0 ] != '\0' ); /* FIXME: is this required? */ + + bv.bv_len = strlen( str ); + bv.bv_val = (char *) str; + + return ldap_bv2rdn_x( &bv, rdn, n_in, flags, NULL ); +} + +int +ldap_bv2rdn( struct berval *bv, LDAPRDN *rdn, + char **n_in, unsigned flags ) +{ + return ldap_bv2rdn_x( bv, rdn, n_in, flags, NULL ); +} + +int +ldap_bv2rdn_x( struct berval *bv, LDAPRDN *rdn, + char **n_in, unsigned flags, void *ctx ) +{ + const char **n = (const char **) n_in; + const char *p; + int navas = 0; + int state = B4AVA; + int rc = LDAP_DECODING_ERROR; + int attrTypeEncoding = LDAP_AVA_STRING, + attrValueEncoding = LDAP_AVA_STRING; + + struct berval attrType = BER_BVNULL; + struct berval attrValue = BER_BVNULL; + + LDAPRDN newRDN = NULL; + LDAPAVA *tmpRDN_[TMP_AVA_SLOTS], **tmpRDN = tmpRDN_; + int num_slots = TMP_AVA_SLOTS; + + char *str; + ber_len_t stoplen; + + assert( bv != NULL ); + assert( bv->bv_len != 0 ); + assert( bv->bv_val != NULL ); + assert( rdn || flags & LDAP_DN_SKIP ); + assert( n != NULL ); + + str = bv->bv_val; + stoplen = bv->bv_len; + + if ( rdn ) { + *rdn = NULL; + } + *n = NULL; + + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAP: + case LDAP_DN_FORMAT_LDAPV3: + case LDAP_DN_FORMAT_LDAPV2: + case LDAP_DN_FORMAT_DCE: + break; + + /* unsupported in str2dn */ + case LDAP_DN_FORMAT_UFN: + case LDAP_DN_FORMAT_AD_CANONICAL: + return LDAP_PARAM_ERROR; + + case LDAP_DN_FORMAT_LBER: + default: + return LDAP_PARAM_ERROR; + } + + if ( bv->bv_len == 0 ) { + return LDAP_SUCCESS; + + } + + if( memchr( bv->bv_val, '\0', bv->bv_len ) != NULL ) { + /* value must have embedded NULs */ + return LDAP_DECODING_ERROR; + } + + p = str; + for ( ; p[ 0 ] || state == GOTAVA; ) { + + /* + * The parser in principle advances one token a time, + * or toggles state if preferable. + */ + switch (state) { + + /* + * an AttributeType can be encoded as: + * - its string representation; in detail, implementations + * MUST recognize AttributeType string type names listed + * in Section 3 of RFC 4514, and MAY recognize other names. + * - its numeric OID (a dotted decimal string) + */ + case B4AVA: + if ( LDAP_DN_ASCII_SPACE( p[ 0 ] ) ) { + if ( !LDAP_DN_ALLOW_ONE_SPACE( flags ) ) { + /* error */ + goto parsing_error; + } + p++; + } + + if ( LDAP_DN_ASCII_SPACE( p[ 0 ] ) ) { + if ( !LDAP_DN_ALLOW_SPACES( flags ) ) { + /* error */ + goto parsing_error; + } + + /* whitespace is allowed (and trimmed) */ + p++; + while ( p[ 0 ] && LDAP_DN_ASCII_SPACE( p[ 0 ] ) ) { + p++; + } + + if ( !p[ 0 ] ) { + /* error: we expected an AVA */ + goto parsing_error; + } + } + + /* oid */ + if ( LDAP_DN_OID_LEADCHAR( p[ 0 ] ) ) { + state = B4OIDATTRTYPE; + break; + } + + /* else must be alpha */ + if ( !LDAP_DN_DESC_LEADCHAR( p[ 0 ] ) ) { + goto parsing_error; + } + + /* LDAPv2 "oid." prefix */ + if ( LDAP_DN_LDAPV2( flags ) ) { + /* + * to be overly pedantic, we only accept + * "OID." or "oid." + */ + if ( flags & LDAP_DN_PEDANTIC ) { + if ( !strncmp( p, "OID.", 4 ) + || !strncmp( p, "oid.", 4 ) ) { + p += 4; + state = B4OIDATTRTYPE; + break; + } + } else { + if ( !strncasecmp( p, "oid.", 4 ) ) { + p += 4; + state = B4OIDATTRTYPE; + break; + } + } + } + + state = B4STRINGATTRTYPE; + break; + + case B4OIDATTRTYPE: { + int err = LDAP_SUCCESS; + + attrType.bv_val = ldap_int_parse_numericoid( &p, &err, + LDAP_SCHEMA_SKIP); + + if ( err != LDAP_SUCCESS ) { + goto parsing_error; + } + attrType.bv_len = p - attrType.bv_val; + + attrTypeEncoding = LDAP_AVA_BINARY; + + state = B4AVAEQUALS; + break; + } + + case B4STRINGATTRTYPE: { + const char *startPos, *endPos = NULL; + ber_len_t len; + + /* + * the starting char has been found to be + * a LDAP_DN_DESC_LEADCHAR so we don't re-check it + * FIXME: DCE attr types seem to have a more + * restrictive syntax (no '-' ...) + */ + for ( startPos = p++; p[ 0 ]; p++ ) { + if ( LDAP_DN_DESC_CHAR( p[ 0 ] ) ) { + continue; + } + + if ( LDAP_DN_LANG_SEP( p[ 0 ] ) ) { + + /* + * RFC 4514 explicitly does not allow attribute + * description options, such as language tags. + */ + if ( flags & LDAP_DN_PEDANTIC ) { + goto parsing_error; + } + + /* + * we trim ';' and following lang + * and so from attribute types + */ + endPos = p; + for ( ; LDAP_DN_ATTRDESC_CHAR( p[ 0 ] ) + || LDAP_DN_LANG_SEP( p[ 0 ] ); p++ ) { + /* no op */ ; + } + break; + } + break; + } + + len = ( endPos ? endPos : p ) - startPos; + if ( len == 0 ) { + goto parsing_error; + } + + attrTypeEncoding = LDAP_AVA_STRING; + + /* + * here we need to decide whether to use it as is + * or turn it in OID form; as a consequence, we + * need to decide whether to binary encode the value + */ + + state = B4AVAEQUALS; + + if ( flags & LDAP_DN_SKIP ) { + break; + } + + attrType.bv_val = (char *)startPos; + attrType.bv_len = len; + + break; + } + + case B4AVAEQUALS: + /* spaces may not be allowed */ + if ( LDAP_DN_ASCII_SPACE( p[ 0 ] ) ) { + if ( !LDAP_DN_ALLOW_SPACES( flags ) ) { + goto parsing_error; + } + + /* trim spaces */ + for ( p++; LDAP_DN_ASCII_SPACE( p[ 0 ] ); p++ ) { + /* no op */ + } + } + + /* need equal sign */ + if ( !LDAP_DN_AVA_EQUALS( p[ 0 ] ) ) { + goto parsing_error; + } + p++; + + /* spaces may not be allowed */ + if ( LDAP_DN_ASCII_SPACE( p[ 0 ] ) ) { + if ( !LDAP_DN_ALLOW_SPACES( flags ) ) { + goto parsing_error; + } + + /* trim spaces */ + for ( p++; LDAP_DN_ASCII_SPACE( p[ 0 ] ); p++ ) { + /* no op */ + } + } + + /* + * octothorpe means a BER encoded value will follow + * FIXME: I don't think DCE will allow it + */ + if ( LDAP_DN_OCTOTHORPE( p[ 0 ] ) ) { + p++; + attrValueEncoding = LDAP_AVA_BINARY; + state = B4BINARYVALUE; + break; + } + + /* STRING value expected */ + + /* + * if we're pedantic, an attribute type in OID form + * SHOULD imply a BER encoded attribute value; we + * should at least issue a warning + */ + if ( ( flags & LDAP_DN_PEDANTIC ) + && ( attrTypeEncoding == LDAP_AVA_BINARY ) ) { + /* OID attrType SHOULD use binary encoding */ + goto parsing_error; + } + + attrValueEncoding = LDAP_AVA_STRING; + + /* + * LDAPv2 allows the attribute value to be quoted; + * also, IA5 values are expected, in principle + */ + if ( LDAP_DN_LDAPV2( flags ) || LDAP_DN_LDAP( flags ) ) { + if ( LDAP_DN_QUOTES( p[ 0 ] ) ) { + p++; + state = B4IA5VALUEQUOTED; + break; + } + + if ( LDAP_DN_LDAPV2( flags ) ) { + state = B4IA5VALUE; + break; + } + } + + /* + * here STRING means RFC 4514 string + * FIXME: what about DCE strings? + */ + if ( !p[ 0 ] ) { + /* empty value */ + state = GOTAVA; + } else { + state = B4STRINGVALUE; + } + break; + + case B4BINARYVALUE: + if ( hexstr2binval( p, &attrValue, &p, flags, ctx ) ) { + goto parsing_error; + } + + state = GOTAVA; + break; + + case B4STRINGVALUE: + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAP: + case LDAP_DN_FORMAT_LDAPV3: + if ( str2strval( p, stoplen - ( p - str ), + &attrValue, &p, flags, + &attrValueEncoding, ctx ) ) { + goto parsing_error; + } + break; + + case LDAP_DN_FORMAT_DCE: + if ( DCE2strval( p, &attrValue, &p, flags, ctx ) ) { + goto parsing_error; + } + break; + + default: + assert( 0 ); + } + + state = GOTAVA; + break; + + case B4IA5VALUE: + if ( IA52strval( p, &attrValue, &p, flags, ctx ) ) { + goto parsing_error; + } + + state = GOTAVA; + break; + + case B4IA5VALUEQUOTED: + + /* lead quote already stripped */ + if ( quotedIA52strval( p, &attrValue, + &p, flags, ctx ) ) { + goto parsing_error; + } + + state = GOTAVA; + break; + + case GOTAVA: { + int rdnsep = 0; + + if ( !( flags & LDAP_DN_SKIP ) ) { + LDAPAVA *ava; + + /* + * we accept empty values + */ + ava = ldapava_new( &attrType, &attrValue, + attrValueEncoding, ctx ); + if ( ava == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + tmpRDN[navas++] = ava; + + attrValue.bv_val = NULL; + attrValue.bv_len = 0; + + /* + * prepare room for new AVAs if needed + */ + if (navas == num_slots) { + LDAPAVA **tmp; + + if ( tmpRDN == tmpRDN_ ) { + tmp = LDAP_MALLOCX( num_slots * 2 * sizeof( LDAPAVA * ), ctx ); + if ( tmp == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + AC_MEMCPY( tmp, tmpRDN, num_slots * sizeof( LDAPAVA * ) ); + + } else { + tmp = LDAP_REALLOCX( tmpRDN, num_slots * 2 * sizeof( LDAPAVA * ), ctx ); + if ( tmp == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + } + + tmpRDN = tmp; + num_slots *= 2; + } + } + + /* + * if we got an AVA separator ('+', or ',' for DCE ) + * we expect a new AVA for this RDN; otherwise + * we add the RDN to the DN + */ + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAP: + case LDAP_DN_FORMAT_LDAPV3: + case LDAP_DN_FORMAT_LDAPV2: + if ( !LDAP_DN_AVA_SEP( p[ 0 ] ) ) { + rdnsep = 1; + } + break; + + case LDAP_DN_FORMAT_DCE: + if ( !LDAP_DN_AVA_SEP_DCE( p[ 0 ] ) ) { + rdnsep = 1; + } + break; + } + + if ( rdnsep ) { + /* + * the RDN is over, phew + */ + *n = p; + if ( !( flags & LDAP_DN_SKIP ) ) { + newRDN = (LDAPRDN)LDAP_MALLOCX( + sizeof(LDAPAVA) * (navas+1), ctx ); + if ( newRDN == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } else { + AC_MEMCPY( newRDN, tmpRDN, sizeof(LDAPAVA *) * navas); + newRDN[navas] = NULL; + } + + } + rc = LDAP_SUCCESS; + goto return_result; + } + + /* they should have been used in an AVA */ + attrType.bv_val = NULL; + attrValue.bv_val = NULL; + + p++; + state = B4AVA; + break; + } + + default: + assert( 0 ); + goto parsing_error; + } + } + *n = p; + +parsing_error:; + /* They are set to NULL after they're used in an AVA */ + + if ( attrValue.bv_val ) { + LDAP_FREEX( attrValue.bv_val, ctx ); + } + + for ( navas-- ; navas >= 0; navas-- ) { + ldapava_free( tmpRDN[navas], ctx ); + } + +return_result:; + + if ( tmpRDN != tmpRDN_ ) { + LDAP_FREEX( tmpRDN, ctx ); + } + + if ( rdn ) { + *rdn = newRDN; + } + + return( rc ); +} + +/* + * reads in a UTF-8 string value, unescaping stuff: + * '\' + LDAP_DN_NEEDESCAPE(c) -> 'c' + * '\' + HEXPAIR(p) -> unhex(p) + */ +static int +str2strval( const char *str, ber_len_t stoplen, struct berval *val, const char **next, unsigned flags, int *retFlags, void *ctx ) +{ + const char *p, *end, *startPos, *endPos = NULL; + ber_len_t len, escapes; + + assert( str != NULL ); + assert( val != NULL ); + assert( next != NULL ); + + *next = NULL; + end = str + stoplen; + for ( startPos = p = str, escapes = 0; p < end; p++ ) { + if ( LDAP_DN_ESCAPE( p[ 0 ] ) ) { + p++; + if ( p[ 0 ] == '\0' ) { + return( 1 ); + } + if ( LDAP_DN_MAYESCAPE( p[ 0 ] ) ) { + escapes++; + continue; + } + + if ( LDAP_DN_HEXPAIR( p ) ) { + char c; + + hexstr2bin( p, &c ); + escapes += 2; + + if ( !LDAP_DN_ASCII_PRINTABLE( c ) ) { + + /* + * we assume the string is UTF-8 + */ + *retFlags = LDAP_AVA_NONPRINTABLE; + } + p++; + + continue; + } + + if ( LDAP_DN_PEDANTIC & flags ) { + return( 1 ); + } + /* + * we do not allow escaping + * of chars that don't need + * to and do not belong to + * HEXDIGITS + */ + return( 1 ); + + } else if ( !LDAP_DN_ASCII_PRINTABLE( p[ 0 ] ) ) { + if ( p[ 0 ] == '\0' ) { + return( 1 ); + } + *retFlags = LDAP_AVA_NONPRINTABLE; + + } else if ( ( LDAP_DN_LDAP( flags ) && LDAP_DN_VALUE_END_V2( p[ 0 ] ) ) + || ( LDAP_DN_LDAPV3( flags ) && LDAP_DN_VALUE_END( p[ 0 ] ) ) ) { + break; + + } else if ( LDAP_DN_NEEDESCAPE( p[ 0 ] ) ) { + /* + * FIXME: maybe we can add + * escapes if not pedantic? + */ + return( 1 ); + } + } + + /* + * we do allow unescaped spaces at the end + * of the value only in non-pedantic mode + */ + if ( p > startPos + 1 && LDAP_DN_ASCII_SPACE( p[ -1 ] ) && + !LDAP_DN_ESCAPE( p[ -2 ] ) ) { + if ( flags & LDAP_DN_PEDANTIC ) { + return( 1 ); + } + + /* strip trailing (unescaped) spaces */ + for ( endPos = p - 1; + endPos > startPos + 1 && + LDAP_DN_ASCII_SPACE( endPos[ -1 ] ) && + !LDAP_DN_ESCAPE( endPos[ -2 ] ); + endPos-- ) { + /* no op */ + } + } + + *next = p; + if ( flags & LDAP_DN_SKIP ) { + return( 0 ); + } + + /* + * FIXME: test memory? + */ + len = ( endPos ? endPos : p ) - startPos - escapes; + val->bv_len = len; + + if ( escapes == 0 ) { + if ( *retFlags & LDAP_AVA_NONPRINTABLE ) { + val->bv_val = LDAP_MALLOCX( len + 1, ctx ); + AC_MEMCPY( val->bv_val, startPos, len ); + val->bv_val[ len ] = '\0'; + } else { + val->bv_val = LDAP_STRNDUPX( startPos, len, ctx ); + } + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOCX( len + 1, ctx ); + for ( s = 0, d = 0; d < len; ) { + if ( LDAP_DN_ESCAPE( startPos[ s ] ) ) { + s++; + if ( LDAP_DN_MAYESCAPE( startPos[ s ] ) ) { + val->bv_val[ d++ ] = + startPos[ s++ ]; + + } else if ( LDAP_DN_HEXPAIR( &startPos[ s ] ) ) { + char c; + + hexstr2bin( &startPos[ s ], &c ); + val->bv_val[ d++ ] = c; + s += 2; + + } else { + /* we should never get here */ + assert( 0 ); + } + + } else { + val->bv_val[ d++ ] = startPos[ s++ ]; + } + } + + val->bv_val[ d ] = '\0'; + assert( d == len ); + } + + return( 0 ); +} + +static int +DCE2strval( const char *str, struct berval *val, const char **next, unsigned flags, void *ctx ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len, escapes; + + assert( str != NULL ); + assert( val != NULL ); + assert( next != NULL ); + + *next = NULL; + + for ( startPos = p = str, escapes = 0; p[ 0 ]; p++ ) { + if ( LDAP_DN_ESCAPE_DCE( p[ 0 ] ) ) { + p++; + if ( LDAP_DN_NEEDESCAPE_DCE( p[ 0 ] ) ) { + escapes++; + + } else { + return( 1 ); + } + + } else if ( LDAP_DN_VALUE_END_DCE( p[ 0 ] ) ) { + break; + } + + /* + * FIXME: can we accept anything else? I guess we need + * to stop if a value is not legal + */ + } + + /* + * (unescaped) trailing spaces are trimmed must be silently ignored; + * so we eat them + */ + if ( p > startPos + 1 && LDAP_DN_ASCII_SPACE( p[ -1 ] ) && + !LDAP_DN_ESCAPE( p[ -2 ] ) ) { + if ( flags & LDAP_DN_PEDANTIC ) { + return( 1 ); + } + + /* strip trailing (unescaped) spaces */ + for ( endPos = p - 1; + endPos > startPos + 1 && + LDAP_DN_ASCII_SPACE( endPos[ -1 ] ) && + !LDAP_DN_ESCAPE( endPos[ -2 ] ); + endPos-- ) { + /* no op */ + } + } + + *next = p; + if ( flags & LDAP_DN_SKIP ) { + return( 0 ); + } + + len = ( endPos ? endPos : p ) - startPos - escapes; + val->bv_len = len; + if ( escapes == 0 ){ + val->bv_val = LDAP_STRNDUPX( startPos, len, ctx ); + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOCX( len + 1, ctx ); + for ( s = 0, d = 0; d < len; ) { + /* + * This point is reached only if escapes + * are properly used, so all we need to + * do is eat them + */ + if ( LDAP_DN_ESCAPE_DCE( startPos[ s ] ) ) { + s++; + + } + val->bv_val[ d++ ] = startPos[ s++ ]; + } + val->bv_val[ d ] = '\0'; + assert( strlen( val->bv_val ) == len ); + } + + return( 0 ); +} + +static int +IA52strval( const char *str, struct berval *val, const char **next, unsigned flags, void *ctx ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len, escapes; + + assert( str != NULL ); + assert( val != NULL ); + assert( next != NULL ); + + *next = NULL; + + /* + * LDAPv2 (RFC 1779) + */ + + for ( startPos = p = str, escapes = 0; p[ 0 ]; p++ ) { + if ( LDAP_DN_ESCAPE( p[ 0 ] ) ) { + p++; + if ( p[ 0 ] == '\0' ) { + return( 1 ); + } + + if ( !LDAP_DN_NEEDESCAPE( p[ 0 ] ) + && ( LDAP_DN_PEDANTIC & flags ) ) { + return( 1 ); + } + escapes++; + + } else if ( LDAP_DN_VALUE_END_V2( p[ 0 ] ) ) { + break; + } + + /* + * FIXME: can we accept anything else? I guess we need + * to stop if a value is not legal + */ + } + + /* strip trailing (unescaped) spaces */ + for ( endPos = p; + endPos > startPos + 1 && + LDAP_DN_ASCII_SPACE( endPos[ -1 ] ) && + !LDAP_DN_ESCAPE( endPos[ -2 ] ); + endPos-- ) { + /* no op */ + } + + *next = p; + if ( flags & LDAP_DN_SKIP ) { + return( 0 ); + } + + len = ( endPos ? endPos : p ) - startPos - escapes; + val->bv_len = len; + if ( escapes == 0 ) { + val->bv_val = LDAP_STRNDUPX( startPos, len, ctx ); + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOCX( len + 1, ctx ); + for ( s = 0, d = 0; d < len; ) { + if ( LDAP_DN_ESCAPE( startPos[ s ] ) ) { + s++; + } + val->bv_val[ d++ ] = startPos[ s++ ]; + } + val->bv_val[ d ] = '\0'; + assert( strlen( val->bv_val ) == len ); + } + + return( 0 ); +} + +static int +quotedIA52strval( const char *str, struct berval *val, const char **next, unsigned flags, void *ctx ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len; + unsigned escapes = 0; + + assert( str != NULL ); + assert( val != NULL ); + assert( next != NULL ); + + *next = NULL; + + /* initial quote already eaten */ + for ( startPos = p = str; p[ 0 ]; p++ ) { + /* + * According to RFC 1779, the quoted value can + * contain escaped as well as unescaped special values; + * as a consequence we tolerate escaped values + * (e.g. '"\,"' -> '\,') and escape unescaped specials + * (e.g. '","' -> '\,'). + */ + if ( LDAP_DN_ESCAPE( p[ 0 ] ) ) { + if ( p[ 1 ] == '\0' ) { + return( 1 ); + } + p++; + + if ( !LDAP_DN_V2_PAIR( p[ 0 ] ) + && ( LDAP_DN_PEDANTIC & flags ) ) { + /* + * do we allow to escape normal chars? + * LDAPv2 does not allow any mechanism + * for escaping chars with '\' and hex + * pair + */ + return( 1 ); + } + escapes++; + + } else if ( LDAP_DN_QUOTES( p[ 0 ] ) ) { + endPos = p; + /* eat closing quotes */ + p++; + break; + } + + /* + * FIXME: can we accept anything else? I guess we need + * to stop if a value is not legal + */ + } + + if ( endPos == NULL ) { + return( 1 ); + } + + /* Strip trailing (unescaped) spaces */ + for ( ; p[ 0 ] && LDAP_DN_ASCII_SPACE( p[ 0 ] ); p++ ) { + /* no op */ + } + + *next = p; + if ( flags & LDAP_DN_SKIP ) { + return( 0 ); + } + + len = endPos - startPos - escapes; + assert( endPos >= startPos + escapes ); + val->bv_len = len; + if ( escapes == 0 ) { + val->bv_val = LDAP_STRNDUPX( startPos, len, ctx ); + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOCX( len + 1, ctx ); + val->bv_len = len; + + for ( s = d = 0; d < len; ) { + if ( LDAP_DN_ESCAPE( str[ s ] ) ) { + s++; + } + val->bv_val[ d++ ] = str[ s++ ]; + } + val->bv_val[ d ] = '\0'; + assert( strlen( val->bv_val ) == len ); + } + + return( 0 ); +} + +static int +hexstr2bin( const char *str, char *c ) +{ + char c1, c2; + + assert( str != NULL ); + assert( c != NULL ); + + c1 = str[ 0 ]; + c2 = str[ 1 ]; + + if ( LDAP_DN_ASCII_DIGIT( c1 ) ) { + *c = c1 - '0'; + + } else { + if ( LDAP_DN_ASCII_UCASE_HEXALPHA( c1 ) ) { + *c = c1 - 'A' + 10; + } else { + assert( LDAP_DN_ASCII_LCASE_HEXALPHA( c1 ) ); + *c = c1 - 'a' + 10; + } + } + + *c <<= 4; + + if ( LDAP_DN_ASCII_DIGIT( c2 ) ) { + *c += c2 - '0'; + + } else { + if ( LDAP_DN_ASCII_UCASE_HEXALPHA( c2 ) ) { + *c += c2 - 'A' + 10; + } else { + assert( LDAP_DN_ASCII_LCASE_HEXALPHA( c2 ) ); + *c += c2 - 'a' + 10; + } + } + + return( 0 ); +} + +static int +hexstr2binval( const char *str, struct berval *val, const char **next, unsigned flags, void *ctx ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len; + ber_len_t s, d; + + assert( str != NULL ); + assert( val != NULL ); + assert( next != NULL ); + + *next = NULL; + + for ( startPos = p = str; p[ 0 ]; p += 2 ) { + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAPV3: + if ( LDAP_DN_VALUE_END( p[ 0 ] ) ) { + goto end_of_value; + } + break; + + case LDAP_DN_FORMAT_LDAP: + case LDAP_DN_FORMAT_LDAPV2: + if ( LDAP_DN_VALUE_END_V2( p[ 0 ] ) ) { + goto end_of_value; + } + break; + + case LDAP_DN_FORMAT_DCE: + if ( LDAP_DN_VALUE_END_DCE( p[ 0 ] ) ) { + goto end_of_value; + } + break; + } + + if ( LDAP_DN_ASCII_SPACE( p[ 0 ] ) ) { + if ( flags & LDAP_DN_PEDANTIC ) { + return( 1 ); + } + endPos = p; + + for ( ; p[ 0 ]; p++ ) { + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAPV3: + if ( LDAP_DN_VALUE_END( p[ 0 ] ) ) { + goto end_of_value; + } + break; + + case LDAP_DN_FORMAT_LDAP: + case LDAP_DN_FORMAT_LDAPV2: + if ( LDAP_DN_VALUE_END_V2( p[ 0 ] ) ) { + goto end_of_value; + } + break; + + case LDAP_DN_FORMAT_DCE: + if ( LDAP_DN_VALUE_END_DCE( p[ 0 ] ) ) { + goto end_of_value; + } + break; + } + } + break; + } + + if ( !LDAP_DN_HEXPAIR( p ) ) { + return( 1 ); + } + } + +end_of_value:; + + *next = p; + if ( flags & LDAP_DN_SKIP ) { + return( 0 ); + } + + len = ( ( endPos ? endPos : p ) - startPos ) / 2; + /* must be even! */ + assert( 2 * len == (ber_len_t) (( endPos ? endPos : p ) - startPos )); + + val->bv_len = len; + val->bv_val = LDAP_MALLOCX( len + 1, ctx ); + if ( val->bv_val == NULL ) { + return( LDAP_NO_MEMORY ); + } + + for ( s = 0, d = 0; d < len; s += 2, d++ ) { + char c; + + hexstr2bin( &startPos[ s ], &c ); + + val->bv_val[ d ] = c; + } + + val->bv_val[ d ] = '\0'; + + return( 0 ); +} + +/* + * convert a byte in a hexadecimal pair + */ +static int +byte2hexpair( const char *val, char *pair ) +{ + static const char hexdig[] = "0123456789ABCDEF"; + + assert( val != NULL ); + assert( pair != NULL ); + + /* + * we assume the string has enough room for the hex encoding + * of the value + */ + + pair[ 0 ] = hexdig[ 0x0f & ( val[ 0 ] >> 4 ) ]; + pair[ 1 ] = hexdig[ 0x0f & val[ 0 ] ]; + + return( 0 ); +} + +/* + * convert a binary value in hexadecimal pairs + */ +static int +binval2hexstr( struct berval *val, char *str ) +{ + ber_len_t s, d; + + assert( val != NULL ); + assert( str != NULL ); + + if ( val->bv_len == 0 ) { + return( 0 ); + } + + /* + * we assume the string has enough room for the hex encoding + * of the value + */ + + for ( s = 0, d = 0; s < val->bv_len; s++, d += 2 ) { + byte2hexpair( &val->bv_val[ s ], &str[ d ] ); + } + + return( 0 ); +} + +/* + * Length of the string representation, accounting for escaped hex + * of UTF-8 chars + */ +static int +strval2strlen( struct berval *val, unsigned flags, ber_len_t *len ) +{ + ber_len_t l, cl = 1; + char *p, *end; + int escaped_byte_len = LDAP_DN_IS_PRETTY( flags ) ? 1 : 3; +#ifdef PRETTY_ESCAPE + int escaped_ascii_len = LDAP_DN_IS_PRETTY( flags ) ? 2 : 3; +#endif /* PRETTY_ESCAPE */ + + assert( val != NULL ); + assert( len != NULL ); + + *len = 0; + if ( val->bv_len == 0 ) { + return( 0 ); + } + + end = val->bv_val + val->bv_len - 1; + for ( l = 0, p = val->bv_val; p <= end; p += cl ) { + + /* + * escape '%x00' + */ + if ( p[ 0 ] == '\0' ) { + cl = 1; + l += 3; + continue; + } + + cl = LDAP_UTF8_CHARLEN2( p, cl ); + if ( cl == 0 ) { + /* illegal utf-8 char! */ + return( -1 ); + + } else if ( cl > 1 ) { + ber_len_t cnt; + + for ( cnt = 1; cnt < cl; cnt++ ) { + if ( ( p[ cnt ] & 0xc0 ) != 0x80 ) { + return( -1 ); + } + } + l += escaped_byte_len * cl; + + } else if ( LDAP_DN_NEEDESCAPE( p[ 0 ] ) + || LDAP_DN_SHOULDESCAPE( p[ 0 ] ) + || ( p == val->bv_val && LDAP_DN_NEEDESCAPE_LEAD( p[ 0 ] ) ) + || ( p == end && LDAP_DN_NEEDESCAPE_TRAIL( p[ 0 ] ) ) ) { +#ifdef PRETTY_ESCAPE +#if 0 + if ( LDAP_DN_WILLESCAPE_HEX( flags, p[ 0 ] ) ) { +#else + if ( LDAP_DN_WILLESCAPE_CHAR( p[ 0 ] ) ) { +#endif + + /* + * there might be some chars we want + * to escape in form of a couple + * of hexdigits for optimization purposes + */ + l += 3; + + } else { + l += escaped_ascii_len; + } +#else /* ! PRETTY_ESCAPE */ + l += 3; +#endif /* ! PRETTY_ESCAPE */ + + } else { + l++; + } + } + + *len = l; + + return( 0 ); +} + +/* + * convert to string representation, escaping with hex the UTF-8 stuff; + * assume the destination has enough room for escaping + */ +static int +strval2str( struct berval *val, char *str, unsigned flags, ber_len_t *len ) +{ + ber_len_t s, d, end; + + assert( val != NULL ); + assert( str != NULL ); + assert( len != NULL ); + + if ( val->bv_len == 0 ) { + *len = 0; + return( 0 ); + } + + /* + * we assume the string has enough room for the hex encoding + * of the value + */ + for ( s = 0, d = 0, end = val->bv_len - 1; s < val->bv_len; ) { + ber_len_t cl; + + /* + * escape '%x00' + */ + if ( val->bv_val[ s ] == '\0' ) { + cl = 1; + str[ d++ ] = '\\'; + str[ d++ ] = '0'; + str[ d++ ] = '0'; + s++; + continue; + } + + /* + * The length was checked in strval2strlen(); + */ + cl = LDAP_UTF8_CHARLEN( &val->bv_val[ s ] ); + + /* + * there might be some chars we want to escape in form + * of a couple of hexdigits for optimization purposes + */ + if ( ( cl > 1 && !LDAP_DN_IS_PRETTY( flags ) ) +#ifdef PRETTY_ESCAPE +#if 0 + || LDAP_DN_WILLESCAPE_HEX( flags, val->bv_val[ s ] ) +#else + || LDAP_DN_WILLESCAPE_CHAR( val->bv_val[ s ] ) +#endif +#else /* ! PRETTY_ESCAPE */ + || LDAP_DN_NEEDESCAPE( val->bv_val[ s ] ) + || LDAP_DN_SHOULDESCAPE( val->bv_val[ s ] ) + || ( d == 0 && LDAP_DN_NEEDESCAPE_LEAD( val->bv_val[ s ] ) ) + || ( s == end && LDAP_DN_NEEDESCAPE_TRAIL( val->bv_val[ s ] ) ) + +#endif /* ! PRETTY_ESCAPE */ + ) { + for ( ; cl--; ) { + str[ d++ ] = '\\'; + byte2hexpair( &val->bv_val[ s ], &str[ d ] ); + s++; + d += 2; + } + + } else if ( cl > 1 ) { + for ( ; cl--; ) { + str[ d++ ] = val->bv_val[ s++ ]; + } + + } else { +#ifdef PRETTY_ESCAPE + if ( LDAP_DN_NEEDESCAPE( val->bv_val[ s ] ) + || LDAP_DN_SHOULDESCAPE( val->bv_val[ s ] ) + || ( d == 0 && LDAP_DN_NEEDESCAPE_LEAD( val->bv_val[ s ] ) ) + || ( s == end && LDAP_DN_NEEDESCAPE_TRAIL( val->bv_val[ s ] ) ) ) { + str[ d++ ] = '\\'; + if ( !LDAP_DN_IS_PRETTY( flags ) ) { + byte2hexpair( &val->bv_val[ s ], &str[ d ] ); + s++; + d += 2; + continue; + } + } +#endif /* PRETTY_ESCAPE */ + str[ d++ ] = val->bv_val[ s++ ]; + } + } + + *len = d; + + return( 0 ); +} + +/* + * Length of the IA5 string representation (no UTF-8 allowed) + */ +static int +strval2IA5strlen( struct berval *val, unsigned flags, ber_len_t *len ) +{ + ber_len_t l; + char *p; + + assert( val != NULL ); + assert( len != NULL ); + + *len = 0; + if ( val->bv_len == 0 ) { + return( 0 ); + } + + if ( flags & LDAP_AVA_NONPRINTABLE ) { + /* + * Turn value into a binary encoded BER + */ + return( -1 ); + + } else { + for ( l = 0, p = val->bv_val; p[ 0 ]; p++ ) { + if ( LDAP_DN_NEEDESCAPE( p[ 0 ] ) + || LDAP_DN_SHOULDESCAPE( p[ 0 ] ) + || ( p == val->bv_val && LDAP_DN_NEEDESCAPE_LEAD( p[ 0 ] ) ) + || ( !p[ 1 ] && LDAP_DN_NEEDESCAPE_TRAIL( p[ 0 ] ) ) ) { + l += 2; + + } else { + l++; + } + } + } + + *len = l; + + return( 0 ); +} + +/* + * convert to string representation (np UTF-8) + * assume the destination has enough room for escaping + */ +static int +strval2IA5str( struct berval *val, char *str, unsigned flags, ber_len_t *len ) +{ + ber_len_t s, d, end; + + assert( val != NULL ); + assert( str != NULL ); + assert( len != NULL ); + + if ( val->bv_len == 0 ) { + *len = 0; + return( 0 ); + } + + if ( flags & LDAP_AVA_NONPRINTABLE ) { + /* + * Turn value into a binary encoded BER + */ + *len = 0; + return( -1 ); + + } else { + /* + * we assume the string has enough room for the hex encoding + * of the value + */ + + for ( s = 0, d = 0, end = val->bv_len - 1; s < val->bv_len; ) { + if ( LDAP_DN_NEEDESCAPE( val->bv_val[ s ] ) + || LDAP_DN_SHOULDESCAPE( val->bv_val[ s ] ) + || ( s == 0 && LDAP_DN_NEEDESCAPE_LEAD( val->bv_val[ s ] ) ) + || ( s == end && LDAP_DN_NEEDESCAPE_TRAIL( val->bv_val[ s ] ) ) ) { + str[ d++ ] = '\\'; + } + str[ d++ ] = val->bv_val[ s++ ]; + } + } + + *len = d; + + return( 0 ); +} + +/* + * Length of the (supposedly) DCE string representation, + * accounting for escaped hex of UTF-8 chars + */ +static int +strval2DCEstrlen( struct berval *val, unsigned flags, ber_len_t *len ) +{ + ber_len_t l; + char *p; + + assert( val != NULL ); + assert( len != NULL ); + + *len = 0; + if ( val->bv_len == 0 ) { + return( 0 ); + } + + if ( flags & LDAP_AVA_NONPRINTABLE ) { + /* + * FIXME: Turn the value into a binary encoded BER? + */ + return( -1 ); + + } else { + for ( l = 0, p = val->bv_val; p[ 0 ]; p++ ) { + if ( LDAP_DN_NEEDESCAPE_DCE( p[ 0 ] ) ) { + l += 2; + + } else { + l++; + } + } + } + + *len = l; + + return( 0 ); +} + +/* + * convert to (supposedly) DCE string representation, + * escaping with hex the UTF-8 stuff; + * assume the destination has enough room for escaping + */ +static int +strval2DCEstr( struct berval *val, char *str, unsigned flags, ber_len_t *len ) +{ + ber_len_t s, d; + + assert( val != NULL ); + assert( str != NULL ); + assert( len != NULL ); + + if ( val->bv_len == 0 ) { + *len = 0; + return( 0 ); + } + + if ( flags & LDAP_AVA_NONPRINTABLE ) { + /* + * FIXME: Turn the value into a binary encoded BER? + */ + *len = 0; + return( -1 ); + + } else { + + /* + * we assume the string has enough room for the hex encoding + * of the value + */ + + for ( s = 0, d = 0; s < val->bv_len; ) { + if ( LDAP_DN_NEEDESCAPE_DCE( val->bv_val[ s ] ) ) { + str[ d++ ] = '\\'; + } + str[ d++ ] = val->bv_val[ s++ ]; + } + } + + *len = d; + + return( 0 ); +} + +/* + * Length of the (supposedly) AD canonical string representation, + * accounting for chars that need to be escaped + */ +static int +strval2ADstrlen( struct berval *val, unsigned flags, ber_len_t *len ) +{ + ber_len_t l, cl; + char *p; + + assert( val != NULL ); + assert( len != NULL ); + + *len = 0; + if ( val->bv_len == 0 ) { + return( 0 ); + } + + for ( l = 0, p = val->bv_val; p[ 0 ]; p += cl ) { + cl = LDAP_UTF8_CHARLEN2( p, cl ); + if ( cl == 0 ) { + /* illegal utf-8 char */ + return -1; + } else if ( (cl == 1) && LDAP_DN_NEEDESCAPE_AD( p[ 0 ] ) ) { + l += 2; + } else { + l += cl; + } + } + + *len = l; + + return( 0 ); +} + +/* + * convert to (supposedly) AD string representation, + * assume the destination has enough room for escaping + */ +static int +strval2ADstr( struct berval *val, char *str, unsigned flags, ber_len_t *len ) +{ + ber_len_t s, d, cl; + + assert( val != NULL ); + assert( str != NULL ); + assert( len != NULL ); + + if ( val->bv_len == 0 ) { + *len = 0; + return( 0 ); + } + + /* + * we assume the string has enough room for the escaping + * of the value + */ + + for ( s = 0, d = 0; s < val->bv_len; ) { + cl = LDAP_UTF8_CHARLEN2( val->bv_val+s, cl ); + if ( cl == 0 ) { + /* illegal utf-8 char */ + return -1; + } else if ( (cl == 1) && LDAP_DN_NEEDESCAPE_AD(val->bv_val[ s ]) ) { + str[ d++ ] = '\\'; + } + for (; cl--;) { + str[ d++ ] = val->bv_val[ s++ ]; + } + } + + *len = d; + + return( 0 ); +} + +/* + * If the DN is terminated by single-AVA RDNs with attribute type of "dc", + * the first part of the AD representation of the DN is written in DNS + * form, i.e. dot separated domain name components (as suggested + * by Luke Howard, http://www.padl.com/~lukeh) + */ +static int +dn2domain( LDAPDN dn, struct berval *bv, int pos, int *iRDN ) +{ + int i; + int domain = 0, first = 1; + ber_len_t l = 1; /* we move the null also */ + char *str; + + /* we are guaranteed there's enough memory in str */ + + /* sanity */ + assert( dn != NULL ); + assert( bv != NULL ); + assert( iRDN != NULL ); + assert( *iRDN >= 0 ); + + str = bv->bv_val + pos; + + for ( i = *iRDN; i >= 0; i-- ) { + LDAPRDN rdn; + LDAPAVA *ava; + + assert( dn[ i ] != NULL ); + rdn = dn[ i ]; + + assert( rdn[ 0 ] != NULL ); + ava = rdn[ 0 ]; + + if ( !LDAP_DN_IS_RDN_DC( rdn ) ) { + break; + } + + if ( ldif_is_not_printable( ava->la_value.bv_val, ava->la_value.bv_len ) ) { + domain = 0; + break; + } + + domain = 1; + + if ( first ) { + first = 0; + AC_MEMCPY( str, ava->la_value.bv_val, + ava->la_value.bv_len + 1); + l += ava->la_value.bv_len; + + } else { + AC_MEMCPY( str + ava->la_value.bv_len + 1, bv->bv_val + pos, l); + AC_MEMCPY( str, ava->la_value.bv_val, + ava->la_value.bv_len ); + str[ ava->la_value.bv_len ] = '.'; + l += ava->la_value.bv_len + 1; + } + } + + *iRDN = i; + bv->bv_len = pos + l - 1; + + return( domain ); +} + +static int +rdn2strlen( LDAPRDN rdn, unsigned flags, ber_len_t *len, + int ( *s2l )( struct berval *v, unsigned f, ber_len_t *l ) ) +{ + int iAVA; + ber_len_t l = 0; + + *len = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + /* len(type) + '=' + '+' | ',' */ + l += ava->la_attr.bv_len + 2; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the length */ + l += 1 + 2 * ava->la_value.bv_len; + + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( ( *s2l )( &ava->la_value, f, &vl ) ) { + return( -1 ); + } + l += vl; + } + } + + *len = l; + + return( 0 ); +} + +static int +rdn2str( LDAPRDN rdn, char *str, unsigned flags, ber_len_t *len, + int ( *s2s ) ( struct berval *v, char * s, unsigned f, ber_len_t *l ) ) +{ + int iAVA; + ber_len_t l = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + AC_MEMCPY( &str[ l ], ava->la_attr.bv_val, + ava->la_attr.bv_len ); + l += ava->la_attr.bv_len; + + str[ l++ ] = '='; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + str[ l++ ] = '#'; + if ( binval2hexstr( &ava->la_value, &str[ l ] ) ) { + return( -1 ); + } + l += 2 * ava->la_value.bv_len; + + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( ( *s2s )( &ava->la_value, &str[ l ], f, &vl ) ) { + return( -1 ); + } + l += vl; + } + str[ l++ ] = ( rdn[ iAVA + 1] ? '+' : ',' ); + } + + *len = l; + + return( 0 ); +} + +static int +rdn2DCEstrlen( LDAPRDN rdn, unsigned flags, ber_len_t *len ) +{ + int iAVA; + ber_len_t l = 0; + + *len = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + /* len(type) + '=' + ',' | '/' */ + l += ava->la_attr.bv_len + 2; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the length */ + l += 1 + 2 * ava->la_value.bv_len; + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2DCEstrlen( &ava->la_value, f, &vl ) ) { + return( -1 ); + } + l += vl; + } + } + + *len = l; + + return( 0 ); +} + +static int +rdn2DCEstr( LDAPRDN rdn, char *str, unsigned flags, ber_len_t *len, int first ) +{ + int iAVA; + ber_len_t l = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + if ( first ) { + first = 0; + } else { + str[ l++ ] = ( iAVA ? ',' : '/' ); + } + + AC_MEMCPY( &str[ l ], ava->la_attr.bv_val, + ava->la_attr.bv_len ); + l += ava->la_attr.bv_len; + + str[ l++ ] = '='; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + str[ l++ ] = '#'; + if ( binval2hexstr( &ava->la_value, &str[ l ] ) ) { + return( -1 ); + } + l += 2 * ava->la_value.bv_len; + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2DCEstr( &ava->la_value, &str[ l ], f, &vl ) ) { + return( -1 ); + } + l += vl; + } + } + + *len = l; + + return( 0 ); +} + +static int +rdn2UFNstrlen( LDAPRDN rdn, unsigned flags, ber_len_t *len ) +{ + int iAVA; + ber_len_t l = 0; + + assert( rdn != NULL ); + assert( len != NULL ); + + *len = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + /* ' + ' | ', ' */ + l += ( rdn[ iAVA + 1 ] ? 3 : 2 ); + + /* FIXME: are binary values allowed in UFN? */ + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the value */ + l += 1 + 2 * ava->la_value.bv_len; + + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2strlen( &ava->la_value, f, &vl ) ) { + return( -1 ); + } + l += vl; + } + } + + *len = l; + + return( 0 ); +} + +static int +rdn2UFNstr( LDAPRDN rdn, char *str, unsigned flags, ber_len_t *len ) +{ + int iAVA; + ber_len_t l = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + str[ l++ ] = '#'; + if ( binval2hexstr( &ava->la_value, &str[ l ] ) ) { + return( -1 ); + } + l += 2 * ava->la_value.bv_len; + + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2str( &ava->la_value, &str[ l ], f, &vl ) ) { + return( -1 ); + } + l += vl; + } + + if ( rdn[ iAVA + 1 ] ) { + AC_MEMCPY( &str[ l ], " + ", 3 ); + l += 3; + + } else { + AC_MEMCPY( &str[ l ], ", ", 2 ); + l += 2; + } + } + + *len = l; + + return( 0 ); +} + +static int +rdn2ADstrlen( LDAPRDN rdn, unsigned flags, ber_len_t *len ) +{ + int iAVA; + ber_len_t l = 0; + + assert( rdn != NULL ); + assert( len != NULL ); + + *len = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + /* ',' | '/' */ + l++; + + /* FIXME: are binary values allowed in UFN? */ + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the value */ + l += 1 + 2 * ava->la_value.bv_len; + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2ADstrlen( &ava->la_value, f, &vl ) ) { + return( -1 ); + } + l += vl; + } + } + + *len = l; + + return( 0 ); +} + +static int +rdn2ADstr( LDAPRDN rdn, char *str, unsigned flags, ber_len_t *len, int first ) +{ + int iAVA; + ber_len_t l = 0; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + + if ( first ) { + first = 0; + } else { + str[ l++ ] = ( iAVA ? ',' : '/' ); + } + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + str[ l++ ] = '#'; + if ( binval2hexstr( &ava->la_value, &str[ l ] ) ) { + return( -1 ); + } + l += 2 * ava->la_value.bv_len; + } else { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2ADstr( &ava->la_value, &str[ l ], f, &vl ) ) { + return( -1 ); + } + l += vl; + } + } + + *len = l; + + return( 0 ); +} + +/* + * ldap_rdn2str + * + * Returns in str a string representation of rdn based on flags. + * There is some duplication of code between this and ldap_dn2str; + * this is wanted to reduce the allocation of temporary buffers. + */ +int +ldap_rdn2str( LDAPRDN rdn, char **str, unsigned flags ) +{ + struct berval bv; + int rc; + + assert( str != NULL ); + + if((flags & LDAP_DN_FORMAT_MASK) == LDAP_DN_FORMAT_LBER) { + return LDAP_PARAM_ERROR; + } + + rc = ldap_rdn2bv_x( rdn, &bv, flags, NULL ); + *str = bv.bv_val; + return rc; +} + +int +ldap_rdn2bv( LDAPRDN rdn, struct berval *bv, unsigned flags ) +{ + return ldap_rdn2bv_x( rdn, bv, flags, NULL ); +} + +int +ldap_rdn2bv_x( LDAPRDN rdn, struct berval *bv, unsigned flags, void *ctx ) +{ + int rc, back; + ber_len_t l; + + assert( bv != NULL ); + + bv->bv_len = 0; + bv->bv_val = NULL; + + if ( rdn == NULL ) { + bv->bv_val = LDAP_STRDUPX( "", ctx ); + return( LDAP_SUCCESS ); + } + + /* + * This routine wastes "back" bytes at the end of the string + */ + + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAPV3: + if ( rdn2strlen( rdn, flags, &l, strval2strlen ) ) { + return LDAP_DECODING_ERROR; + } + break; + + case LDAP_DN_FORMAT_LDAPV2: + if ( rdn2strlen( rdn, flags, &l, strval2IA5strlen ) ) { + return LDAP_DECODING_ERROR; + } + break; + + case LDAP_DN_FORMAT_UFN: + if ( rdn2UFNstrlen( rdn, flags, &l ) ) { + return LDAP_DECODING_ERROR; + } + break; + + case LDAP_DN_FORMAT_DCE: + if ( rdn2DCEstrlen( rdn, flags, &l ) ) { + return LDAP_DECODING_ERROR; + } + break; + + case LDAP_DN_FORMAT_AD_CANONICAL: + if ( rdn2ADstrlen( rdn, flags, &l ) ) { + return LDAP_DECODING_ERROR; + } + break; + + default: + return LDAP_PARAM_ERROR; + } + + bv->bv_val = LDAP_MALLOCX( l + 1, ctx ); + + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAPV3: + rc = rdn2str( rdn, bv->bv_val, flags, &l, strval2str ); + back = 1; + break; + + case LDAP_DN_FORMAT_LDAPV2: + rc = rdn2str( rdn, bv->bv_val, flags, &l, strval2IA5str ); + back = 1; + break; + + case LDAP_DN_FORMAT_UFN: + rc = rdn2UFNstr( rdn, bv->bv_val, flags, &l ); + back = 2; + break; + + case LDAP_DN_FORMAT_DCE: + rc = rdn2DCEstr( rdn, bv->bv_val, flags, &l, 1 ); + back = 0; + break; + + case LDAP_DN_FORMAT_AD_CANONICAL: + rc = rdn2ADstr( rdn, bv->bv_val, flags, &l, 1 ); + back = 0; + break; + + default: + /* need at least one of the previous */ + return LDAP_PARAM_ERROR; + } + + if ( rc ) { + LDAP_FREEX( bv->bv_val, ctx ); + return rc; + } + + bv->bv_len = l - back; + bv->bv_val[ bv->bv_len ] = '\0'; + + return LDAP_SUCCESS; +} + +/* + * Very bulk implementation; many optimizations can be performed + * - a NULL dn results in an empty string "" + * + * FIXME: doubts + * a) what do we do if a UTF-8 string must be converted in LDAPv2? + * we must encode it in binary form ('#' + HEXPAIRs) + * b) does DCE/AD support UTF-8? + * no clue; don't think so. + * c) what do we do when binary values must be converted in UTF/DCE/AD? + * use binary encoded BER + */ +int ldap_dn2str( LDAPDN dn, char **str, unsigned flags ) +{ + struct berval bv; + int rc; + + assert( str != NULL ); + + if((flags & LDAP_DN_FORMAT_MASK) == LDAP_DN_FORMAT_LBER) { + return LDAP_PARAM_ERROR; + } + + rc = ldap_dn2bv_x( dn, &bv, flags, NULL ); + *str = bv.bv_val; + return rc; +} + +int ldap_dn2bv( LDAPDN dn, struct berval *bv, unsigned flags ) +{ + return ldap_dn2bv_x( dn, bv, flags, NULL ); +} + +int ldap_dn2bv_x( LDAPDN dn, struct berval *bv, unsigned flags, void *ctx ) +{ + int iRDN; + int rc = LDAP_ENCODING_ERROR; + ber_len_t len, l; + + /* stringifying helpers for LDAPv3/LDAPv2 */ + int ( *sv2l ) ( struct berval *v, unsigned f, ber_len_t *l ); + int ( *sv2s ) ( struct berval *v, char *s, unsigned f, ber_len_t *l ); + + assert( bv != NULL ); + bv->bv_len = 0; + bv->bv_val = NULL; + + Debug( LDAP_DEBUG_ARGS, "=> ldap_dn2bv(%u)\n", flags, 0, 0 ); + + /* + * a null dn means an empty dn string + * FIXME: better raise an error? + */ + if ( dn == NULL || dn[0] == NULL ) { + bv->bv_val = LDAP_STRDUPX( "", ctx ); + return( LDAP_SUCCESS ); + } + + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAPV3: + sv2l = strval2strlen; + sv2s = strval2str; + + if( 0 ) { + case LDAP_DN_FORMAT_LDAPV2: + sv2l = strval2IA5strlen; + sv2s = strval2IA5str; + } + + for ( iRDN = 0, len = 0; dn[ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + if ( rdn2strlen( dn[ iRDN ], flags, &rdnl, sv2l ) ) { + goto return_results; + } + + len += rdnl; + } + + if ( ( bv->bv_val = LDAP_MALLOCX( len + 1, ctx ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + + for ( l = 0, iRDN = 0; dn[ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + + if ( rdn2str( dn[ iRDN ], &bv->bv_val[ l ], flags, + &rdnl, sv2s ) ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + goto return_results; + } + l += rdnl; + } + + assert( l == len ); + + /* + * trim the last ',' (the allocated memory + * is one byte longer than required) + */ + bv->bv_len = len - 1; + bv->bv_val[ bv->bv_len ] = '\0'; + + rc = LDAP_SUCCESS; + break; + + case LDAP_DN_FORMAT_UFN: { + /* + * FIXME: quoting from RFC 1781: + * + To take a distinguished name, and generate a name of this format with + attribute types omitted, the following steps are followed. + + 1. If the first attribute is of type CommonName, the type may be + omitted. + + 2. If the last attribute is of type Country, the type may be + omitted. + + 3. If the last attribute is of type Country, the last + Organisation attribute may have the type omitted. + + 4. All attributes of type OrganisationalUnit may have the type + omitted, unless they are after an Organisation attribute or + the first attribute is of type OrganisationalUnit. + + * this should be the pedantic implementation. + * + * Here the standard implementation reflects + * the one historically provided by OpenLDAP + * (and UMIch, I presume), with the variant + * of spaces and plusses (' + ') separating + * rdn components. + * + * A non-standard but nice implementation could + * be to turn the final "dc" attributes into a + * dot-separated domain. + * + * Other improvements could involve the use of + * friendly country names and so. + */ +#ifdef DC_IN_UFN + int leftmost_dc = -1; + int last_iRDN = -1; +#endif /* DC_IN_UFN */ + + for ( iRDN = 0, len = 0; dn[ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + + if ( rdn2UFNstrlen( dn[ iRDN ], flags, &rdnl ) ) { + goto return_results; + } + len += rdnl; + +#ifdef DC_IN_UFN + if ( LDAP_DN_IS_RDN_DC( dn[ iRDN ] ) ) { + if ( leftmost_dc == -1 ) { + leftmost_dc = iRDN; + } + } else { + leftmost_dc = -1; + } +#endif /* DC_IN_UFN */ + } + + if ( ( bv->bv_val = LDAP_MALLOCX( len + 1, ctx ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + +#ifdef DC_IN_UFN + if ( leftmost_dc == -1 ) { +#endif /* DC_IN_UFN */ + for ( l = 0, iRDN = 0; dn[ iRDN ]; iRDN++ ) { + ber_len_t vl; + + if ( rdn2UFNstr( dn[ iRDN ], &bv->bv_val[ l ], + flags, &vl ) ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + goto return_results; + } + l += vl; + } + + /* + * trim the last ', ' (the allocated memory + * is two bytes longer than required) + */ + bv->bv_len = len - 2; + bv->bv_val[ bv->bv_len ] = '\0'; +#ifdef DC_IN_UFN + } else { + last_iRDN = iRDN - 1; + + for ( l = 0, iRDN = 0; iRDN < leftmost_dc; iRDN++ ) { + ber_len_t vl; + + if ( rdn2UFNstr( dn[ iRDN ], &bv->bv_val[ l ], + flags, &vl ) ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + goto return_results; + } + l += vl; + } + + if ( !dn2domain( dn, bv, l, &last_iRDN ) ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + goto return_results; + } + + /* the string is correctly terminated by dn2domain */ + } +#endif /* DC_IN_UFN */ + + rc = LDAP_SUCCESS; + + } break; + + case LDAP_DN_FORMAT_DCE: + for ( iRDN = 0, len = 0; dn[ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + if ( rdn2DCEstrlen( dn[ iRDN ], flags, &rdnl ) ) { + goto return_results; + } + + len += rdnl; + } + + if ( ( bv->bv_val = LDAP_MALLOCX( len + 1, ctx ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + + for ( l = 0; iRDN--; ) { + ber_len_t rdnl; + + if ( rdn2DCEstr( dn[ iRDN ], &bv->bv_val[ l ], flags, + &rdnl, 0 ) ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + goto return_results; + } + l += rdnl; + } + + assert( l == len ); + + bv->bv_len = len; + bv->bv_val[ bv->bv_len ] = '\0'; + + rc = LDAP_SUCCESS; + break; + + case LDAP_DN_FORMAT_AD_CANONICAL: { + int trailing_slash = 1; + + /* + * Sort of UFN for DCE DNs: a slash ('/') separated + * global->local DN with no types; strictly speaking, + * the naming context should be a domain, which is + * written in DNS-style, e.g. dot-deparated. + * + * Example: + * + * "givenName=Bill+sn=Gates,ou=People,dc=microsoft,dc=com" + * + * will read + * + * "microsoft.com/People/Bill,Gates" + */ + for ( iRDN = 0, len = -1; dn[ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + + if ( rdn2ADstrlen( dn[ iRDN ], flags, &rdnl ) ) { + goto return_results; + } + + len += rdnl; + } + + /* reserve room for trailing '/' in case the DN + * is exactly a domain */ + if ( ( bv->bv_val = LDAP_MALLOCX( len + 1 + 1, ctx ) ) == NULL ) + { + rc = LDAP_NO_MEMORY; + break; + } + + iRDN--; + if ( iRDN && dn2domain( dn, bv, 0, &iRDN ) != 0 ) { + for ( l = bv->bv_len; iRDN >= 0 ; iRDN-- ) { + ber_len_t rdnl; + + trailing_slash = 0; + + if ( rdn2ADstr( dn[ iRDN ], &bv->bv_val[ l ], + flags, &rdnl, 0 ) ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + goto return_results; + } + l += rdnl; + } + + } else { + int first = 1; + + /* + * Strictly speaking, AD canonical requires + * a DN to be in the form "..., dc=smtg", + * i.e. terminated by a domain component + */ + if ( flags & LDAP_DN_PEDANTIC ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + rc = LDAP_ENCODING_ERROR; + break; + } + + for ( l = 0; iRDN >= 0 ; iRDN-- ) { + ber_len_t rdnl; + + if ( rdn2ADstr( dn[ iRDN ], &bv->bv_val[ l ], + flags, &rdnl, first ) ) { + LDAP_FREEX( bv->bv_val, ctx ); + bv->bv_val = NULL; + goto return_results; + } + if ( first ) { + first = 0; + } + l += rdnl; + } + } + + if ( trailing_slash ) { + /* the DN is exactly a domain -- need a trailing + * slash; room was reserved in advance */ + bv->bv_val[ len ] = '/'; + len++; + } + + bv->bv_len = len; + bv->bv_val[ bv->bv_len ] = '\0'; + + rc = LDAP_SUCCESS; + } break; + + default: + return LDAP_PARAM_ERROR; + } + + Debug( LDAP_DEBUG_ARGS, "<= ldap_dn2bv(%s)=%d %s\n", + bv->bv_val, rc, rc ? ldap_err2string( rc ) : "" ); + +return_results:; + return( rc ); +} + diff --git a/libraries/libldap/getentry.c b/libraries/libldap/getentry.c new file mode 100644 index 0000000..063dc08 --- /dev/null +++ b/libraries/libldap/getentry.c @@ -0,0 +1,124 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* ARGSUSED */ +LDAPMessage * +ldap_first_entry( LDAP *ld, LDAPMessage *chain ) +{ + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( chain != NULL ); + + return chain->lm_msgtype == LDAP_RES_SEARCH_ENTRY + ? chain + : ldap_next_entry( ld, chain ); +} + +LDAPMessage * +ldap_next_entry( LDAP *ld, LDAPMessage *entry ) +{ + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( entry != NULL ); + + for( + entry = entry->lm_chain; + entry != NULL; + entry = entry->lm_chain ) + { + if( entry->lm_msgtype == LDAP_RES_SEARCH_ENTRY ) { + return( entry ); + } + } + + return( NULL ); +} + +int +ldap_count_entries( LDAP *ld, LDAPMessage *chain ) +{ + int i; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + for ( i = 0; chain != NULL; chain = chain->lm_chain ) { + if( chain->lm_msgtype == LDAP_RES_SEARCH_ENTRY ) { + i++; + } + } + + return( i ); +} + +int +ldap_get_entry_controls( + LDAP *ld, + LDAPMessage *entry, + LDAPControl ***sctrls ) +{ + int rc; + BerElement be; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( entry != NULL ); + assert( sctrls != NULL ); + + if ( entry->lm_msgtype != LDAP_RES_SEARCH_ENTRY ) { + return LDAP_PARAM_ERROR; + } + + /* make a local copy of the BerElement */ + AC_MEMCPY(&be, entry->lm_ber, sizeof(be)); + + if ( ber_scanf( &be, "{xx" /*}*/ ) == LBER_ERROR ) { + rc = LDAP_DECODING_ERROR; + goto cleanup_and_return; + } + + rc = ldap_pvt_get_controls( &be, sctrls ); + +cleanup_and_return: + if( rc != LDAP_SUCCESS ) { + ld->ld_errno = rc; + + if( ld->ld_matched != NULL ) { + LDAP_FREE( ld->ld_matched ); + ld->ld_matched = NULL; + } + + if( ld->ld_error != NULL ) { + LDAP_FREE( ld->ld_error ); + ld->ld_error = NULL; + } + } + + return rc; +} diff --git a/libraries/libldap/getvalues.c b/libraries/libldap/getvalues.c new file mode 100644 index 0000000..ca82cbb --- /dev/null +++ b/libraries/libldap/getvalues.c @@ -0,0 +1,211 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +char ** +ldap_get_values( LDAP *ld, LDAPMessage *entry, LDAP_CONST char *target ) +{ + BerElement ber; + char *attr; + int found = 0; + char **vals; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( entry != NULL ); + assert( target != NULL ); + + Debug( LDAP_DEBUG_TRACE, "ldap_get_values\n", 0, 0, 0 ); + + ber = *entry->lm_ber; + + /* skip sequence, dn, sequence of, and snag the first attr */ + if ( ber_scanf( &ber, "{x{{a" /*}}}*/, &attr ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + if ( strcasecmp( target, attr ) == 0 ) + found = 1; + + /* break out on success, return out on error */ + while ( ! found ) { + LDAP_FREE(attr); + attr = NULL; + + if ( ber_scanf( &ber, /*{*/ "x}{a" /*}*/, &attr ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + if ( strcasecmp( target, attr ) == 0 ) + break; + + } + + LDAP_FREE(attr); + attr = NULL; + + /* + * if we get this far, we've found the attribute and are sitting + * just before the set of values. + */ + + if ( ber_scanf( &ber, "[v]", &vals ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + return( vals ); +} + +struct berval ** +ldap_get_values_len( LDAP *ld, LDAPMessage *entry, LDAP_CONST char *target ) +{ + BerElement ber; + char *attr; + int found = 0; + struct berval **vals; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( entry != NULL ); + assert( target != NULL ); + + Debug( LDAP_DEBUG_TRACE, "ldap_get_values_len\n", 0, 0, 0 ); + + ber = *entry->lm_ber; + + /* skip sequence, dn, sequence of, and snag the first attr */ + if ( ber_scanf( &ber, "{x{{a" /* }}} */, &attr ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + if ( strcasecmp( target, attr ) == 0 ) + found = 1; + + /* break out on success, return out on error */ + while ( ! found ) { + LDAP_FREE( attr ); + attr = NULL; + + if ( ber_scanf( &ber, /*{*/ "x}{a" /*}*/, &attr ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + if ( strcasecmp( target, attr ) == 0 ) + break; + } + + LDAP_FREE( attr ); + attr = NULL; + + /* + * if we get this far, we've found the attribute and are sitting + * just before the set of values. + */ + + if ( ber_scanf( &ber, "[V]", &vals ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + return( vals ); +} + +int +ldap_count_values( char **vals ) +{ + int i; + + if ( vals == NULL ) + return( 0 ); + + for ( i = 0; vals[i] != NULL; i++ ) + ; /* NULL */ + + return( i ); +} + +int +ldap_count_values_len( struct berval **vals ) +{ + return( ldap_count_values( (char **) vals ) ); +} + +void +ldap_value_free( char **vals ) +{ + LDAP_VFREE( vals ); +} + +void +ldap_value_free_len( struct berval **vals ) +{ + ber_bvecfree( vals ); +} + +char ** +ldap_value_dup( char *const *vals ) +{ + char **new; + int i; + + if( vals == NULL ) { + return NULL; + } + + for( i=0; vals[i]; i++ ) { + ; /* Count the number of values */ + } + + if( i == 0 ) { + return NULL; + } + + new = LDAP_MALLOC( (i+1)*sizeof(char *) ); /* Alloc array of pointers */ + if( new == NULL ) { + return NULL; + } + + for( i=0; vals[i]; i++ ) { + new[i] = LDAP_STRDUP( vals[i] ); /* Dup each value */ + if( new[i] == NULL ) { + LDAP_VFREE( new ); + return NULL; + } + } + new[i] = NULL; + + return new; +} + diff --git a/libraries/libldap/gssapi.c b/libraries/libldap/gssapi.c new file mode 100644 index 0000000..bbd9f4c --- /dev/null +++ b/libraries/libldap/gssapi.c @@ -0,0 +1,1011 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * All rights reserved. + * + * Author: Stefan Metzmacher <metze@sernet.de> + * + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/errno.h> +#include <ac/ctype.h> +#include <ac/unistd.h> + +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include "ldap-int.h" + +#ifdef HAVE_GSSAPI + +#ifdef HAVE_GSSAPI_GSSAPI_H +#include <gssapi/gssapi.h> +#else +#include <gssapi.h> +#endif + +static char * +gsserrstr( + char *buf, + ber_len_t buf_len, + gss_OID mech, + int gss_rc, + OM_uint32 minor_status ) +{ + OM_uint32 min2; + gss_buffer_desc mech_msg = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_msg = GSS_C_EMPTY_BUFFER; + gss_buffer_desc minor_msg = GSS_C_EMPTY_BUFFER; + OM_uint32 msg_ctx = 0; + + if (buf == NULL) { + return NULL; + } + + if (buf_len == 0) { + return NULL; + } + +#ifdef HAVE_GSS_OID_TO_STR + gss_oid_to_str(&min2, mech, &mech_msg); +#endif + gss_display_status(&min2, gss_rc, GSS_C_GSS_CODE, + mech, &msg_ctx, &gss_msg); + gss_display_status(&min2, minor_status, GSS_C_MECH_CODE, + mech, &msg_ctx, &minor_msg); + + snprintf(buf, buf_len, "gss_rc[%d:%*s] mech[%*s] minor[%u:%*s]", + gss_rc, (int)gss_msg.length, + (const char *)(gss_msg.value?gss_msg.value:""), + (int)mech_msg.length, + (const char *)(mech_msg.value?mech_msg.value:""), + minor_status, (int)minor_msg.length, + (const char *)(minor_msg.value?minor_msg.value:"")); + + gss_release_buffer(&min2, &mech_msg); + gss_release_buffer(&min2, &gss_msg); + gss_release_buffer(&min2, &minor_msg); + + buf[buf_len-1] = '\0'; + + return buf; +} + +static void +sb_sasl_gssapi_init( + struct sb_sasl_generic_data *p, + ber_len_t *min_send, + ber_len_t *max_send, + ber_len_t *max_recv ) +{ + gss_ctx_id_t gss_ctx = (gss_ctx_id_t)p->ops_private; + int gss_rc; + OM_uint32 minor_status; + gss_OID ctx_mech = GSS_C_NO_OID; + OM_uint32 ctx_flags = 0; + int conf_req_flag = 0; + OM_uint32 max_input_size; + + gss_inquire_context(&minor_status, + gss_ctx, + NULL, + NULL, + NULL, + &ctx_mech, + &ctx_flags, + NULL, + NULL); + + if (ctx_flags & (GSS_C_CONF_FLAG)) { + conf_req_flag = 1; + } + +#if defined(HAVE_CYRUS_SASL) +#define SEND_PREALLOC_SIZE SASL_MIN_BUFF_SIZE +#else +#define SEND_PREALLOC_SIZE 4096 +#endif +#define SEND_MAX_WIRE_SIZE 0x00A00000 +#define RECV_MAX_WIRE_SIZE 0x0FFFFFFF +#define FALLBACK_SEND_MAX_SIZE 0x009FFFB8 /* from MIT 1.5.x */ + + gss_rc = gss_wrap_size_limit(&minor_status, gss_ctx, + conf_req_flag, GSS_C_QOP_DEFAULT, + SEND_MAX_WIRE_SIZE, &max_input_size); + if ( gss_rc != GSS_S_COMPLETE ) { + char msg[256]; + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_init: failed to wrap size limit: %s\n", + gsserrstr( msg, sizeof(msg), ctx_mech, gss_rc, minor_status ) ); + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_init: fallback to default wrap size limit\n"); + /* + * some libgssglue/libgssapi versions + * have a broken gss_wrap_size_limit() + * implementation + */ + max_input_size = FALLBACK_SEND_MAX_SIZE; + } + + *min_send = SEND_PREALLOC_SIZE; + *max_send = max_input_size; + *max_recv = RECV_MAX_WIRE_SIZE; +} + +static ber_int_t +sb_sasl_gssapi_encode( + struct sb_sasl_generic_data *p, + unsigned char *buf, + ber_len_t len, + Sockbuf_Buf *dst ) +{ + gss_ctx_id_t gss_ctx = (gss_ctx_id_t)p->ops_private; + int gss_rc; + OM_uint32 minor_status; + gss_buffer_desc unwrapped, wrapped; + gss_OID ctx_mech = GSS_C_NO_OID; + OM_uint32 ctx_flags = 0; + int conf_req_flag = 0; + int conf_state; + unsigned char *b; + ber_len_t pkt_len; + + unwrapped.value = buf; + unwrapped.length = len; + + gss_inquire_context(&minor_status, + gss_ctx, + NULL, + NULL, + NULL, + &ctx_mech, + &ctx_flags, + NULL, + NULL); + + if (ctx_flags & (GSS_C_CONF_FLAG)) { + conf_req_flag = 1; + } + + gss_rc = gss_wrap(&minor_status, gss_ctx, + conf_req_flag, GSS_C_QOP_DEFAULT, + &unwrapped, &conf_state, + &wrapped); + if ( gss_rc != GSS_S_COMPLETE ) { + char msg[256]; + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_encode: failed to encode packet: %s\n", + gsserrstr( msg, sizeof(msg), ctx_mech, gss_rc, minor_status ) ); + return -1; + } + + if ( conf_req_flag && conf_state == 0 ) { + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_encode: GSS_C_CONF_FLAG was ignored by our gss_wrap()\n" ); + return -1; + } + + pkt_len = 4 + wrapped.length; + + /* Grow the packet buffer if neccessary */ + if ( dst->buf_size < pkt_len && + ber_pvt_sb_grow_buffer( dst, pkt_len ) < 0 ) + { + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_encode: failed to grow the buffer to %lu bytes\n", + pkt_len ); + return -1; + } + + dst->buf_end = pkt_len; + + b = (unsigned char *)dst->buf_base; + + b[0] = (unsigned char)(wrapped.length >> 24); + b[1] = (unsigned char)(wrapped.length >> 16); + b[2] = (unsigned char)(wrapped.length >> 8); + b[3] = (unsigned char)(wrapped.length >> 0); + + /* copy the wrapped blob to the right location */ + memcpy(b + 4, wrapped.value, wrapped.length); + + gss_release_buffer(&minor_status, &wrapped); + + return 0; +} + +static ber_int_t +sb_sasl_gssapi_decode( + struct sb_sasl_generic_data *p, + const Sockbuf_Buf *src, + Sockbuf_Buf *dst ) +{ + gss_ctx_id_t gss_ctx = (gss_ctx_id_t)p->ops_private; + int gss_rc; + OM_uint32 minor_status; + gss_buffer_desc unwrapped, wrapped; + gss_OID ctx_mech = GSS_C_NO_OID; + OM_uint32 ctx_flags = 0; + int conf_req_flag = 0; + int conf_state; + unsigned char *b; + + wrapped.value = src->buf_base + 4; + wrapped.length = src->buf_end - 4; + + gss_inquire_context(&minor_status, + gss_ctx, + NULL, + NULL, + NULL, + &ctx_mech, + &ctx_flags, + NULL, + NULL); + + if (ctx_flags & (GSS_C_CONF_FLAG)) { + conf_req_flag = 1; + } + + gss_rc = gss_unwrap(&minor_status, gss_ctx, + &wrapped, &unwrapped, + &conf_state, GSS_C_QOP_DEFAULT); + if ( gss_rc != GSS_S_COMPLETE ) { + char msg[256]; + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_decode: failed to decode packet: %s\n", + gsserrstr( msg, sizeof(msg), ctx_mech, gss_rc, minor_status ) ); + return -1; + } + + if ( conf_req_flag && conf_state == 0 ) { + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_encode: GSS_C_CONF_FLAG was ignored by our peer\n" ); + return -1; + } + + /* Grow the packet buffer if neccessary */ + if ( dst->buf_size < unwrapped.length && + ber_pvt_sb_grow_buffer( dst, unwrapped.length ) < 0 ) + { + ber_log_printf( LDAP_DEBUG_ANY, p->sbiod->sbiod_sb->sb_debug, + "sb_sasl_gssapi_decode: failed to grow the buffer to %lu bytes\n", + unwrapped.length ); + return -1; + } + + dst->buf_end = unwrapped.length; + + b = (unsigned char *)dst->buf_base; + + /* copy the wrapped blob to the right location */ + memcpy(b, unwrapped.value, unwrapped.length); + + gss_release_buffer(&minor_status, &unwrapped); + + return 0; +} + +static void +sb_sasl_gssapi_reset_buf( + struct sb_sasl_generic_data *p, + Sockbuf_Buf *buf ) +{ + ber_pvt_sb_buf_destroy( buf ); +} + +static void +sb_sasl_gssapi_fini( struct sb_sasl_generic_data *p ) +{ +} + +static const struct sb_sasl_generic_ops sb_sasl_gssapi_ops = { + sb_sasl_gssapi_init, + sb_sasl_gssapi_encode, + sb_sasl_gssapi_decode, + sb_sasl_gssapi_reset_buf, + sb_sasl_gssapi_fini +}; + +static int +sb_sasl_gssapi_install( + Sockbuf *sb, + gss_ctx_id_t gss_ctx ) +{ + struct sb_sasl_generic_install install_arg; + + install_arg.ops = &sb_sasl_gssapi_ops; + install_arg.ops_private = gss_ctx; + + return ldap_pvt_sasl_generic_install( sb, &install_arg ); +} + +static void +sb_sasl_gssapi_remove( Sockbuf *sb ) +{ + ldap_pvt_sasl_generic_remove( sb ); +} + +static int +map_gsserr2ldap( + LDAP *ld, + gss_OID mech, + int gss_rc, + OM_uint32 minor_status ) +{ + char msg[256]; + + Debug( LDAP_DEBUG_ANY, "%s\n", + gsserrstr( msg, sizeof(msg), mech, gss_rc, minor_status ), + NULL, NULL ); + + if (gss_rc == GSS_S_COMPLETE) { + ld->ld_errno = LDAP_SUCCESS; + } else if (GSS_CALLING_ERROR(gss_rc)) { + ld->ld_errno = LDAP_LOCAL_ERROR; + } else if (GSS_ROUTINE_ERROR(gss_rc)) { + ld->ld_errno = LDAP_INAPPROPRIATE_AUTH; + } else if (gss_rc == GSS_S_CONTINUE_NEEDED) { + ld->ld_errno = LDAP_SASL_BIND_IN_PROGRESS; + } else if (GSS_SUPPLEMENTARY_INFO(gss_rc)) { + ld->ld_errno = LDAP_AUTH_UNKNOWN; + } else if (GSS_ERROR(gss_rc)) { + ld->ld_errno = LDAP_AUTH_UNKNOWN; + } else { + ld->ld_errno = LDAP_OTHER; + } + + return ld->ld_errno; +} + + +static int +ldap_gssapi_get_rootdse_infos ( + LDAP *ld, + char **pmechlist, + char **pldapServiceName, + char **pdnsHostName ) +{ + /* we need to query the server for supported mechs anyway */ + LDAPMessage *res, *e; + char *attrs[] = { + "supportedSASLMechanisms", + "ldapServiceName", + "dnsHostName", + NULL + }; + char **values, *mechlist; + char *ldapServiceName = NULL; + char *dnsHostName = NULL; + int rc; + + Debug( LDAP_DEBUG_TRACE, "ldap_gssapi_get_rootdse_infos\n", 0, 0, 0 ); + + rc = ldap_search_s( ld, "", LDAP_SCOPE_BASE, + NULL, attrs, 0, &res ); + + if ( rc != LDAP_SUCCESS ) { + return ld->ld_errno; + } + + e = ldap_first_entry( ld, res ); + if ( e == NULL ) { + ldap_msgfree( res ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = LDAP_NO_SUCH_OBJECT; + } + return ld->ld_errno; + } + + values = ldap_get_values( ld, e, "supportedSASLMechanisms" ); + if ( values == NULL ) { + ldap_msgfree( res ); + ld->ld_errno = LDAP_NO_SUCH_ATTRIBUTE; + return ld->ld_errno; + } + + mechlist = ldap_charray2str( values, " " ); + if ( mechlist == NULL ) { + LDAP_VFREE( values ); + ldap_msgfree( res ); + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + LDAP_VFREE( values ); + + values = ldap_get_values( ld, e, "ldapServiceName" ); + if ( values == NULL ) { + goto get_dns_host_name; + } + + ldapServiceName = ldap_charray2str( values, " " ); + if ( ldapServiceName == NULL ) { + LDAP_FREE( mechlist ); + LDAP_VFREE( values ); + ldap_msgfree( res ); + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + LDAP_VFREE( values ); + +get_dns_host_name: + + values = ldap_get_values( ld, e, "dnsHostName" ); + if ( values == NULL ) { + goto done; + } + + dnsHostName = ldap_charray2str( values, " " ); + if ( dnsHostName == NULL ) { + LDAP_FREE( mechlist ); + LDAP_FREE( ldapServiceName ); + LDAP_VFREE( values ); + ldap_msgfree( res ); + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + LDAP_VFREE( values ); + +done: + ldap_msgfree( res ); + + *pmechlist = mechlist; + *pldapServiceName = ldapServiceName; + *pdnsHostName = dnsHostName; + + return LDAP_SUCCESS; +} + + +static int check_for_gss_spnego_support( LDAP *ld, const char *mechs_str ) +{ + int rc; + char **mechs_list = NULL; + + mechs_list = ldap_str2charray( mechs_str, " " ); + if ( mechs_list == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + rc = ldap_charray_inlist( mechs_list, "GSS-SPNEGO" ); + ldap_charray_free( mechs_list ); + if ( rc != 1) { + ld->ld_errno = LDAP_STRONG_AUTH_NOT_SUPPORTED; + return ld->ld_errno; + } + + return LDAP_SUCCESS; +} + +static int +guess_service_principal( + LDAP *ld, + const char *ldapServiceName, + const char *dnsHostName, + gss_name_t *principal ) +{ + gss_buffer_desc input_name; + /* GSS_KRB5_NT_PRINCIPAL_NAME */ + gss_OID_desc nt_principal = + {10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01"}; + const char *host = ld->ld_defconn->lconn_server->lud_host; + OM_uint32 minor_status; + int gss_rc; + int ret; + size_t svc_principal_size; + char *svc_principal = NULL; + const char *principal_fmt = NULL; + const char *str = NULL; + const char *givenstr = NULL; + const char *ignore = "not_defined_in_RFC4178@please_ignore"; + int allow_remote = 0; + + if (ldapServiceName) { + givenstr = strchr(ldapServiceName, ':'); + if (givenstr && givenstr[1]) { + givenstr++; + if (strcmp(givenstr, ignore) == 0) { + givenstr = NULL; + } + } else { + givenstr = NULL; + } + } + + if ( ld->ld_options.ldo_gssapi_options & LDAP_GSSAPI_OPT_ALLOW_REMOTE_PRINCIPAL ) { + allow_remote = 1; + } + + if (allow_remote && givenstr) { + principal_fmt = "%s"; + svc_principal_size = strlen(givenstr) + 1; + str = givenstr; + + } else if (allow_remote && dnsHostName) { + principal_fmt = "ldap/%s"; + svc_principal_size = STRLENOF("ldap/") + strlen(dnsHostName) + 1; + str = dnsHostName; + + } else { + principal_fmt = "ldap/%s"; + svc_principal_size = STRLENOF("ldap/") + strlen(host) + 1; + str = host; + } + + svc_principal = (char*) ldap_memalloc(svc_principal_size * sizeof(char)); + if ( svc_principal == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + ret = snprintf( svc_principal, svc_principal_size, principal_fmt, str ); + if (ret < 0 || (size_t)ret >= svc_principal_size) { + ld->ld_errno = LDAP_LOCAL_ERROR; + return ld->ld_errno; + } + + Debug( LDAP_DEBUG_TRACE, "principal for host[%s]: '%s'\n", + host, svc_principal, 0 ); + + input_name.value = svc_principal; + input_name.length = (size_t)ret; + + gss_rc = gss_import_name( &minor_status, &input_name, &nt_principal, principal ); + ldap_memfree( svc_principal ); + if ( gss_rc != GSS_S_COMPLETE ) { + return map_gsserr2ldap( ld, GSS_C_NO_OID, gss_rc, minor_status ); + } + + return LDAP_SUCCESS; +} + +void ldap_int_gssapi_close( LDAP *ld, LDAPConn *lc ) +{ + if ( lc && lc->lconn_gss_ctx ) { + OM_uint32 minor_status; + OM_uint32 ctx_flags = 0; + gss_ctx_id_t old_gss_ctx = GSS_C_NO_CONTEXT; + old_gss_ctx = (gss_ctx_id_t)lc->lconn_gss_ctx; + + gss_inquire_context(&minor_status, + old_gss_ctx, + NULL, + NULL, + NULL, + NULL, + &ctx_flags, + NULL, + NULL); + + if (!( ld->ld_options.ldo_gssapi_options & LDAP_GSSAPI_OPT_DO_NOT_FREE_GSS_CONTEXT )) { + gss_delete_sec_context( &minor_status, &old_gss_ctx, GSS_C_NO_BUFFER ); + } + lc->lconn_gss_ctx = GSS_C_NO_CONTEXT; + + if (ctx_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG)) { + /* remove wrapping layer */ + sb_sasl_gssapi_remove( lc->lconn_sb ); + } + } +} + +static void +ldap_int_gssapi_setup( + LDAP *ld, + LDAPConn *lc, + gss_ctx_id_t gss_ctx) +{ + OM_uint32 minor_status; + OM_uint32 ctx_flags = 0; + + ldap_int_gssapi_close( ld, lc ); + + gss_inquire_context(&minor_status, + gss_ctx, + NULL, + NULL, + NULL, + NULL, + &ctx_flags, + NULL, + NULL); + + lc->lconn_gss_ctx = gss_ctx; + + if (ctx_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG)) { + /* setup wrapping layer */ + sb_sasl_gssapi_install( lc->lconn_sb, gss_ctx ); + } +} + +#ifdef LDAP_R_COMPILE +ldap_pvt_thread_mutex_t ldap_int_gssapi_mutex; +#endif + +static int +ldap_int_gss_spnego_bind_s( LDAP *ld ) +{ + int rc; + int gss_rc; + OM_uint32 minor_status; + char *mechlist = NULL; + char *ldapServiceName = NULL; + char *dnsHostName = NULL; + gss_OID_set supported_mechs = GSS_C_NO_OID_SET; + int spnego_support = 0; +#define __SPNEGO_OID_LENGTH 6 +#define __SPNEGO_OID "\053\006\001\005\005\002" + gss_OID_desc spnego_oid = {__SPNEGO_OID_LENGTH, __SPNEGO_OID}; + gss_OID req_mech = GSS_C_NO_OID; + gss_OID ret_mech = GSS_C_NO_OID; + gss_ctx_id_t gss_ctx = GSS_C_NO_CONTEXT; + gss_name_t principal = GSS_C_NO_NAME; + OM_uint32 req_flags; + OM_uint32 ret_flags; + gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; + struct berval cred, *scred = NULL; + + LDAP_MUTEX_LOCK( &ldap_int_gssapi_mutex ); + + /* get information from RootDSE entry */ + rc = ldap_gssapi_get_rootdse_infos ( ld, &mechlist, + &ldapServiceName, &dnsHostName); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + /* check that the server supports GSS-SPNEGO */ + rc = check_for_gss_spnego_support( ld, mechlist ); + if ( rc != LDAP_SUCCESS ) { + goto rc_error; + } + + /* prepare new gss_ctx_id_t */ + rc = guess_service_principal( ld, ldapServiceName, dnsHostName, &principal ); + if ( rc != LDAP_SUCCESS ) { + goto rc_error; + } + + /* see if our gssapi library supports spnego */ + gss_rc = gss_indicate_mechs( &minor_status, &supported_mechs ); + if ( gss_rc != GSS_S_COMPLETE ) { + goto gss_error; + } + gss_rc = gss_test_oid_set_member( &minor_status, + &spnego_oid, supported_mechs, &spnego_support); + gss_release_oid_set( &minor_status, &supported_mechs); + if ( gss_rc != GSS_S_COMPLETE ) { + goto gss_error; + } + if ( spnego_support != 0 ) { + req_mech = &spnego_oid; + } + + req_flags = ld->ld_options.ldo_gssapi_flags; + req_flags |= GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG; + + /* + * loop around gss_init_sec_context() and ldap_sasl_bind_s() + */ + input_token.value = NULL; + input_token.length = 0; + gss_rc = gss_init_sec_context(&minor_status, + GSS_C_NO_CREDENTIAL, + &gss_ctx, + principal, + req_mech, + req_flags, + 0, + NULL, + &input_token, + &ret_mech, + &output_token, + &ret_flags, + NULL); + if ( gss_rc == GSS_S_COMPLETE ) { + rc = LDAP_INAPPROPRIATE_AUTH; + goto rc_error; + } + if ( gss_rc != GSS_S_CONTINUE_NEEDED ) { + goto gss_error; + } + while (1) { + cred.bv_val = (char *)output_token.value; + cred.bv_len = output_token.length; + rc = ldap_sasl_bind_s( ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred ); + gss_release_buffer( &minor_status, &output_token ); + if ( rc != LDAP_SUCCESS && rc != LDAP_SASL_BIND_IN_PROGRESS ) { + goto rc_error; + } + + if ( scred ) { + input_token.value = scred->bv_val; + input_token.length = scred->bv_len; + } else { + input_token.value = NULL; + input_token.length = 0; + } + + gss_rc = gss_init_sec_context(&minor_status, + GSS_C_NO_CREDENTIAL, + &gss_ctx, + principal, + req_mech, + req_flags, + 0, + NULL, + &input_token, + &ret_mech, + &output_token, + &ret_flags, + NULL); + if ( scred ) { + ber_bvfree( scred ); + } + if ( gss_rc == GSS_S_COMPLETE ) { + gss_release_buffer( &minor_status, &output_token ); + break; + } + + if ( gss_rc != GSS_S_CONTINUE_NEEDED ) { + goto gss_error; + } + } + + ldap_int_gssapi_setup( ld, ld->ld_defconn, gss_ctx); + gss_ctx = GSS_C_NO_CONTEXT; + + rc = LDAP_SUCCESS; + goto rc_error; + +gss_error: + rc = map_gsserr2ldap( ld, + (ret_mech != GSS_C_NO_OID ? ret_mech : req_mech ), + gss_rc, minor_status ); +rc_error: + LDAP_MUTEX_UNLOCK( &ldap_int_gssapi_mutex ); + LDAP_FREE( mechlist ); + LDAP_FREE( ldapServiceName ); + LDAP_FREE( dnsHostName ); + gss_release_buffer( &minor_status, &output_token ); + if ( gss_ctx != GSS_C_NO_CONTEXT ) { + gss_delete_sec_context( &minor_status, &gss_ctx, GSS_C_NO_BUFFER ); + } + if ( principal != GSS_C_NO_NAME ) { + gss_release_name( &minor_status, &principal ); + } + return rc; +} + +int +ldap_int_gssapi_config( struct ldapoptions *lo, int option, const char *arg ) +{ + int ok = 0; + + switch( option ) { + case LDAP_OPT_SIGN: + + if (!arg) { + } else if (strcasecmp(arg, "on") == 0) { + ok = 1; + } else if (strcasecmp(arg, "yes") == 0) { + ok = 1; + } else if (strcasecmp(arg, "true") == 0) { + ok = 1; + + } + if (ok) { + lo->ldo_gssapi_flags |= GSS_C_INTEG_FLAG; + } + + return 0; + + case LDAP_OPT_ENCRYPT: + + if (!arg) { + } else if (strcasecmp(arg, "on") == 0) { + ok = 1; + } else if (strcasecmp(arg, "yes") == 0) { + ok = 1; + } else if (strcasecmp(arg, "true") == 0) { + ok = 1; + } + + if (ok) { + lo->ldo_gssapi_flags |= GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG; + } + + return 0; + + case LDAP_OPT_X_GSSAPI_ALLOW_REMOTE_PRINCIPAL: + + if (!arg) { + } else if (strcasecmp(arg, "on") == 0) { + ok = 1; + } else if (strcasecmp(arg, "yes") == 0) { + ok = 1; + } else if (strcasecmp(arg, "true") == 0) { + ok = 1; + } + + if (ok) { + lo->ldo_gssapi_options |= LDAP_GSSAPI_OPT_ALLOW_REMOTE_PRINCIPAL; + } + + return 0; + } + + return -1; +} + +int +ldap_int_gssapi_get_option( LDAP *ld, int option, void *arg ) +{ + if ( ld == NULL ) + return -1; + + switch ( option ) { + case LDAP_OPT_SSPI_FLAGS: + * (unsigned *) arg = (unsigned) ld->ld_options.ldo_gssapi_flags; + break; + + case LDAP_OPT_SIGN: + if ( ld->ld_options.ldo_gssapi_flags & GSS_C_INTEG_FLAG ) { + * (int *) arg = (int)-1; + } else { + * (int *) arg = (int)0; + } + break; + + case LDAP_OPT_ENCRYPT: + if ( ld->ld_options.ldo_gssapi_flags & GSS_C_CONF_FLAG ) { + * (int *) arg = (int)-1; + } else { + * (int *) arg = (int)0; + } + break; + + case LDAP_OPT_SASL_METHOD: + * (char **) arg = LDAP_STRDUP("GSS-SPNEGO"); + break; + + case LDAP_OPT_SECURITY_CONTEXT: + if ( ld->ld_defconn && ld->ld_defconn->lconn_gss_ctx ) { + * (gss_ctx_id_t *) arg = (gss_ctx_id_t)ld->ld_defconn->lconn_gss_ctx; + } else { + * (gss_ctx_id_t *) arg = GSS_C_NO_CONTEXT; + } + break; + + case LDAP_OPT_X_GSSAPI_DO_NOT_FREE_CONTEXT: + if ( ld->ld_options.ldo_gssapi_options & LDAP_GSSAPI_OPT_DO_NOT_FREE_GSS_CONTEXT ) { + * (int *) arg = (int)-1; + } else { + * (int *) arg = (int)0; + } + break; + + case LDAP_OPT_X_GSSAPI_ALLOW_REMOTE_PRINCIPAL: + if ( ld->ld_options.ldo_gssapi_options & LDAP_GSSAPI_OPT_ALLOW_REMOTE_PRINCIPAL ) { + * (int *) arg = (int)-1; + } else { + * (int *) arg = (int)0; + } + break; + + default: + return -1; + } + + return 0; +} + +int +ldap_int_gssapi_set_option( LDAP *ld, int option, void *arg ) +{ + if ( ld == NULL ) + return -1; + + switch ( option ) { + case LDAP_OPT_SSPI_FLAGS: + if ( arg != LDAP_OPT_OFF ) { + ld->ld_options.ldo_gssapi_flags = * (unsigned *)arg; + } + break; + + case LDAP_OPT_SIGN: + if ( arg != LDAP_OPT_OFF ) { + ld->ld_options.ldo_gssapi_flags |= GSS_C_INTEG_FLAG; + } + break; + + case LDAP_OPT_ENCRYPT: + if ( arg != LDAP_OPT_OFF ) { + ld->ld_options.ldo_gssapi_flags |= GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG; + } + break; + + case LDAP_OPT_SASL_METHOD: + if ( arg != LDAP_OPT_OFF ) { + const char *m = (const char *)arg; + if ( strcmp( "GSS-SPNEGO", m ) != 0 ) { + /* we currently only support GSS-SPNEGO */ + return -1; + } + } + break; + + case LDAP_OPT_SECURITY_CONTEXT: + if ( arg != LDAP_OPT_OFF && ld->ld_defconn) { + ldap_int_gssapi_setup( ld, ld->ld_defconn, + (gss_ctx_id_t) arg); + } + break; + + case LDAP_OPT_X_GSSAPI_DO_NOT_FREE_CONTEXT: + if ( arg != LDAP_OPT_OFF ) { + ld->ld_options.ldo_gssapi_options |= LDAP_GSSAPI_OPT_DO_NOT_FREE_GSS_CONTEXT; + } + break; + + case LDAP_OPT_X_GSSAPI_ALLOW_REMOTE_PRINCIPAL: + if ( arg != LDAP_OPT_OFF ) { + ld->ld_options.ldo_gssapi_options |= LDAP_GSSAPI_OPT_ALLOW_REMOTE_PRINCIPAL; + } + break; + + default: + return -1; + } + + return 0; +} + +#else /* HAVE_GSSAPI */ +#define ldap_int_gss_spnego_bind_s(ld) LDAP_NOT_SUPPORTED +#endif /* HAVE_GSSAPI */ + +int +ldap_gssapi_bind( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *creds ) +{ + return LDAP_NOT_SUPPORTED; +} + +int +ldap_gssapi_bind_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *creds ) +{ + if ( dn != NULL ) { + return LDAP_NOT_SUPPORTED; + } + + if ( creds != NULL ) { + return LDAP_NOT_SUPPORTED; + } + + return ldap_int_gss_spnego_bind_s(ld); +} diff --git a/libraries/libldap/init.c b/libraries/libldap/init.c new file mode 100644 index 0000000..9b877a9 --- /dev/null +++ b/libraries/libldap/init.c @@ -0,0 +1,723 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#ifdef HAVE_GETEUID +#include <ac/unistd.h> +#endif + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> + +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include "ldap-int.h" +#include "ldap_defaults.h" +#include "lutil.h" + +struct ldapoptions ldap_int_global_options = + { LDAP_UNINITIALIZED, LDAP_DEBUG_NONE + LDAP_LDO_NULLARG + LDAP_LDO_CONNECTIONLESS_NULLARG + LDAP_LDO_TLS_NULLARG + LDAP_LDO_SASL_NULLARG + LDAP_LDO_GSSAPI_NULLARG + LDAP_LDO_MUTEX_NULLARG }; + +#define ATTR_NONE 0 +#define ATTR_BOOL 1 +#define ATTR_INT 2 +#define ATTR_KV 3 +#define ATTR_STRING 4 +#define ATTR_OPTION 5 + +#define ATTR_SASL 6 +#define ATTR_TLS 7 + +#define ATTR_OPT_TV 8 +#define ATTR_OPT_INT 9 + +#define ATTR_GSSAPI 10 + +struct ol_keyvalue { + const char * key; + int value; +}; + +static const struct ol_keyvalue deref_kv[] = { + {"never", LDAP_DEREF_NEVER}, + {"searching", LDAP_DEREF_SEARCHING}, + {"finding", LDAP_DEREF_FINDING}, + {"always", LDAP_DEREF_ALWAYS}, + {NULL, 0} +}; + +static const struct ol_attribute { + int useronly; + int type; + const char * name; + const void * data; + size_t offset; +} attrs[] = { + {0, ATTR_OPT_TV, "TIMEOUT", NULL, LDAP_OPT_TIMEOUT}, + {0, ATTR_OPT_TV, "NETWORK_TIMEOUT", NULL, LDAP_OPT_NETWORK_TIMEOUT}, + {0, ATTR_OPT_INT, "VERSION", NULL, LDAP_OPT_PROTOCOL_VERSION}, + {0, ATTR_KV, "DEREF", deref_kv, /* or &deref_kv[0] */ + offsetof(struct ldapoptions, ldo_deref)}, + {0, ATTR_INT, "SIZELIMIT", NULL, + offsetof(struct ldapoptions, ldo_sizelimit)}, + {0, ATTR_INT, "TIMELIMIT", NULL, + offsetof(struct ldapoptions, ldo_timelimit)}, + {1, ATTR_STRING, "BINDDN", NULL, + offsetof(struct ldapoptions, ldo_defbinddn)}, + {0, ATTR_STRING, "BASE", NULL, + offsetof(struct ldapoptions, ldo_defbase)}, + {0, ATTR_INT, "PORT", NULL, /* deprecated */ + 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_BOOL, "REFERRALS", NULL, LDAP_BOOL_REFERRALS}, +#if 0 + /* This should only be allowed via ldap_set_option(3) */ + {0, ATTR_BOOL, "RESTART", NULL, LDAP_BOOL_RESTART}, +#endif + +#ifdef HAVE_CYRUS_SASL + {0, ATTR_STRING, "SASL_MECH", NULL, + offsetof(struct ldapoptions, ldo_def_sasl_mech)}, + {0, ATTR_STRING, "SASL_REALM", NULL, + offsetof(struct ldapoptions, ldo_def_sasl_realm)}, + {1, ATTR_STRING, "SASL_AUTHCID", NULL, + offsetof(struct ldapoptions, ldo_def_sasl_authcid)}, + {1, ATTR_STRING, "SASL_AUTHZID", NULL, + offsetof(struct ldapoptions, ldo_def_sasl_authzid)}, + {0, ATTR_SASL, "SASL_SECPROPS", NULL, LDAP_OPT_X_SASL_SECPROPS}, + {0, ATTR_BOOL, "SASL_NOCANON", NULL, LDAP_BOOL_SASL_NOCANON}, +#endif + +#ifdef HAVE_GSSAPI + {0, ATTR_GSSAPI,"GSSAPI_SIGN", NULL, LDAP_OPT_SIGN}, + {0, ATTR_GSSAPI,"GSSAPI_ENCRYPT", NULL, LDAP_OPT_ENCRYPT}, + {0, ATTR_GSSAPI,"GSSAPI_ALLOW_REMOTE_PRINCIPAL",NULL, LDAP_OPT_X_GSSAPI_ALLOW_REMOTE_PRINCIPAL}, +#endif + +#ifdef HAVE_TLS + {1, ATTR_TLS, "TLS_CERT", NULL, LDAP_OPT_X_TLS_CERTFILE}, + {1, ATTR_TLS, "TLS_KEY", NULL, LDAP_OPT_X_TLS_KEYFILE}, + {0, ATTR_TLS, "TLS_CACERT", NULL, LDAP_OPT_X_TLS_CACERTFILE}, + {0, ATTR_TLS, "TLS_CACERTDIR", NULL, LDAP_OPT_X_TLS_CACERTDIR}, + {0, ATTR_TLS, "TLS_REQCERT", NULL, LDAP_OPT_X_TLS_REQUIRE_CERT}, + {0, ATTR_TLS, "TLS_RANDFILE", NULL, LDAP_OPT_X_TLS_RANDOM_FILE}, + {0, ATTR_TLS, "TLS_CIPHER_SUITE", NULL, LDAP_OPT_X_TLS_CIPHER_SUITE}, + {0, ATTR_TLS, "TLS_PROTOCOL_MIN", NULL, LDAP_OPT_X_TLS_PROTOCOL_MIN}, + +#ifdef HAVE_OPENSSL_CRL + {0, ATTR_TLS, "TLS_CRLCHECK", NULL, LDAP_OPT_X_TLS_CRLCHECK}, +#endif +#ifdef HAVE_GNUTLS + {0, ATTR_TLS, "TLS_CRLFILE", NULL, LDAP_OPT_X_TLS_CRLFILE}, +#endif + +#endif + + {0, ATTR_NONE, NULL, NULL, 0} +}; + +#define MAX_LDAP_ATTR_LEN sizeof("GSSAPI_ALLOW_REMOTE_PRINCIPAL") +#define MAX_LDAP_ENV_PREFIX_LEN 8 + +static void openldap_ldap_init_w_conf( + const char *file, int userconf ) +{ + char linebuf[ AC_LINE_MAX ]; + FILE *fp; + int i; + char *cmd, *opt; + char *start, *end; + struct ldapoptions *gopts; + + if ((gopts = LDAP_INT_GLOBAL_OPT()) == NULL) { + return; /* Could not allocate mem for global options */ + } + + if (file == NULL) { + /* no file name */ + return; + } + + Debug(LDAP_DEBUG_TRACE, "ldap_init: trying %s\n", file, 0, 0); + + fp = fopen(file, "r"); + if(fp == NULL) { + /* could not open file */ + return; + } + + Debug(LDAP_DEBUG_TRACE, "ldap_init: using %s\n", file, 0, 0); + + while((start = fgets(linebuf, sizeof(linebuf), fp)) != NULL) { + /* skip lines starting with '#' */ + if(*start == '#') continue; + + /* trim leading white space */ + while((*start != '\0') && isspace((unsigned char) *start)) + start++; + + /* anything left? */ + if(*start == '\0') continue; + + /* trim trailing white space */ + end = &start[strlen(start)-1]; + while(isspace((unsigned char)*end)) end--; + end[1] = '\0'; + + /* anything left? */ + if(*start == '\0') continue; + + + /* parse the command */ + cmd=start; + while((*start != '\0') && !isspace((unsigned char)*start)) { + start++; + } + if(*start == '\0') { + /* command has no argument */ + continue; + } + + *start++ = '\0'; + + /* we must have some whitespace to skip */ + while(isspace((unsigned char)*start)) start++; + opt = start; + + for(i=0; attrs[i].type != ATTR_NONE; i++) { + void *p; + + if( !userconf && attrs[i].useronly ) { + continue; + } + + if(strcasecmp(cmd, attrs[i].name) != 0) { + continue; + } + + switch(attrs[i].type) { + case ATTR_BOOL: + if((strcasecmp(opt, "on") == 0) + || (strcasecmp(opt, "yes") == 0) + || (strcasecmp(opt, "true") == 0)) + { + LDAP_BOOL_SET(gopts, attrs[i].offset); + + } else { + LDAP_BOOL_CLR(gopts, attrs[i].offset); + } + + break; + + case ATTR_INT: { + char *next; + long l; + p = &((char *) gopts)[attrs[i].offset]; + l = strtol( opt, &next, 10 ); + if ( next != opt && next[ 0 ] == '\0' ) { + * (int*) p = l; + } + } break; + + case ATTR_KV: { + const struct ol_keyvalue *kv; + + for(kv = attrs[i].data; + kv->key != NULL; + kv++) { + + if(strcasecmp(opt, kv->key) == 0) { + p = &((char *) gopts)[attrs[i].offset]; + * (int*) p = kv->value; + break; + } + } + } break; + + case ATTR_STRING: + p = &((char *) gopts)[attrs[i].offset]; + if (* (char**) p != NULL) LDAP_FREE(* (char**) p); + * (char**) p = LDAP_STRDUP(opt); + break; + case ATTR_OPTION: + ldap_set_option( NULL, attrs[i].offset, opt ); + break; + case ATTR_SASL: +#ifdef HAVE_CYRUS_SASL + ldap_int_sasl_config( gopts, attrs[i].offset, opt ); +#endif + break; + case ATTR_GSSAPI: +#ifdef HAVE_GSSAPI + ldap_int_gssapi_config( gopts, attrs[i].offset, opt ); +#endif + break; + case ATTR_TLS: +#ifdef HAVE_TLS + ldap_int_tls_config( NULL, attrs[i].offset, opt ); +#endif + break; + case ATTR_OPT_TV: { + struct timeval tv; + char *next; + tv.tv_usec = 0; + tv.tv_sec = strtol( opt, &next, 10 ); + if ( next != opt && next[ 0 ] == '\0' && tv.tv_sec > 0 ) { + (void)ldap_set_option( NULL, attrs[i].offset, (const void *)&tv ); + } + } break; + case ATTR_OPT_INT: { + long l; + char *next; + l = strtol( opt, &next, 10 ); + if ( next != opt && next[ 0 ] == '\0' && l > 0 && (long)((int)l) == l ) { + int v = (int)l; + (void)ldap_set_option( NULL, attrs[i].offset, (const void *)&v ); + } + } break; + } + + break; + } + } + + fclose(fp); +} + +static void openldap_ldap_init_w_sysconf(const char *file) +{ + openldap_ldap_init_w_conf( file, 0 ); +} + +static void openldap_ldap_init_w_userconf(const char *file) +{ + char *home; + char *path = NULL; + + if (file == NULL) { + /* no file name */ + return; + } + + home = getenv("HOME"); + + if (home != NULL) { + Debug(LDAP_DEBUG_TRACE, "ldap_init: HOME env is %s\n", + home, 0, 0); + path = LDAP_MALLOC(strlen(home) + strlen(file) + sizeof( LDAP_DIRSEP ".")); + } else { + Debug(LDAP_DEBUG_TRACE, "ldap_init: HOME env is NULL\n", + 0, 0, 0); + } + + if(home != NULL && path != NULL) { + /* we assume UNIX path syntax is used... */ + + /* try ~/file */ + sprintf(path, "%s" LDAP_DIRSEP "%s", home, file); + openldap_ldap_init_w_conf(path, 1); + + /* try ~/.file */ + sprintf(path, "%s" LDAP_DIRSEP ".%s", home, file); + openldap_ldap_init_w_conf(path, 1); + } + + if(path != NULL) { + LDAP_FREE(path); + } + + /* try file */ + openldap_ldap_init_w_conf(file, 1); +} + +static void openldap_ldap_init_w_env( + struct ldapoptions *gopts, + const char *prefix) +{ + char buf[MAX_LDAP_ATTR_LEN+MAX_LDAP_ENV_PREFIX_LEN]; + int len; + int i; + void *p; + char *value; + + if (prefix == NULL) { + prefix = LDAP_ENV_PREFIX; + } + + strncpy(buf, prefix, MAX_LDAP_ENV_PREFIX_LEN); + buf[MAX_LDAP_ENV_PREFIX_LEN] = '\0'; + len = strlen(buf); + + for(i=0; attrs[i].type != ATTR_NONE; i++) { + strcpy(&buf[len], attrs[i].name); + value = getenv(buf); + + if(value == NULL) { + continue; + } + + switch(attrs[i].type) { + case ATTR_BOOL: + if((strcasecmp(value, "on") == 0) + || (strcasecmp(value, "yes") == 0) + || (strcasecmp(value, "true") == 0)) + { + LDAP_BOOL_SET(gopts, attrs[i].offset); + + } else { + LDAP_BOOL_CLR(gopts, attrs[i].offset); + } + break; + + case ATTR_INT: + p = &((char *) gopts)[attrs[i].offset]; + * (int*) p = atoi(value); + break; + + case ATTR_KV: { + const struct ol_keyvalue *kv; + + for(kv = attrs[i].data; + kv->key != NULL; + kv++) { + + if(strcasecmp(value, kv->key) == 0) { + p = &((char *) gopts)[attrs[i].offset]; + * (int*) p = kv->value; + break; + } + } + } break; + + case ATTR_STRING: + p = &((char *) gopts)[attrs[i].offset]; + if (* (char**) p != NULL) LDAP_FREE(* (char**) p); + if (*value == '\0') { + * (char**) p = NULL; + } else { + * (char**) p = LDAP_STRDUP(value); + } + break; + case ATTR_OPTION: + ldap_set_option( NULL, attrs[i].offset, value ); + break; + case ATTR_SASL: +#ifdef HAVE_CYRUS_SASL + ldap_int_sasl_config( gopts, attrs[i].offset, value ); +#endif + break; + case ATTR_GSSAPI: +#ifdef HAVE_GSSAPI + ldap_int_gssapi_config( gopts, attrs[i].offset, value ); +#endif + break; + case ATTR_TLS: +#ifdef HAVE_TLS + ldap_int_tls_config( NULL, attrs[i].offset, value ); +#endif + break; + case ATTR_OPT_TV: { + struct timeval tv; + char *next; + tv.tv_usec = 0; + tv.tv_sec = strtol( value, &next, 10 ); + if ( next != value && next[ 0 ] == '\0' && tv.tv_sec > 0 ) { + (void)ldap_set_option( NULL, attrs[i].offset, (const void *)&tv ); + } + } break; + case ATTR_OPT_INT: { + long l; + char *next; + l = strtol( value, &next, 10 ); + if ( next != value && next[ 0 ] == '\0' && l > 0 && (long)((int)l) == l ) { + int v = (int)l; + (void)ldap_set_option( NULL, attrs[i].offset, (const void *)&v ); + } + } break; + } + } +} + +#if defined(__GNUC__) +/* Declare this function as a destructor so that it will automatically be + * invoked either at program exit (if libldap is a static library) or + * at unload time (if libldap is a dynamic library). + * + * Sorry, don't know how to handle this for non-GCC environments. + */ +static void ldap_int_destroy_global_options(void) + __attribute__ ((destructor)); +#endif + +static void +ldap_int_destroy_global_options(void) +{ + struct ldapoptions *gopts = LDAP_INT_GLOBAL_OPT(); + + if ( gopts == NULL ) + return; + + gopts->ldo_valid = LDAP_UNINITIALIZED; + + if ( gopts->ldo_defludp ) { + ldap_free_urllist( gopts->ldo_defludp ); + gopts->ldo_defludp = NULL; + } +#if defined(HAVE_WINSOCK) || defined(HAVE_WINSOCK2) + WSACleanup( ); +#endif + +#if defined(HAVE_TLS) || defined(HAVE_CYRUS_SASL) + if ( ldap_int_hostname ) { + LDAP_FREE( ldap_int_hostname ); + ldap_int_hostname = NULL; + } +#endif +#ifdef HAVE_CYRUS_SASL + if ( gopts->ldo_def_sasl_authcid ) { + LDAP_FREE( gopts->ldo_def_sasl_authcid ); + gopts->ldo_def_sasl_authcid = NULL; + } +#endif +#ifdef HAVE_TLS + ldap_int_tls_destroy( gopts ); +#endif +} + +/* + * Initialize the global options structure with default values. + */ +void ldap_int_initialize_global_options( struct ldapoptions *gopts, int *dbglvl ) +{ +#ifdef LDAP_R_COMPILE + LDAP_PVT_MUTEX_FIRSTCREATE(gopts->ldo_mutex); +#endif + LDAP_MUTEX_LOCK( &gopts->ldo_mutex ); + if (gopts->ldo_valid == LDAP_INITIALIZED) { + /* someone else got here first */ + LDAP_MUTEX_UNLOCK( &gopts->ldo_mutex ); + return; + } + if (dbglvl) + gopts->ldo_debug = *dbglvl; + else + 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_tm_api.tv_sec = -1; + gopts->ldo_tm_net.tv_sec = -1; + + /* ldo_defludp will be freed by the termination handler + */ + ldap_url_parselist(&gopts->ldo_defludp, "ldap://localhost/"); + gopts->ldo_defport = LDAP_PORT; +#if !defined(__GNUC__) && !defined(PIC) + /* Do this only for a static library, and only if we can't + * arrange for it to be executed as a library destructor + */ + atexit(ldap_int_destroy_global_options); +#endif + + gopts->ldo_refhoplimit = LDAP_DEFAULT_REFHOPLIMIT; + gopts->ldo_rebind_proc = NULL; + gopts->ldo_rebind_params = NULL; + + LDAP_BOOL_ZERO(gopts); + + LDAP_BOOL_SET(gopts, LDAP_BOOL_REFERRALS); + +#ifdef LDAP_CONNECTIONLESS + gopts->ldo_peer = NULL; + gopts->ldo_cldapdn = NULL; + gopts->ldo_is_udp = 0; +#endif + +#ifdef HAVE_CYRUS_SASL + gopts->ldo_def_sasl_mech = NULL; + gopts->ldo_def_sasl_realm = NULL; + gopts->ldo_def_sasl_authcid = NULL; + gopts->ldo_def_sasl_authzid = NULL; + + memset( &gopts->ldo_sasl_secprops, + '\0', sizeof(gopts->ldo_sasl_secprops) ); + + gopts->ldo_sasl_secprops.max_ssf = INT_MAX; + gopts->ldo_sasl_secprops.maxbufsize = SASL_MAX_BUFF_SIZE; + gopts->ldo_sasl_secprops.security_flags = + SASL_SEC_NOPLAINTEXT | SASL_SEC_NOANONYMOUS; +#endif + +#ifdef HAVE_TLS + gopts->ldo_tls_connect_cb = NULL; + gopts->ldo_tls_connect_arg = NULL; + gopts->ldo_tls_require_cert = LDAP_OPT_X_TLS_DEMAND; +#endif + gopts->ldo_keepalive_probes = 0; + gopts->ldo_keepalive_interval = 0; + gopts->ldo_keepalive_idle = 0; + + gopts->ldo_valid = LDAP_INITIALIZED; + LDAP_MUTEX_UNLOCK( &gopts->ldo_mutex ); + return; +} + +#if defined(HAVE_TLS) || defined(HAVE_CYRUS_SASL) +char * ldap_int_hostname = NULL; +#endif + +void ldap_int_initialize( struct ldapoptions *gopts, int *dbglvl ) +{ + if ( gopts->ldo_valid == LDAP_INITIALIZED ) { + return; + } + + ldap_int_error_init(); + + ldap_int_utils_init(); + +#ifdef HAVE_WINSOCK2 +{ WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD( 2, 0 ); + if ( WSAStartup( wVersionRequested, &wsaData ) != 0 ) { + /* Tell the user that we couldn't find a usable */ + /* WinSock DLL. */ + return; + } + + /* Confirm that the WinSock DLL supports 2.0.*/ + /* Note that if the DLL supports versions greater */ + /* than 2.0 in addition to 2.0, it will still return */ + /* 2.0 in wVersion since that is the version we */ + /* requested. */ + + if ( LOBYTE( wsaData.wVersion ) != 2 || + HIBYTE( wsaData.wVersion ) != 0 ) + { + /* Tell the user that we couldn't find a usable */ + /* WinSock DLL. */ + WSACleanup( ); + return; + } +} /* The WinSock DLL is acceptable. Proceed. */ +#elif defined(HAVE_WINSOCK) +{ WSADATA wsaData; + if ( WSAStartup( 0x0101, &wsaData ) != 0 ) { + return; + } +} +#endif + +#if defined(HAVE_TLS) || defined(HAVE_CYRUS_SASL) + LDAP_MUTEX_LOCK( &ldap_int_hostname_mutex ); + { + char *name = ldap_int_hostname; + + ldap_int_hostname = ldap_pvt_get_fqdn( name ); + + if ( name != NULL && name != ldap_int_hostname ) { + LDAP_FREE( name ); + } + } + LDAP_MUTEX_UNLOCK( &ldap_int_hostname_mutex ); +#endif + +#ifndef HAVE_POLL + if ( ldap_int_tblsize == 0 ) ldap_int_ip_init(); +#endif + +#ifdef HAVE_CYRUS_SASL + if ( ldap_int_sasl_init() != 0 ) { + return; + } +#endif + + ldap_int_initialize_global_options(gopts, dbglvl); + + if( getenv("LDAPNOINIT") != NULL ) { + return; + } + +#ifdef HAVE_CYRUS_SASL + { + /* set authentication identity to current user name */ + char *user = getenv("USER"); + + if( user == NULL ) user = getenv("USERNAME"); + if( user == NULL ) user = getenv("LOGNAME"); + + if( user != NULL ) { + gopts->ldo_def_sasl_authcid = LDAP_STRDUP( user ); + } + } +#endif + + openldap_ldap_init_w_sysconf(LDAP_CONF_FILE); + +#ifdef HAVE_GETEUID + if ( geteuid() != getuid() ) + return; +#endif + + openldap_ldap_init_w_userconf(LDAP_USERRC_FILE); + + { + char *altfile = getenv(LDAP_ENV_PREFIX "CONF"); + + if( altfile != NULL ) { + Debug(LDAP_DEBUG_TRACE, "ldap_init: %s env is %s\n", + LDAP_ENV_PREFIX "CONF", altfile, 0); + openldap_ldap_init_w_sysconf( altfile ); + } + else + Debug(LDAP_DEBUG_TRACE, "ldap_init: %s env is NULL\n", + LDAP_ENV_PREFIX "CONF", 0, 0); + } + + { + char *altfile = getenv(LDAP_ENV_PREFIX "RC"); + + if( altfile != NULL ) { + Debug(LDAP_DEBUG_TRACE, "ldap_init: %s env is %s\n", + LDAP_ENV_PREFIX "RC", altfile, 0); + openldap_ldap_init_w_userconf( altfile ); + } + else + Debug(LDAP_DEBUG_TRACE, "ldap_init: %s env is NULL\n", + LDAP_ENV_PREFIX "RC", 0, 0); + } + + openldap_ldap_init_w_env(gopts, NULL); +} diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h new file mode 100644 index 0000000..66e04ae --- /dev/null +++ b/libraries/libldap/ldap-int.h @@ -0,0 +1,886 @@ +/* ldap-int.h - defines & prototypes internal to the LDAP library */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + */ + +#ifndef _LDAP_INT_H +#define _LDAP_INT_H 1 + +#ifdef LDAP_R_COMPILE +#define LDAP_THREAD_SAFE 1 +#endif + +#include "../liblber/lber-int.h" +#include "lutil.h" + +#ifdef LDAP_R_COMPILE +#include <ldap_pvt_thread.h> +#endif + +#ifdef HAVE_CYRUS_SASL + /* the need for this should be removed */ +#ifdef HAVE_SASL_SASL_H +#include <sasl/sasl.h> +#else +#include <sasl.h> +#endif + +#define SASL_MAX_BUFF_SIZE (0xffffff) +#define SASL_MIN_BUFF_SIZE 4096 +#endif + +/* for struct timeval */ +#include <ac/time.h> +#ifdef _WIN32 +#include <ac/socket.h> +#endif + +#undef TV2MILLISEC +#define TV2MILLISEC(tv) (((tv)->tv_sec * 1000) + ((tv)->tv_usec/1000)) + +/* + * Support needed if the library is running in the kernel + */ +#if LDAP_INT_IN_KERNEL + /* + * Platform specific function to return a pointer to the + * process-specific global options. + * + * This function should perform the following functions: + * Allocate and initialize a global options struct on a per process basis + * Use callers process identifier to return its global options struct + * Note: Deallocate structure when the process exits + */ +# define LDAP_INT_GLOBAL_OPT() ldap_int_global_opt() + struct ldapoptions *ldap_int_global_opt(void); +#else +# define LDAP_INT_GLOBAL_OPT() (&ldap_int_global_options) +#endif + +#define ldap_debug ((LDAP_INT_GLOBAL_OPT())->ldo_debug) + +#include "ldap_log.h" + +#undef Debug + +#ifdef LDAP_DEBUG + +#define DebugTest( level ) \ + ( ldap_debug & level ) + +#define Debug( level, fmt, arg1, arg2, arg3 ) \ + do { if ( ldap_debug & level ) \ + ldap_log_printf( NULL, (level), (fmt), (arg1), (arg2), (arg3) ); \ + } while ( 0 ) + +#define LDAP_Debug( subsystem, level, fmt, arg1, arg2, arg3 )\ + ldap_log_printf( NULL, (level), (fmt), (arg1), (arg2), (arg3) ) + +#else + +#define DebugTest( level ) (0 == 1) +#define Debug( level, fmt, arg1, arg2, arg3 ) ((void)0) +#define LDAP_Debug( subsystem, level, fmt, arg1, arg2, arg3 ) ((void)0) + +#endif /* LDAP_DEBUG */ + +#define LDAP_DEPRECATED 1 +#include "ldap.h" + +#include "ldap_pvt.h" + +LDAP_BEGIN_DECL + +#define LDAP_URL_PREFIX "ldap://" +#define LDAP_URL_PREFIX_LEN STRLENOF(LDAP_URL_PREFIX) +#define LDAPS_URL_PREFIX "ldaps://" +#define LDAPS_URL_PREFIX_LEN STRLENOF(LDAPS_URL_PREFIX) +#define LDAPI_URL_PREFIX "ldapi://" +#define LDAPI_URL_PREFIX_LEN STRLENOF(LDAPI_URL_PREFIX) +#ifdef LDAP_CONNECTIONLESS +#define LDAPC_URL_PREFIX "cldap://" +#define LDAPC_URL_PREFIX_LEN STRLENOF(LDAPC_URL_PREFIX) +#endif +#define LDAP_URL_URLCOLON "URL:" +#define LDAP_URL_URLCOLON_LEN STRLENOF(LDAP_URL_URLCOLON) + +#define LDAP_REF_STR "Referral:\n" +#define LDAP_REF_STR_LEN STRLENOF(LDAP_REF_STR) +#define LDAP_LDAP_REF_STR LDAP_URL_PREFIX +#define LDAP_LDAP_REF_STR_LEN LDAP_URL_PREFIX_LEN + +#define LDAP_DEFAULT_REFHOPLIMIT 5 + +#define LDAP_BOOL_REFERRALS 0 +#define LDAP_BOOL_RESTART 1 +#define LDAP_BOOL_TLS 3 +#define LDAP_BOOL_CONNECT_ASYNC 4 +#define LDAP_BOOL_SASL_NOCANON 5 + +#define LDAP_BOOLEANS unsigned long +#define LDAP_BOOL(n) ((LDAP_BOOLEANS)1 << (n)) +#define LDAP_BOOL_GET(lo, bool) \ + ((lo)->ldo_booleans & LDAP_BOOL(bool) ? -1 : 0) +#define LDAP_BOOL_SET(lo, bool) ((lo)->ldo_booleans |= LDAP_BOOL(bool)) +#define LDAP_BOOL_CLR(lo, bool) ((lo)->ldo_booleans &= ~LDAP_BOOL(bool)) +#define LDAP_BOOL_ZERO(lo) ((lo)->ldo_booleans = 0) + +/* + * This structure represents both ldap messages and ldap responses. + * These are really the same, except in the case of search responses, + * where a response has multiple messages. + */ + +struct ldapmsg { + ber_int_t lm_msgid; /* the message id */ + ber_tag_t lm_msgtype; /* the message type */ + BerElement *lm_ber; /* the ber encoded message contents */ + struct ldapmsg *lm_chain; /* for search - next msg in the resp */ + struct ldapmsg *lm_chain_tail; + struct ldapmsg *lm_next; /* next response */ + time_t lm_time; /* used to maintain cache */ +}; + +#ifdef HAVE_TLS +struct ldaptls { + char *lt_certfile; + char *lt_keyfile; + char *lt_dhfile; + char *lt_cacertfile; + char *lt_cacertdir; + char *lt_ciphersuite; + char *lt_crlfile; + char *lt_randfile; /* OpenSSL only */ + int lt_protocol_min; +}; +#endif + +typedef struct ldaplist { + struct ldaplist *ll_next; + void *ll_data; +} ldaplist; + +/* + * structure representing get/set'able options + * which have global defaults. + * Protect access to this struct with ldo_mutex + * ldap_log.h:ldapoptions_prefix must match the head of this struct. + */ +struct ldapoptions { + short ldo_valid; +#define LDAP_UNINITIALIZED 0x0 +#define LDAP_INITIALIZED 0x1 +#define LDAP_VALID_SESSION 0x2 +#define LDAP_TRASHED_SESSION 0xFF + int ldo_debug; + + ber_int_t ldo_version; + ber_int_t ldo_deref; + ber_int_t ldo_timelimit; + ber_int_t ldo_sizelimit; + + /* per API call timeout */ + struct timeval ldo_tm_api; + struct timeval ldo_tm_net; + + LDAPURLDesc *ldo_defludp; + int ldo_defport; + char* ldo_defbase; + char* ldo_defbinddn; /* bind dn */ + + /* + * Per connection tcp-keepalive settings (Linux only, + * ignored where unsupported) + */ + ber_int_t ldo_keepalive_idle; + ber_int_t ldo_keepalive_probes; + ber_int_t ldo_keepalive_interval; + + int ldo_refhoplimit; /* limit on referral nesting */ + + /* LDAPv3 server and client controls */ + LDAPControl **ldo_sctrls; + LDAPControl **ldo_cctrls; + + /* LDAP rebind callback function */ + LDAP_REBIND_PROC *ldo_rebind_proc; + void *ldo_rebind_params; + LDAP_NEXTREF_PROC *ldo_nextref_proc; + void *ldo_nextref_params; + LDAP_URLLIST_PROC *ldo_urllist_proc; + void *ldo_urllist_params; + + /* LDAP connection callback stack */ + ldaplist *ldo_conn_cbs; + + LDAP_BOOLEANS ldo_booleans; /* boolean options */ + +#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 + +#ifdef LDAP_CONNECTIONLESS +#define LDAP_IS_UDP(ld) ((ld)->ld_options.ldo_is_udp) + void* ldo_peer; /* struct sockaddr* */ + char* ldo_cldapdn; + int ldo_is_udp; +#define LDAP_LDO_CONNECTIONLESS_NULLARG ,0,0,0 +#else +#define LDAP_LDO_CONNECTIONLESS_NULLARG +#endif + +#ifdef HAVE_TLS + /* tls context */ + void *ldo_tls_ctx; + LDAP_TLS_CONNECT_CB *ldo_tls_connect_cb; + void* ldo_tls_connect_arg; + struct ldaptls ldo_tls_info; +#define ldo_tls_certfile ldo_tls_info.lt_certfile +#define ldo_tls_keyfile ldo_tls_info.lt_keyfile +#define ldo_tls_dhfile ldo_tls_info.lt_dhfile +#define ldo_tls_cacertfile ldo_tls_info.lt_cacertfile +#define ldo_tls_cacertdir ldo_tls_info.lt_cacertdir +#define ldo_tls_ciphersuite ldo_tls_info.lt_ciphersuite +#define ldo_tls_protocol_min ldo_tls_info.lt_protocol_min +#define ldo_tls_crlfile ldo_tls_info.lt_crlfile +#define ldo_tls_randfile ldo_tls_info.lt_randfile + int ldo_tls_mode; + int ldo_tls_require_cert; + int ldo_tls_impl; + int ldo_tls_crlcheck; +#define LDAP_LDO_TLS_NULLARG ,0,0,0,{0,0,0,0,0,0,0,0,0},0,0,0,0 +#else +#define LDAP_LDO_TLS_NULLARG +#endif + +#ifdef HAVE_CYRUS_SASL + char* ldo_def_sasl_mech; /* SASL Mechanism(s) */ + char* ldo_def_sasl_realm; /* SASL realm */ + char* ldo_def_sasl_authcid; /* SASL authentication identity */ + char* ldo_def_sasl_authzid; /* SASL authorization identity */ + + /* SASL Security Properties */ + struct sasl_security_properties ldo_sasl_secprops; +#define LDAP_LDO_SASL_NULLARG ,0,0,0,0,{0} +#else +#define LDAP_LDO_SASL_NULLARG +#endif + +#ifdef HAVE_GSSAPI + unsigned ldo_gssapi_flags; +#define LDAP_GSSAPI_OPT_DO_NOT_FREE_GSS_CONTEXT 0x0001 +#define LDAP_GSSAPI_OPT_ALLOW_REMOTE_PRINCIPAL 0x0002 + unsigned ldo_gssapi_options; +#define LDAP_LDO_GSSAPI_NULLARG ,0,0 +#else +#define LDAP_LDO_GSSAPI_NULLARG +#endif + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t ldo_mutex; +#define LDAP_LDO_MUTEX_NULLARG , LDAP_PVT_MUTEX_NULL +#else +#define LDAP_LDO_MUTEX_NULLARG +#endif +}; + + +/* + * structure for representing an LDAP server connection + */ +typedef struct ldap_conn { + Sockbuf *lconn_sb; +#ifdef HAVE_CYRUS_SASL + void *lconn_sasl_authctx; /* context for bind */ + void *lconn_sasl_sockctx; /* for security layer */ +#endif +#ifdef HAVE_GSSAPI + void *lconn_gss_ctx; /* gss_ctx_id_t */ +#endif + int lconn_refcnt; + time_t lconn_created; /* time */ + time_t lconn_lastused; /* time */ + int lconn_rebind_inprogress; /* set if rebind in progress */ + char ***lconn_rebind_queue; /* used if rebind in progress */ + int lconn_status; +#define LDAP_CONNST_NEEDSOCKET 1 +#define LDAP_CONNST_CONNECTING 2 +#define LDAP_CONNST_CONNECTED 3 + LDAPURLDesc *lconn_server; + BerElement *lconn_ber; /* ber receiving on this conn. */ + + struct ldap_conn *lconn_next; +} LDAPConn; + + +/* + * structure used to track outstanding requests + */ +typedef struct ldapreq { + ber_int_t lr_msgid; /* the message id */ + int lr_status; /* status of request */ +#define LDAP_REQST_COMPLETED 0 +#define LDAP_REQST_INPROGRESS 1 +#define LDAP_REQST_CHASINGREFS 2 +#define LDAP_REQST_NOTCONNECTED 3 +#define LDAP_REQST_WRITING 4 + int lr_refcnt; /* count of references */ + int lr_outrefcnt; /* count of outstanding referrals */ + int lr_abandoned; /* the request has been abandoned */ + ber_int_t lr_origid; /* original request's message id */ + int lr_parentcnt; /* count of parent requests */ + ber_tag_t lr_res_msgtype; /* result message type */ + ber_int_t lr_res_errno; /* result LDAP errno */ + char *lr_res_error; /* result error string */ + char *lr_res_matched;/* result matched DN string */ + BerElement *lr_ber; /* ber encoded request contents */ + LDAPConn *lr_conn; /* connection used to send request */ + struct berval lr_dn; /* DN of request, in lr_ber */ + struct ldapreq *lr_parent; /* request that spawned this referral */ + struct ldapreq *lr_child; /* first child request */ + struct ldapreq *lr_refnext; /* next referral spawned */ + struct ldapreq *lr_prev; /* previous request */ + struct ldapreq *lr_next; /* next request */ +} LDAPRequest; + +/* + * structure for client cache + */ +#define LDAP_CACHE_BUCKETS 31 /* cache hash table size */ +typedef struct ldapcache { + LDAPMessage *lc_buckets[LDAP_CACHE_BUCKETS];/* hash table */ + LDAPMessage *lc_requests; /* unfulfilled reqs */ + long lc_timeout; /* request timeout */ + ber_len_t lc_maxmem; /* memory to use */ + ber_len_t lc_memused; /* memory in use */ + int lc_enabled; /* enabled? */ + unsigned long lc_options; /* options */ +#define LDAP_CACHE_OPT_CACHENOERRS 0x00000001 +#define LDAP_CACHE_OPT_CACHEALLERRS 0x00000002 +} LDAPCache; + +/* + * structure containing referral request info for rebind procedure + */ +typedef struct ldapreqinfo { + ber_len_t ri_msgid; + int ri_request; + char *ri_url; +} LDAPreqinfo; + +/* + * structure representing an ldap connection + */ + +struct ldap_common { + Sockbuf *ldc_sb; /* socket descriptor & buffer */ +#define ld_sb ldc->ldc_sb + + unsigned short ldc_lberoptions; +#define ld_lberoptions ldc->ldc_lberoptions + + /* protected by msgid_mutex */ + ber_len_t ldc_msgid; +#define ld_msgid ldc->ldc_msgid + + /* do not mess with these */ + /* protected by req_mutex */ + LDAPRequest *ldc_requests; /* list of outstanding requests */ + /* protected by res_mutex */ + LDAPMessage *ldc_responses; /* list of outstanding responses */ +#define ld_requests ldc->ldc_requests +#define ld_responses ldc->ldc_responses + + /* protected by abandon_mutex */ + ber_len_t ldc_nabandoned; + ber_int_t *ldc_abandoned; /* array of abandoned requests */ +#define ld_nabandoned ldc->ldc_nabandoned +#define ld_abandoned ldc->ldc_abandoned + + /* unused by libldap */ + LDAPCache *ldc_cache; /* non-null if cache is initialized */ +#define ld_cache ldc->ldc_cache + + /* do not mess with the rest though */ + + /* protected by conn_mutex */ + LDAPConn *ldc_defconn; /* default connection */ +#define ld_defconn ldc->ldc_defconn + LDAPConn *ldc_conns; /* list of server connections */ +#define ld_conns ldc->ldc_conns + void *ldc_selectinfo;/* platform specifics for select */ +#define ld_selectinfo ldc->ldc_selectinfo + + /* ldap_common refcnt - free only if 0 */ + /* protected by ldc_mutex */ + unsigned int ldc_refcnt; +#define ld_ldcrefcnt ldc->ldc_refcnt + + /* protected by ldo_mutex */ + struct ldapoptions ldc_options; +#define ld_options ldc->ldc_options + +#define ld_valid ld_options.ldo_valid +#define ld_debug ld_options.ldo_debug + +#define ld_deref ld_options.ldo_deref +#define ld_timelimit ld_options.ldo_timelimit +#define ld_sizelimit ld_options.ldo_sizelimit + +#define ld_defbinddn ld_options.ldo_defbinddn +#define ld_defbase ld_options.ldo_defbase +#define ld_defhost ld_options.ldo_defhost +#define ld_defport ld_options.ldo_defport + +#define ld_refhoplimit ld_options.ldo_refhoplimit + +#define ld_sctrls ld_options.ldo_sctrls +#define ld_cctrls ld_options.ldo_cctrls +#define ld_rebind_proc ld_options.ldo_rebind_proc +#define ld_rebind_params ld_options.ldo_rebind_params +#define ld_nextref_proc ld_options.ldo_nextref_proc +#define ld_nextref_params ld_options.ldo_nextref_params +#define ld_urllist_proc ld_options.ldo_urllist_proc +#define ld_urllist_params ld_options.ldo_urllist_params + +#define ld_version ld_options.ldo_version + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t ldc_mutex; + ldap_pvt_thread_mutex_t ldc_msgid_mutex; + ldap_pvt_thread_mutex_t ldc_conn_mutex; + ldap_pvt_thread_mutex_t ldc_req_mutex; + ldap_pvt_thread_mutex_t ldc_res_mutex; + ldap_pvt_thread_mutex_t ldc_abandon_mutex; +#define ld_ldopts_mutex ld_options.ldo_mutex +#define ld_ldcmutex ldc->ldc_mutex +#define ld_msgid_mutex ldc->ldc_msgid_mutex +#define ld_conn_mutex ldc->ldc_conn_mutex +#define ld_req_mutex ldc->ldc_req_mutex +#define ld_res_mutex ldc->ldc_res_mutex +#define ld_abandon_mutex ldc->ldc_abandon_mutex +#endif +}; + +struct ldap { + /* thread shared */ + struct ldap_common *ldc; + + /* thread specific */ + ber_int_t ld_errno; + char *ld_error; + char *ld_matched; + char **ld_referrals; +}; + +#define LDAP_VALID(ld) ( (ld)->ld_valid == LDAP_VALID_SESSION ) +#define LDAP_TRASHED(ld) ( (ld)->ld_valid == LDAP_TRASHED_SESSION ) +#define LDAP_TRASH(ld) ( (ld)->ld_valid = LDAP_TRASHED_SESSION ) + +#ifdef LDAP_R_COMPILE +LDAP_V ( ldap_pvt_thread_mutex_t ) ldap_int_resolv_mutex; +LDAP_V ( ldap_pvt_thread_mutex_t ) ldap_int_hostname_mutex; + +#ifdef HAVE_GSSAPI +LDAP_V( ldap_pvt_thread_mutex_t ) ldap_int_gssapi_mutex; +#endif +#endif + +#ifdef LDAP_R_COMPILE +#define LDAP_MUTEX_LOCK(mutex) ldap_pvt_thread_mutex_lock( mutex ) +#define LDAP_MUTEX_UNLOCK(mutex) ldap_pvt_thread_mutex_unlock( mutex ) +#define LDAP_ASSERT_MUTEX_OWNER(mutex) \ + LDAP_PVT_THREAD_ASSERT_MUTEX_OWNER(mutex) +#else +#define LDAP_MUTEX_LOCK(mutex) ((void) 0) +#define LDAP_MUTEX_UNLOCK(mutex) ((void) 0) +#define LDAP_ASSERT_MUTEX_OWNER(mutex) ((void) 0) +#endif + +#define LDAP_NEXT_MSGID(ld, id) do { \ + LDAP_MUTEX_LOCK( &(ld)->ld_msgid_mutex ); \ + (id) = ++(ld)->ld_msgid; \ + LDAP_MUTEX_UNLOCK( &(ld)->ld_msgid_mutex ); \ +} while (0) + +/* + * in abandon.c + */ + +LDAP_F (int) +ldap_int_bisect_find( ber_int_t *v, ber_len_t n, ber_int_t id, int *idxp ); +LDAP_F (int) +ldap_int_bisect_insert( ber_int_t **vp, ber_len_t *np, int id, int idx ); +LDAP_F (int) +ldap_int_bisect_delete( ber_int_t **vp, ber_len_t *np, int id, int idx ); + +/* + * in add.c + */ + +LDAP_F (BerElement *) ldap_build_add_req LDAP_P(( + LDAP *ld, + const char *dn, + LDAPMod **attrs, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp )); + +/* + * in compare.c + */ + +LDAP_F (BerElement *) ldap_build_compare_req LDAP_P(( + LDAP *ld, + const char *dn, + const char *attr, + struct berval *bvalue, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp )); + +/* + * in delete.c + */ + +LDAP_F (BerElement *) ldap_build_delete_req LDAP_P(( + LDAP *ld, + const char *dn, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp )); + +/* + * in extended.c + */ + +LDAP_F (BerElement *) ldap_build_extended_req LDAP_P(( + LDAP *ld, + const char *reqoid, + struct berval *reqdata, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp )); + +/* + * in init.c + */ + +LDAP_V ( struct ldapoptions ) ldap_int_global_options; + +LDAP_F ( void ) ldap_int_initialize LDAP_P((struct ldapoptions *, int *)); +LDAP_F ( void ) ldap_int_initialize_global_options LDAP_P(( + struct ldapoptions *, int *)); + +/* memory.c */ + /* simple macros to realloc for now */ +#define LDAP_MALLOC(s) (ber_memalloc_x((s),NULL)) +#define LDAP_CALLOC(n,s) (ber_memcalloc_x((n),(s),NULL)) +#define LDAP_REALLOC(p,s) (ber_memrealloc_x((p),(s),NULL)) +#define LDAP_FREE(p) (ber_memfree_x((p),NULL)) +#define LDAP_VFREE(v) (ber_memvfree_x((void **)(v),NULL)) +#define LDAP_STRDUP(s) (ber_strdup_x((s),NULL)) +#define LDAP_STRNDUP(s,l) (ber_strndup_x((s),(l),NULL)) + +#define LDAP_MALLOCX(s,x) (ber_memalloc_x((s),(x))) +#define LDAP_CALLOCX(n,s,x) (ber_memcalloc_x((n),(s),(x))) +#define LDAP_REALLOCX(p,s,x) (ber_memrealloc_x((p),(s),(x))) +#define LDAP_FREEX(p,x) (ber_memfree_x((p),(x))) +#define LDAP_VFREEX(v,x) (ber_memvfree_x((void **)(v),(x))) +#define LDAP_STRDUPX(s,x) (ber_strdup_x((s),(x))) +#define LDAP_STRNDUPX(s,l,x) (ber_strndup_x((s),(l),(x))) + +/* + * in error.c + */ +LDAP_F (void) ldap_int_error_init( void ); + +/* + * in modify.c + */ + +LDAP_F (BerElement *) ldap_build_modify_req LDAP_P(( + LDAP *ld, + const char *dn, + LDAPMod **mods, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp )); + +/* + * in modrdn.c + */ + +LDAP_F (BerElement *) ldap_build_moddn_req LDAP_P(( + LDAP *ld, + const char *dn, + const char *newrdn, + const char *newSuperior, + int deleteoldrdn, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp )); + +/* + * in unit-int.c + */ +LDAP_F (void) ldap_int_utils_init LDAP_P(( void )); + + +/* + * in print.c + */ +LDAP_F (int) ldap_log_printf LDAP_P((LDAP *ld, int level, const char *fmt, ...)) LDAP_GCCATTR((format(printf, 3, 4))); + +/* + * in cache.c + */ +LDAP_F (void) ldap_add_request_to_cache LDAP_P(( LDAP *ld, ber_tag_t msgtype, + BerElement *request )); +LDAP_F (void) ldap_add_result_to_cache LDAP_P(( LDAP *ld, LDAPMessage *result )); +LDAP_F (int) ldap_check_cache LDAP_P(( LDAP *ld, ber_tag_t msgtype, BerElement *request )); + +/* + * in controls.c + */ +LDAP_F (int) ldap_int_put_controls LDAP_P(( + LDAP *ld, + LDAPControl *const *ctrls, + BerElement *ber )); + +LDAP_F (int) ldap_int_client_controls LDAP_P(( + LDAP *ld, + LDAPControl **ctrlp )); + +/* + * in dsparse.c + */ +LDAP_F (int) ldap_int_next_line_tokens LDAP_P(( char **bufp, ber_len_t *blenp, char ***toksp )); + + +/* + * in open.c + */ +LDAP_F (int) ldap_open_defconn( LDAP *ld ); +LDAP_F (int) ldap_int_open_connection( LDAP *ld, + LDAPConn *conn, LDAPURLDesc *srvlist, int async ); +LDAP_F (int) ldap_int_check_async_open( LDAP *ld, ber_socket_t sd ); + +/* + * in os-ip.c + */ +#ifndef HAVE_POLL +LDAP_V (int) ldap_int_tblsize; +LDAP_F (void) ldap_int_ip_init( void ); +#endif + +LDAP_F (int) ldap_int_timeval_dup( struct timeval **dest, + const struct timeval *tm ); +LDAP_F (int) ldap_connect_to_host( LDAP *ld, Sockbuf *sb, + int proto, LDAPURLDesc *srv, int async ); +LDAP_F (int) ldap_int_poll( LDAP *ld, ber_socket_t s, + struct timeval *tvp, int wr ); + +#if defined(HAVE_TLS) || defined(HAVE_CYRUS_SASL) +LDAP_V (char *) ldap_int_hostname; +LDAP_F (char *) ldap_host_connected_to( Sockbuf *sb, + const char *host ); +#endif + +LDAP_F (int) ldap_int_select( LDAP *ld, struct timeval *timeout ); +LDAP_F (void *) ldap_new_select_info( void ); +LDAP_F (void) ldap_free_select_info( void *sip ); +LDAP_F (void) ldap_mark_select_write( LDAP *ld, Sockbuf *sb ); +LDAP_F (void) ldap_mark_select_read( LDAP *ld, Sockbuf *sb ); +LDAP_F (void) ldap_mark_select_clear( LDAP *ld, Sockbuf *sb ); +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_int_connect_cbs( LDAP *ld, Sockbuf *sb, + ber_socket_t *s, LDAPURLDesc *srv, struct sockaddr *addr ); + +/* + * in os-local.c + */ +#ifdef LDAP_PF_LOCAL +LDAP_F (int) ldap_connect_to_path( LDAP *ld, Sockbuf *sb, + LDAPURLDesc *srv, int async ); +#endif /* LDAP_PF_LOCAL */ + +/* + * in request.c + */ +LDAP_F (ber_int_t) ldap_send_initial_request( LDAP *ld, ber_tag_t msgtype, + const char *dn, BerElement *ber, ber_int_t msgid ); +LDAP_F (BerElement *) ldap_alloc_ber_with_options( LDAP *ld ); +LDAP_F (void) ldap_set_ber_options( LDAP *ld, BerElement *ber ); + +LDAP_F (int) ldap_send_server_request( LDAP *ld, BerElement *ber, + ber_int_t msgid, LDAPRequest *parentreq, LDAPURLDesc **srvlist, + LDAPConn *lc, LDAPreqinfo *bind, int noconn, int m_res ); +LDAP_F (LDAPConn *) ldap_new_connection( LDAP *ld, LDAPURLDesc **srvlist, + int use_ldsb, int connect, LDAPreqinfo *bind, int m_req, int m_res ); +LDAP_F (LDAPRequest *) ldap_find_request_by_msgid( LDAP *ld, ber_int_t msgid ); +LDAP_F (void) ldap_return_request( LDAP *ld, LDAPRequest *lr, int freeit ); +LDAP_F (void) ldap_free_request( LDAP *ld, LDAPRequest *lr ); +LDAP_F (void) ldap_free_connection( LDAP *ld, LDAPConn *lc, int force, int unbind ); +LDAP_F (void) ldap_dump_connection( LDAP *ld, LDAPConn *lconns, int all ); +LDAP_F (void) ldap_dump_requests_and_responses( LDAP *ld ); +LDAP_F (int) ldap_chase_referrals( LDAP *ld, LDAPRequest *lr, + char **errstrp, int sref, int *hadrefp ); +LDAP_F (int) ldap_chase_v3referrals( LDAP *ld, LDAPRequest *lr, + char **refs, int sref, char **referralsp, int *hadrefp ); +LDAP_F (int) ldap_append_referral( LDAP *ld, char **referralsp, char *s ); +LDAP_F (int) ldap_int_flush_request( LDAP *ld, LDAPRequest *lr ); + +/* + * in result.c: + */ +LDAP_F (const char *) ldap_int_msgtype2str( ber_tag_t tag ); + +/* + * in search.c + */ +LDAP_F (BerElement *) ldap_build_search_req LDAP_P(( + LDAP *ld, + const char *base, + ber_int_t scope, + const char *filter, + char **attrs, + ber_int_t attrsonly, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t timelimit, + ber_int_t sizelimit, + ber_int_t deref, + ber_int_t *msgidp)); + + +/* + * in unbind.c + */ +LDAP_F (int) ldap_ld_free LDAP_P(( + LDAP *ld, + int close, + LDAPControl **sctrls, + LDAPControl **cctrls )); + +LDAP_F (int) ldap_send_unbind LDAP_P(( + LDAP *ld, + Sockbuf *sb, + LDAPControl **sctrls, + LDAPControl **cctrls )); + +/* + * in url.c + */ +LDAP_F (LDAPURLDesc *) ldap_url_dup LDAP_P(( + LDAPURLDesc *ludp )); + +LDAP_F (LDAPURLDesc *) ldap_url_duplist LDAP_P(( + LDAPURLDesc *ludlist )); + +LDAP_F (int) ldap_url_parsehosts LDAP_P(( + LDAPURLDesc **ludlist, + const char *hosts, + int port )); + +LDAP_F (char *) ldap_url_list2hosts LDAP_P(( + LDAPURLDesc *ludlist )); + +/* + * in cyrus.c + */ + +LDAP_F (int) ldap_int_sasl_init LDAP_P(( void )); + +LDAP_F (int) ldap_int_sasl_open LDAP_P(( + LDAP *ld, LDAPConn *conn, + const char* host )); +LDAP_F (int) ldap_int_sasl_close LDAP_P(( LDAP *ld, LDAPConn *conn )); + +LDAP_F (int) ldap_int_sasl_external LDAP_P(( + LDAP *ld, LDAPConn *conn, + const char* authid, ber_len_t ssf )); + +LDAP_F (int) ldap_int_sasl_get_option LDAP_P(( LDAP *ld, + int option, void *arg )); +LDAP_F (int) ldap_int_sasl_set_option LDAP_P(( LDAP *ld, + int option, void *arg )); +LDAP_F (int) ldap_int_sasl_config LDAP_P(( struct ldapoptions *lo, + int option, const char *arg )); + +LDAP_F (int) ldap_int_sasl_bind LDAP_P(( + LDAP *ld, + const char *, + const char *, + LDAPControl **, LDAPControl **, + + /* should be passed in client controls */ + unsigned flags, + LDAP_SASL_INTERACT_PROC *interact, + void *defaults, + LDAPMessage *result, + const char **rmech, + int *msgid )); + +/* in sasl.c */ + +LDAP_F (BerElement *) ldap_build_bind_req LDAP_P(( + LDAP *ld, + const char *dn, + const char *mech, + struct berval *cred, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp )); + +/* in schema.c */ +LDAP_F (char *) ldap_int_parse_numericoid LDAP_P(( + const char **sp, + int *code, + const int flags )); + +/* + * in tls.c + */ +LDAP_F (int) ldap_int_tls_config LDAP_P(( LDAP *ld, + int option, const char *arg )); + +LDAP_F (int) ldap_int_tls_start LDAP_P(( LDAP *ld, + LDAPConn *conn, LDAPURLDesc *srv )); + +LDAP_F (void) ldap_int_tls_destroy LDAP_P(( struct ldapoptions *lo )); + +/* + * in getvalues.c + */ +LDAP_F (char **) ldap_value_dup LDAP_P(( + char *const *vals )); + +/* + * in gssapi.c + */ +#ifdef HAVE_GSSAPI +LDAP_F(int) ldap_int_gssapi_get_option LDAP_P(( LDAP *ld, int option, void *arg )); +LDAP_F(int) ldap_int_gssapi_set_option LDAP_P(( LDAP *ld, int option, void *arg )); +LDAP_F(int) ldap_int_gssapi_config LDAP_P(( struct ldapoptions *lo, int option, const char *arg )); +LDAP_F(void) ldap_int_gssapi_close LDAP_P(( LDAP *ld, LDAPConn *lc )); +#endif + +LDAP_END_DECL + +#endif /* _LDAP_INT_H */ diff --git a/libraries/libldap/ldap-tls.h b/libraries/libldap/ldap-tls.h new file mode 100644 index 0000000..2da9e16 --- /dev/null +++ b/libraries/libldap/ldap-tls.h @@ -0,0 +1,77 @@ +/* ldap-tls.h - TLS defines & prototypes internal to the LDAP library */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _LDAP_TLS_H +#define _LDAP_TLS_H 1 + +struct tls_impl; + +struct tls_ctx; +struct tls_session; + +typedef struct tls_ctx tls_ctx; +typedef struct tls_session tls_session; + +typedef int (TI_tls_init)(void); +typedef void (TI_tls_destroy)(void); + +typedef tls_ctx *(TI_ctx_new)(struct ldapoptions *lo); +typedef void (TI_ctx_ref)(tls_ctx *ctx); +typedef void (TI_ctx_free)(tls_ctx *ctx); +typedef int (TI_ctx_init)(struct ldapoptions *lo, struct ldaptls *lt, int is_server); + +typedef tls_session *(TI_session_new)(tls_ctx *ctx, int is_server); +typedef int (TI_session_connect)(LDAP *ld, tls_session *s); +typedef int (TI_session_accept)(tls_session *s); +typedef int (TI_session_upflags)(Sockbuf *sb, tls_session *s, int rc); +typedef char *(TI_session_errmsg)(tls_session *s, int rc, char *buf, size_t len ); +typedef int (TI_session_dn)(tls_session *sess, struct berval *dn); +typedef int (TI_session_chkhost)(LDAP *ld, tls_session *s, const char *name_in); +typedef int (TI_session_strength)(tls_session *sess); + +typedef void (TI_thr_init)(void); + +typedef struct tls_impl { + const char *ti_name; + + TI_tls_init *ti_tls_init; /* library initialization */ + TI_tls_destroy *ti_tls_destroy; + + TI_ctx_new *ti_ctx_new; + TI_ctx_ref *ti_ctx_ref; + TI_ctx_free *ti_ctx_free; + TI_ctx_init *ti_ctx_init; + + TI_session_new *ti_session_new; + TI_session_connect *ti_session_connect; + TI_session_accept *ti_session_accept; + TI_session_upflags *ti_session_upflags; + TI_session_errmsg *ti_session_errmsg; + TI_session_dn *ti_session_my_dn; + TI_session_dn *ti_session_peer_dn; + TI_session_chkhost *ti_session_chkhost; + TI_session_strength *ti_session_strength; + + Sockbuf_IO *ti_sbio; + + TI_thr_init *ti_thr_init; + + int ti_inited; +} tls_impl; + +extern tls_impl ldap_int_tls_impl; + +#endif /* _LDAP_TLS_H */ diff --git a/libraries/libldap/ldap.conf b/libraries/libldap/ldap.conf new file mode 100644 index 0000000..a94cfaa --- /dev/null +++ b/libraries/libldap/ldap.conf @@ -0,0 +1,13 @@ +# +# LDAP Defaults +# + +# See ldap.conf(5) for details +# This file should be world readable but not world writable. + +#BASE dc=example,dc=com +#URI ldap://ldap.example.com ldap://ldap-master.example.com:666 + +#SIZELIMIT 12 +#TIMELIMIT 15 +#DEREF never diff --git a/libraries/libldap/ldap_sync.c b/libraries/libldap/ldap_sync.c new file mode 100644 index 0000000..4018c6c --- /dev/null +++ b/libraries/libldap/ldap_sync.c @@ -0,0 +1,928 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2006-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This program was originally developed by Pierangelo Masarati + * for inclusion in OpenLDAP Software. + */ + +/* + * Proof-of-concept API that implement the client-side + * of the "LDAP Content Sync Operation" (RFC 4533) + */ + +#include "portable.h" + +#include <ac/time.h> + +#include "ldap-int.h" + +#ifdef LDAP_SYNC_TRACE +static const char * +ldap_sync_state2str( int state ) +{ + switch ( state ) { + case LDAP_SYNC_PRESENT: + return "LDAP_SYNC_PRESENT"; + + case LDAP_SYNC_ADD: + return "LDAP_SYNC_ADD"; + + case LDAP_SYNC_MODIFY: + return "LDAP_SYNC_MODIFY"; + + case LDAP_SYNC_DELETE: + return "LDAP_SYNC_DELETE"; + + default: + return "(unknown)"; + } +} +#endif + +/* + * initialize the persistent search structure + */ +ldap_sync_t * +ldap_sync_initialize( ldap_sync_t *ls_in ) +{ + ldap_sync_t *ls = ls_in; + + if ( ls == NULL ) { + ls = ldap_memalloc( sizeof( ldap_sync_t ) ); + if ( ls == NULL ) { + return NULL; + } + } + memset( ls, 0, sizeof( ldap_sync_t ) ); + + ls->ls_scope = LDAP_SCOPE_SUBTREE; + ls->ls_timeout = -1; + + return ls; +} + +/* + * destroy the persistent search structure + */ +void +ldap_sync_destroy( ldap_sync_t *ls, int freeit ) +{ + assert( ls != NULL ); + + if ( ls->ls_base != NULL ) { + ldap_memfree( ls->ls_base ); + ls->ls_base = NULL; + } + + if ( ls->ls_filter != NULL ) { + ldap_memfree( ls->ls_filter ); + ls->ls_filter = NULL; + } + + if ( ls->ls_attrs != NULL ) { + int i; + + for ( i = 0; ls->ls_attrs[ i ] != NULL; i++ ) { + ldap_memfree( ls->ls_attrs[ i ] ); + } + ldap_memfree( ls->ls_attrs ); + ls->ls_attrs = NULL; + } + + if ( ls->ls_ld != NULL ) { + (void)ldap_unbind_ext( ls->ls_ld, NULL, NULL ); +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "ldap_unbind_ext()\n" ); +#endif /* LDAP_SYNC_TRACE */ + ls->ls_ld = NULL; + } + + if ( ls->ls_cookie.bv_val != NULL ) { + ldap_memfree( ls->ls_cookie.bv_val ); + ls->ls_cookie.bv_val = NULL; + } + + if ( freeit ) { + ldap_memfree( ls ); + } +} + +/* + * handle the LDAP_RES_SEARCH_ENTRY response + */ +static int +ldap_sync_search_entry( ldap_sync_t *ls, LDAPMessage *res ) +{ + LDAPControl **ctrls = NULL; + int rc = LDAP_OTHER, + i; + BerElement *ber = NULL; + struct berval entryUUID = { 0 }, + cookie = { 0 }; + int state = -1; + ber_len_t len; + ldap_sync_refresh_t phase; + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\tgot LDAP_RES_SEARCH_ENTRY\n" ); +#endif /* LDAP_SYNC_TRACE */ + + assert( ls != NULL ); + assert( res != NULL ); + + phase = ls->ls_refreshPhase; + + /* OK */ + + /* extract: + * - data + * - entryUUID + * + * check that: + * - Sync State Control is "add" + */ + + /* the control MUST be present */ + + /* extract controls */ + ldap_get_entry_controls( ls->ls_ld, res, &ctrls ); + if ( ctrls == NULL ) { + goto done; + } + + /* lookup the sync state control */ + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + if ( strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_SYNC_STATE ) == 0 ) { + break; + } + } + + /* control must be present; there might be other... */ + if ( ctrls[ i ] == NULL ) { + goto done; + } + + /* extract data */ + ber = ber_init( &ctrls[ i ]->ldctl_value ); + if ( ber == NULL ) { + goto done; + } + /* scan entryUUID in-place ("m") */ + if ( ber_scanf( ber, "{em" /*"}"*/, &state, &entryUUID ) == LBER_ERROR + || entryUUID.bv_len == 0 ) + { + goto done; + } + + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { + /* scan cookie in-place ("m") */ + if ( ber_scanf( ber, /*"{"*/ "m}", &cookie ) == LBER_ERROR ) { + goto done; + } + if ( cookie.bv_val != NULL ) { + ber_bvreplace( &ls->ls_cookie, &cookie ); + } +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot cookie=%s\n", + cookie.bv_val ? cookie.bv_val : "(null)" ); +#endif /* LDAP_SYNC_TRACE */ + } + + switch ( state ) { + case LDAP_SYNC_PRESENT: + case LDAP_SYNC_DELETE: + case LDAP_SYNC_ADD: + case LDAP_SYNC_MODIFY: + /* NOTE: ldap_sync_refresh_t is defined + * as the corresponding LDAP_SYNC_* + * for the 4 above cases */ + phase = state; +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot syncState=%s\n", ldap_sync_state2str( state ) ); +#endif /* LDAP_SYNC_TRACE */ + break; + + default: +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot unknown syncState=%d\n", state ); +#endif /* LDAP_SYNC_TRACE */ + goto done; + } + + rc = ls->ls_search_entry + ? ls->ls_search_entry( ls, res, &entryUUID, phase ) + : LDAP_SUCCESS; + +done:; + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + if ( ctrls != NULL ) { + ldap_controls_free( ctrls ); + } + + return rc; +} + +/* + * handle the LDAP_RES_SEARCH_REFERENCE response + * (to be implemented yet) + */ +static int +ldap_sync_search_reference( ldap_sync_t *ls, LDAPMessage *res ) +{ + int rc = 0; + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\tgot LDAP_RES_SEARCH_REFERENCE\n" ); +#endif /* LDAP_SYNC_TRACE */ + + assert( ls != NULL ); + assert( res != NULL ); + + if ( ls->ls_search_reference ) { + rc = ls->ls_search_reference( ls, res ); + } + + return rc; +} + +/* + * handle the LDAP_RES_SEARCH_RESULT response + */ +static int +ldap_sync_search_result( ldap_sync_t *ls, LDAPMessage *res ) +{ + int err; + char *matched = NULL, + *msg = NULL; + LDAPControl **ctrls = NULL; + int rc; + int refreshDeletes = -1; + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\tgot LDAP_RES_SEARCH_RESULT\n" ); +#endif /* LDAP_SYNC_TRACE */ + + assert( ls != NULL ); + assert( res != NULL ); + + /* should not happen in refreshAndPersist... */ + rc = ldap_parse_result( ls->ls_ld, + res, &err, &matched, &msg, NULL, &ctrls, 0 ); +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, + "\tldap_parse_result(%d, \"%s\", \"%s\") == %d\n", + err, + matched ? matched : "", + msg ? msg : "", + rc ); +#endif /* LDAP_SYNC_TRACE */ + if ( rc == LDAP_SUCCESS ) { + rc = err; + } + + ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE; + + switch ( rc ) { + case LDAP_SUCCESS: { + int i; + BerElement *ber = NULL; + ber_len_t len; + struct berval cookie = { 0 }; + + rc = LDAP_OTHER; + + /* deal with control; then fallthru to handler */ + if ( ctrls == NULL ) { + goto done; + } + + /* lookup the sync state control */ + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + if ( strcmp( ctrls[ i ]->ldctl_oid, + LDAP_CONTROL_SYNC_DONE ) == 0 ) + { + break; + } + } + + /* control must be present; there might be other... */ + if ( ctrls[ i ] == NULL ) { + goto done; + } + + /* extract data */ + ber = ber_init( &ctrls[ i ]->ldctl_value ); + if ( ber == NULL ) { + goto done; + } + + if ( ber_scanf( ber, "{" /*"}"*/) == LBER_ERROR ) { + goto ber_done; + } + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { + if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) { + goto ber_done; + } + if ( cookie.bv_val != NULL ) { + ber_bvreplace( &ls->ls_cookie, &cookie ); + } +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot cookie=%s\n", + cookie.bv_val ? cookie.bv_val : "(null)" ); +#endif /* LDAP_SYNC_TRACE */ + } + + refreshDeletes = 0; + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) { + if ( ber_scanf( ber, "b", &refreshDeletes ) == LBER_ERROR ) { + goto ber_done; + } + if ( refreshDeletes ) { + refreshDeletes = 1; + } + } + + if ( ber_scanf( ber, /*"{"*/ "}" ) != LBER_ERROR ) { + rc = LDAP_SUCCESS; + } + + ber_done:; + ber_free( ber, 1 ); + if ( rc != LDAP_SUCCESS ) { + break; + } + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot refreshDeletes=%s\n", + refreshDeletes ? "TRUE" : "FALSE" ); +#endif /* LDAP_SYNC_TRACE */ + + /* FIXME: what should we do with the refreshDelete? */ + switch ( refreshDeletes ) { + case 0: + ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS; + break; + + default: + ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES; + break; + } + + } /* fallthru */ + + case LDAP_SYNC_REFRESH_REQUIRED: + /* TODO: check for Sync Done Control */ + /* FIXME: perhaps the handler should be called + * also in case of failure; we'll deal with this + * later when implementing refreshOnly */ + if ( ls->ls_search_result ) { + err = ls->ls_search_result( ls, res, refreshDeletes ); + } + break; + } + +done:; + if ( matched != NULL ) { + ldap_memfree( matched ); + } + + if ( msg != NULL ) { + ldap_memfree( msg ); + } + + if ( ctrls != NULL ) { + ldap_controls_free( ctrls ); + } + + ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE; + + return rc; +} + +/* + * handle the LDAP_RES_INTERMEDIATE response + */ +static int +ldap_sync_search_intermediate( ldap_sync_t *ls, LDAPMessage *res, int *refreshDone ) +{ + int rc; + char *retoid = NULL; + struct berval *retdata = NULL; + BerElement *ber = NULL; + ber_len_t len; + ber_tag_t syncinfo_tag; + struct berval cookie; + int refreshDeletes = 0; + BerVarray syncUUIDs = NULL; + ldap_sync_refresh_t phase; + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\tgot LDAP_RES_INTERMEDIATE\n" ); +#endif /* LDAP_SYNC_TRACE */ + + assert( ls != NULL ); + assert( res != NULL ); + assert( refreshDone != NULL ); + + *refreshDone = 0; + + rc = ldap_parse_intermediate( ls->ls_ld, res, + &retoid, &retdata, NULL, 0 ); +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t%sldap_parse_intermediate(%s) == %d\n", + rc != LDAP_SUCCESS ? "!!! " : "", + retoid == NULL ? "\"\"" : retoid, + rc ); +#endif /* LDAP_SYNC_TRACE */ + /* parsing must be successful, and yield the OID + * of the sync info intermediate response */ + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + rc = LDAP_OTHER; + + if ( retoid == NULL || strcmp( retoid, LDAP_SYNC_INFO ) != 0 ) { + goto done; + } + + /* init ber using the value in the response */ + ber = ber_init( retdata ); + if ( ber == NULL ) { + goto done; + } + + syncinfo_tag = ber_peek_tag( ber, &len ); + switch ( syncinfo_tag ) { + case LDAP_TAG_SYNC_NEW_COOKIE: + if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) { + goto done; + } + if ( cookie.bv_val != NULL ) { + ber_bvreplace( &ls->ls_cookie, &cookie ); + } +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot cookie=%s\n", + cookie.bv_val ? cookie.bv_val : "(null)" ); +#endif /* LDAP_SYNC_TRACE */ + break; + + case LDAP_TAG_SYNC_REFRESH_DELETE: + case LDAP_TAG_SYNC_REFRESH_PRESENT: + if ( syncinfo_tag == LDAP_TAG_SYNC_REFRESH_DELETE ) { +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot refreshDelete\n" ); +#endif /* LDAP_SYNC_TRACE */ + switch ( ls->ls_refreshPhase ) { + case LDAP_SYNC_CAPI_NONE: + case LDAP_SYNC_CAPI_PRESENTS: + ls->ls_refreshPhase = LDAP_SYNC_CAPI_DELETES; + break; + + default: + /* TODO: impossible; handle */ + goto done; + } + + } else { +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot refreshPresent\n" ); +#endif /* LDAP_SYNC_TRACE */ + switch ( ls->ls_refreshPhase ) { + case LDAP_SYNC_CAPI_NONE: + ls->ls_refreshPhase = LDAP_SYNC_CAPI_PRESENTS; + break; + + default: + /* TODO: impossible; handle */ + goto done; + } + } + + if ( ber_scanf( ber, "{" /*"}"*/ ) == LBER_ERROR ) { + goto done; + } + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { + if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) { + goto done; + } + if ( cookie.bv_val != NULL ) { + ber_bvreplace( &ls->ls_cookie, &cookie ); + } +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot cookie=%s\n", + cookie.bv_val ? cookie.bv_val : "(null)" ); +#endif /* LDAP_SYNC_TRACE */ + } + + *refreshDone = 1; + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDONE ) { + if ( ber_scanf( ber, "b", refreshDone ) == LBER_ERROR ) { + goto done; + } + } + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot refreshDone=%s\n", + *refreshDone ? "TRUE" : "FALSE" ); +#endif /* LDAP_SYNC_TRACE */ + + if ( ber_scanf( ber, /*"{"*/ "}" ) == LBER_ERROR ) { + goto done; + } + + if ( *refreshDone ) { + ls->ls_refreshPhase = LDAP_SYNC_CAPI_DONE; + } + + if ( ls->ls_intermediate ) { + ls->ls_intermediate( ls, res, NULL, ls->ls_refreshPhase ); + } + + break; + + case LDAP_TAG_SYNC_ID_SET: +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot syncIdSet\n" ); +#endif /* LDAP_SYNC_TRACE */ + if ( ber_scanf( ber, "{" /*"}"*/ ) == LBER_ERROR ) { + goto done; + } + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) { + if ( ber_scanf( ber, "m", &cookie ) == LBER_ERROR ) { + goto done; + } + if ( cookie.bv_val != NULL ) { + ber_bvreplace( &ls->ls_cookie, &cookie ); + } +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tgot cookie=%s\n", + cookie.bv_val ? cookie.bv_val : "(null)" ); +#endif /* LDAP_SYNC_TRACE */ + } + + if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES ) { + if ( ber_scanf( ber, "b", &refreshDeletes ) == LBER_ERROR ) { + goto done; + } + } + + if ( ber_scanf( ber, /*"{"*/ "[W]}", &syncUUIDs ) == LBER_ERROR + || syncUUIDs == NULL ) + { + goto done; + } + +#ifdef LDAP_SYNC_TRACE + { + int i; + + fprintf( stderr, "\t\tgot refreshDeletes=%s\n", + refreshDeletes ? "TRUE" : "FALSE" ); + for ( i = 0; syncUUIDs[ i ].bv_val != NULL; i++ ) { + char buf[ BUFSIZ ]; + fprintf( stderr, "\t\t%s\n", + lutil_uuidstr_from_normalized( + syncUUIDs[ i ].bv_val, syncUUIDs[ i ].bv_len, + buf, sizeof( buf ) ) ); + } + } +#endif /* LDAP_SYNC_TRACE */ + + if ( refreshDeletes ) { + phase = LDAP_SYNC_CAPI_DELETES_IDSET; + + } else { + phase = LDAP_SYNC_CAPI_PRESENTS_IDSET; + } + + /* FIXME: should touch ls->ls_refreshPhase? */ + if ( ls->ls_intermediate ) { + ls->ls_intermediate( ls, res, syncUUIDs, phase ); + } + + ber_bvarray_free( syncUUIDs ); + break; + + default: +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\t\tunknown tag!\n" ); +#endif /* LDAP_SYNC_TRACE */ + goto done; + } + + rc = LDAP_SUCCESS; + +done:; + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + if ( retoid != NULL ) { + ldap_memfree( retoid ); + } + + if ( retdata != NULL ) { + ber_bvfree( retdata ); + } + + return rc; +} + +/* + * initialize the sync + */ +int +ldap_sync_init( ldap_sync_t *ls, int mode ) +{ + LDAPControl ctrl = { 0 }, + *ctrls[ 2 ]; + BerElement *ber = NULL; + int rc; + struct timeval tv = { 0 }, + *tvp = NULL; + LDAPMessage *res = NULL; + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "ldap_sync_init(%s)...\n", + mode == LDAP_SYNC_REFRESH_AND_PERSIST ? + "LDAP_SYNC_REFRESH_AND_PERSIST" : + ( mode == LDAP_SYNC_REFRESH_ONLY ? + "LDAP_SYNC_REFRESH_ONLY" : "unknown" ) ); +#endif /* LDAP_SYNC_TRACE */ + + assert( ls != NULL ); + assert( ls->ls_ld != NULL ); + + /* support both refreshOnly and refreshAndPersist */ + switch ( mode ) { + case LDAP_SYNC_REFRESH_AND_PERSIST: + case LDAP_SYNC_REFRESH_ONLY: + break; + + default: + fprintf( stderr, "ldap_sync_init: unknown mode=%d\n", mode ); + return LDAP_PARAM_ERROR; + } + + /* check consistency of cookie and reloadHint at initial refresh */ + if ( ls->ls_cookie.bv_val == NULL && ls->ls_reloadHint != 0 ) { + fprintf( stderr, "ldap_sync_init: inconsistent cookie/rhint\n" ); + return LDAP_PARAM_ERROR; + } + + ctrls[ 0 ] = &ctrl; + ctrls[ 1 ] = NULL; + + /* prepare the Sync Request control */ + ber = ber_alloc_t( LBER_USE_DER ); +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "%sber_alloc_t() %s= NULL\n", + ber == NULL ? "!!! " : "", + ber == NULL ? "=" : "!" ); +#endif /* LDAP_SYNC_TRACE */ + if ( ber == NULL ) { + rc = LDAP_NO_MEMORY; + goto done; + } + + ls->ls_refreshPhase = LDAP_SYNC_CAPI_NONE; + + if ( ls->ls_cookie.bv_val != NULL ) { + ber_printf( ber, "{eOb}", mode, + &ls->ls_cookie, ls->ls_reloadHint ); + + } else { + ber_printf( ber, "{eb}", mode, ls->ls_reloadHint ); + } + + rc = ber_flatten2( ber, &ctrl.ldctl_value, 0 ); +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, + "%sber_flatten2() == %d\n", + rc ? "!!! " : "", + rc ); +#endif /* LDAP_SYNC_TRACE */ + if ( rc < 0 ) { + rc = LDAP_OTHER; + goto done; + } + + /* make the control critical, as we cannot proceed without */ + ctrl.ldctl_oid = LDAP_CONTROL_SYNC; + ctrl.ldctl_iscritical = 1; + + /* timelimit? */ + if ( ls->ls_timelimit ) { + tv.tv_sec = ls->ls_timelimit; + tvp = &tv; + } + + /* actually run the search */ + rc = ldap_search_ext( ls->ls_ld, + ls->ls_base, ls->ls_scope, ls->ls_filter, + ls->ls_attrs, 0, ctrls, NULL, + tvp, ls->ls_sizelimit, &ls->ls_msgid ); +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, + "%sldap_search_ext(\"%s\", %d, \"%s\") == %d\n", + rc ? "!!! " : "", + ls->ls_base, ls->ls_scope, ls->ls_filter, rc ); +#endif /* LDAP_SYNC_TRACE */ + if ( rc != LDAP_SUCCESS ) { + goto done; + } + + /* initial content/content update phase */ + for ( ; ; ) { + LDAPMessage *msg = NULL; + + /* NOTE: this very short timeout is just to let + * ldap_result() yield long enough to get something */ + tv.tv_sec = 0; + tv.tv_usec = 100000; + + rc = ldap_result( ls->ls_ld, ls->ls_msgid, + LDAP_MSG_RECEIVED, &tv, &res ); +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, + "\t%sldap_result(%d) == %d\n", + rc == -1 ? "!!! " : "", + ls->ls_msgid, rc ); +#endif /* LDAP_SYNC_TRACE */ + switch ( rc ) { + case 0: + /* + * timeout + * + * TODO: can do something else in the meanwhile) + */ + break; + + case -1: + /* smtg bad! */ + goto done; + + default: + for ( msg = ldap_first_message( ls->ls_ld, res ); + msg != NULL; + msg = ldap_next_message( ls->ls_ld, msg ) ) + { + int refreshDone; + + switch ( ldap_msgtype( msg ) ) { + case LDAP_RES_SEARCH_ENTRY: + rc = ldap_sync_search_entry( ls, res ); + break; + + case LDAP_RES_SEARCH_REFERENCE: + rc = ldap_sync_search_reference( ls, res ); + break; + + case LDAP_RES_SEARCH_RESULT: + rc = ldap_sync_search_result( ls, res ); + goto done_search; + + case LDAP_RES_INTERMEDIATE: + rc = ldap_sync_search_intermediate( ls, res, &refreshDone ); + if ( rc != LDAP_SUCCESS || refreshDone ) { + goto done_search; + } + break; + + default: +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\tgot something unexpected...\n" ); +#endif /* LDAP_SYNC_TRACE */ + + ldap_msgfree( res ); + + rc = LDAP_OTHER; + goto done; + } + } + ldap_msgfree( res ); + res = NULL; + break; + } + } + +done_search:; + ldap_msgfree( res ); + +done:; + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + return rc; +} + +/* + * initialize the refreshOnly sync + */ +int +ldap_sync_init_refresh_only( ldap_sync_t *ls ) +{ + return ldap_sync_init( ls, LDAP_SYNC_REFRESH_ONLY ); +} + +/* + * initialize the refreshAndPersist sync + */ +int +ldap_sync_init_refresh_and_persist( ldap_sync_t *ls ) +{ + return ldap_sync_init( ls, LDAP_SYNC_REFRESH_AND_PERSIST ); +} + +/* + * poll for new responses + */ +int +ldap_sync_poll( ldap_sync_t *ls ) +{ + struct timeval tv, + *tvp = NULL; + LDAPMessage *res = NULL, + *msg; + int rc = 0; + +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "ldap_sync_poll...\n" ); +#endif /* LDAP_SYNC_TRACE */ + + assert( ls != NULL ); + assert( ls->ls_ld != NULL ); + + if ( ls->ls_timeout != -1 ) { + tv.tv_sec = ls->ls_timeout; + tv.tv_usec = 0; + tvp = &tv; + } + + rc = ldap_result( ls->ls_ld, ls->ls_msgid, + LDAP_MSG_RECEIVED, tvp, &res ); + if ( rc <= 0 ) { + return rc; + } + + for ( msg = ldap_first_message( ls->ls_ld, res ); + msg; + msg = ldap_next_message( ls->ls_ld, msg ) ) + { + int refreshDone; + + switch ( ldap_msgtype( msg ) ) { + case LDAP_RES_SEARCH_ENTRY: + rc = ldap_sync_search_entry( ls, res ); + break; + + case LDAP_RES_SEARCH_REFERENCE: + rc = ldap_sync_search_reference( ls, res ); + break; + + case LDAP_RES_SEARCH_RESULT: + rc = ldap_sync_search_result( ls, res ); + goto done_search; + + case LDAP_RES_INTERMEDIATE: + rc = ldap_sync_search_intermediate( ls, res, &refreshDone ); + if ( rc != LDAP_SUCCESS || refreshDone ) { + goto done_search; + } + break; + + default: +#ifdef LDAP_SYNC_TRACE + fprintf( stderr, "\tgot something unexpected...\n" ); +#endif /* LDAP_SYNC_TRACE */ + + ldap_msgfree( res ); + + rc = LDAP_OTHER; + goto done; + } + } + +done_search:; + ldap_msgfree( res ); + +done:; + return rc; +} diff --git a/libraries/libldap/ldif.c b/libraries/libldap/ldif.c new file mode 100644 index 0000000..ba36c22 --- /dev/null +++ b/libraries/libldap/ldif.c @@ -0,0 +1,944 @@ +/* ldif.c - routines for dealing with LDIF files */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1992-1996 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. This + * software is provided ``as is'' without express or implied warranty. + */ +/* This work was originally developed by the University of Michigan + * and distributed as part of U-MICH LDAP. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/ctype.h> + +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/time.h> + +int ldif_debug = 0; + +#include "ldap_log.h" +#include "lber_pvt.h" +#include "ldif.h" + +#define RIGHT2 0x03 +#define RIGHT4 0x0f +#define CONTINUED_LINE_MARKER '\r' + +#ifdef CSRIMALLOC +#define ber_memalloc malloc +#define ber_memcalloc calloc +#define ber_memrealloc realloc +#define ber_strdup strdup +#endif + +static const char nib2b64[0x40] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const unsigned char b642nib[0x80] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* + * ldif_parse_line - takes a line of the form "type:[:] value" and splits it + * into components "type" and "value". if a double colon separates type from + * value, then value is encoded in base 64, and parse_line un-decodes it + * (in place) before returning. The type and value are stored in malloc'd + * memory which must be freed by the caller. + * + * ldif_parse_line2 - operates in-place on input buffer, returning type + * in-place. Will return value in-place if possible, (must malloc for + * fetched URLs). If freeval is NULL, all return data will be malloc'd + * and the input line will be unmodified. Otherwise freeval is set to + * True if the value was malloc'd. + */ + +int +ldif_parse_line( + LDAP_CONST char *line, + char **typep, + char **valuep, + ber_len_t *vlenp +) +{ + struct berval type, value; + int rc = ldif_parse_line2( (char *)line, &type, &value, NULL ); + + *typep = type.bv_val; + *valuep = value.bv_val; + *vlenp = value.bv_len; + return rc; +} + +int +ldif_parse_line2( + char *line, + struct berval *type, + struct berval *value, + int *freeval +) +{ + char *s, *p, *d; + char nib; + int b64, url; + + BER_BVZERO( type ); + BER_BVZERO( value ); + + /* skip any leading space */ + while ( isspace( (unsigned char) *line ) ) { + line++; + } + + if ( freeval ) { + *freeval = 0; + } else { + line = ber_strdup( line ); + + if( line == NULL ) { + ber_pvt_log_printf( LDAP_DEBUG_ANY, ldif_debug, + _("ldif_parse_line: line malloc failed\n")); + return( -1 ); + } + } + + type->bv_val = line; + + s = strchr( type->bv_val, ':' ); + + if ( s == NULL ) { + ber_pvt_log_printf( LDAP_DEBUG_PARSE, ldif_debug, + _("ldif_parse_line: missing ':' after %s\n"), + type->bv_val ); + if ( !freeval ) ber_memfree( line ); + return( -1 ); + } + + /* trim any space between type and : */ + for ( p = &s[-1]; p > type->bv_val && isspace( * (unsigned char *) p ); p-- ) { + *p = '\0'; + } + *s++ = '\0'; + type->bv_len = s - type->bv_val - 1; + + url = 0; + b64 = 0; + + if ( *s == '<' ) { + s++; + url = 1; + + } else if ( *s == ':' ) { + /* base 64 encoded value */ + s++; + b64 = 1; + } + + /* skip space between : and value */ + while ( isspace( (unsigned char) *s ) ) { + s++; + } + + /* check for continued line markers that should be deleted */ + for ( p = s, d = s; *p; p++ ) { + if ( *p != CONTINUED_LINE_MARKER ) + *d++ = *p; + } + *d = '\0'; + + if ( b64 ) { + char *byte = s; + + if ( *s == '\0' ) { + /* no value is present, error out */ + ber_pvt_log_printf( LDAP_DEBUG_PARSE, ldif_debug, + _("ldif_parse_line: %s missing base64 value\n"), + type->bv_val ); + if ( !freeval ) ber_memfree( line ); + return( -1 ); + } + + byte = value->bv_val = s; + + for ( p = s, value->bv_len = 0; p < d; p += 4, value->bv_len += 3 ) { + int i; + for ( i = 0; i < 4; i++ ) { + if ( p[i] != '=' && (p[i] & 0x80 || + b642nib[ p[i] & 0x7f ] > 0x3f) ) { + ber_pvt_log_printf( LDAP_DEBUG_ANY, ldif_debug, + _("ldif_parse_line: %s: invalid base64 encoding" + " char (%c) 0x%x\n"), + type->bv_val, p[i], p[i] ); + if ( !freeval ) ber_memfree( line ); + return( -1 ); + } + } + + /* first digit */ + nib = b642nib[ p[0] & 0x7f ]; + byte[0] = nib << 2; + /* second digit */ + nib = b642nib[ p[1] & 0x7f ]; + byte[0] |= nib >> 4; + byte[1] = (nib & RIGHT4) << 4; + /* third digit */ + if ( p[2] == '=' ) { + value->bv_len += 1; + break; + } + nib = b642nib[ p[2] & 0x7f ]; + byte[1] |= nib >> 2; + byte[2] = (nib & RIGHT2) << 6; + /* fourth digit */ + if ( p[3] == '=' ) { + value->bv_len += 2; + break; + } + nib = b642nib[ p[3] & 0x7f ]; + byte[2] |= nib; + + byte += 3; + } + s[ value->bv_len ] = '\0'; + + } else if ( url ) { + if ( *s == '\0' ) { + /* no value is present, error out */ + ber_pvt_log_printf( LDAP_DEBUG_PARSE, ldif_debug, + _("ldif_parse_line: %s missing URL value\n"), + type->bv_val ); + if ( !freeval ) ber_memfree( line ); + return( -1 ); + } + + if( ldif_fetch_url( s, &value->bv_val, &value->bv_len ) ) { + ber_pvt_log_printf( LDAP_DEBUG_ANY, ldif_debug, + _("ldif_parse_line: %s: URL \"%s\" fetch failed\n"), + type->bv_val, s ); + if ( !freeval ) ber_memfree( line ); + return( -1 ); + } + if ( freeval ) *freeval = 1; + + } else { + value->bv_val = s; + value->bv_len = (int) (d - s); + } + + if ( !freeval ) { + struct berval bv = *type; + + ber_dupbv( type, &bv ); + + if( BER_BVISNULL( type )) { + ber_pvt_log_printf( LDAP_DEBUG_ANY, ldif_debug, + _("ldif_parse_line: type malloc failed\n")); + if( url ) ber_memfree( value->bv_val ); + ber_memfree( line ); + return( -1 ); + } + + if( !url ) { + bv = *value; + ber_dupbv( value, &bv ); + if( BER_BVISNULL( value )) { + ber_pvt_log_printf( LDAP_DEBUG_ANY, ldif_debug, + _("ldif_parse_line: value malloc failed\n")); + ber_memfree( type->bv_val ); + ber_memfree( line ); + return( -1 ); + } + } + + ber_memfree( line ); + } + + return( 0 ); +} + +/* + * ldif_getline - return the next "line" (minus newline) of input from a + * string buffer of lines separated by newlines, terminated by \n\n + * or \0. this routine handles continued lines, bundling them into + * a single big line before returning. if a line begins with a white + * space character, it is a continuation of the previous line. the white + * space character (nb: only one char), and preceeding newline are changed + * into CONTINUED_LINE_MARKER chars, to be deleted later by the + * ldif_parse_line() routine above. + * + * ldif_getline will skip over any line which starts '#'. + * + * ldif_getline takes a pointer to a pointer to the buffer on the first call, + * which it updates and must be supplied on subsequent calls. + */ + +int +ldif_countlines( LDAP_CONST char *buf ) +{ + char *nl; + int ret = 0; + + if ( !buf ) return ret; + + for ( nl = strchr(buf, '\n'); nl; nl = strchr(nl, '\n') ) { + nl++; + if ( *nl != ' ' ) ret++; + } + return ret; +} + +char * +ldif_getline( char **next ) +{ + char *line; + + do { + if ( *next == NULL || **next == '\n' || **next == '\0' ) { + return( NULL ); + } + + line = *next; + + while ( (*next = strchr( *next, '\n' )) != NULL ) { +#if CONTINUED_LINE_MARKER != '\r' + if ( (*next)[-1] == '\r' ) { + (*next)[-1] = CONTINUED_LINE_MARKER; + } +#endif + + if ( (*next)[1] != ' ' ) { + if ( (*next)[1] == '\r' && (*next)[2] == '\n' ) { + *(*next)++ = '\0'; + } + *(*next)++ = '\0'; + break; + } + + **next = CONTINUED_LINE_MARKER; + (*next)[1] = CONTINUED_LINE_MARKER; + (*next)++; + } + } while( *line == '#' ); + + return( line ); +} + +/* + * name and OID of attributeTypes that must be base64 encoded in any case + */ +typedef struct must_b64_encode_s { + struct berval name; + struct berval oid; +} must_b64_encode_s; + +static must_b64_encode_s default_must_b64_encode[] = { + { BER_BVC( "userPassword" ), BER_BVC( "2.5.4.35" ) }, + { BER_BVNULL, BER_BVNULL } +}; + +static must_b64_encode_s *must_b64_encode = default_must_b64_encode; + +/* + * register name and OID of attributeTypes that must always be base64 + * encoded + * + * NOTE: this routine mallocs memory in a static struct which must + * be explicitly freed when no longer required + */ +int +ldif_must_b64_encode_register( LDAP_CONST char *name, LDAP_CONST char *oid ) +{ + int i; + ber_len_t len; + + assert( must_b64_encode != NULL ); + assert( name != NULL ); + assert( oid != NULL ); + + len = strlen( name ); + + for ( i = 0; !BER_BVISNULL( &must_b64_encode[i].name ); i++ ) { + if ( len != must_b64_encode[i].name.bv_len ) { + continue; + } + + if ( strcasecmp( name, must_b64_encode[i].name.bv_val ) == 0 ) { + break; + } + } + + if ( !BER_BVISNULL( &must_b64_encode[i].name ) ) { + return 1; + } + + for ( i = 0; !BER_BVISNULL( &must_b64_encode[i].name ); i++ ) + /* just count */ ; + + if ( must_b64_encode == default_must_b64_encode ) { + must_b64_encode = ber_memalloc( sizeof( must_b64_encode_s ) * ( i + 2 ) ); + + for ( i = 0; !BER_BVISNULL( &default_must_b64_encode[i].name ); i++ ) { + ber_dupbv( &must_b64_encode[i].name, &default_must_b64_encode[i].name ); + ber_dupbv( &must_b64_encode[i].oid, &default_must_b64_encode[i].oid ); + } + + } else { + must_b64_encode_s *tmp; + + tmp = ber_memrealloc( must_b64_encode, + sizeof( must_b64_encode_s ) * ( i + 2 ) ); + if ( tmp == NULL ) { + return 1; + } + must_b64_encode = tmp; + } + + ber_str2bv( name, len, 1, &must_b64_encode[i].name ); + ber_str2bv( oid, 0, 1, &must_b64_encode[i].oid ); + + BER_BVZERO( &must_b64_encode[i + 1].name ); + + return 0; +} + +void +ldif_must_b64_encode_release( void ) +{ + int i; + + assert( must_b64_encode != NULL ); + + if ( must_b64_encode == default_must_b64_encode ) { + return; + } + + for ( i = 0; !BER_BVISNULL( &must_b64_encode[i].name ); i++ ) { + ber_memfree( must_b64_encode[i].name.bv_val ); + ber_memfree( must_b64_encode[i].oid.bv_val ); + } + + ber_memfree( must_b64_encode ); + + must_b64_encode = default_must_b64_encode; +} + +/* + * returns 1 iff the string corresponds to the name or the OID of any + * of the attributeTypes listed in must_b64_encode + */ +static int +ldif_must_b64_encode( LDAP_CONST char *s ) +{ + int i; + struct berval bv; + + assert( must_b64_encode != NULL ); + assert( s != NULL ); + + ber_str2bv( s, 0, 0, &bv ); + + for ( i = 0; !BER_BVISNULL( &must_b64_encode[i].name ); i++ ) { + if ( ber_bvstrcasecmp( &must_b64_encode[i].name, &bv ) == 0 + || ber_bvcmp( &must_b64_encode[i].oid, &bv ) == 0 ) + { + return 1; + } + } + + return 0; +} + +/* compatibility with U-Mich off by two bug */ +#define LDIF_KLUDGE 2 + +/* NOTE: only preserved for binary compatibility */ +void +ldif_sput( + char **out, + int type, + LDAP_CONST char *name, + LDAP_CONST char *val, + ber_len_t vlen ) +{ + ldif_sput_wrap( out, type, name, val, vlen, LDIF_LINE_WIDTH+LDIF_KLUDGE ); +} + +void +ldif_sput_wrap( + char **out, + int type, + LDAP_CONST char *name, + LDAP_CONST char *val, + ber_len_t vlen, + ber_len_t wrap ) +{ + const unsigned char *byte, *stop; + unsigned char buf[3]; + unsigned long bits; + char *save; + int pad; + int namelen = 0; + + ber_len_t savelen; + ber_len_t len=0; + ber_len_t i; + + if ( !wrap ) + wrap = LDIF_LINE_WIDTH+LDIF_KLUDGE; + + /* prefix */ + switch( type ) { + case LDIF_PUT_COMMENT: + *(*out)++ = '#'; + len++; + + if( vlen ) { + *(*out)++ = ' '; + len++; + } + + break; + + case LDIF_PUT_SEP: + *(*out)++ = '\n'; + return; + } + + /* name (attribute type) */ + if( name != NULL ) { + /* put the name + ":" */ + namelen = strlen(name); + strcpy(*out, name); + *out += namelen; + len += namelen; + + if( type != LDIF_PUT_COMMENT ) { + *(*out)++ = ':'; + len++; + } + + } +#ifdef LDAP_DEBUG + else { + assert( type == LDIF_PUT_COMMENT ); + } +#endif + + if( vlen == 0 ) { + *(*out)++ = '\n'; + return; + } + + switch( type ) { + case LDIF_PUT_NOVALUE: + *(*out)++ = '\n'; + return; + + case LDIF_PUT_URL: /* url value */ + *(*out)++ = '<'; + len++; + break; + + case LDIF_PUT_B64: /* base64 value */ + *(*out)++ = ':'; + len++; + break; + } + + switch( type ) { + case LDIF_PUT_TEXT: + case LDIF_PUT_URL: + case LDIF_PUT_B64: + *(*out)++ = ' '; + len++; + /* fall-thru */ + + case LDIF_PUT_COMMENT: + /* pre-encoded names */ + for ( i=0; i < vlen; i++ ) { + if ( len > wrap ) { + *(*out)++ = '\n'; + *(*out)++ = ' '; + len = 1; + } + + *(*out)++ = val[i]; + len++; + } + *(*out)++ = '\n'; + return; + } + + save = *out; + savelen = len; + + *(*out)++ = ' '; + len++; + + stop = (const unsigned char *) (val + vlen); + + if ( type == LDIF_PUT_VALUE + && isgraph( (unsigned char) val[0] ) && val[0] != ':' && val[0] != '<' + && isgraph( (unsigned char) val[vlen-1] ) +#ifndef LDAP_BINARY_DEBUG + && strstr( name, ";binary" ) == NULL +#endif +#ifndef LDAP_PASSWD_DEBUG + && !ldif_must_b64_encode( name ) +#endif + ) { + int b64 = 0; + + for ( byte = (const unsigned char *) val; byte < stop; + byte++, len++ ) + { + if ( !isascii( *byte ) || !isprint( *byte ) ) { + b64 = 1; + break; + } + if ( len >= wrap ) { + *(*out)++ = '\n'; + *(*out)++ = ' '; + len = 1; + } + *(*out)++ = *byte; + } + + if( !b64 ) { + *(*out)++ = '\n'; + return; + } + } + + *out = save; + *(*out)++ = ':'; + *(*out)++ = ' '; + len = savelen + 2; + + /* convert to base 64 (3 bytes => 4 base 64 digits) */ + for ( byte = (const unsigned char *) val; + byte < stop - 2; + byte += 3 ) + { + bits = (byte[0] & 0xff) << 16; + bits |= (byte[1] & 0xff) << 8; + bits |= (byte[2] & 0xff); + + for ( i = 0; i < 4; i++, len++, bits <<= 6 ) { + if ( len >= wrap ) { + *(*out)++ = '\n'; + *(*out)++ = ' '; + len = 1; + } + + /* get b64 digit from high order 6 bits */ + *(*out)++ = nib2b64[ (bits & 0xfc0000L) >> 18 ]; + } + } + + /* add padding if necessary */ + if ( byte < stop ) { + for ( i = 0; byte + i < stop; i++ ) { + buf[i] = byte[i]; + } + for ( pad = 0; i < 3; i++, pad++ ) { + buf[i] = '\0'; + } + byte = buf; + bits = (byte[0] & 0xff) << 16; + bits |= (byte[1] & 0xff) << 8; + bits |= (byte[2] & 0xff); + + for ( i = 0; i < 4; i++, len++, bits <<= 6 ) { + if ( len >= wrap ) { + *(*out)++ = '\n'; + *(*out)++ = ' '; + len = 1; + } + + if( i + pad < 4 ) { + /* get b64 digit from low order 6 bits */ + *(*out)++ = nib2b64[ (bits & 0xfc0000L) >> 18 ]; + } else { + *(*out)++ = '='; + } + } + } + *(*out)++ = '\n'; +} + + +/* + * ldif_type_and_value return BER malloc'd, zero-terminated LDIF line + */ + +/* NOTE: only preserved for binary compatibility */ +char * +ldif_put( + int type, + LDAP_CONST char *name, + LDAP_CONST char *val, + ber_len_t vlen ) +{ + return ldif_put_wrap( type, name, val, vlen, LDIF_LINE_WIDTH ); +} + +char * +ldif_put_wrap( + int type, + LDAP_CONST char *name, + LDAP_CONST char *val, + ber_len_t vlen, + ber_len_t wrap ) +{ + char *buf, *p; + ber_len_t nlen; + + nlen = ( name != NULL ) ? strlen( name ) : 0; + + buf = (char *) ber_memalloc( LDIF_SIZE_NEEDED_WRAP( nlen, vlen, wrap ) + 1 ); + + if ( buf == NULL ) { + ber_pvt_log_printf( LDAP_DEBUG_ANY, ldif_debug, + _("ldif_type_and_value: malloc failed!")); + return NULL; + } + + p = buf; + ldif_sput_wrap( &p, type, name, val, vlen, wrap ); + *p = '\0'; + + return( buf ); +} + +int ldif_is_not_printable( + LDAP_CONST char *val, + ber_len_t vlen ) +{ + if( vlen == 0 || val == NULL ) { + return -1; + } + + if( isgraph( (unsigned char) val[0] ) && val[0] != ':' && val[0] != '<' && + isgraph( (unsigned char) val[vlen-1] ) ) + { + ber_len_t i; + + for ( i = 0; val[i]; i++ ) { + if ( !isascii( val[i] ) || !isprint( (unsigned char) val[i] ) ) { + return 1; + } + } + + return 0; + } + + return 1; +} + +LDIFFP * +ldif_open( + LDAP_CONST char *file, + LDAP_CONST char *mode +) +{ + FILE *fp = fopen( file, mode ); + LDIFFP *lfp = NULL; + + if ( fp ) { + lfp = ber_memalloc( sizeof( LDIFFP )); + lfp->fp = fp; + lfp->prev = NULL; + } + return lfp; +} + +void +ldif_close( + LDIFFP *lfp +) +{ + LDIFFP *prev; + + while ( lfp ) { + fclose( lfp->fp ); + prev = lfp->prev; + ber_memfree( lfp ); + lfp = prev; + } +} + +#define LDIF_MAXLINE 4096 + +/* + * ldif_read_record - read an ldif record. Return 1 for success, 0 for EOF, + * -1 for error. + */ +int +ldif_read_record( + LDIFFP *lfp, + unsigned long *lno, /* ptr to line number counter */ + char **bufp, /* ptr to malloced output buffer */ + int *buflenp ) /* ptr to length of *bufp */ +{ + char line[LDIF_MAXLINE], *nbufp; + ber_len_t lcur = 0, len; + int last_ch = '\n', found_entry = 0, stop, top_comment = 0; + + for ( stop = 0; !stop; last_ch = line[len-1] ) { + /* If we're at the end of this file, see if we should pop + * back to a previous file. (return from an include) + */ + while ( feof( lfp->fp )) { + if ( lfp->prev ) { + LDIFFP *tmp = lfp->prev; + fclose( lfp->fp ); + *lfp = *tmp; + ber_memfree( tmp ); + } else { + stop = 1; + break; + } + } + if ( !stop ) { + if ( fgets( line, sizeof( line ), lfp->fp ) == NULL ) { + stop = 1; + len = 0; + } else { + len = strlen( line ); + } + } + + if ( stop ) { + /* Add \n in case the file does not end with newline */ + if (last_ch != '\n') { + len = 1; + line[0] = '\n'; + line[1] = '\0'; + goto last; + } + break; + } + + /* Squash \r\n to \n */ + if ( len > 1 && line[len-2] == '\r' ) { + len--; + line[len-1] = '\n'; + } + + if ( last_ch == '\n' ) { + (*lno)++; + + if ( line[0] == '\n' ) { + if ( !found_entry ) { + lcur = 0; + top_comment = 0; + continue; + } + break; + } + + if ( !found_entry ) { + if ( line[0] == '#' ) { + top_comment = 1; + } else if ( ! ( top_comment && line[0] == ' ' ) ) { + /* Found a new entry */ + found_entry = 1; + + if ( isdigit( (unsigned char) line[0] ) ) { + /* skip index */ + continue; + } + if ( !strncasecmp( line, "include:", + STRLENOF("include:"))) { + FILE *fp2; + char *ptr; + found_entry = 0; + + if ( line[len-1] == '\n' ) { + len--; + line[len] = '\0'; + } + + ptr = line + STRLENOF("include:"); + while (isspace((unsigned char) *ptr)) ptr++; + fp2 = ldif_open_url( ptr ); + if ( fp2 ) { + LDIFFP *lnew = ber_memalloc( sizeof( LDIFFP )); + if ( lnew == NULL ) { + fclose( fp2 ); + return 0; + } + lnew->prev = lfp->prev; + lnew->fp = lfp->fp; + lfp->prev = lnew; + lfp->fp = fp2; + line[len] = '\n'; + len++; + continue; + } else { + /* We failed to open the file, this should + * be reported as an error somehow. + */ + ber_pvt_log_printf( LDAP_DEBUG_ANY, ldif_debug, + _("ldif_read_record: include %s failed\n"), ptr ); + return -1; + } + } + } + } + } + +last: + if ( *buflenp - lcur <= len ) { + *buflenp += len + LDIF_MAXLINE; + nbufp = ber_memrealloc( *bufp, *buflenp ); + if( nbufp == NULL ) { + return 0; + } + *bufp = nbufp; + } + strcpy( *bufp + lcur, line ); + lcur += len; + } + + return( found_entry ); +} diff --git a/libraries/libldap/messages.c b/libraries/libldap/messages.c new file mode 100644 index 0000000..8aa2dce --- /dev/null +++ b/libraries/libldap/messages.c @@ -0,0 +1,68 @@ +/* messages.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +LDAPMessage * +ldap_first_message( LDAP *ld, LDAPMessage *chain ) +{ + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( chain != NULL ); + + return chain; +} + +LDAPMessage * +ldap_next_message( LDAP *ld, LDAPMessage *msg ) +{ + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( msg != NULL ); + + return msg->lm_chain; +} + +int +ldap_count_messages( LDAP *ld, LDAPMessage *chain ) +{ + int i; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + for ( i = 0; chain != NULL; chain = chain->lm_chain ) { + i++; + } + + return( i ); +} + +BerElement* +ldap_get_message_ber( LDAPMessage *ld ) +{ + return ld->lm_ber; +} diff --git a/libraries/libldap/modify.c b/libraries/libldap/modify.c new file mode 100644 index 0000000..eb37afb --- /dev/null +++ b/libraries/libldap/modify.c @@ -0,0 +1,233 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* A modify request/response looks like this: + * ModifyRequest ::= [APPLICATION 6] SEQUENCE { + * object LDAPDN, + * changes SEQUENCE OF change SEQUENCE { + * operation ENUMERATED { + * add (0), + * delete (1), + * replace (2), + * ... }, + * modification PartialAttribute } } + * + * PartialAttribute ::= SEQUENCE { + * type AttributeDescription, + * vals SET OF value AttributeValue } + * + * AttributeDescription ::= LDAPString + * -- Constrained to <attributedescription> [RFC4512] + * + * AttributeValue ::= OCTET STRING + * + * ModifyResponse ::= [APPLICATION 7] LDAPResult + * + * (Source: RFC 4511) + */ + +BerElement * +ldap_build_modify_req( + LDAP *ld, + LDAP_CONST char *dn, + LDAPMod **mods, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp ) +{ + BerElement *ber; + int i, rc; + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + LDAP_NEXT_MSGID( ld, *msgidp ); + rc = ber_printf( ber, "{it{s{" /*}}}*/, *msgidp, LDAP_REQ_MODIFY, dn ); + if ( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* allow mods to be NULL ("touch") */ + if ( mods ) { + /* for each modification to be performed... */ + for ( i = 0; mods[i] != NULL; i++ ) { + if (( mods[i]->mod_op & LDAP_MOD_BVALUES) != 0 ) { + rc = ber_printf( ber, "{e{s[V]N}N}", + (ber_int_t) ( mods[i]->mod_op & ~LDAP_MOD_BVALUES ), + mods[i]->mod_type, mods[i]->mod_bvalues ); + } else { + rc = ber_printf( ber, "{e{s[v]N}N}", + (ber_int_t) mods[i]->mod_op, + mods[i]->mod_type, mods[i]->mod_values ); + } + + if ( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + } + } + + if ( ber_printf( ber, /*{{*/ "N}N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +/* + * ldap_modify_ext - initiate an ldap extended modify operation. + * + * Parameters: + * + * ld LDAP descriptor + * dn DN of the object to modify + * mods List of modifications to make. This is null-terminated + * array of struct ldapmod's, specifying the modifications + * to perform. + * sctrls Server Controls + * cctrls Client Controls + * msgidp Message ID pointer + * + * Example: + * LDAPMod *mods[] = { + * { LDAP_MOD_ADD, "cn", { "babs jensen", "babs", 0 } }, + * { LDAP_MOD_REPLACE, "sn", { "babs jensen", "babs", 0 } }, + * { LDAP_MOD_DELETE, "ou", 0 }, + * { LDAP_MOD_INCREMENT, "uidNumber, { "1", 0 } } + * 0 + * } + * rc= ldap_modify_ext( ld, dn, mods, sctrls, cctrls, &msgid ); + */ +int +ldap_modify_ext( LDAP *ld, + LDAP_CONST char *dn, + LDAPMod **mods, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *ber; + int rc; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_modify_ext\n", 0, 0, 0 ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + ber = ldap_build_modify_req( ld, dn, mods, sctrls, cctrls, &id ); + if( !ber ) + return ld->ld_errno; + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_MODIFY, dn, ber, id ); + return( *msgidp < 0 ? ld->ld_errno : LDAP_SUCCESS ); +} + +/* + * ldap_modify - initiate an ldap modify operation. + * + * Parameters: + * + * ld LDAP descriptor + * dn DN of the object to modify + * mods List of modifications to make. This is null-terminated + * array of struct ldapmod's, specifying the modifications + * to perform. + * + * Example: + * LDAPMod *mods[] = { + * { LDAP_MOD_ADD, "cn", { "babs jensen", "babs", 0 } }, + * { LDAP_MOD_REPLACE, "sn", { "babs jensen", "babs", 0 } }, + * { LDAP_MOD_DELETE, "ou", 0 }, + * { LDAP_MOD_INCREMENT, "uidNumber, { "1", 0 } } + * 0 + * } + * msgid = ldap_modify( ld, dn, mods ); + */ +int +ldap_modify( LDAP *ld, LDAP_CONST char *dn, LDAPMod **mods ) +{ + int rc, msgid; + + Debug( LDAP_DEBUG_TRACE, "ldap_modify\n", 0, 0, 0 ); + + rc = ldap_modify_ext( ld, dn, mods, NULL, NULL, &msgid ); + + if ( rc != LDAP_SUCCESS ) + return -1; + + return msgid; +} + +int +ldap_modify_ext_s( LDAP *ld, LDAP_CONST char *dn, + LDAPMod **mods, LDAPControl **sctrl, LDAPControl **cctrl ) +{ + int rc; + int msgid; + LDAPMessage *res; + + rc = ldap_modify_ext( ld, dn, mods, sctrl, cctrl, &msgid ); + + if ( rc != LDAP_SUCCESS ) + return( rc ); + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, &res ) == -1 || !res ) + return( ld->ld_errno ); + + return( ldap_result2error( ld, res, 1 ) ); +} + +int +ldap_modify_s( LDAP *ld, LDAP_CONST char *dn, LDAPMod **mods ) +{ + return ldap_modify_ext_s( ld, dn, mods, NULL, NULL ); +} + diff --git a/libraries/libldap/modrdn.c b/libraries/libldap/modrdn.c new file mode 100644 index 0000000..72198b3 --- /dev/null +++ b/libraries/libldap/modrdn.c @@ -0,0 +1,273 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ +/* Copyright 1999, Juan C. Gomez, All rights reserved. + * This software is not subject to any license of Silicon Graphics + * Inc. or Purdue University. + * + * Redistribution and use in source and binary forms are permitted + * without restriction or fee of any kind as long as this notice + * is preserved. + */ + +/* ACKNOWLEDGEMENTS: + * Juan C. Gomez + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * A modify rdn request looks like this: + * ModifyRDNRequest ::= SEQUENCE { + * entry DistinguishedName, + * newrdn RelativeDistinguishedName, + * deleteoldrdn BOOLEAN + * newSuperior [0] DistinguishedName [v3 only] + * } + */ + +BerElement * +ldap_build_moddn_req( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *newrdn, + LDAP_CONST char *newSuperior, + int deleteoldrdn, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp ) +{ + BerElement *ber; + int rc; + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + LDAP_NEXT_MSGID( ld, *msgidp ); + if( newSuperior != NULL ) { + /* must be version 3 (or greater) */ + if ( ld->ld_version < LDAP_VERSION3 ) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + ber_free( ber, 1 ); + return( NULL ); + } + rc = ber_printf( ber, "{it{ssbtsN}", /* '}' */ + *msgidp, LDAP_REQ_MODDN, + dn, newrdn, (ber_int_t) deleteoldrdn, + LDAP_TAG_NEWSUPERIOR, newSuperior ); + + } else { + rc = ber_printf( ber, "{it{ssbN}", /* '}' */ + *msgidp, LDAP_REQ_MODDN, + dn, newrdn, (ber_int_t) deleteoldrdn ); + } + + if ( rc < 0 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + rc = ber_printf( ber, /*{*/ "N}" ); + if ( rc < 0 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +/* + * ldap_rename - initiate an ldap extended modifyDN operation. + * + * Parameters: + * ld LDAP descriptor + * dn DN of the object to modify + * newrdn RDN to give the object + * deleteoldrdn nonzero means to delete old rdn values from the entry + * newSuperior DN of the new parent if applicable + * + * Returns the LDAP error code. + */ + +int +ldap_rename( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *newrdn, + LDAP_CONST char *newSuperior, + int deleteoldrdn, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *ber; + int rc; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_rename\n", 0, 0, 0 ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + ber = ldap_build_moddn_req( ld, dn, newrdn, newSuperior, + deleteoldrdn, sctrls, cctrls, &id ); + if( !ber ) + return ld->ld_errno; + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_MODRDN, dn, ber, id ); + + if( *msgidp < 0 ) { + return( ld->ld_errno ); + } + + return LDAP_SUCCESS; +} + + +/* + * ldap_rename2 - initiate an ldap (and X.500) modifyDN operation. Parameters: + * (LDAP V3 MODIFYDN REQUEST) + * ld LDAP descriptor + * dn DN of the object to modify + * newrdn RDN to give the object + * deleteoldrdn nonzero means to delete old rdn values from the entry + * newSuperior DN of the new parent if applicable + * + * ldap_rename2 uses a U-Mich Style API. It returns the msgid. + */ + +int +ldap_rename2( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *newrdn, + LDAP_CONST char *newSuperior, + int deleteoldrdn ) +{ + int msgid; + int rc; + + Debug( LDAP_DEBUG_TRACE, "ldap_rename2\n", 0, 0, 0 ); + + rc = ldap_rename( ld, dn, newrdn, newSuperior, + deleteoldrdn, NULL, NULL, &msgid ); + + return rc == LDAP_SUCCESS ? msgid : -1; +} + + +/* + * ldap_modrdn2 - initiate an ldap modifyRDN operation. Parameters: + * + * ld LDAP descriptor + * dn DN of the object to modify + * newrdn RDN to give the object + * deleteoldrdn nonzero means to delete old rdn values from the entry + * + * Example: + * msgid = ldap_modrdn( ld, dn, newrdn ); + */ +int +ldap_modrdn2( LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *newrdn, + int deleteoldrdn ) +{ + return ldap_rename2( ld, dn, newrdn, NULL, deleteoldrdn ); +} + +int +ldap_modrdn( LDAP *ld, LDAP_CONST char *dn, LDAP_CONST char *newrdn ) +{ + return( ldap_rename2( ld, dn, newrdn, NULL, 1 ) ); +} + + +int +ldap_rename_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *newrdn, + LDAP_CONST char *newSuperior, + int deleteoldrdn, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int rc; + int msgid; + LDAPMessage *res; + + rc = ldap_rename( ld, dn, newrdn, newSuperior, + deleteoldrdn, sctrls, cctrls, &msgid ); + + if( rc != LDAP_SUCCESS ) { + return rc; + } + + rc = ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &res ); + + if( rc == -1 || !res ) { + return ld->ld_errno; + } + + return ldap_result2error( ld, res, 1 ); +} + +int +ldap_rename2_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *newrdn, + LDAP_CONST char *newSuperior, + int deleteoldrdn ) +{ + return ldap_rename_s( ld, dn, newrdn, newSuperior, + deleteoldrdn, NULL, NULL ); +} + +int +ldap_modrdn2_s( LDAP *ld, LDAP_CONST char *dn, LDAP_CONST char *newrdn, int deleteoldrdn ) +{ + return ldap_rename_s( ld, dn, newrdn, NULL, deleteoldrdn, NULL, NULL ); +} + +int +ldap_modrdn_s( LDAP *ld, LDAP_CONST char *dn, LDAP_CONST char *newrdn ) +{ + return ldap_rename_s( ld, dn, newrdn, NULL, 1, NULL, NULL ); +} + diff --git a/libraries/libldap/open.c b/libraries/libldap/open.c new file mode 100644 index 0000000..022965e --- /dev/null +++ b/libraries/libldap/open.c @@ -0,0 +1,609 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include <ac/stdlib.h> + +#include <ac/param.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include <ac/unistd.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +/* Caller must hold the conn_mutex since simultaneous accesses are possible */ +int ldap_open_defconn( LDAP *ld ) +{ + ld->ld_defconn = ldap_new_connection( ld, + &ld->ld_options.ldo_defludp, 1, 1, NULL, 0, 0 ); + + if( ld->ld_defconn == NULL ) { + ld->ld_errno = LDAP_SERVER_DOWN; + return -1; + } + + ++ld->ld_defconn->lconn_refcnt; /* so it never gets closed/freed */ + return 0; +} + +/* + * ldap_open - initialize and connect to an ldap server. A magic cookie to + * be used for future communication is returned on success, NULL on failure. + * "host" may be a space-separated list of hosts or IP addresses + * + * Example: + * LDAP *ld; + * ld = ldap_open( hostname, port ); + */ + +LDAP * +ldap_open( LDAP_CONST char *host, int port ) +{ + int rc; + LDAP *ld; + + Debug( LDAP_DEBUG_TRACE, "ldap_open(%s, %d)\n", + host, port, 0 ); + + ld = ldap_init( host, port ); + if ( ld == NULL ) { + return( NULL ); + } + + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + rc = ldap_open_defconn( ld ); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + + if( rc < 0 ) { + ldap_ld_free( ld, 0, NULL, NULL ); + ld = NULL; + } + + Debug( LDAP_DEBUG_TRACE, "ldap_open: %s\n", + ld != NULL ? "succeeded" : "failed", 0, 0 ); + + return ld; +} + + + +int +ldap_create( LDAP **ldp ) +{ + LDAP *ld; + struct ldapoptions *gopts; + + *ldp = NULL; + /* Get pointer to global option structure */ + if ( (gopts = LDAP_INT_GLOBAL_OPT()) == NULL) { + return LDAP_NO_MEMORY; + } + + /* Initialize the global options, if not already done. */ + if( gopts->ldo_valid != LDAP_INITIALIZED ) { + ldap_int_initialize(gopts, NULL); + if ( gopts->ldo_valid != LDAP_INITIALIZED ) + return LDAP_LOCAL_ERROR; + } + + Debug( LDAP_DEBUG_TRACE, "ldap_create\n", 0, 0, 0 ); + + if ( (ld = (LDAP *) LDAP_CALLOC( 1, sizeof(LDAP) )) == NULL ) { + return( LDAP_NO_MEMORY ); + } + + if ( (ld->ldc = (struct ldap_common *) LDAP_CALLOC( 1, + sizeof(struct ldap_common) )) == NULL ) { + LDAP_FREE( (char *)ld ); + return( LDAP_NO_MEMORY ); + } + /* copy the global options */ + LDAP_MUTEX_LOCK( &gopts->ldo_mutex ); + AC_MEMCPY(&ld->ld_options, gopts, sizeof(ld->ld_options)); +#ifdef LDAP_R_COMPILE + /* Properly initialize the structs mutex */ + ldap_pvt_thread_mutex_init( &(ld->ld_ldopts_mutex) ); +#endif + LDAP_MUTEX_UNLOCK( &gopts->ldo_mutex ); + + ld->ld_valid = LDAP_VALID_SESSION; + + /* but not pointers to malloc'ed items */ + ld->ld_options.ldo_sctrls = NULL; + ld->ld_options.ldo_cctrls = NULL; + ld->ld_options.ldo_defludp = NULL; + ld->ld_options.ldo_conn_cbs = NULL; + +#ifdef HAVE_CYRUS_SASL + ld->ld_options.ldo_def_sasl_mech = gopts->ldo_def_sasl_mech + ? LDAP_STRDUP( gopts->ldo_def_sasl_mech ) : NULL; + ld->ld_options.ldo_def_sasl_realm = gopts->ldo_def_sasl_realm + ? LDAP_STRDUP( gopts->ldo_def_sasl_realm ) : NULL; + ld->ld_options.ldo_def_sasl_authcid = gopts->ldo_def_sasl_authcid + ? LDAP_STRDUP( gopts->ldo_def_sasl_authcid ) : NULL; + ld->ld_options.ldo_def_sasl_authzid = gopts->ldo_def_sasl_authzid + ? LDAP_STRDUP( gopts->ldo_def_sasl_authzid ) : NULL; +#endif + +#ifdef HAVE_TLS + /* We explicitly inherit the SSL_CTX, don't need the names/paths. Leave + * them empty to allow new SSL_CTX's to be created from scratch. + */ + memset( &ld->ld_options.ldo_tls_info, 0, + sizeof( ld->ld_options.ldo_tls_info )); + ld->ld_options.ldo_tls_ctx = NULL; +#endif + + if ( gopts->ldo_defludp ) { + ld->ld_options.ldo_defludp = ldap_url_duplist(gopts->ldo_defludp); + + if ( ld->ld_options.ldo_defludp == NULL ) goto nomem; + } + + if (( ld->ld_selectinfo = ldap_new_select_info()) == NULL ) goto nomem; + + ld->ld_lberoptions = LBER_USE_DER; + + ld->ld_sb = ber_sockbuf_alloc( ); + if ( ld->ld_sb == NULL ) goto nomem; + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &ld->ld_msgid_mutex ); + ldap_pvt_thread_mutex_init( &ld->ld_conn_mutex ); + ldap_pvt_thread_mutex_init( &ld->ld_req_mutex ); + ldap_pvt_thread_mutex_init( &ld->ld_res_mutex ); + ldap_pvt_thread_mutex_init( &ld->ld_abandon_mutex ); + ldap_pvt_thread_mutex_init( &ld->ld_ldcmutex ); +#endif + ld->ld_ldcrefcnt = 1; + *ldp = ld; + return LDAP_SUCCESS; + +nomem: + ldap_free_select_info( ld->ld_selectinfo ); + ldap_free_urllist( ld->ld_options.ldo_defludp ); +#ifdef HAVE_CYRUS_SASL + LDAP_FREE( ld->ld_options.ldo_def_sasl_authzid ); + LDAP_FREE( ld->ld_options.ldo_def_sasl_authcid ); + LDAP_FREE( ld->ld_options.ldo_def_sasl_realm ); + LDAP_FREE( ld->ld_options.ldo_def_sasl_mech ); +#endif + LDAP_FREE( (char *)ld ); + return LDAP_NO_MEMORY; +} + +/* + * ldap_init - initialize the LDAP library. A magic cookie to be used for + * future communication is returned on success, NULL on failure. + * "host" may be a space-separated list of hosts or IP addresses + * + * Example: + * LDAP *ld; + * ld = ldap_init( host, port ); + */ +LDAP * +ldap_init( LDAP_CONST char *defhost, int defport ) +{ + LDAP *ld; + int rc; + + rc = ldap_create(&ld); + if ( rc != LDAP_SUCCESS ) + return NULL; + + if (defport != 0) + ld->ld_options.ldo_defport = defport; + + if (defhost != NULL) { + rc = ldap_set_option(ld, LDAP_OPT_HOST_NAME, defhost); + if ( rc != LDAP_SUCCESS ) { + ldap_ld_free(ld, 1, NULL, NULL); + return NULL; + } + } + + return( ld ); +} + + +int +ldap_initialize( LDAP **ldp, LDAP_CONST char *url ) +{ + int rc; + LDAP *ld; + + *ldp = NULL; + rc = ldap_create(&ld); + if ( rc != LDAP_SUCCESS ) + return rc; + + if (url != NULL) { + rc = ldap_set_option(ld, LDAP_OPT_URI, url); + if ( rc != LDAP_SUCCESS ) { + ldap_ld_free(ld, 1, NULL, NULL); + return rc; + } +#ifdef LDAP_CONNECTIONLESS + if (ldap_is_ldapc_url(url)) + LDAP_IS_UDP(ld) = 1; +#endif + } + + *ldp = ld; + return LDAP_SUCCESS; +} + +int +ldap_init_fd( + ber_socket_t fd, + int proto, + LDAP_CONST char *url, + LDAP **ldp +) +{ + int rc; + LDAP *ld; + LDAPConn *conn; +#ifdef LDAP_CONNECTIONLESS + ber_socklen_t len; +#endif + + *ldp = NULL; + rc = ldap_create( &ld ); + if( rc != LDAP_SUCCESS ) + return( rc ); + + if (url != NULL) { + rc = ldap_set_option(ld, LDAP_OPT_URI, url); + if ( rc != LDAP_SUCCESS ) { + ldap_ld_free(ld, 1, NULL, NULL); + return rc; + } + } + + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + /* Attach the passed socket as the LDAP's connection */ + conn = ldap_new_connection( ld, NULL, 1, 0, NULL, 0, 0 ); + if( conn == NULL ) { + ldap_unbind_ext( ld, NULL, NULL ); + return( LDAP_NO_MEMORY ); + } + if( url ) + conn->lconn_server = ldap_url_dup( ld->ld_options.ldo_defludp ); + ber_sockbuf_ctrl( conn->lconn_sb, LBER_SB_OPT_SET_FD, &fd ); + ld->ld_defconn = conn; + ++ld->ld_defconn->lconn_refcnt; /* so it never gets closed/freed */ + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + + switch( proto ) { + case LDAP_PROTO_TCP: +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"tcp_" ); +#endif + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_tcp, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + break; + +#ifdef LDAP_CONNECTIONLESS + case LDAP_PROTO_UDP: + LDAP_IS_UDP(ld) = 1; + if( ld->ld_options.ldo_peer ) + ldap_memfree( ld->ld_options.ldo_peer ); + ld->ld_options.ldo_peer = ldap_memcalloc( 1, sizeof( struct sockaddr_storage ) ); + len = sizeof( struct sockaddr_storage ); + if( getpeername ( fd, ld->ld_options.ldo_peer, &len ) < 0) { + ldap_unbind_ext( ld, NULL, NULL ); + return( AC_SOCKET_ERROR ); + } +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"udp_" ); +#endif + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_udp, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_readahead, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + break; +#endif /* LDAP_CONNECTIONLESS */ + + case LDAP_PROTO_IPC: +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"ipc_" ); +#endif + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_fd, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + break; + + case LDAP_PROTO_EXT: + /* caller must supply sockbuf handlers */ + break; + + default: + ldap_unbind_ext( ld, NULL, NULL ); + return LDAP_PARAM_ERROR; + } + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + INT_MAX, (void *)"ldap_" ); +#endif + + /* Add the connection to the *LDAP's select pool */ + ldap_mark_select_read( ld, conn->lconn_sb ); + + *ldp = ld; + return LDAP_SUCCESS; +} + +/* Protected by ld_conn_mutex */ +int +ldap_int_open_connection( + LDAP *ld, + LDAPConn *conn, + LDAPURLDesc *srv, + int async ) +{ + int rc = -1; + int proto; + + Debug( LDAP_DEBUG_TRACE, "ldap_int_open_connection\n", 0, 0, 0 ); + + switch ( proto = ldap_pvt_url_scheme2proto( srv->lud_scheme ) ) { + case LDAP_PROTO_TCP: + rc = ldap_connect_to_host( ld, conn->lconn_sb, + proto, srv, async ); + + if ( rc == -1 ) return rc; +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"tcp_" ); +#endif + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_tcp, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + + break; + +#ifdef LDAP_CONNECTIONLESS + case LDAP_PROTO_UDP: + LDAP_IS_UDP(ld) = 1; + rc = ldap_connect_to_host( ld, conn->lconn_sb, + proto, srv, async ); + + if ( rc == -1 ) return rc; +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"udp_" ); +#endif + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_udp, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_readahead, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + + break; +#endif + case LDAP_PROTO_IPC: +#ifdef LDAP_PF_LOCAL + /* only IPC mechanism supported is PF_LOCAL (PF_UNIX) */ + rc = ldap_connect_to_path( ld, conn->lconn_sb, + srv, async ); + if ( rc == -1 ) return rc; +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"ipc_" ); +#endif + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_fd, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + + break; +#endif /* LDAP_PF_LOCAL */ + default: + return -1; + break; + } + + conn->lconn_created = time( NULL ); + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, + INT_MAX, (void *)"ldap_" ); +#endif + +#ifdef LDAP_CONNECTIONLESS + if( proto == LDAP_PROTO_UDP ) return 0; +#endif + +#ifdef HAVE_TLS + if (rc == 0 && ( ld->ld_options.ldo_tls_mode == LDAP_OPT_X_TLS_HARD || + strcmp( srv->lud_scheme, "ldaps" ) == 0 )) + { + ++conn->lconn_refcnt; /* avoid premature free */ + + rc = ldap_int_tls_start( ld, conn, srv ); + + --conn->lconn_refcnt; + + if (rc != LDAP_SUCCESS) { + /* process connection callbacks */ + { + struct ldapoptions *lo; + ldaplist *ll; + ldap_conncb *cb; + + lo = &ld->ld_options; + LDAP_MUTEX_LOCK( &lo->ldo_mutex ); + if ( lo->ldo_conn_cbs ) { + for ( ll=lo->ldo_conn_cbs; ll; ll=ll->ll_next ) { + cb = ll->ll_data; + cb->lc_del( ld, conn->lconn_sb, cb ); + } + } + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + lo = LDAP_INT_GLOBAL_OPT(); + LDAP_MUTEX_LOCK( &lo->ldo_mutex ); + if ( lo->ldo_conn_cbs ) { + for ( ll=lo->ldo_conn_cbs; ll; ll=ll->ll_next ) { + cb = ll->ll_data; + cb->lc_del( ld, conn->lconn_sb, cb ); + } + } + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + } + return -1; + } + } +#endif + + return( 0 ); +} + +/* + * ldap_open_internal_connection - open connection and set file descriptor + * + * note: ldap_init_fd() may be preferable + */ + +int +ldap_open_internal_connection( LDAP **ldp, ber_socket_t *fdp ) +{ + int rc; + LDAPConn *c; + LDAPRequest *lr; + LDAP *ld; + + rc = ldap_create( &ld ); + if( rc != LDAP_SUCCESS ) { + *ldp = NULL; + return( rc ); + } + + /* Make it appear that a search request, msgid 0, was sent */ + lr = (LDAPRequest *)LDAP_CALLOC( 1, sizeof( LDAPRequest )); + if( lr == NULL ) { + ldap_unbind_ext( ld, NULL, NULL ); + *ldp = NULL; + return( LDAP_NO_MEMORY ); + } + memset(lr, 0, sizeof( LDAPRequest )); + lr->lr_msgid = 0; + lr->lr_status = LDAP_REQST_INPROGRESS; + lr->lr_res_errno = LDAP_SUCCESS; + /* no mutex lock needed, we just created this ld here */ + ld->ld_requests = lr; + + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + /* Attach the passed socket as the *LDAP's connection */ + c = ldap_new_connection( ld, NULL, 1, 0, NULL, 0, 0 ); + if( c == NULL ) { + ldap_unbind_ext( ld, NULL, NULL ); + *ldp = NULL; + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + return( LDAP_NO_MEMORY ); + } + ber_sockbuf_ctrl( c->lconn_sb, LBER_SB_OPT_SET_FD, fdp ); +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( c->lconn_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"int_" ); +#endif + ber_sockbuf_add_io( c->lconn_sb, &ber_sockbuf_io_tcp, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + ld->ld_defconn = c; + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + + /* Add the connection to the *LDAP's select pool */ + ldap_mark_select_read( ld, c->lconn_sb ); + + /* Make this connection an LDAP V3 protocol connection */ + rc = LDAP_VERSION3; + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &rc ); + *ldp = ld; + + ++ld->ld_defconn->lconn_refcnt; /* so it never gets closed/freed */ + + return( LDAP_SUCCESS ); +} + +LDAP * +ldap_dup( LDAP *old ) +{ + LDAP *ld; + + if ( old == NULL ) { + return( NULL ); + } + + Debug( LDAP_DEBUG_TRACE, "ldap_dup\n", 0, 0, 0 ); + + if ( (ld = (LDAP *) LDAP_CALLOC( 1, sizeof(LDAP) )) == NULL ) { + return( NULL ); + } + + LDAP_MUTEX_LOCK( &old->ld_ldcmutex ); + ld->ldc = old->ldc; + old->ld_ldcrefcnt++; + LDAP_MUTEX_UNLOCK( &old->ld_ldcmutex ); + return ( ld ); +} + +int +ldap_int_check_async_open( LDAP *ld, ber_socket_t sd ) +{ + struct timeval tv = { 0 }; + int rc; + + rc = ldap_int_poll( ld, sd, &tv, 1 ); + switch ( rc ) { + case 0: + /* now ready to start tls */ + ld->ld_defconn->lconn_status = LDAP_CONNST_CONNECTED; + break; + + default: + ld->ld_errno = LDAP_CONNECT_ERROR; + return -1; + + case -2: + /* connect not completed yet */ + ld->ld_errno = LDAP_X_CONNECTING; + return rc; + } + +#ifdef HAVE_TLS + if ( ld->ld_options.ldo_tls_mode == LDAP_OPT_X_TLS_HARD || + !strcmp( ld->ld_defconn->lconn_server->lud_scheme, "ldaps" )) { + + ++ld->ld_defconn->lconn_refcnt; /* avoid premature free */ + + rc = ldap_int_tls_start( ld, ld->ld_defconn, ld->ld_defconn->lconn_server ); + + --ld->ld_defconn->lconn_refcnt; + } +#endif + return rc; +} diff --git a/libraries/libldap/options.c b/libraries/libldap/options.c new file mode 100644 index 0000000..0549040 --- /dev/null +++ b/libraries/libldap/options.c @@ -0,0 +1,940 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +#define LDAP_OPT_REBIND_PROC 0x4e814d +#define LDAP_OPT_REBIND_PARAMS 0x4e814e + +#define LDAP_OPT_NEXTREF_PROC 0x4e815d +#define LDAP_OPT_NEXTREF_PARAMS 0x4e815e + +#define LDAP_OPT_URLLIST_PROC 0x4e816d +#define LDAP_OPT_URLLIST_PARAMS 0x4e816e + +static const LDAPAPIFeatureInfo features[] = { +#ifdef LDAP_API_FEATURE_X_OPENLDAP + { /* OpenLDAP Extensions API Feature */ + LDAP_FEATURE_INFO_VERSION, + "X_OPENLDAP", + LDAP_API_FEATURE_X_OPENLDAP + }, +#endif + +#ifdef LDAP_API_FEATURE_THREAD_SAFE + { /* Basic Thread Safe */ + LDAP_FEATURE_INFO_VERSION, + "THREAD_SAFE", + LDAP_API_FEATURE_THREAD_SAFE + }, +#endif +#ifdef LDAP_API_FEATURE_SESSION_THREAD_SAFE + { /* Session Thread Safe */ + LDAP_FEATURE_INFO_VERSION, + "SESSION_THREAD_SAFE", + LDAP_API_FEATURE_SESSION_THREAD_SAFE + }, +#endif +#ifdef LDAP_API_FEATURE_OPERATION_THREAD_SAFE + { /* Operation Thread Safe */ + LDAP_FEATURE_INFO_VERSION, + "OPERATION_THREAD_SAFE", + LDAP_API_FEATURE_OPERATION_THREAD_SAFE + }, +#endif +#ifdef LDAP_API_FEATURE_X_OPENLDAP_REENTRANT + { /* OpenLDAP Reentrant */ + LDAP_FEATURE_INFO_VERSION, + "X_OPENLDAP_REENTRANT", + LDAP_API_FEATURE_X_OPENLDAP_REENTRANT + }, +#endif +#if defined( LDAP_API_FEATURE_X_OPENLDAP_THREAD_SAFE ) && \ + defined( LDAP_THREAD_SAFE ) + { /* OpenLDAP Thread Safe */ + LDAP_FEATURE_INFO_VERSION, + "X_OPENLDAP_THREAD_SAFE", + LDAP_API_FEATURE_X_OPENLDAP_THREAD_SAFE + }, +#endif +#ifdef LDAP_API_FEATURE_X_OPENLDAP_V2_REFERRALS + { /* V2 Referrals */ + LDAP_FEATURE_INFO_VERSION, + "X_OPENLDAP_V2_REFERRALS", + LDAP_API_FEATURE_X_OPENLDAP_V2_REFERRALS + }, +#endif + {0, NULL, 0} +}; + +int +ldap_get_option( + LDAP *ld, + int option, + void *outvalue) +{ + struct ldapoptions *lo; + int rc = LDAP_OPT_ERROR; + + /* Get pointer to global option structure */ + lo = LDAP_INT_GLOBAL_OPT(); + if (NULL == lo) { + return LDAP_NO_MEMORY; + } + + if( lo->ldo_valid != LDAP_INITIALIZED ) { + ldap_int_initialize(lo, NULL); + if ( lo->ldo_valid != LDAP_INITIALIZED ) + return LDAP_LOCAL_ERROR; + } + + if(ld != NULL) { + assert( LDAP_VALID( ld ) ); + + if( !LDAP_VALID( ld ) ) { + return LDAP_OPT_ERROR; + } + + lo = &ld->ld_options; + } + + if(outvalue == NULL) { + /* no place to get to */ + return LDAP_OPT_ERROR; + } + + LDAP_MUTEX_LOCK( &lo->ldo_mutex ); + + switch(option) { + case LDAP_OPT_API_INFO: { + struct ldapapiinfo *info = (struct ldapapiinfo *) outvalue; + + if(info == NULL) { + /* outvalue must point to an apiinfo structure */ + break; /* LDAP_OPT_ERROR */ + } + + if(info->ldapai_info_version != LDAP_API_INFO_VERSION) { + /* api info version mismatch */ + info->ldapai_info_version = LDAP_API_INFO_VERSION; + break; /* LDAP_OPT_ERROR */ + } + + info->ldapai_api_version = LDAP_API_VERSION; + info->ldapai_protocol_version = LDAP_VERSION_MAX; + + if(features[0].ldapaif_name == NULL) { + info->ldapai_extensions = NULL; + } else { + int i; + info->ldapai_extensions = LDAP_MALLOC(sizeof(char *) * + sizeof(features)/sizeof(LDAPAPIFeatureInfo)); + + for(i=0; features[i].ldapaif_name != NULL; i++) { + info->ldapai_extensions[i] = + LDAP_STRDUP(features[i].ldapaif_name); + } + + info->ldapai_extensions[i] = NULL; + } + + info->ldapai_vendor_name = LDAP_STRDUP(LDAP_VENDOR_NAME); + info->ldapai_vendor_version = LDAP_VENDOR_VERSION; + + rc = LDAP_OPT_SUCCESS; + break; + } break; + + case LDAP_OPT_DESC: + if( ld == NULL || ld->ld_sb == NULL ) { + /* bad param */ + break; + } + + ber_sockbuf_ctrl( ld->ld_sb, LBER_SB_OPT_GET_FD, outvalue ); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_SOCKBUF: + if( ld == NULL ) break; + *(Sockbuf **)outvalue = ld->ld_sb; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_TIMEOUT: + /* the caller has to free outvalue ! */ + if ( lo->ldo_tm_api.tv_sec < 0 ) { + *(void **)outvalue = NULL; + } else if ( ldap_int_timeval_dup( outvalue, &lo->ldo_tm_api ) != 0 ) { + break; /* LDAP_OPT_ERROR */ + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_NETWORK_TIMEOUT: + /* the caller has to free outvalue ! */ + if ( lo->ldo_tm_net.tv_sec < 0 ) { + *(void **)outvalue = NULL; + } else if ( ldap_int_timeval_dup( outvalue, &lo->ldo_tm_net ) != 0 ) { + break; /* LDAP_OPT_ERROR */ + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_DEREF: + * (int *) outvalue = lo->ldo_deref; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_SIZELIMIT: + * (int *) outvalue = lo->ldo_sizelimit; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_TIMELIMIT: + * (int *) outvalue = lo->ldo_timelimit; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_REFERRALS: + * (int *) outvalue = (int) LDAP_BOOL_GET(lo, LDAP_BOOL_REFERRALS); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_RESTART: + * (int *) outvalue = (int) LDAP_BOOL_GET(lo, LDAP_BOOL_RESTART); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_PROTOCOL_VERSION: + * (int *) outvalue = lo->ldo_version; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_SERVER_CONTROLS: + * (LDAPControl ***) outvalue = + ldap_controls_dup( lo->ldo_sctrls ); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_CLIENT_CONTROLS: + * (LDAPControl ***) outvalue = + ldap_controls_dup( lo->ldo_cctrls ); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_HOST_NAME: + * (char **) outvalue = ldap_url_list2hosts(lo->ldo_defludp); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_URI: + * (char **) outvalue = ldap_url_list2urls(lo->ldo_defludp); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_DEFBASE: + if( lo->ldo_defbase == NULL ) { + * (char **) outvalue = NULL; + } else { + * (char **) outvalue = LDAP_STRDUP(lo->ldo_defbase); + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_CONNECT_ASYNC: + * (int *) outvalue = (int) LDAP_BOOL_GET(lo, LDAP_BOOL_CONNECT_ASYNC); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_CONNECT_CB: + { + /* Getting deletes the specified callback */ + ldaplist **ll = &lo->ldo_conn_cbs; + for (;*ll;ll = &(*ll)->ll_next) { + if ((*ll)->ll_data == outvalue) { + ldaplist *lc = *ll; + *ll = lc->ll_next; + LDAP_FREE(lc); + break; + } + } + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_RESULT_CODE: + if(ld == NULL) { + /* bad param */ + break; + } + * (int *) outvalue = ld->ld_errno; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_DIAGNOSTIC_MESSAGE: + if(ld == NULL) { + /* bad param */ + break; + } + + if( ld->ld_error == NULL ) { + * (char **) outvalue = NULL; + } else { + * (char **) outvalue = LDAP_STRDUP(ld->ld_error); + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_MATCHED_DN: + if(ld == NULL) { + /* bad param */ + break; + } + + if( ld->ld_matched == NULL ) { + * (char **) outvalue = NULL; + } else { + * (char **) outvalue = LDAP_STRDUP( ld->ld_matched ); + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_REFERRAL_URLS: + if(ld == NULL) { + /* bad param */ + break; + } + + if( ld->ld_referrals == NULL ) { + * (char ***) outvalue = NULL; + } else { + * (char ***) outvalue = ldap_value_dup(ld->ld_referrals); + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_API_FEATURE_INFO: { + LDAPAPIFeatureInfo *info = (LDAPAPIFeatureInfo *) outvalue; + int i; + + if(info == NULL) + break; /* LDAP_OPT_ERROR */ + + if(info->ldapaif_info_version != LDAP_FEATURE_INFO_VERSION) { + /* api info version mismatch */ + info->ldapaif_info_version = LDAP_FEATURE_INFO_VERSION; + break; /* LDAP_OPT_ERROR */ + } + + if(info->ldapaif_name == NULL) + break; /* LDAP_OPT_ERROR */ + + for(i=0; features[i].ldapaif_name != NULL; i++) { + if(!strcmp(info->ldapaif_name, features[i].ldapaif_name)) { + info->ldapaif_version = + features[i].ldapaif_version; + rc = LDAP_OPT_SUCCESS; + break; + } + } + } + break; + + case LDAP_OPT_DEBUG_LEVEL: + * (int *) outvalue = lo->ldo_debug; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_SESSION_REFCNT: + if(ld == NULL) { + /* bad param */ + break; + } + LDAP_MUTEX_LOCK( &ld->ld_ldcmutex ); + * (int *) outvalue = ld->ld_ldcrefcnt; + LDAP_MUTEX_UNLOCK( &ld->ld_ldcmutex ); + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_X_KEEPALIVE_IDLE: + * (int *) outvalue = lo->ldo_keepalive_idle; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_X_KEEPALIVE_PROBES: + * (int *) outvalue = lo->ldo_keepalive_probes; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_X_KEEPALIVE_INTERVAL: + * (int *) outvalue = lo->ldo_keepalive_interval; + rc = LDAP_OPT_SUCCESS; + break; + + default: +#ifdef HAVE_TLS + if ( ldap_pvt_tls_get_option( ld, option, outvalue ) == 0 ) { + rc = LDAP_OPT_SUCCESS; + break; + } +#endif +#ifdef HAVE_CYRUS_SASL + if ( ldap_int_sasl_get_option( ld, option, outvalue ) == 0 ) { + rc = LDAP_OPT_SUCCESS; + break; + } +#endif +#ifdef HAVE_GSSAPI + if ( ldap_int_gssapi_get_option( ld, option, outvalue ) == 0 ) { + rc = LDAP_OPT_SUCCESS; + break; + } +#endif + /* bad param */ + break; + } + + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + return ( rc ); +} + +int +ldap_set_option( + LDAP *ld, + int option, + LDAP_CONST void *invalue) +{ + struct ldapoptions *lo; + int *dbglvl = NULL; + int rc = LDAP_OPT_ERROR; + + /* Get pointer to global option structure */ + lo = LDAP_INT_GLOBAL_OPT(); + if (lo == NULL) { + return LDAP_NO_MEMORY; + } + + /* + * The architecture to turn on debugging has a chicken and egg + * problem. Thus, we introduce a fix here. + */ + + if (option == LDAP_OPT_DEBUG_LEVEL) { + dbglvl = (int *) invalue; + } + + if( lo->ldo_valid != LDAP_INITIALIZED ) { + ldap_int_initialize(lo, dbglvl); + if ( lo->ldo_valid != LDAP_INITIALIZED ) + return LDAP_LOCAL_ERROR; + } + + if(ld != NULL) { + assert( LDAP_VALID( ld ) ); + + if( !LDAP_VALID( ld ) ) { + return LDAP_OPT_ERROR; + } + + lo = &ld->ld_options; + } + + LDAP_MUTEX_LOCK( &lo->ldo_mutex ); + + switch ( option ) { + + /* options with boolean values */ + case LDAP_OPT_REFERRALS: + if(invalue == LDAP_OPT_OFF) { + LDAP_BOOL_CLR(lo, LDAP_BOOL_REFERRALS); + } else { + LDAP_BOOL_SET(lo, LDAP_BOOL_REFERRALS); + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_RESTART: + if(invalue == LDAP_OPT_OFF) { + LDAP_BOOL_CLR(lo, LDAP_BOOL_RESTART); + } else { + LDAP_BOOL_SET(lo, LDAP_BOOL_RESTART); + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_CONNECT_ASYNC: + if(invalue == LDAP_OPT_OFF) { + LDAP_BOOL_CLR(lo, LDAP_BOOL_CONNECT_ASYNC); + } else { + LDAP_BOOL_SET(lo, LDAP_BOOL_CONNECT_ASYNC); + } + rc = LDAP_OPT_SUCCESS; + break; + + /* options which can withstand invalue == NULL */ + case LDAP_OPT_SERVER_CONTROLS: { + LDAPControl *const *controls = + (LDAPControl *const *) invalue; + + if( lo->ldo_sctrls ) + ldap_controls_free( lo->ldo_sctrls ); + + if( controls == NULL || *controls == NULL ) { + lo->ldo_sctrls = NULL; + rc = LDAP_OPT_SUCCESS; + break; + } + + lo->ldo_sctrls = ldap_controls_dup( controls ); + + if(lo->ldo_sctrls == NULL) { + /* memory allocation error ? */ + break; /* LDAP_OPT_ERROR */ + } + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_CLIENT_CONTROLS: { + LDAPControl *const *controls = + (LDAPControl *const *) invalue; + + if( lo->ldo_cctrls ) + ldap_controls_free( lo->ldo_cctrls ); + + if( controls == NULL || *controls == NULL ) { + lo->ldo_cctrls = NULL; + rc = LDAP_OPT_SUCCESS; + break; + } + + lo->ldo_cctrls = ldap_controls_dup( controls ); + + if(lo->ldo_cctrls == NULL) { + /* memory allocation error ? */ + break; /* LDAP_OPT_ERROR */ + } + } + rc = LDAP_OPT_SUCCESS; + break; + + + case LDAP_OPT_HOST_NAME: { + const char *host = (const char *) invalue; + LDAPURLDesc *ludlist = NULL; + rc = LDAP_OPT_SUCCESS; + + if(host != NULL) { + rc = ldap_url_parsehosts( &ludlist, host, + lo->ldo_defport ? lo->ldo_defport : LDAP_PORT ); + + } else if(ld == NULL) { + /* + * must want global default returned + * to initial condition. + */ + rc = ldap_url_parselist_ext(&ludlist, "ldap://localhost/", NULL, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST + | LDAP_PVT_URL_PARSE_DEF_PORT ); + + } else { + /* + * must want the session default + * updated to the current global default + */ + ludlist = ldap_url_duplist( + ldap_int_global_options.ldo_defludp); + if (ludlist == NULL) + rc = LDAP_NO_MEMORY; + } + + if (rc == LDAP_OPT_SUCCESS) { + if (lo->ldo_defludp != NULL) + ldap_free_urllist(lo->ldo_defludp); + lo->ldo_defludp = ludlist; + } + break; + } + + case LDAP_OPT_URI: { + const char *urls = (const char *) invalue; + LDAPURLDesc *ludlist = NULL; + rc = LDAP_OPT_SUCCESS; + + if(urls != NULL) { + rc = ldap_url_parselist_ext(&ludlist, urls, NULL, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST + | LDAP_PVT_URL_PARSE_DEF_PORT ); + } else if(ld == NULL) { + /* + * must want global default returned + * to initial condition. + */ + rc = ldap_url_parselist_ext(&ludlist, "ldap://localhost/", NULL, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST + | LDAP_PVT_URL_PARSE_DEF_PORT ); + + } else { + /* + * must want the session default + * updated to the current global default + */ + ludlist = ldap_url_duplist( + ldap_int_global_options.ldo_defludp); + if (ludlist == NULL) + rc = LDAP_URL_ERR_MEM; + } + + switch (rc) { + case LDAP_URL_SUCCESS: /* Success */ + rc = LDAP_SUCCESS; + break; + + case LDAP_URL_ERR_MEM: /* can't allocate memory space */ + rc = LDAP_NO_MEMORY; + break; + + case LDAP_URL_ERR_PARAM: /* parameter is bad */ + case LDAP_URL_ERR_BADSCHEME: /* URL doesn't begin with "ldap[si]://" */ + case LDAP_URL_ERR_BADENCLOSURE: /* URL is missing trailing ">" */ + case LDAP_URL_ERR_BADURL: /* URL is bad */ + case LDAP_URL_ERR_BADHOST: /* host port is bad */ + case LDAP_URL_ERR_BADATTRS: /* bad (or missing) attributes */ + case LDAP_URL_ERR_BADSCOPE: /* scope string is invalid (or missing) */ + case LDAP_URL_ERR_BADFILTER: /* bad or missing filter */ + case LDAP_URL_ERR_BADEXTS: /* bad or missing extensions */ + rc = LDAP_PARAM_ERROR; + break; + } + + if (rc == LDAP_SUCCESS) { + if (lo->ldo_defludp != NULL) + ldap_free_urllist(lo->ldo_defludp); + lo->ldo_defludp = ludlist; + } + break; + } + + case LDAP_OPT_DEFBASE: { + const char *newbase = (const char *) invalue; + char *defbase = NULL; + + if ( newbase != NULL ) { + defbase = LDAP_STRDUP( newbase ); + if ( defbase == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + + } else if ( ld != NULL ) { + defbase = LDAP_STRDUP( ldap_int_global_options.ldo_defbase ); + if ( defbase == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + } + + if ( lo->ldo_defbase != NULL ) + LDAP_FREE( lo->ldo_defbase ); + lo->ldo_defbase = defbase; + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_DIAGNOSTIC_MESSAGE: { + const char *err = (const char *) invalue; + + if(ld == NULL) { + /* need a struct ldap */ + break; /* LDAP_OPT_ERROR */ + } + + if( ld->ld_error ) { + LDAP_FREE(ld->ld_error); + ld->ld_error = NULL; + } + + if ( err ) { + ld->ld_error = LDAP_STRDUP(err); + } + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_MATCHED_DN: { + const char *matched = (const char *) invalue; + + if (ld == NULL) { + /* need a struct ldap */ + break; /* LDAP_OPT_ERROR */ + } + + if( ld->ld_matched ) { + LDAP_FREE(ld->ld_matched); + ld->ld_matched = NULL; + } + + if ( matched ) { + ld->ld_matched = LDAP_STRDUP( matched ); + } + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_REFERRAL_URLS: { + char *const *referrals = (char *const *) invalue; + + if(ld == NULL) { + /* need a struct ldap */ + break; /* LDAP_OPT_ERROR */ + } + + if( ld->ld_referrals ) { + LDAP_VFREE(ld->ld_referrals); + } + + if ( referrals ) { + ld->ld_referrals = ldap_value_dup(referrals); + } + } + rc = LDAP_OPT_SUCCESS; + break; + + /* Only accessed from inside this function by ldap_set_rebind_proc() */ + case LDAP_OPT_REBIND_PROC: { + lo->ldo_rebind_proc = (LDAP_REBIND_PROC *)invalue; + } + rc = LDAP_OPT_SUCCESS; + break; + case LDAP_OPT_REBIND_PARAMS: { + lo->ldo_rebind_params = (void *)invalue; + } + rc = LDAP_OPT_SUCCESS; + break; + + /* Only accessed from inside this function by ldap_set_nextref_proc() */ + case LDAP_OPT_NEXTREF_PROC: { + lo->ldo_nextref_proc = (LDAP_NEXTREF_PROC *)invalue; + } + rc = LDAP_OPT_SUCCESS; + break; + case LDAP_OPT_NEXTREF_PARAMS: { + lo->ldo_nextref_params = (void *)invalue; + } + rc = LDAP_OPT_SUCCESS; + break; + + /* Only accessed from inside this function by ldap_set_urllist_proc() */ + case LDAP_OPT_URLLIST_PROC: { + lo->ldo_urllist_proc = (LDAP_URLLIST_PROC *)invalue; + } + rc = LDAP_OPT_SUCCESS; + break; + case LDAP_OPT_URLLIST_PARAMS: { + lo->ldo_urllist_params = (void *)invalue; + } + rc = LDAP_OPT_SUCCESS; + break; + + /* read-only options */ + case LDAP_OPT_API_INFO: + case LDAP_OPT_DESC: + case LDAP_OPT_SOCKBUF: + case LDAP_OPT_API_FEATURE_INFO: + break; /* LDAP_OPT_ERROR */ + + /* options which cannot withstand invalue == NULL */ + case LDAP_OPT_DEREF: + case LDAP_OPT_SIZELIMIT: + case LDAP_OPT_TIMELIMIT: + case LDAP_OPT_PROTOCOL_VERSION: + case LDAP_OPT_RESULT_CODE: + case LDAP_OPT_DEBUG_LEVEL: + case LDAP_OPT_TIMEOUT: + case LDAP_OPT_NETWORK_TIMEOUT: + case LDAP_OPT_CONNECT_CB: + case LDAP_OPT_X_KEEPALIVE_IDLE: + case LDAP_OPT_X_KEEPALIVE_PROBES : + case LDAP_OPT_X_KEEPALIVE_INTERVAL : + if(invalue == NULL) { + /* no place to set from */ + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + return ( LDAP_OPT_ERROR ); + } + break; + + default: +#ifdef HAVE_TLS + if ( ldap_pvt_tls_set_option( ld, option, (void *)invalue ) == 0 ) { + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + return ( LDAP_OPT_SUCCESS ); + } +#endif +#ifdef HAVE_CYRUS_SASL + if ( ldap_int_sasl_set_option( ld, option, (void *)invalue ) == 0 ) { + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + return ( LDAP_OPT_SUCCESS ); + } +#endif +#ifdef HAVE_GSSAPI + if ( ldap_int_gssapi_set_option( ld, option, (void *)invalue ) == 0 ) { + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + return ( LDAP_OPT_SUCCESS ); + } +#endif + /* bad param */ + break; /* LDAP_OPT_ERROR */ + } + + /* options which cannot withstand invalue == NULL */ + + switch(option) { + case LDAP_OPT_DEREF: + /* FIXME: check value for protocol compliance? */ + lo->ldo_deref = * (const int *) invalue; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_SIZELIMIT: + /* FIXME: check value for protocol compliance? */ + lo->ldo_sizelimit = * (const int *) invalue; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_TIMELIMIT: + /* FIXME: check value for protocol compliance? */ + lo->ldo_timelimit = * (const int *) invalue; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_TIMEOUT: { + const struct timeval *tv = + (const struct timeval *) invalue; + + lo->ldo_tm_api = *tv; + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_NETWORK_TIMEOUT: { + const struct timeval *tv = + (const struct timeval *) invalue; + + lo->ldo_tm_net = *tv; + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_PROTOCOL_VERSION: { + int vers = * (const int *) invalue; + if (vers < LDAP_VERSION_MIN || vers > LDAP_VERSION_MAX) { + /* not supported */ + break; + } + lo->ldo_version = vers; + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_RESULT_CODE: { + int err = * (const int *) invalue; + + if(ld == NULL) { + /* need a struct ldap */ + break; + } + + ld->ld_errno = err; + } + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_DEBUG_LEVEL: + lo->ldo_debug = * (const int *) invalue; + rc = LDAP_OPT_SUCCESS; + break; + + case LDAP_OPT_CONNECT_CB: + { + /* setting pushes the callback */ + ldaplist *ll; + ll = LDAP_MALLOC( sizeof( *ll )); + ll->ll_data = (void *)invalue; + ll->ll_next = lo->ldo_conn_cbs; + lo->ldo_conn_cbs = ll; + } + rc = LDAP_OPT_SUCCESS; + break; + case LDAP_OPT_X_KEEPALIVE_IDLE: + lo->ldo_keepalive_idle = * (const int *) invalue; + rc = LDAP_OPT_SUCCESS; + break; + case LDAP_OPT_X_KEEPALIVE_PROBES : + lo->ldo_keepalive_probes = * (const int *) invalue; + rc = LDAP_OPT_SUCCESS; + break; + case LDAP_OPT_X_KEEPALIVE_INTERVAL : + lo->ldo_keepalive_interval = * (const int *) invalue; + rc = LDAP_OPT_SUCCESS; + break; + + } + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + return ( rc ); +} + +int +ldap_set_rebind_proc( LDAP *ld, LDAP_REBIND_PROC *proc, void *params ) +{ + int rc; + rc = ldap_set_option( ld, LDAP_OPT_REBIND_PROC, (void *)proc ); + if( rc != LDAP_OPT_SUCCESS ) return rc; + + rc = ldap_set_option( ld, LDAP_OPT_REBIND_PARAMS, (void *)params ); + return rc; +} + +int +ldap_set_nextref_proc( LDAP *ld, LDAP_NEXTREF_PROC *proc, void *params ) +{ + int rc; + rc = ldap_set_option( ld, LDAP_OPT_NEXTREF_PROC, (void *)proc ); + if( rc != LDAP_OPT_SUCCESS ) return rc; + + rc = ldap_set_option( ld, LDAP_OPT_NEXTREF_PARAMS, (void *)params ); + return rc; +} + +int +ldap_set_urllist_proc( LDAP *ld, LDAP_URLLIST_PROC *proc, void *params ) +{ + int rc; + rc = ldap_set_option( ld, LDAP_OPT_URLLIST_PROC, (void *)proc ); + if( rc != LDAP_OPT_SUCCESS ) return rc; + + rc = ldap_set_option( ld, LDAP_OPT_URLLIST_PARAMS, (void *)params ); + return rc; +} diff --git a/libraries/libldap/os-ip.c b/libraries/libldap/os-ip.c new file mode 100644 index 0000000..623dfad --- /dev/null +++ b/libraries/libldap/os-ip.c @@ -0,0 +1,1151 @@ +/* os-ip.c -- platform-specific TCP & UDP related code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * Portions Copyright 1999 Lars Uffmann. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + */ +/* Significant additional contributors include: + * Lars Uffman + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#ifdef HAVE_IO_H +#include <io.h> +#endif /* HAVE_IO_H */ +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include "ldap-int.h" + +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) +# ifdef LDAP_PF_INET6 +int ldap_int_inet4or6 = AF_UNSPEC; +# else +int ldap_int_inet4or6 = AF_INET; +# endif +#endif + +#ifdef LDAP_DEBUG + +#define osip_debug(ld,fmt,arg1,arg2,arg3) \ +do { \ + ldap_log_printf(NULL, LDAP_DEBUG_TRACE, fmt, arg1, arg2, arg3); \ +} while(0) + +#else + +#define osip_debug(ld,fmt,arg1,arg2,arg3) ((void)0) + +#endif /* LDAP_DEBUG */ + +static void +ldap_pvt_set_errno(int err) +{ + sock_errset(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 *) LDAP_MALLOC(sizeof(struct timeval)); + + if( new == NULL ) { + *dest = NULL; + return 1; + } + + AC_MEMCPY( (char *) new, (const 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); + return ber_pvt_socket_set_nonblock( fd, 1 ); +} + +static int +ldap_pvt_ndelay_off(LDAP *ld, int fd) +{ + osip_debug(ld, "ldap_ndelay_off: %d\n",fd,0,0); + return ber_pvt_socket_set_nonblock( fd, 0 ); +} + +static ber_socket_t +ldap_int_socket(LDAP *ld, int family, int type ) +{ + ber_socket_t s = socket(family, type, 0); + osip_debug(ld, "ldap_new_socket: %d\n",s,0,0); +#ifdef FD_CLOEXEC + fcntl(s, F_SETFD, FD_CLOEXEC); +#endif + 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_int_prepare_socket(LDAP *ld, int s, int proto ) +{ + osip_debug( ld, "ldap_prepare_socket: %d\n", s, 0, 0 ); + +#if defined( SO_KEEPALIVE ) || defined( TCP_NODELAY ) + if ( proto == LDAP_PROTO_TCP ) { + int dummy = 1; +#ifdef SO_KEEPALIVE + if ( setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, + (char*) &dummy, sizeof(dummy) ) == AC_SOCKET_ERROR ) + { + osip_debug( ld, "ldap_prepare_socket: " + "setsockopt(%d, SO_KEEPALIVE) failed (ignored).\n", + s, 0, 0 ); + } + if ( ld->ld_options.ldo_keepalive_idle > 0 ) + { +#ifdef TCP_KEEPIDLE + if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPIDLE, + (void*) &ld->ld_options.ldo_keepalive_idle, + sizeof(ld->ld_options.ldo_keepalive_idle) ) == AC_SOCKET_ERROR ) + { + osip_debug( ld, "ldap_prepare_socket: " + "setsockopt(%d, TCP_KEEPIDLE) failed (ignored).\n", + s, 0, 0 ); + } +#else + osip_debug( ld, "ldap_prepare_socket: " + "sockopt TCP_KEEPIDLE not supported on this system.\n", + 0, 0, 0 ); +#endif /* TCP_KEEPIDLE */ + } + if ( ld->ld_options.ldo_keepalive_probes > 0 ) + { +#ifdef TCP_KEEPCNT + if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPCNT, + (void*) &ld->ld_options.ldo_keepalive_probes, + sizeof(ld->ld_options.ldo_keepalive_probes) ) == AC_SOCKET_ERROR ) + { + osip_debug( ld, "ldap_prepare_socket: " + "setsockopt(%d, TCP_KEEPCNT) failed (ignored).\n", + s, 0, 0 ); + } +#else + osip_debug( ld, "ldap_prepare_socket: " + "sockopt TCP_KEEPCNT not supported on this system.\n", + 0, 0, 0 ); +#endif /* TCP_KEEPCNT */ + } + if ( ld->ld_options.ldo_keepalive_interval > 0 ) + { +#ifdef TCP_KEEPINTVL + if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPINTVL, + (void*) &ld->ld_options.ldo_keepalive_interval, + sizeof(ld->ld_options.ldo_keepalive_interval) ) == AC_SOCKET_ERROR ) + { + osip_debug( ld, "ldap_prepare_socket: " + "setsockopt(%d, TCP_KEEPINTVL) failed (ignored).\n", + s, 0, 0 ); + } +#else + osip_debug( ld, "ldap_prepare_socket: " + "sockopt TCP_KEEPINTVL not supported on this system.\n", + 0, 0, 0 ); +#endif /* TCP_KEEPINTVL */ + } +#endif /* SO_KEEPALIVE */ +#ifdef TCP_NODELAY + if ( setsockopt( s, IPPROTO_TCP, TCP_NODELAY, + (char*) &dummy, sizeof(dummy) ) == AC_SOCKET_ERROR ) + { + osip_debug( ld, "ldap_prepare_socket: " + "setsockopt(%d, TCP_NODELAY) failed (ignored).\n", + s, 0, 0 ); + } +#endif /* TCP_NODELAY */ + } +#endif /* SO_KEEPALIVE || TCP_NODELAY */ + + return 0; +} + +#ifndef HAVE_WINSOCK + +#undef TRACE +#define TRACE do { \ + osip_debug(ld, \ + "ldap_is_socket_ready: error on socket %d: errno: %d (%s)\n", \ + s, \ + errno, \ + sock_errstr(errno) ); \ +} while( 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); + +#if defined( notyet ) /* && defined( SO_ERROR ) */ +{ + int so_errno; + ber_socklen_t dummy = sizeof(so_errno); + if ( getsockopt( s, SOL_SOCKET, SO_ERROR, &so_errno, &dummy ) + == AC_SOCKET_ERROR ) + { + return -1; + } + if ( so_errno ) { + ldap_pvt_set_errno(so_errno); + TRACE; + return -1; + } + return 0; +} +#else +{ + /* error slippery */ +#ifdef LDAP_PF_INET6 + struct sockaddr_storage sin; +#else + struct sockaddr_in sin; +#endif + char ch; + ber_socklen_t dummy = sizeof(sin); + if ( getpeername( s, (struct sockaddr *) &sin, &dummy ) + == AC_SOCKET_ERROR ) + { + /* XXX: needs to be replace with ber_stream_read() */ + (void)read(s, &ch, 1); + TRACE; + return -1; + } + return 0; +} +#endif + return -1; +} +#undef TRACE + +#endif /* HAVE_WINSOCK */ + +/* NOTE: this is identical to analogous code in os-local.c */ +int +ldap_int_poll( + LDAP *ld, + ber_socket_t s, + struct timeval *tvp, + int wr ) +{ + int rc; + + + osip_debug(ld, "ldap_int_poll: fd: %d tm: %ld\n", + s, tvp ? tvp->tv_sec : -1L, 0); + +#ifdef HAVE_POLL + { + struct pollfd fd; + int timeout = INFTIM; + short event = wr ? POLL_WRITE : POLL_READ; + + fd.fd = s; + fd.events = event; + + if ( tvp != NULL ) { + timeout = TV2MILLISEC( tvp ); + } + do { + fd.revents = 0; + rc = poll( &fd, 1, timeout ); + + } while ( rc == AC_SOCKET_ERROR && errno == EINTR && + LDAP_BOOL_GET( &ld->ld_options, LDAP_BOOL_RESTART ) ); + + if ( rc == AC_SOCKET_ERROR ) { + return rc; + } + + if ( timeout == 0 && rc == 0 ) { + return -2; + } + + if ( fd.revents & event ) { + if ( ldap_pvt_is_socket_ready( ld, s ) == -1 ) { + return -1; + } + + if ( ldap_pvt_ndelay_off( ld, s ) == -1 ) { + return -1; + } + return 0; + } + } +#else + { + fd_set wfds, *z = NULL; +#ifdef HAVE_WINSOCK + fd_set efds; +#endif + struct timeval tv = { 0 }; + +#if defined( FD_SETSIZE ) && !defined( HAVE_WINSOCK ) + if ( s >= FD_SETSIZE ) { + rc = AC_SOCKET_ERROR; + tcp_close( s ); + ldap_pvt_set_errno( EMFILE ); + return rc; + } +#endif + + if ( tvp != NULL ) { + tv = *tvp; + } + + do { + FD_ZERO(&wfds); + FD_SET(s, &wfds ); + +#ifdef HAVE_WINSOCK + FD_ZERO(&efds); + FD_SET(s, &efds ); +#endif + + rc = select( ldap_int_tblsize, z, &wfds, +#ifdef HAVE_WINSOCK + &efds, +#else + z, +#endif + tvp ? &tv : NULL ); + } while ( rc == AC_SOCKET_ERROR && errno == EINTR && + LDAP_BOOL_GET( &ld->ld_options, LDAP_BOOL_RESTART ) ); + + if ( rc == AC_SOCKET_ERROR ) { + return rc; + } + + if ( rc == 0 && tvp && tvp->tv_sec == 0 && tvp->tv_usec == 0 ) { + return -2; + } + +#ifdef HAVE_WINSOCK + /* This means the connection failed */ + if ( FD_ISSET(s, &efds) ) { + int so_errno; + ber_socklen_t dummy = sizeof(so_errno); + if ( getsockopt( s, SOL_SOCKET, SO_ERROR, + (char *) &so_errno, &dummy ) == AC_SOCKET_ERROR || !so_errno ) + { + /* impossible */ + so_errno = WSAGetLastError(); + } + ldap_pvt_set_errno( so_errno ); + osip_debug(ld, "ldap_int_poll: error on socket %d: " + "errno: %d (%s)\n", s, errno, sock_errstr( errno )); + return -1; + } +#endif + if ( FD_ISSET(s, &wfds) ) { +#ifndef HAVE_WINSOCK + if ( ldap_pvt_is_socket_ready( ld, s ) == -1 ) { + return -1; + } +#endif + if ( ldap_pvt_ndelay_off(ld, s) == -1 ) { + return -1; + } + return 0; + } + } +#endif + + osip_debug(ld, "ldap_int_poll: timed out\n",0,0,0); + ldap_pvt_set_errno( ETIMEDOUT ); + return -1; +} + +static int +ldap_pvt_connect(LDAP *ld, ber_socket_t s, + struct sockaddr *sin, ber_socklen_t addrlen, + int async) +{ + int rc, err; + struct timeval tv, *opt_tv = NULL; + +#ifdef LDAP_CONNECTIONLESS + /* We could do a connect() but that would interfere with + * attempts to poll a broadcast address + */ + if (LDAP_IS_UDP(ld)) { + if (ld->ld_options.ldo_peer) + ldap_memfree(ld->ld_options.ldo_peer); + ld->ld_options.ldo_peer=ldap_memcalloc(1, sizeof(struct sockaddr_storage)); + AC_MEMCPY(ld->ld_options.ldo_peer,sin,addrlen); + return ( 0 ); + } +#endif + if ( ld->ld_options.ldo_tm_net.tv_sec >= 0 ) { + tv = ld->ld_options.ldo_tm_net; + opt_tv = &tv; + } + + osip_debug(ld, "ldap_pvt_connect: fd: %d tm: %ld async: %d\n", + s, opt_tv ? tv.tv_sec : -1L, async); + + if ( opt_tv && ldap_pvt_ndelay_on(ld, s) == -1 ) + return ( -1 ); + + do{ + osip_debug(ld, "attempting to connect: \n", 0, 0, 0); + if ( connect(s, sin, addrlen) != AC_SOCKET_ERROR ) { + osip_debug(ld, "connect success\n", 0, 0, 0); + + if ( opt_tv && ldap_pvt_ndelay_off(ld, s) == -1 ) + return ( -1 ); + return ( 0 ); + } + err = sock_errno(); + osip_debug(ld, "connect errno: %d\n", err, 0, 0); + + } while(err == EINTR && + LDAP_BOOL_GET( &ld->ld_options, LDAP_BOOL_RESTART )); + + if ( err != EINPROGRESS && err != EWOULDBLOCK ) { + return ( -1 ); + } + + if ( async ) { + /* caller will call ldap_int_poll() as appropriate? */ + return ( -2 ); + } + + rc = ldap_int_poll( ld, s, opt_tv, 1 ); + + osip_debug(ld, "ldap_pvt_connect: %d\n", rc, 0, 0); + + return rc; +} + +#ifndef HAVE_INET_ATON +int +ldap_pvt_inet_aton( const char *host, struct in_addr *in) +{ + unsigned long u = inet_addr( host ); + +#ifdef INADDR_NONE + if ( u == INADDR_NONE ) return 0; +#endif + if ( u == 0xffffffffUL || u == (unsigned long) -1L ) return 0; + + in->s_addr = u; + return 1; +} +#endif + +int +ldap_int_connect_cbs(LDAP *ld, Sockbuf *sb, ber_socket_t *s, LDAPURLDesc *srv, struct sockaddr *addr) +{ + struct ldapoptions *lo; + ldaplist *ll; + ldap_conncb *cb; + int rc; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_SET_FD, s ); + + /* Invoke all handle-specific callbacks first */ + lo = &ld->ld_options; + for (ll = lo->ldo_conn_cbs; ll; ll = ll->ll_next) { + cb = ll->ll_data; + rc = cb->lc_add( ld, sb, srv, addr, cb ); + /* on any failure, call the teardown functions for anything + * that previously succeeded + */ + if ( rc ) { + ldaplist *l2; + for (l2 = lo->ldo_conn_cbs; l2 != ll; l2 = l2->ll_next) { + cb = l2->ll_data; + cb->lc_del( ld, sb, cb ); + } + /* a failure might have implicitly closed the fd */ + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, s ); + return rc; + } + } + lo = LDAP_INT_GLOBAL_OPT(); + for (ll = lo->ldo_conn_cbs; ll; ll = ll->ll_next) { + cb = ll->ll_data; + rc = cb->lc_add( ld, sb, srv, addr, cb ); + if ( rc ) { + ldaplist *l2; + for (l2 = lo->ldo_conn_cbs; l2 != ll; l2 = l2->ll_next) { + cb = l2->ll_data; + cb->lc_del( ld, sb, cb ); + } + lo = &ld->ld_options; + for (l2 = lo->ldo_conn_cbs; l2; l2 = l2->ll_next) { + cb = l2->ll_data; + cb->lc_del( ld, sb, cb ); + } + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, s ); + return rc; + } + } + return 0; +} + +int +ldap_connect_to_host(LDAP *ld, Sockbuf *sb, + int proto, LDAPURLDesc *srv, + int async ) +{ + int rc; + int socktype, port; + ber_socket_t s = AC_SOCKET_INVALID; + char *host; + +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + char serv[7]; + int err; + struct addrinfo hints, *res, *sai; +#else + int i; + int use_hp = 0; + struct hostent *hp = NULL; + struct hostent he_buf; + struct in_addr in; + char *ha_buf=NULL; +#endif + + if ( srv->lud_host == NULL || *srv->lud_host == 0 ) { + host = "localhost"; + } else { + host = srv->lud_host; + } + + port = srv->lud_port; + + if( !port ) { + if( strcmp(srv->lud_scheme, "ldaps") == 0 ) { + port = LDAPS_PORT; + } else { + port = LDAP_PORT; + } + } + + switch(proto) { + case LDAP_PROTO_TCP: socktype = SOCK_STREAM; + osip_debug( ld, + "ldap_connect_to_host: TCP %s:%d\n", + host, port, 0); + break; + case LDAP_PROTO_UDP: socktype = SOCK_DGRAM; + osip_debug( ld, + "ldap_connect_to_host: UDP %s:%d\n", + host, port, 0); + break; + default: + osip_debug( ld, "ldap_connect_to_host: unknown proto: %d\n", + proto, 0, 0 ); + return -1; + } + +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + memset( &hints, '\0', sizeof(hints) ); +#ifdef USE_AI_ADDRCONFIG /* FIXME: configure test needed */ + /* Use AI_ADDRCONFIG only on systems where its known to be needed. */ + hints.ai_flags = AI_ADDRCONFIG; +#endif + hints.ai_family = ldap_int_inet4or6; + hints.ai_socktype = socktype; + snprintf(serv, sizeof serv, "%d", port ); + + /* most getaddrinfo(3) use non-threadsafe resolver libraries */ + LDAP_MUTEX_LOCK(&ldap_int_resolv_mutex); + + err = getaddrinfo( host, serv, &hints, &res ); + + LDAP_MUTEX_UNLOCK(&ldap_int_resolv_mutex); + + if ( err != 0 ) { + osip_debug(ld, "ldap_connect_to_host: getaddrinfo failed: %s\n", + AC_GAI_STRERROR(err), 0, 0); + return -1; + } + rc = -1; + + for( sai=res; sai != NULL; sai=sai->ai_next) { + if( sai->ai_addr == NULL ) { + osip_debug(ld, "ldap_connect_to_host: getaddrinfo " + "ai_addr is NULL?\n", 0, 0, 0); + continue; + } + + /* we assume AF_x and PF_x are equal for all x */ + s = ldap_int_socket( ld, sai->ai_family, socktype ); + if ( s == AC_SOCKET_INVALID ) { + continue; + } + + if ( ldap_int_prepare_socket(ld, s, proto ) == -1 ) { + ldap_pvt_close_socket(ld, s); + break; + } + + switch (sai->ai_family) { +#ifdef LDAP_PF_INET6 + case AF_INET6: { + char addr[INET6_ADDRSTRLEN]; + inet_ntop( AF_INET6, + &((struct sockaddr_in6 *)sai->ai_addr)->sin6_addr, + addr, sizeof addr); + osip_debug(ld, "ldap_connect_to_host: Trying %s %s\n", + addr, serv, 0); + } break; +#endif + case AF_INET: { + char addr[INET_ADDRSTRLEN]; + inet_ntop( AF_INET, + &((struct sockaddr_in *)sai->ai_addr)->sin_addr, + addr, sizeof addr); + osip_debug(ld, "ldap_connect_to_host: Trying %s:%s\n", + addr, serv, 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; + } + ldap_pvt_close_socket(ld, s); + } + freeaddrinfo(res); + +#else + if (! inet_aton( host, &in ) ) { + int local_h_errno; + rc = ldap_pvt_gethostbyname_a( host, &he_buf, &ha_buf, + &hp, &local_h_errno ); + + if ( (rc < 0) || (hp == NULL) ) { +#ifdef HAVE_WINSOCK + ldap_pvt_set_errno( WSAGetLastError() ); +#else + /* not exactly right, but... */ + ldap_pvt_set_errno( EHOSTUNREACH ); +#endif + if (ha_buf) LDAP_FREE(ha_buf); + return -1; + } + + use_hp = 1; + } + + rc = s = -1; + for ( i = 0; !use_hp || (hp->h_addr_list[i] != 0); ++i, rc = -1 ) { + struct sockaddr_in sin; + + s = ldap_int_socket( ld, PF_INET, socktype ); + if ( s == AC_SOCKET_INVALID ) { + /* use_hp ? continue : break; */ + break; + } + + if ( ldap_int_prepare_socket( ld, s, proto ) == -1 ) { + ldap_pvt_close_socket(ld, s); + break; + } + + (void)memset((char *)&sin, '\0', sizeof sin); + sin.sin_family = AF_INET; + sin.sin_port = htons((unsigned short) port); + + if( use_hp ) { + AC_MEMCPY( &sin.sin_addr, hp->h_addr_list[i], + sizeof(sin.sin_addr) ); + } else { + AC_MEMCPY( &sin.sin_addr, &in.s_addr, + sizeof(sin.sin_addr) ); + } + +#ifdef HAVE_INET_NTOA_B + { + /* for VxWorks */ + char address[INET_ADDR_LEN]; + inet_ntoa_b(sin.sin_address, address); + osip_debug(ld, "ldap_connect_to_host: Trying %s:%d\n", + address, port, 0); + } +#else + osip_debug(ld, "ldap_connect_to_host: Trying %s:%d\n", + inet_ntoa(sin.sin_addr), port, 0); +#endif + + 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; + } + + ldap_pvt_close_socket(ld, s); + + if (!use_hp) break; + } + if (ha_buf) LDAP_FREE(ha_buf); +#endif + + return rc; +} + +#if defined( HAVE_CYRUS_SASL ) +char * +ldap_host_connected_to( Sockbuf *sb, const char *host ) +{ + ber_socklen_t len; +#ifdef LDAP_PF_INET6 + struct sockaddr_storage sabuf; +#else + struct sockaddr sabuf; +#endif + struct sockaddr *sa = (struct sockaddr *) &sabuf; + ber_socket_t sd; + + (void)memset( (char *)sa, '\0', sizeof sabuf ); + len = sizeof sabuf; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + if ( getpeername( sd, sa, &len ) == -1 ) { + return( NULL ); + } + + /* + * do a reverse lookup on the addr to get the official hostname. + * this is necessary for kerberos to work right, since the official + * hostname is used as the kerberos instance. + */ + + switch (sa->sa_family) { +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: + return LDAP_STRDUP( ldap_int_hostname ); +#endif +#ifdef LDAP_PF_INET6 + case AF_INET6: + { + struct in6_addr localhost = IN6ADDR_LOOPBACK_INIT; + if( memcmp ( &((struct sockaddr_in6 *)sa)->sin6_addr, + &localhost, sizeof(localhost)) == 0 ) + { + return LDAP_STRDUP( ldap_int_hostname ); + } + } + break; +#endif + case AF_INET: + { + struct in_addr localhost; + localhost.s_addr = htonl( INADDR_ANY ); + + if( memcmp ( &((struct sockaddr_in *)sa)->sin_addr, + &localhost, sizeof(localhost) ) == 0 ) + { + return LDAP_STRDUP( ldap_int_hostname ); + } + +#ifdef INADDR_LOOPBACK + localhost.s_addr = htonl( INADDR_LOOPBACK ); + + if( memcmp ( &((struct sockaddr_in *)sa)->sin_addr, + &localhost, sizeof(localhost) ) == 0 ) + { + return LDAP_STRDUP( ldap_int_hostname ); + } +#endif + } + break; + + default: + return( NULL ); + break; + } + + { + char *herr; +#ifdef NI_MAXHOST + char hbuf[NI_MAXHOST]; +#elif defined( MAXHOSTNAMELEN ) + char hbuf[MAXHOSTNAMELEN]; +#else + char hbuf[256]; +#endif + hbuf[0] = 0; + + if (ldap_pvt_get_hname( sa, len, hbuf, sizeof(hbuf), &herr ) == 0 + && hbuf[0] ) + { + return LDAP_STRDUP( hbuf ); + } + } + + return host ? LDAP_STRDUP( host ) : NULL; +} +#endif + + +struct selectinfo { +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + int si_maxfd; + struct pollfd si_fds[FD_SETSIZE]; +#else + /* for UNIX select(2) */ + fd_set si_readfds; + fd_set si_writefds; + fd_set si_use_readfds; + fd_set si_use_writefds; +#endif +}; + +void +ldap_mark_select_write( LDAP *ld, Sockbuf *sb ) +{ + struct selectinfo *sip; + ber_socket_t sd; + + sip = (struct selectinfo *)ld->ld_selectinfo; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + { + int empty=-1; + int i; + for(i=0; i < sip->si_maxfd; i++) { + if( sip->si_fds[i].fd == sd ) { + sip->si_fds[i].events |= POLL_WRITE; + return; + } + if( empty==-1 && sip->si_fds[i].fd == -1 ) { + empty=i; + } + } + + if( empty == -1 ) { + if( sip->si_maxfd >= FD_SETSIZE ) { + /* FIXME */ + return; + } + empty = sip->si_maxfd++; + } + + sip->si_fds[empty].fd = sd; + sip->si_fds[empty].events = POLL_WRITE; + } +#else + /* for UNIX select(2) */ + if ( !FD_ISSET( sd, &sip->si_writefds )) { + FD_SET( sd, &sip->si_writefds ); + } +#endif +} + + +void +ldap_mark_select_read( LDAP *ld, Sockbuf *sb ) +{ + struct selectinfo *sip; + ber_socket_t sd; + + sip = (struct selectinfo *)ld->ld_selectinfo; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + { + int empty=-1; + int i; + for(i=0; i < sip->si_maxfd; i++) { + if( sip->si_fds[i].fd == sd ) { + sip->si_fds[i].events |= POLL_READ; + return; + } + if( empty==-1 && sip->si_fds[i].fd == -1 ) { + empty=i; + } + } + + if( empty == -1 ) { + if( sip->si_maxfd >= FD_SETSIZE ) { + /* FIXME */ + return; + } + empty = sip->si_maxfd++; + } + + sip->si_fds[empty].fd = sd; + sip->si_fds[empty].events = POLL_READ; + } +#else + /* for UNIX select(2) */ + if ( !FD_ISSET( sd, &sip->si_readfds )) { + FD_SET( sd, &sip->si_readfds ); + } +#endif +} + + +void +ldap_mark_select_clear( LDAP *ld, Sockbuf *sb ) +{ + struct selectinfo *sip; + ber_socket_t sd; + + sip = (struct selectinfo *)ld->ld_selectinfo; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + { + int i; + for(i=0; i < sip->si_maxfd; i++) { + if( sip->si_fds[i].fd == sd ) { + sip->si_fds[i].fd = -1; + } + } + } +#else + /* for UNIX select(2) */ + FD_CLR( sd, &sip->si_writefds ); + FD_CLR( sd, &sip->si_readfds ); +#endif +} + +void +ldap_clear_select_write( LDAP *ld, Sockbuf *sb ) +{ + struct selectinfo *sip; + ber_socket_t sd; + + sip = (struct selectinfo *)ld->ld_selectinfo; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + { + int i; + for(i=0; i < sip->si_maxfd; i++) { + if( sip->si_fds[i].fd == sd ) { + sip->si_fds[i].events &= ~POLL_WRITE; + } + } + } +#else + /* for UNIX select(2) */ + FD_CLR( sd, &sip->si_writefds ); +#endif +} + + +int +ldap_is_write_ready( LDAP *ld, Sockbuf *sb ) +{ + struct selectinfo *sip; + ber_socket_t sd; + + sip = (struct selectinfo *)ld->ld_selectinfo; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + { + int i; + for(i=0; i < sip->si_maxfd; i++) { + if( sip->si_fds[i].fd == sd ) { + return sip->si_fds[i].revents & POLL_WRITE; + } + } + + return 0; + } +#else + /* for UNIX select(2) */ + return( FD_ISSET( sd, &sip->si_use_writefds )); +#endif +} + + +int +ldap_is_read_ready( LDAP *ld, Sockbuf *sb ) +{ + struct selectinfo *sip; + ber_socket_t sd; + + sip = (struct selectinfo *)ld->ld_selectinfo; + + if (ber_sockbuf_ctrl( sb, LBER_SB_OPT_DATA_READY, NULL )) + return 1; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + { + int i; + for(i=0; i < sip->si_maxfd; i++) { + if( sip->si_fds[i].fd == sd ) { + return sip->si_fds[i].revents & POLL_READ; + } + } + + return 0; + } +#else + /* for UNIX select(2) */ + return( FD_ISSET( sd, &sip->si_use_readfds )); +#endif +} + + +void * +ldap_new_select_info( void ) +{ + struct selectinfo *sip; + + sip = (struct selectinfo *)LDAP_CALLOC( 1, sizeof( struct selectinfo )); + + if ( sip == NULL ) return NULL; + +#ifdef HAVE_POLL + /* for UNIX poll(2) */ + /* sip->si_maxfd=0 */ +#else + /* for UNIX select(2) */ + FD_ZERO( &sip->si_readfds ); + FD_ZERO( &sip->si_writefds ); +#endif + + return( (void *)sip ); +} + + +void +ldap_free_select_info( void *sip ) +{ + LDAP_FREE( sip ); +} + + +#ifndef HAVE_POLL +int ldap_int_tblsize = 0; + +void +ldap_int_ip_init( void ) +{ +#if defined( HAVE_SYSCONF ) + long tblsize = sysconf( _SC_OPEN_MAX ); + if( tblsize > INT_MAX ) tblsize = INT_MAX; + +#elif defined( HAVE_GETDTABLESIZE ) + int tblsize = getdtablesize(); +#else + int tblsize = FD_SETSIZE; +#endif /* !USE_SYSCONF */ + +#ifdef FD_SETSIZE + if( tblsize > FD_SETSIZE ) tblsize = FD_SETSIZE; +#endif /* FD_SETSIZE */ + + ldap_int_tblsize = tblsize; +} +#endif + + +int +ldap_int_select( LDAP *ld, struct timeval *timeout ) +{ + int rc; + struct selectinfo *sip; + + Debug( LDAP_DEBUG_TRACE, "ldap_int_select\n", 0, 0, 0 ); + +#ifndef HAVE_POLL + if ( ldap_int_tblsize == 0 ) ldap_int_ip_init(); +#endif + + sip = (struct selectinfo *)ld->ld_selectinfo; + assert( sip != NULL ); + +#ifdef HAVE_POLL + { + int to = timeout ? TV2MILLISEC( timeout ) : INFTIM; + rc = poll( sip->si_fds, sip->si_maxfd, to ); + } +#else + sip->si_use_readfds = sip->si_readfds; + sip->si_use_writefds = sip->si_writefds; + + rc = select( ldap_int_tblsize, + &sip->si_use_readfds, &sip->si_use_writefds, + NULL, timeout ); +#endif + + return rc; +} diff --git a/libraries/libldap/os-local.c b/libraries/libldap/os-local.c new file mode 100644 index 0000000..7172b33 --- /dev/null +++ b/libraries/libldap/os-local.c @@ -0,0 +1,363 @@ +/* os-local.c -- platform-specific domain socket code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + */ +/* Portions (C) Copyright PADL Software Pty Ltd. 1999 + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that this notice is preserved + * and that due credit is given to PADL Software Pty Ltd. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#ifdef LDAP_PF_LOCAL + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif + +#ifdef HAVE_IO_H +#include <io.h> +#endif /* HAVE_IO_H */ +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include "ldap-int.h" +#include "ldap_defaults.h" + +#ifdef LDAP_DEBUG + +#define oslocal_debug(ld,fmt,arg1,arg2,arg3) \ +do { \ + ldap_log_printf(ld, LDAP_DEBUG_TRACE, fmt, arg1, arg2, arg3); \ +} while(0) + +#else + +#define oslocal_debug(ld,fmt,arg1,arg2,arg3) ((void)0) + +#endif /* LDAP_DEBUG */ + +static void +ldap_pvt_set_errno(int err) +{ + errno = err; +} + +static int +ldap_pvt_ndelay_on(LDAP *ld, int fd) +{ + oslocal_debug(ld, "ldap_ndelay_on: %d\n",fd,0,0); + return ber_pvt_socket_set_nonblock( fd, 1 ); +} + +static int +ldap_pvt_ndelay_off(LDAP *ld, int fd) +{ + oslocal_debug(ld, "ldap_ndelay_off: %d\n",fd,0,0); + return ber_pvt_socket_set_nonblock( fd, 0 ); +} + +static ber_socket_t +ldap_pvt_socket(LDAP *ld) +{ + ber_socket_t s = socket(PF_LOCAL, SOCK_STREAM, 0); + oslocal_debug(ld, "ldap_new_socket: %d\n",s,0,0); +#ifdef FD_CLOEXEC + fcntl(s, F_SETFD, FD_CLOEXEC); +#endif + return ( s ); +} + +static int +ldap_pvt_close_socket(LDAP *ld, int s) +{ + oslocal_debug(ld, "ldap_close_socket: %d\n",s,0,0); + return tcp_close(s); +} + +#undef TRACE +#define TRACE do { \ + char ebuf[128]; \ + oslocal_debug(ld, \ + "ldap_is_socket_ready: error on socket %d: errno: %d (%s)\n", \ + s, \ + errno, \ + AC_STRERROR_R(errno, ebuf, sizeof ebuf)); \ +} while( 0 ) + +/* + * check the socket for errors after select returned. + */ +static int +ldap_pvt_is_socket_ready(LDAP *ld, int s) +{ + oslocal_debug(ld, "ldap_is_sock_ready: %d\n",s,0,0); + +#if defined( notyet ) /* && defined( SO_ERROR ) */ +{ + int so_errno; + ber_socklen_t dummy = sizeof(so_errno); + if ( getsockopt( s, SOL_SOCKET, SO_ERROR, &so_errno, &dummy ) + == AC_SOCKET_ERROR ) + { + return -1; + } + if ( so_errno ) { + ldap_pvt_set_errno(so_errno); + TRACE; + return -1; + } + return 0; +} +#else +{ + /* error slippery */ + struct sockaddr_un sa; + char ch; + ber_socklen_t dummy = sizeof(sa); + if ( getpeername( s, (struct sockaddr *) &sa, &dummy ) + == AC_SOCKET_ERROR ) + { + /* XXX: needs to be replace with ber_stream_read() */ + (void)read(s, &ch, 1); + TRACE; + return -1; + } + return 0; +} +#endif + return -1; +} +#undef TRACE + +#ifdef LDAP_PF_LOCAL_SENDMSG +static const char abandonPDU[] = {LDAP_TAG_MESSAGE, 6, + LDAP_TAG_MSGID, 1, 0, LDAP_REQ_ABANDON, 1, 0}; +#endif + +static int +ldap_pvt_connect(LDAP *ld, ber_socket_t s, struct sockaddr_un *sa, int async) +{ + int rc; + struct timeval tv, *opt_tv = NULL; + + if ( ld->ld_options.ldo_tm_net.tv_sec >= 0 ) { + tv = ld->ld_options.ldo_tm_net; + opt_tv = &tv; + } + + oslocal_debug(ld, "ldap_connect_timeout: fd: %d tm: %ld async: %d\n", + s, opt_tv ? tv.tv_sec : -1L, async); + + if ( ldap_pvt_ndelay_on(ld, s) == -1 ) return -1; + + if ( connect(s, (struct sockaddr *) sa, sizeof(struct sockaddr_un)) + != AC_SOCKET_ERROR ) + { + if ( ldap_pvt_ndelay_off(ld, s) == -1 ) return -1; + +#ifdef LDAP_PF_LOCAL_SENDMSG + /* Send a dummy message with access rights. Remote side will + * obtain our uid/gid by fstat'ing this descriptor. The + * descriptor permissions must match exactly, and we also + * send the socket name, which must also match. + */ +sendcred: + { + int fds[2]; + ber_socklen_t salen = sizeof(*sa); + if (pipe(fds) == 0) { + /* Abandon, noop, has no reply */ + struct iovec iov; + struct msghdr msg = {0}; +# ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL +# ifndef CMSG_SPACE +# define CMSG_SPACE(len) (_CMSG_ALIGN( sizeof(struct cmsghdr)) + _CMSG_ALIGN(len) ) +# endif +# ifndef CMSG_LEN +# define CMSG_LEN(len) (_CMSG_ALIGN( sizeof(struct cmsghdr)) + (len) ) +# endif + union { + struct cmsghdr cm; + unsigned char control[CMSG_SPACE(sizeof(int))]; + } control_un; + struct cmsghdr *cmsg; +# endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ + msg.msg_name = NULL; + msg.msg_namelen = 0; + iov.iov_base = (char *) abandonPDU; + iov.iov_len = sizeof abandonPDU; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; +# ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof( control_un.control ); + msg.msg_flags = 0; + + cmsg = CMSG_FIRSTHDR( &msg ); + cmsg->cmsg_len = CMSG_LEN( sizeof(int) ); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + *((int *)CMSG_DATA(cmsg)) = fds[0]; +# else + msg.msg_accrights = (char *)fds; + msg.msg_accrightslen = sizeof(int); +# endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ + getpeername( s, (struct sockaddr *) sa, &salen ); + fchmod( fds[0], S_ISUID|S_IRWXU ); + write( fds[1], sa, salen ); + sendmsg( s, &msg, 0 ); + close(fds[0]); + close(fds[1]); + } + } +#endif + return 0; + } + + if ( errno != EINPROGRESS && errno != EWOULDBLOCK ) return -1; + +#ifdef notyet + if ( async ) return -2; +#endif + +#ifdef HAVE_POLL + { + struct pollfd fd; + int timeout = INFTIM; + + if( opt_tv != NULL ) timeout = TV2MILLISEC( &tv ); + + fd.fd = s; + fd.events = POLL_WRITE; + + do { + fd.revents = 0; + rc = poll( &fd, 1, timeout ); + } while( rc == AC_SOCKET_ERROR && errno == EINTR && + LDAP_BOOL_GET(&ld->ld_options, LDAP_BOOL_RESTART )); + + if( rc == AC_SOCKET_ERROR ) return rc; + + if( fd.revents & POLL_WRITE ) { + if ( ldap_pvt_is_socket_ready(ld, s) == -1 ) return -1; + if ( ldap_pvt_ndelay_off(ld, s) == -1 ) return -1; +#ifdef LDAP_PF_LOCAL_SENDMSG + goto sendcred; +#else + return ( 0 ); +#endif + } + } +#else + { + fd_set wfds, *z=NULL; + +#ifdef FD_SETSIZE + if ( s >= FD_SETSIZE ) { + rc = AC_SOCKET_ERROR; + tcp_close( s ); + ldap_pvt_set_errno( EMFILE ); + return rc; + } +#endif + do { + FD_ZERO(&wfds); + FD_SET(s, &wfds ); + rc = select( ldap_int_tblsize, z, &wfds, z, opt_tv ? &tv : NULL ); + } while( rc == AC_SOCKET_ERROR && errno == EINTR && + LDAP_BOOL_GET(&ld->ld_options, LDAP_BOOL_RESTART )); + + if( rc == AC_SOCKET_ERROR ) return rc; + + 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; +#ifdef LDAP_PF_LOCAL_SENDMSG + goto sendcred; +#else + return ( 0 ); +#endif + } + } +#endif + + oslocal_debug(ld, "ldap_connect_timeout: timed out\n",0,0,0); + ldap_pvt_set_errno( ETIMEDOUT ); + return ( -1 ); +} + +int +ldap_connect_to_path(LDAP *ld, Sockbuf *sb, LDAPURLDesc *srv, int async) +{ + struct sockaddr_un server; + ber_socket_t s; + int rc; + const char *path = srv->lud_host; + + oslocal_debug(ld, "ldap_connect_to_path\n",0,0,0); + + if ( path == NULL || path[0] == '\0' ) { + path = LDAPI_SOCK; + } else { + if ( strlen(path) > (sizeof( server.sun_path ) - 1) ) { + ldap_pvt_set_errno( ENAMETOOLONG ); + return -1; + } + } + + s = ldap_pvt_socket( ld ); + if ( s == AC_SOCKET_INVALID ) { + return -1; + } + + oslocal_debug(ld, "ldap_connect_to_path: Trying %s\n", path, 0, 0); + + memset( &server, '\0', sizeof(server) ); + server.sun_family = AF_LOCAL; + strcpy( server.sun_path, path ); + + rc = ldap_pvt_connect(ld, s, &server, async); + + if (rc == 0) { + rc = ldap_int_connect_cbs( ld, sb, &s, srv, (struct sockaddr *)&server ); + } + if ( rc ) { + ldap_pvt_close_socket(ld, s); + } + return rc; +} +#else +static int dummy; /* generate also a warning: 'dummy' defined but not used (at least here) */ +#endif /* LDAP_PF_LOCAL */ diff --git a/libraries/libldap/pagectrl.c b/libraries/libldap/pagectrl.c new file mode 100644 index 0000000..b41dca6 --- /dev/null +++ b/libraries/libldap/pagectrl.c @@ -0,0 +1,271 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * Copyright 2006 Hans Leidekker + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* --------------------------------------------------------------------------- + ldap_create_page_control_value + + Create and encode the value of the paged results control (RFC 2696). + + ld (IN) An LDAP session handle + pagesize (IN) Page size requested + cookie (IN) Opaque structure used by the server to track its + location in the search results. NULL on the + first call. + value (OUT) Control value, SHOULD be freed by calling + ldap_memfree() when done. + + pagedResultsControl ::= SEQUENCE { + controlType 1.2.840.113556.1.4.319, + criticality BOOLEAN DEFAULT FALSE, + controlValue searchControlValue } + + searchControlValue ::= SEQUENCE { + size INTEGER (0..maxInt), + -- requested page size from client + -- result set size estimate from server + cookie OCTET STRING } + + ---------------------------------------------------------------------------*/ + +int +ldap_create_page_control_value( + LDAP *ld, + ber_int_t pagesize, + struct berval *cookie, + struct berval *value ) +{ + BerElement *ber = NULL; + ber_tag_t tag; + struct berval null_cookie = { 0, NULL }; + + if ( ld == NULL || value == NULL || + pagesize < 1 || pagesize > LDAP_MAXINT ) + { + if ( ld ) + ld->ld_errno = LDAP_PARAM_ERROR; + return LDAP_PARAM_ERROR; + } + + assert( LDAP_VALID( ld ) ); + + value->bv_val = NULL; + value->bv_len = 0; + ld->ld_errno = LDAP_SUCCESS; + + if ( cookie == NULL ) { + cookie = &null_cookie; + } + + ber = ldap_alloc_ber_with_options( ld ); + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + tag = ber_printf( ber, "{iO}", pagesize, cookie ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + + if ( ber_flatten2( ber, value, 1 ) == -1 ) { + ld->ld_errno = LDAP_NO_MEMORY; + } + +done:; + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + return ld->ld_errno; +} + + +/* --------------------------------------------------------------------------- + ldap_create_page_control + + Create and encode a page control. + + ld (IN) An LDAP session handle + pagesize (IN) Page size requested + cookie (IN) Opaque structure used by the server to track its + location in the search results. NULL on the + first call. + value (OUT) Control value, SHOULD be freed by calling + ldap_memfree() when done. + iscritical (IN) Criticality + ctrlp (OUT) LDAP control, SHOULD be freed by calling + ldap_control_free() when done. + + pagedResultsControl ::= SEQUENCE { + controlType 1.2.840.113556.1.4.319, + criticality BOOLEAN DEFAULT FALSE, + controlValue searchControlValue } + + searchControlValue ::= SEQUENCE { + size INTEGER (0..maxInt), + -- requested page size from client + -- result set size estimate from server + cookie OCTET STRING } + + ---------------------------------------------------------------------------*/ + +int +ldap_create_page_control( + LDAP *ld, + ber_int_t pagesize, + struct berval *cookie, + int iscritical, + LDAPControl **ctrlp ) +{ + struct berval value; + + if ( ctrlp == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + ld->ld_errno = ldap_create_page_control_value( ld, + pagesize, cookie, &value ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = ldap_control_create( LDAP_CONTROL_PAGEDRESULTS, + iscritical, &value, 0, ctrlp ); + if ( ld->ld_errno != LDAP_SUCCESS ) { + LDAP_FREE( value.bv_val ); + } + } + + return ld->ld_errno; +} + + +/* --------------------------------------------------------------------------- + ldap_parse_pageresponse_control + + Decode a page control. + + ld (IN) An LDAP session handle + ctrl (IN) The page response control + count (OUT) The number of entries in the page. + cookie (OUT) Opaque cookie. Use ldap_memfree() to + free the bv_val member of this structure. + + ---------------------------------------------------------------------------*/ + +int +ldap_parse_pageresponse_control( + LDAP *ld, + LDAPControl *ctrl, + ber_int_t *countp, + struct berval *cookie ) +{ + BerElement *ber; + ber_tag_t tag; + ber_int_t count; + + if ( ld == NULL || ctrl == NULL || cookie == NULL ) { + if ( ld ) + ld->ld_errno = LDAP_PARAM_ERROR; + return LDAP_PARAM_ERROR; + } + + /* Create a BerElement from the berval returned in the control. */ + ber = ber_init( &ctrl->ldctl_value ); + + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + /* Extract the count and cookie from the control. */ + tag = ber_scanf( ber, "{io}", &count, cookie ); + ber_free( ber, 1 ); + + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + } else { + ld->ld_errno = LDAP_SUCCESS; + + if ( countp != NULL ) { + *countp = (unsigned long)count; + } + } + + return ld->ld_errno; +} + +/* --------------------------------------------------------------------------- + ldap_parse_page_control + + Decode a page control. + + ld (IN) An LDAP session handle + ctrls (IN) Response controls + count (OUT) The number of entries in the page. + cookie (OUT) Opaque cookie. Use ldap_memfree() to + free the bv_val member of this structure. + + ---------------------------------------------------------------------------*/ + +int +ldap_parse_page_control( + LDAP *ld, + LDAPControl **ctrls, + ber_int_t *countp, + struct berval **cookiep ) +{ + LDAPControl *c; + struct berval cookie; + + if ( cookiep == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + if ( ctrls == NULL ) { + ld->ld_errno = LDAP_CONTROL_NOT_FOUND; + return ld->ld_errno; + } + + c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL ); + if ( c == NULL ) { + /* No page control was found. */ + ld->ld_errno = LDAP_CONTROL_NOT_FOUND; + return ld->ld_errno; + } + + ld->ld_errno = ldap_parse_pageresponse_control( ld, c, countp, &cookie ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + *cookiep = LDAP_MALLOC( sizeof( struct berval ) ); + if ( *cookiep == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + } else { + **cookiep = cookie; + } + } + + return ld->ld_errno; +} + diff --git a/libraries/libldap/passwd.c b/libraries/libldap/passwd.c new file mode 100644 index 0000000..59c2832 --- /dev/null +++ b/libraries/libldap/passwd.c @@ -0,0 +1,170 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This program was orignally developed by Kurt D. Zeilenga for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * LDAP Password Modify (Extended) Operation (RFC 3062) + */ + +int ldap_parse_passwd( + LDAP *ld, + LDAPMessage *res, + struct berval *newpasswd ) +{ + int rc; + struct berval *retdata = NULL; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( res != NULL ); + assert( newpasswd != NULL ); + + newpasswd->bv_val = NULL; + newpasswd->bv_len = 0; + + rc = ldap_parse_extended_result( ld, res, NULL, &retdata, 0 ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( retdata != NULL ) { + ber_tag_t tag; + BerElement *ber = ber_init( retdata ); + + if ( ber == NULL ) { + rc = ld->ld_errno = LDAP_NO_MEMORY; + goto done; + } + + /* we should check the tag */ + tag = ber_scanf( ber, "{o}", newpasswd ); + ber_free( ber, 1 ); + + if ( tag == LBER_ERROR ) { + rc = ld->ld_errno = LDAP_DECODING_ERROR; + } + } + +done:; + ber_bvfree( retdata ); + + return rc; +} + +int +ldap_passwd( LDAP *ld, + struct berval *user, + struct berval *oldpw, + struct berval *newpw, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + int rc; + struct berval bv = BER_BVNULL; + BerElement *ber = NULL; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( msgidp != NULL ); + + if( user != NULL || oldpw != NULL || newpw != NULL ) { + /* build change password control */ + ber = ber_alloc_t( LBER_USE_DER ); + + if( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + ber_printf( ber, "{" /*}*/ ); + + if( user != NULL ) { + ber_printf( ber, "tO", + LDAP_TAG_EXOP_MODIFY_PASSWD_ID, user ); + } + + if( oldpw != NULL ) { + ber_printf( ber, "tO", + LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, oldpw ); + } + + if( newpw != NULL ) { + ber_printf( ber, "tO", + LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, newpw ); + } + + ber_printf( ber, /*{*/ "N}" ); + + rc = ber_flatten2( ber, &bv, 0 ); + + if( rc < 0 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + return ld->ld_errno; + } + + } + + rc = ldap_extended_operation( ld, LDAP_EXOP_MODIFY_PASSWD, + bv.bv_val ? &bv : NULL, sctrls, cctrls, msgidp ); + + ber_free( ber, 1 ); + + return rc; +} + +int +ldap_passwd_s( + LDAP *ld, + struct berval *user, + struct berval *oldpw, + struct berval *newpw, + struct berval *newpasswd, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int rc; + int msgid; + LDAPMessage *res; + + rc = ldap_passwd( ld, user, oldpw, newpw, sctrls, cctrls, &msgid ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, &res ) == -1 || !res ) { + return ld->ld_errno; + } + + rc = ldap_parse_passwd( ld, res, newpasswd ); + if( rc != LDAP_SUCCESS ) { + ldap_msgfree( res ); + return rc; + } + + return( ldap_result2error( ld, res, 1 ) ); +} diff --git a/libraries/libldap/ppolicy.c b/libraries/libldap/ppolicy.c new file mode 100644 index 0000000..dfe3cbf --- /dev/null +++ b/libraries/libldap/ppolicy.c @@ -0,0 +1,214 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2018 The OpenLDAP Foundation. + * Portions Copyright 2004 Hewlett-Packard Company. + * Portions Copyright 2004 Howard Chu, Symas Corp. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was developed by Howard Chu for inclusion in + * OpenLDAP Software, based on prior work by Neil Dunbar (HP). + * This work was sponsored by the Hewlett-Packard Company. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST + +/* IMPLICIT TAGS, all context-specific */ +#define PPOLICY_WARNING 0xa0L /* constructed + 0 */ +#define PPOLICY_ERROR 0x81L /* primitive + 1 */ + +#define PPOLICY_EXPIRE 0x80L /* primitive + 0 */ +#define PPOLICY_GRACE 0x81L /* primitive + 1 */ + +/*--- + ldap_create_passwordpolicy_control + + Create and encode the Password Policy Request + + ld (IN) An LDAP session handle, as obtained from a call to + ldap_init(). + + ctrlp (OUT) A result parameter that will be assigned the address + of an LDAPControl structure that contains the + passwordPolicyRequest control created by this function. + The memory occupied by the LDAPControl structure + SHOULD be freed when it is no longer in use by + calling ldap_control_free(). + + + There is no control value for a password policy request + ---*/ + +int +ldap_create_passwordpolicy_control( LDAP *ld, + LDAPControl **ctrlp ) +{ + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( ctrlp != NULL ); + + ld->ld_errno = ldap_control_create( LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, ctrlp ); + + return ld->ld_errno; +} + + +/*--- + ldap_parse_passwordpolicy_control + + Decode the passwordPolicyResponse control and return information. + + ld (IN) An LDAP session handle. + + ctrl (IN) The address of an + LDAPControl structure, either obtained + by running thorugh the list of response controls or + by a call to ldap_control_find(). + + exptimep (OUT) This result parameter is filled in with the number of seconds before + the password will expire, if expiration is imminent + (imminency defined by the password policy). If expiration + is not imminent, the value is set to -1. + + gracep (OUT) This result parameter is filled in with the number of grace logins after + the password has expired, before no further login attempts + will be allowed. + + errorcodep (OUT) This result parameter is filled in with the error code of the password operation + If no error was detected, this error is set to PP_noError. + + Ber encoding + + PasswordPolicyResponseValue ::= SEQUENCE { + warning [0] CHOICE { + timeBeforeExpiration [0] INTEGER (0 .. maxInt), + graceLoginsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL + error [1] ENUMERATED { + passwordExpired (0), + accountLocked (1), + changeAfterReset (2), + passwordModNotAllowed (3), + mustSupplyOldPassword (4), + invalidPasswordSyntax (5), + passwordTooShort (6), + passwordTooYoung (7), + passwordInHistory (8) } OPTIONAL } + +---*/ + +int +ldap_parse_passwordpolicy_control( + LDAP *ld, + LDAPControl *ctrl, + ber_int_t *expirep, + ber_int_t *gracep, + LDAPPasswordPolicyError *errorp ) +{ + BerElement *ber; + int exp = -1, grace = -1; + ber_tag_t tag; + ber_len_t berLen; + char *last; + int err = PP_noError; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( ctrl != NULL ); + + if ( !ctrl->ldctl_value.bv_val ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return(ld->ld_errno); + } + + /* Create a BerElement from the berval returned in the control. */ + ber = ber_init(&ctrl->ldctl_value); + + if (ber == NULL) { + ld->ld_errno = LDAP_NO_MEMORY; + return(ld->ld_errno); + } + + tag = ber_peek_tag( ber, &berLen ); + if (tag != LBER_SEQUENCE) goto exit; + + for( tag = ber_first_element( ber, &berLen, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &berLen, last ) ) + { + switch (tag) { + case PPOLICY_WARNING: + ber_skip_tag(ber, &berLen ); + tag = ber_peek_tag( ber, &berLen ); + switch( tag ) { + case PPOLICY_EXPIRE: + if (ber_get_int( ber, &exp ) == LBER_DEFAULT) goto exit; + break; + case PPOLICY_GRACE: + if (ber_get_int( ber, &grace ) == LBER_DEFAULT) goto exit; + break; + default: + goto exit; + } + break; + case PPOLICY_ERROR: + if (ber_get_enum( ber, &err ) == LBER_DEFAULT) goto exit; + break; + default: + goto exit; + } + } + + ber_free(ber, 1); + + /* Return data to the caller for items that were requested. */ + if (expirep) *expirep = exp; + if (gracep) *gracep = grace; + if (errorp) *errorp = err; + + ld->ld_errno = LDAP_SUCCESS; + return(ld->ld_errno); + + exit: + ber_free(ber, 1); + ld->ld_errno = LDAP_DECODING_ERROR; + return(ld->ld_errno); +} + +const char * +ldap_passwordpolicy_err2txt( LDAPPasswordPolicyError err ) +{ + switch(err) { + case PP_passwordExpired: return "Password expired"; + case PP_accountLocked: return "Account locked"; + case PP_changeAfterReset: return "Password must be changed"; + case PP_passwordModNotAllowed: return "Policy prevents password modification"; + case PP_mustSupplyOldPassword: return "Policy requires old password in order to change password"; + case PP_insufficientPasswordQuality: return "Password fails quality checks"; + case PP_passwordTooShort: return "Password is too short for policy"; + case PP_passwordTooYoung: return "Password has been changed too recently"; + case PP_passwordInHistory: return "New password is in list of old passwords"; + case PP_noError: return "No error"; + default: return "Unknown error code"; + } +} + +#endif /* LDAP_CONTROL_PASSWORDPOLICYREQUEST */ diff --git a/libraries/libldap/print.c b/libraries/libldap/print.c new file mode 100644 index 0000000..a4d1f4d --- /dev/null +++ b/libraries/libldap/print.c @@ -0,0 +1,62 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/ctype.h> +#include <ac/stdarg.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * ldap log + */ + +static int ldap_log_check( LDAP *ld, int loglvl ) +{ + int errlvl; + + if(ld == NULL) { + errlvl = ldap_debug; + } else { + errlvl = ld->ld_debug; + } + + return errlvl & loglvl ? 1 : 0; +} + +int ldap_log_printf( LDAP *ld, int loglvl, const char *fmt, ... ) +{ + char buf[ 1024 ]; + va_list ap; + + if ( !ldap_log_check( ld, loglvl )) { + return 0; + } + + va_start( ap, fmt ); + + buf[sizeof(buf) - 1] = '\0'; + vsnprintf( buf, sizeof(buf)-1, fmt, ap ); + + va_end(ap); + + (*ber_pvt_log_print)( buf ); + return 1; +} diff --git a/libraries/libldap/references.c b/libraries/libldap/references.c new file mode 100644 index 0000000..cefd14d --- /dev/null +++ b/libraries/libldap/references.c @@ -0,0 +1,147 @@ +/* references.c */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +LDAPMessage * +ldap_first_reference( LDAP *ld, LDAPMessage *chain ) +{ + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( chain != NULL ); + + return chain->lm_msgtype == LDAP_RES_SEARCH_REFERENCE + ? chain + : ldap_next_reference( ld, chain ); +} + +LDAPMessage * +ldap_next_reference( LDAP *ld, LDAPMessage *ref ) +{ + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( ref != NULL ); + + for ( + ref = ref->lm_chain; + ref != NULL; + ref = ref->lm_chain ) + { + if( ref->lm_msgtype == LDAP_RES_SEARCH_REFERENCE ) { + return( ref ); + } + } + + return( NULL ); +} + +int +ldap_count_references( LDAP *ld, LDAPMessage *chain ) +{ + int i; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + for ( i = 0; chain != NULL; chain = chain->lm_chain ) { + if( chain->lm_msgtype == LDAP_RES_SEARCH_REFERENCE ) { + i++; + } + } + + return( i ); +} + +int +ldap_parse_reference( + LDAP *ld, + LDAPMessage *ref, + char ***referralsp, + LDAPControl ***serverctrls, + int freeit) +{ + BerElement be; + char **refs = NULL; + int rc; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( ref != NULL ); + + if( ref->lm_msgtype != LDAP_RES_SEARCH_REFERENCE ) { + return LDAP_PARAM_ERROR; + } + + /* make a private copy of BerElement */ + AC_MEMCPY(&be, ref->lm_ber, sizeof(be)); + + if ( ber_scanf( &be, "{v" /*}*/, &refs ) == LBER_ERROR ) { + rc = LDAP_DECODING_ERROR; + goto free_and_return; + } + + if ( serverctrls == NULL ) { + rc = LDAP_SUCCESS; + goto free_and_return; + } + + if ( ber_scanf( &be, /*{*/ "}" ) == LBER_ERROR ) { + rc = LDAP_DECODING_ERROR; + goto free_and_return; + } + + rc = ldap_pvt_get_controls( &be, serverctrls ); + +free_and_return: + + if( referralsp != NULL ) { + /* provide references regradless of return code */ + *referralsp = refs; + + } else { + LDAP_VFREE( refs ); + } + + if( freeit ) { + ldap_msgfree( ref ); + } + + if( rc != LDAP_SUCCESS ) { + ld->ld_errno = rc; + + if( ld->ld_matched != NULL ) { + LDAP_FREE( ld->ld_matched ); + ld->ld_matched = NULL; + } + + if( ld->ld_error != NULL ) { + LDAP_FREE( ld->ld_error ); + ld->ld_error = NULL; + } + } + + return rc; +} diff --git a/libraries/libldap/request.c b/libraries/libldap/request.c new file mode 100644 index 0000000..3114543 --- /dev/null +++ b/libraries/libldap/request.c @@ -0,0 +1,1696 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + */ +/* This notice applies to changes, created by or for Novell, Inc., + * to preexisting works for which notices appear elsewhere in this file. + * + * Copyright (C) 1999, 2000 Novell, Inc. All Rights Reserved. + * + * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES. + * USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO VERSION + * 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS AVAILABLE AT + * HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" IN THE + * TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION OF THIS + * WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP PUBLIC + * LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT THE + * PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. + *--- + * Modification to OpenLDAP source by Novell, Inc. + * April 2000 sfs Added code to chase V3 referrals + * request.c - sending of ldap requests; handling of referrals + *--- + * Note: A verbatim copy of version 2.0.1 of the OpenLDAP Public License + * can be found in the file "build/LICENSE-2.0.1" in this distribution + * of OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include "ldap-int.h" +#include "lber.h" + +/* used by ldap_send_server_request and ldap_new_connection */ +#ifdef LDAP_R_COMPILE +#define LDAP_CONN_LOCK_IF(nolock) \ + { if (nolock) LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); } +#define LDAP_CONN_UNLOCK_IF(nolock) \ + { if (nolock) LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); } +#define LDAP_REQ_LOCK_IF(nolock) \ + { if (nolock) LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); } +#define LDAP_REQ_UNLOCK_IF(nolock) \ + { if (nolock) LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); } +#define LDAP_RES_LOCK_IF(nolock) \ + { if (nolock) LDAP_MUTEX_LOCK( &ld->ld_res_mutex ); } +#define LDAP_RES_UNLOCK_IF(nolock) \ + { if (nolock) LDAP_MUTEX_UNLOCK( &ld->ld_res_mutex ); } +#else +#define LDAP_CONN_LOCK_IF(nolock) +#define LDAP_CONN_UNLOCK_IF(nolock) +#define LDAP_REQ_LOCK_IF(nolock) +#define LDAP_REQ_UNLOCK_IF(nolock) +#define LDAP_RES_LOCK_IF(nolock) +#define LDAP_RES_UNLOCK_IF(nolock) +#endif + +static LDAPConn *find_connection LDAP_P(( LDAP *ld, LDAPURLDesc *srv, int any )); +static void use_connection LDAP_P(( LDAP *ld, LDAPConn *lc )); +static void ldap_free_request_int LDAP_P(( LDAP *ld, LDAPRequest *lr )); + +static BerElement * +re_encode_request( LDAP *ld, + BerElement *origber, + ber_int_t msgid, + int sref, + LDAPURLDesc *srv, + int *type ); + +BerElement * +ldap_alloc_ber_with_options( LDAP *ld ) +{ + BerElement *ber; + + ber = ber_alloc_t( ld->ld_lberoptions ); + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + } + + return( ber ); +} + + +void +ldap_set_ber_options( LDAP *ld, BerElement *ber ) +{ + /* ld_lberoptions is constant, hence no lock */ + ber->ber_options = ld->ld_lberoptions; +} + + +/* sets needed mutexes - no mutexes set to this point */ +ber_int_t +ldap_send_initial_request( + LDAP *ld, + ber_tag_t msgtype, + const char *dn, + BerElement *ber, + ber_int_t msgid) +{ + int rc = 1; + ber_socket_t sd = AC_SOCKET_INVALID; + + Debug( LDAP_DEBUG_TRACE, "ldap_send_initial_request\n", 0, 0, 0 ); + + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + if ( ber_sockbuf_ctrl( ld->ld_sb, LBER_SB_OPT_GET_FD, &sd ) == -1 ) { + /* not connected yet */ + rc = ldap_open_defconn( ld ); + if ( rc == 0 ) { + ber_sockbuf_ctrl( ld->ld_defconn->lconn_sb, + LBER_SB_OPT_GET_FD, &sd ); + } + } + if ( ld->ld_defconn && ld->ld_defconn->lconn_status == LDAP_CONNST_CONNECTING ) + rc = ldap_int_check_async_open( ld, sd ); + if( rc < 0 ) { + ber_free( ber, 1 ); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + return( -1 ); + } else if ( rc == 0 ) { + Debug( LDAP_DEBUG_TRACE, + "ldap_open_defconn: successful\n", + 0, 0, 0 ); + } + +#ifdef LDAP_CONNECTIONLESS + if (LDAP_IS_UDP(ld)) { + if (msgtype == LDAP_REQ_BIND) { + LDAP_MUTEX_LOCK( &ld->ld_options.ldo_mutex ); + if (ld->ld_options.ldo_cldapdn) + ldap_memfree(ld->ld_options.ldo_cldapdn); + ld->ld_options.ldo_cldapdn = ldap_strdup(dn); + ber_free( ber, 1 ); + LDAP_MUTEX_UNLOCK( &ld->ld_options.ldo_mutex ); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + return 0; + } + if (msgtype != LDAP_REQ_ABANDON && msgtype != LDAP_REQ_SEARCH) + { + ber_free( ber, 1 ); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + return LDAP_PARAM_ERROR; + } + } +#endif + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + rc = ldap_send_server_request( ld, ber, msgid, NULL, + NULL, NULL, NULL, 0, 0 ); + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + return(rc); +} + + +/* protected by conn_mutex */ +int +ldap_int_flush_request( + LDAP *ld, + LDAPRequest *lr ) +{ + LDAPConn *lc = lr->lr_conn; + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + if ( ber_flush2( lc->lconn_sb, lr->lr_ber, LBER_FLUSH_FREE_NEVER ) != 0 ) { + if ( sock_errno() == EAGAIN ) { + /* need to continue write later */ + lr->lr_status = LDAP_REQST_WRITING; + ldap_mark_select_write( ld, lc->lconn_sb ); + ld->ld_errno = LDAP_BUSY; + return -2; + } else { + ld->ld_errno = LDAP_SERVER_DOWN; + ldap_free_request( ld, lr ); + ldap_free_connection( ld, lc, 0, 0 ); + return( -1 ); + } + } else { + if ( lr->lr_parent == NULL ) { + lr->lr_ber->ber_end = lr->lr_ber->ber_ptr; + lr->lr_ber->ber_ptr = lr->lr_ber->ber_buf; + } + lr->lr_status = LDAP_REQST_INPROGRESS; + + /* sent -- waiting for a response */ + ldap_mark_select_read( ld, lc->lconn_sb ); + ldap_clear_select_write( ld, lc->lconn_sb ); + } + return 0; +} + +/* + * protected by req_mutex + * if m_noconn then protect using conn_lock + * else already protected with conn_lock + * if m_res then also protected by res_mutex + */ + +int +ldap_send_server_request( + LDAP *ld, + BerElement *ber, + ber_int_t msgid, + LDAPRequest *parentreq, + LDAPURLDesc **srvlist, + LDAPConn *lc, + LDAPreqinfo *bind, + int m_noconn, + int m_res ) +{ + LDAPRequest *lr; + int incparent, rc; + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_req_mutex ); + Debug( LDAP_DEBUG_TRACE, "ldap_send_server_request\n", 0, 0, 0 ); + + incparent = 0; + ld->ld_errno = LDAP_SUCCESS; /* optimistic */ + + LDAP_CONN_LOCK_IF(m_noconn); + if ( lc == NULL ) { + if ( srvlist == NULL ) { + lc = ld->ld_defconn; + } else { + lc = find_connection( ld, *srvlist, 1 ); + if ( lc == NULL ) { + if ( (bind != NULL) && (parentreq != NULL) ) { + /* Remember the bind in the parent */ + incparent = 1; + ++parentreq->lr_outrefcnt; + } + lc = ldap_new_connection( ld, srvlist, 0, + 1, bind, 1, m_res ); + } + } + } + + /* async connect... */ + if ( lc != NULL && lc->lconn_status == LDAP_CONNST_CONNECTING ) { + ber_socket_t sd = AC_SOCKET_ERROR; + struct timeval tv = { 0 }; + + ber_sockbuf_ctrl( lc->lconn_sb, LBER_SB_OPT_GET_FD, &sd ); + + /* poll ... */ + switch ( ldap_int_poll( ld, sd, &tv, 1 ) ) { + case 0: + /* go on! */ + lc->lconn_status = LDAP_CONNST_CONNECTED; + break; + + case -2: + /* async only occurs if a network timeout is set */ + + /* honor network timeout */ + LDAP_MUTEX_LOCK( &ld->ld_options.ldo_mutex ); + if ( time( NULL ) - lc->lconn_created <= ld->ld_options.ldo_tm_net.tv_sec ) + { + /* caller will have to call again */ + ld->ld_errno = LDAP_X_CONNECTING; + } + LDAP_MUTEX_UNLOCK( &ld->ld_options.ldo_mutex ); + /* fallthru */ + + default: + /* error */ + break; + } + } + + if ( lc == NULL || lc->lconn_status != LDAP_CONNST_CONNECTED ) { + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = LDAP_SERVER_DOWN; + } + + ber_free( ber, 1 ); + if ( incparent ) { + /* Forget about the bind */ + --parentreq->lr_outrefcnt; + } + LDAP_CONN_UNLOCK_IF(m_noconn); + return( -1 ); + } + + use_connection( ld, lc ); + +#ifdef LDAP_CONNECTIONLESS + if ( LDAP_IS_UDP( ld )) { + BerElement tmpber = *ber; + ber_rewind( &tmpber ); + LDAP_MUTEX_LOCK( &ld->ld_options.ldo_mutex ); + rc = ber_write( &tmpber, ld->ld_options.ldo_peer, + sizeof( struct sockaddr_storage ), 0 ); + LDAP_MUTEX_UNLOCK( &ld->ld_options.ldo_mutex ); + if ( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + LDAP_CONN_UNLOCK_IF(m_noconn); + return rc; + } + } +#endif + + /* If we still have an incomplete write, try to finish it before + * dealing with the new request. If we don't finish here, return + * LDAP_BUSY and let the caller retry later. We only allow a single + * request to be in WRITING state. + */ + rc = 0; + if ( ld->ld_requests && + ld->ld_requests->lr_status == LDAP_REQST_WRITING && + ldap_int_flush_request( ld, ld->ld_requests ) < 0 ) + { + rc = -1; + } + if ( rc ) { + LDAP_CONN_UNLOCK_IF(m_noconn); + return rc; + } + + lr = (LDAPRequest *)LDAP_CALLOC( 1, sizeof( LDAPRequest ) ); + if ( lr == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + ldap_free_connection( ld, lc, 0, 0 ); + ber_free( ber, 1 ); + if ( incparent ) { + /* Forget about the bind */ + --parentreq->lr_outrefcnt; + } + LDAP_CONN_UNLOCK_IF(m_noconn); + return( -1 ); + } + lr->lr_msgid = msgid; + lr->lr_status = LDAP_REQST_INPROGRESS; + lr->lr_res_errno = LDAP_SUCCESS; /* optimistic */ + lr->lr_ber = ber; + lr->lr_conn = lc; + if ( parentreq != NULL ) { /* sub-request */ + if ( !incparent ) { + /* Increment if we didn't do it before the bind */ + ++parentreq->lr_outrefcnt; + } + lr->lr_origid = parentreq->lr_origid; + lr->lr_parentcnt = ++parentreq->lr_parentcnt; + lr->lr_parent = parentreq; + lr->lr_refnext = parentreq->lr_child; + parentreq->lr_child = lr; + } else { /* original request */ + lr->lr_origid = lr->lr_msgid; + } + + /* Extract requestDN for future reference */ +#ifdef LDAP_CONNECTIONLESS + if ( !LDAP_IS_UDP(ld) ) +#endif + { + BerElement tmpber = *ber; + ber_int_t bint; + ber_tag_t tag, rtag; + + ber_reset( &tmpber, 1 ); + rtag = ber_scanf( &tmpber, "{it", /*}*/ &bint, &tag ); + switch ( tag ) { + case LDAP_REQ_BIND: + rtag = ber_scanf( &tmpber, "{i" /*}*/, &bint ); + break; + case LDAP_REQ_DELETE: + break; + default: + rtag = ber_scanf( &tmpber, "{" /*}*/ ); + case LDAP_REQ_ABANDON: + break; + } + if ( tag != LDAP_REQ_ABANDON ) { + ber_skip_tag( &tmpber, &lr->lr_dn.bv_len ); + lr->lr_dn.bv_val = tmpber.ber_ptr; + } + } + + lr->lr_prev = NULL; + lr->lr_next = ld->ld_requests; + if ( lr->lr_next != NULL ) { + lr->lr_next->lr_prev = lr; + } + ld->ld_requests = lr; + + ld->ld_errno = LDAP_SUCCESS; + if ( ldap_int_flush_request( ld, lr ) == -1 ) { + msgid = -1; + } + + LDAP_CONN_UNLOCK_IF(m_noconn); + return( msgid ); +} + +/* return 0 if no StartTLS ext, 1 if present, 2 if critical */ +static int +find_tls_ext( LDAPURLDesc *srv ) +{ + int i, crit; + char *ext; + + if ( !srv->lud_exts ) + return 0; + + for (i=0; srv->lud_exts[i]; i++) { + crit = 0; + ext = srv->lud_exts[i]; + if ( ext[0] == '!') { + ext++; + crit = 1; + } + if ( !strcasecmp( ext, "StartTLS" ) || + !strcasecmp( ext, "X-StartTLS" ) || + !strcmp( ext, LDAP_EXOP_START_TLS )) { + return crit + 1; + } + } + return 0; +} + +/* + * always protected by conn_mutex + * optionally protected by req_mutex and res_mutex + */ +LDAPConn * +ldap_new_connection( LDAP *ld, LDAPURLDesc **srvlist, int use_ldsb, + int connect, LDAPreqinfo *bind, int m_req, int m_res ) +{ + LDAPConn *lc; + int async = 0; + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + Debug( LDAP_DEBUG_TRACE, "ldap_new_connection %d %d %d\n", + use_ldsb, connect, (bind != NULL) ); + /* + * make a new LDAP server connection + * XXX open connection synchronously for now + */ + lc = (LDAPConn *)LDAP_CALLOC( 1, sizeof( LDAPConn ) ); + if ( lc == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return( NULL ); + } + + if ( use_ldsb ) { + assert( ld->ld_sb != NULL ); + lc->lconn_sb = ld->ld_sb; + + } else { + lc->lconn_sb = ber_sockbuf_alloc(); + if ( lc->lconn_sb == NULL ) { + LDAP_FREE( (char *)lc ); + ld->ld_errno = LDAP_NO_MEMORY; + return( NULL ); + } + } + + if ( connect ) { + LDAPURLDesc **srvp, *srv = NULL; + + async = LDAP_BOOL_GET( &ld->ld_options, LDAP_BOOL_CONNECT_ASYNC ); + + for ( srvp = srvlist; *srvp != NULL; srvp = &(*srvp)->lud_next ) { + int rc; + + rc = ldap_int_open_connection( ld, lc, *srvp, async ); + if ( rc != -1 ) { + srv = *srvp; + + /* If we fully connected, async is moot */ + if ( rc == 0 ) + async = 0; + + if ( ld->ld_urllist_proc && ( !async || rc != -2 ) ) { + ld->ld_urllist_proc( ld, srvlist, srvp, ld->ld_urllist_params ); + } + + break; + } + } + + if ( srv == NULL ) { + if ( !use_ldsb ) { + ber_sockbuf_free( lc->lconn_sb ); + } + LDAP_FREE( (char *)lc ); + ld->ld_errno = LDAP_SERVER_DOWN; + return( NULL ); + } + + lc->lconn_server = ldap_url_dup( srv ); + if ( !lc->lconn_server ) { + if ( !use_ldsb ) + ber_sockbuf_free( lc->lconn_sb ); + LDAP_FREE( (char *)lc ); + ld->ld_errno = LDAP_NO_MEMORY; + return( NULL ); + } + } + + lc->lconn_status = async ? LDAP_CONNST_CONNECTING : LDAP_CONNST_CONNECTED; + lc->lconn_next = ld->ld_conns; + ld->ld_conns = lc; + + if ( connect ) { +#ifdef HAVE_TLS + if ( lc->lconn_server->lud_exts ) { + int rc, ext = find_tls_ext( lc->lconn_server ); + if ( ext ) { + LDAPConn *savedefconn; + + savedefconn = ld->ld_defconn; + ++lc->lconn_refcnt; /* avoid premature free */ + ld->ld_defconn = lc; + + LDAP_REQ_UNLOCK_IF(m_req); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + LDAP_RES_UNLOCK_IF(m_res); + rc = ldap_start_tls_s( ld, NULL, NULL ); + LDAP_RES_LOCK_IF(m_res); + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + LDAP_REQ_LOCK_IF(m_req); + ld->ld_defconn = savedefconn; + --lc->lconn_refcnt; + + if ( rc != LDAP_SUCCESS && ext == 2 ) { + ldap_free_connection( ld, lc, 1, 0 ); + return NULL; + } + } + } +#endif + } + + if ( bind != NULL ) { + int err = 0; + LDAPConn *savedefconn; + + /* Set flag to prevent additional referrals + * from being processed on this + * connection until the bind has completed + */ + lc->lconn_rebind_inprogress = 1; + /* V3 rebind function */ + if ( ld->ld_rebind_proc != NULL) { + LDAPURLDesc *srvfunc; + + srvfunc = ldap_url_dup( *srvlist ); + if ( srvfunc == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + err = -1; + } else { + savedefconn = ld->ld_defconn; + ++lc->lconn_refcnt; /* avoid premature free */ + ld->ld_defconn = lc; + + Debug( LDAP_DEBUG_TRACE, "Call application rebind_proc\n", 0, 0, 0); + LDAP_REQ_UNLOCK_IF(m_req); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + LDAP_RES_UNLOCK_IF(m_res); + err = (*ld->ld_rebind_proc)( ld, + bind->ri_url, bind->ri_request, bind->ri_msgid, + ld->ld_rebind_params ); + LDAP_RES_LOCK_IF(m_res); + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + LDAP_REQ_LOCK_IF(m_req); + + ld->ld_defconn = savedefconn; + --lc->lconn_refcnt; + + if ( err != 0 ) { + err = -1; + ldap_free_connection( ld, lc, 1, 0 ); + lc = NULL; + } + ldap_free_urldesc( srvfunc ); + } + + } else { + int msgid, rc; + struct berval passwd = BER_BVNULL; + + savedefconn = ld->ld_defconn; + ++lc->lconn_refcnt; /* avoid premature free */ + ld->ld_defconn = lc; + + Debug( LDAP_DEBUG_TRACE, + "anonymous rebind via ldap_sasl_bind(\"\")\n", + 0, 0, 0); + + LDAP_REQ_UNLOCK_IF(m_req); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + LDAP_RES_UNLOCK_IF(m_res); + rc = ldap_sasl_bind( ld, "", LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &msgid ); + if ( rc != LDAP_SUCCESS ) { + err = -1; + + } else { + for ( err = 1; err > 0; ) { + struct timeval tv = { 0, 100000 }; + LDAPMessage *res = NULL; + + switch ( ldap_result( ld, msgid, LDAP_MSG_ALL, &tv, &res ) ) { + case -1: + err = -1; + break; + + case 0: +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_yield(); +#endif + break; + + case LDAP_RES_BIND: + rc = ldap_parse_result( ld, res, &err, NULL, NULL, NULL, NULL, 1 ); + if ( rc != LDAP_SUCCESS ) { + err = -1; + + } else if ( err != LDAP_SUCCESS ) { + err = -1; + } + /* else err == LDAP_SUCCESS == 0 */ + break; + + default: + Debug( LDAP_DEBUG_TRACE, + "ldap_new_connection %p: " + "unexpected response %d " + "from BIND request id=%d\n", + (void *) ld, ldap_msgtype( res ), msgid ); + err = -1; + break; + } + } + } + LDAP_RES_LOCK_IF(m_res); + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + LDAP_REQ_LOCK_IF(m_req); + ld->ld_defconn = savedefconn; + --lc->lconn_refcnt; + + if ( err != 0 ) { + ldap_free_connection( ld, lc, 1, 0 ); + lc = NULL; + } + } + if ( lc != NULL ) + lc->lconn_rebind_inprogress = 0; + } + return( lc ); +} + + +/* protected by ld_conn_mutex */ +static LDAPConn * +find_connection( LDAP *ld, LDAPURLDesc *srv, int any ) +/* + * return an existing connection (if any) to the server srv + * if "any" is non-zero, check for any server in the "srv" chain + */ +{ + LDAPConn *lc; + LDAPURLDesc *lcu, *lsu; + int lcu_port, lsu_port; + int found = 0; + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + for ( lc = ld->ld_conns; lc != NULL; lc = lc->lconn_next ) { + lcu = lc->lconn_server; + lcu_port = ldap_pvt_url_scheme_port( lcu->lud_scheme, + lcu->lud_port ); + + for ( lsu = srv; lsu != NULL; lsu = lsu->lud_next ) { + lsu_port = ldap_pvt_url_scheme_port( lsu->lud_scheme, + lsu->lud_port ); + + if ( lsu_port == lcu_port + && strcmp( lcu->lud_scheme, lsu->lud_scheme ) == 0 + && lcu->lud_host != NULL && lsu->lud_host != NULL + && strcasecmp( lsu->lud_host, lcu->lud_host ) == 0 ) + { + found = 1; + break; + } + + if ( !any ) break; + } + if ( found ) + break; + } + return lc; +} + + + +/* protected by ld_conn_mutex */ +static void +use_connection( LDAP *ld, LDAPConn *lc ) +{ + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + ++lc->lconn_refcnt; + lc->lconn_lastused = time( NULL ); +} + + +/* protected by ld_conn_mutex */ +void +ldap_free_connection( LDAP *ld, LDAPConn *lc, int force, int unbind ) +{ + LDAPConn *tmplc, *prevlc; + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + Debug( LDAP_DEBUG_TRACE, + "ldap_free_connection %d %d\n", + force, unbind, 0 ); + + if ( force || --lc->lconn_refcnt <= 0 ) { + /* remove from connections list first */ + + for ( prevlc = NULL, tmplc = ld->ld_conns; + tmplc != NULL; + tmplc = tmplc->lconn_next ) + { + if ( tmplc == lc ) { + if ( prevlc == NULL ) { + ld->ld_conns = tmplc->lconn_next; + } else { + prevlc->lconn_next = tmplc->lconn_next; + } + if ( ld->ld_defconn == lc ) { + ld->ld_defconn = NULL; + } + break; + } + prevlc = tmplc; + } + + /* process connection callbacks */ + { + struct ldapoptions *lo; + ldaplist *ll; + ldap_conncb *cb; + + lo = &ld->ld_options; + LDAP_MUTEX_LOCK( &lo->ldo_mutex ); + if ( lo->ldo_conn_cbs ) { + for ( ll=lo->ldo_conn_cbs; ll; ll=ll->ll_next ) { + cb = ll->ll_data; + cb->lc_del( ld, lc->lconn_sb, cb ); + } + } + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + lo = LDAP_INT_GLOBAL_OPT(); + LDAP_MUTEX_LOCK( &lo->ldo_mutex ); + if ( lo->ldo_conn_cbs ) { + for ( ll=lo->ldo_conn_cbs; ll; ll=ll->ll_next ) { + cb = ll->ll_data; + cb->lc_del( ld, lc->lconn_sb, cb ); + } + } + LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); + } + + if ( lc->lconn_status == LDAP_CONNST_CONNECTED ) { + ldap_mark_select_clear( ld, lc->lconn_sb ); + if ( unbind ) { + ldap_send_unbind( ld, lc->lconn_sb, + NULL, NULL ); + } + } + + if ( lc->lconn_ber != NULL ) { + ber_free( lc->lconn_ber, 1 ); + } + + ldap_int_sasl_close( ld, lc ); +#ifdef HAVE_GSSAPI + ldap_int_gssapi_close( ld, lc ); +#endif + + ldap_free_urllist( lc->lconn_server ); + + /* FIXME: is this at all possible? + * ldap_ld_free() in unbind.c calls ldap_free_connection() + * with force == 1 __after__ explicitly calling + * ldap_free_request() on all requests */ + if ( force ) { + LDAPRequest *lr; + + for ( lr = ld->ld_requests; lr; ) { + LDAPRequest *lr_next = lr->lr_next; + + if ( lr->lr_conn == lc ) { + ldap_free_request_int( ld, lr ); + } + + lr = lr_next; + } + } + + if ( lc->lconn_sb != ld->ld_sb ) { + ber_sockbuf_free( lc->lconn_sb ); + } else { + ber_int_sb_close( lc->lconn_sb ); + } + + if ( lc->lconn_rebind_queue != NULL) { + int i; + for( i = 0; lc->lconn_rebind_queue[i] != NULL; i++ ) { + LDAP_VFREE( lc->lconn_rebind_queue[i] ); + } + LDAP_FREE( lc->lconn_rebind_queue ); + } + + LDAP_FREE( lc ); + + Debug( LDAP_DEBUG_TRACE, + "ldap_free_connection: actually freed\n", + 0, 0, 0 ); + + } else { + lc->lconn_lastused = time( NULL ); + Debug( LDAP_DEBUG_TRACE, "ldap_free_connection: refcnt %d\n", + lc->lconn_refcnt, 0, 0 ); + } +} + + +/* Protects self with ld_conn_mutex */ +#ifdef LDAP_DEBUG +void +ldap_dump_connection( LDAP *ld, LDAPConn *lconns, int all ) +{ + LDAPConn *lc; + char timebuf[32]; + + Debug( LDAP_DEBUG_TRACE, "** ld %p Connection%s:\n", (void *)ld, all ? "s" : "", 0 ); + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + for ( lc = lconns; lc != NULL; lc = lc->lconn_next ) { + if ( lc->lconn_server != NULL ) { + Debug( LDAP_DEBUG_TRACE, "* host: %s port: %d%s\n", + ( lc->lconn_server->lud_host == NULL ) ? "(null)" + : lc->lconn_server->lud_host, + lc->lconn_server->lud_port, ( lc->lconn_sb == + ld->ld_sb ) ? " (default)" : "" ); + } + Debug( LDAP_DEBUG_TRACE, " refcnt: %d status: %s\n", lc->lconn_refcnt, + ( lc->lconn_status == LDAP_CONNST_NEEDSOCKET ) + ? "NeedSocket" : + ( lc->lconn_status == LDAP_CONNST_CONNECTING ) + ? "Connecting" : "Connected", 0 ); + Debug( LDAP_DEBUG_TRACE, " last used: %s%s\n", + ldap_pvt_ctime( &lc->lconn_lastused, timebuf ), + lc->lconn_rebind_inprogress ? " rebind in progress" : "", 0 ); + if ( lc->lconn_rebind_inprogress ) { + if ( lc->lconn_rebind_queue != NULL) { + int i; + + for ( i = 0; lc->lconn_rebind_queue[i] != NULL; i++ ) { + int j; + for( j = 0; lc->lconn_rebind_queue[i][j] != 0; j++ ) { + Debug( LDAP_DEBUG_TRACE, " queue %d entry %d - %s\n", + i, j, lc->lconn_rebind_queue[i][j] ); + } + } + } else { + Debug( LDAP_DEBUG_TRACE, " queue is empty\n", 0, 0, 0 ); + } + } + Debug( LDAP_DEBUG_TRACE, "\n", 0, 0, 0 ); + if ( !all ) { + break; + } + } + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); +} + + +/* protected by req_mutex and res_mutex */ +void +ldap_dump_requests_and_responses( LDAP *ld ) +{ + LDAPRequest *lr; + LDAPMessage *lm, *l; + int i; + + Debug( LDAP_DEBUG_TRACE, "** ld %p Outstanding Requests:\n", + (void *)ld, 0, 0 ); + lr = ld->ld_requests; + if ( lr == NULL ) { + Debug( LDAP_DEBUG_TRACE, " Empty\n", 0, 0, 0 ); + } + for ( i = 0; lr != NULL; lr = lr->lr_next, i++ ) { + Debug( LDAP_DEBUG_TRACE, " * msgid %d, origid %d, status %s\n", + lr->lr_msgid, lr->lr_origid, + ( lr->lr_status == LDAP_REQST_INPROGRESS ) ? "InProgress" : + ( lr->lr_status == LDAP_REQST_CHASINGREFS ) ? "ChasingRefs" : + ( lr->lr_status == LDAP_REQST_NOTCONNECTED ) ? "NotConnected" : + ( lr->lr_status == LDAP_REQST_WRITING ) ? "Writing" : + ( lr->lr_status == LDAP_REQST_COMPLETED ) ? "RequestCompleted" + : "InvalidStatus" ); + Debug( LDAP_DEBUG_TRACE, " outstanding referrals %d, parent count %d\n", + lr->lr_outrefcnt, lr->lr_parentcnt, 0 ); + } + Debug( LDAP_DEBUG_TRACE, " ld %p request count %d (abandoned %lu)\n", + (void *)ld, i, ld->ld_nabandoned ); + Debug( LDAP_DEBUG_TRACE, "** ld %p Response Queue:\n", (void *)ld, 0, 0 ); + if ( ( lm = ld->ld_responses ) == NULL ) { + Debug( LDAP_DEBUG_TRACE, " Empty\n", 0, 0, 0 ); + } + for ( i = 0; lm != NULL; lm = lm->lm_next, i++ ) { + Debug( LDAP_DEBUG_TRACE, " * msgid %d, type %lu\n", + lm->lm_msgid, (unsigned long)lm->lm_msgtype, 0 ); + if ( lm->lm_chain != NULL ) { + Debug( LDAP_DEBUG_TRACE, " chained responses:\n", 0, 0, 0 ); + for ( l = lm->lm_chain; l != NULL; l = l->lm_chain ) { + Debug( LDAP_DEBUG_TRACE, + " * msgid %d, type %lu\n", + l->lm_msgid, + (unsigned long)l->lm_msgtype, 0 ); + } + } + } + Debug( LDAP_DEBUG_TRACE, " ld %p response count %d\n", (void *)ld, i, 0 ); +} +#endif /* LDAP_DEBUG */ + +/* protected by req_mutex */ +static void +ldap_free_request_int( LDAP *ld, LDAPRequest *lr ) +{ + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_req_mutex ); + /* if lr_refcnt > 0, the request has been looked up + * by ldap_find_request_by_msgid(); if in the meanwhile + * the request is free()'d by someone else, just decrease + * the reference count and extract it from the request + * list; later on, it will be freed. */ + if ( lr->lr_prev == NULL ) { + if ( lr->lr_refcnt == 0 ) { + /* free'ing the first request? */ + assert( ld->ld_requests == lr ); + } + + if ( ld->ld_requests == lr ) { + ld->ld_requests = lr->lr_next; + } + + } else { + lr->lr_prev->lr_next = lr->lr_next; + } + + if ( lr->lr_next != NULL ) { + lr->lr_next->lr_prev = lr->lr_prev; + } + + if ( lr->lr_refcnt > 0 ) { + lr->lr_refcnt = -lr->lr_refcnt; + + lr->lr_prev = NULL; + lr->lr_next = NULL; + + return; + } + + if ( lr->lr_ber != NULL ) { + ber_free( lr->lr_ber, 1 ); + lr->lr_ber = NULL; + } + + if ( lr->lr_res_error != NULL ) { + LDAP_FREE( lr->lr_res_error ); + lr->lr_res_error = NULL; + } + + if ( lr->lr_res_matched != NULL ) { + LDAP_FREE( lr->lr_res_matched ); + lr->lr_res_matched = NULL; + } + + LDAP_FREE( lr ); +} + +/* protected by req_mutex */ +void +ldap_free_request( LDAP *ld, LDAPRequest *lr ) +{ + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_req_mutex ); + Debug( LDAP_DEBUG_TRACE, "ldap_free_request (origid %d, msgid %d)\n", + lr->lr_origid, lr->lr_msgid, 0 ); + + /* free all referrals (child requests) */ + while ( lr->lr_child ) { + ldap_free_request( ld, lr->lr_child ); + } + + if ( lr->lr_parent != NULL ) { + LDAPRequest **lrp; + + --lr->lr_parent->lr_outrefcnt; + for ( lrp = &lr->lr_parent->lr_child; + *lrp && *lrp != lr; + lrp = &(*lrp)->lr_refnext ); + + if ( *lrp == lr ) { + *lrp = lr->lr_refnext; + } + } + ldap_free_request_int( ld, lr ); +} + +/* + * call first time with *cntp = -1 + * when returns *cntp == -1, no referrals are left + * + * NOTE: may replace *refsp, or shuffle the contents + * of the original array. + */ +static int ldap_int_nextref( + LDAP *ld, + char ***refsp, + int *cntp, + void *params ) +{ + assert( refsp != NULL ); + assert( *refsp != NULL ); + assert( cntp != NULL ); + + if ( *cntp < -1 ) { + *cntp = -1; + return -1; + } + + (*cntp)++; + + if ( (*refsp)[ *cntp ] == NULL ) { + *cntp = -1; + } + + return 0; +} + +/* + * Chase v3 referrals + * + * Parameters: + * (IN) ld = LDAP connection handle + * (IN) lr = LDAP Request structure + * (IN) refs = array of pointers to referral strings that we will chase + * The array will be free'd by this function when no longer needed + * (IN) sref != 0 if following search reference + * (OUT) errstrp = Place to return a string of referrals which could not be followed + * (OUT) hadrefp = 1 if sucessfully followed referral + * + * Return value - number of referrals followed + * + * Protected by res_mutex, conn_mutex and req_mutex (try_read1msg) + */ +int +ldap_chase_v3referrals( LDAP *ld, LDAPRequest *lr, char **refs, int sref, char **errstrp, int *hadrefp ) +{ + char *unfollowed; + int unfollowedcnt = 0; + LDAPRequest *origreq; + LDAPURLDesc *srv = NULL; + BerElement *ber; + char **refarray = NULL; + LDAPConn *lc; + int rc, count, i, j, id; + LDAPreqinfo rinfo; + LDAP_NEXTREF_PROC *nextref_proc = ld->ld_nextref_proc ? ld->ld_nextref_proc : ldap_int_nextref; + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_res_mutex ); + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_req_mutex ); + Debug( LDAP_DEBUG_TRACE, "ldap_chase_v3referrals\n", 0, 0, 0 ); + + ld->ld_errno = LDAP_SUCCESS; /* optimistic */ + *hadrefp = 0; + + unfollowed = NULL; + rc = count = 0; + + /* If no referrals in array, return */ + if ( (refs == NULL) || ( (refs)[0] == NULL) ) { + rc = 0; + goto done; + } + + /* Check for hop limit exceeded */ + if ( lr->lr_parentcnt >= ld->ld_refhoplimit ) { + Debug( LDAP_DEBUG_ANY, + "more than %d referral hops (dropping)\n", ld->ld_refhoplimit, 0, 0 ); + ld->ld_errno = LDAP_REFERRAL_LIMIT_EXCEEDED; + rc = -1; + goto done; + } + + /* find original request */ + for ( origreq = lr; + origreq->lr_parent != NULL; + origreq = origreq->lr_parent ) + { + /* empty */ ; + } + + refarray = refs; + refs = NULL; + + /* parse out & follow referrals */ + /* NOTE: if nextref_proc == ldap_int_nextref, params is ignored */ + i = -1; + for ( nextref_proc( ld, &refarray, &i, ld->ld_nextref_params ); + i != -1; + nextref_proc( ld, &refarray, &i, ld->ld_nextref_params ) ) + { + + /* Parse the referral URL */ + rc = ldap_url_parse_ext( refarray[i], &srv, LDAP_PVT_URL_PARSE_NOEMPTY_DN ); + if ( rc != LDAP_URL_SUCCESS ) { + /* ldap_url_parse_ext() returns LDAP_URL_* errors + * which do not map on API errors */ + ld->ld_errno = LDAP_PARAM_ERROR; + rc = -1; + goto done; + } + + if( srv->lud_crit_exts ) { + int ok = 0; +#ifdef HAVE_TLS + /* If StartTLS is the only critical ext, OK. */ + if ( find_tls_ext( srv ) == 2 && srv->lud_crit_exts == 1 ) + ok = 1; +#endif + if ( !ok ) { + /* we do not support any other extensions */ + ld->ld_errno = LDAP_NOT_SUPPORTED; + rc = -1; + goto done; + } + } + + /* check connection for re-bind in progress */ + if (( lc = find_connection( ld, srv, 1 )) != NULL ) { + /* See if we've already requested this DN with this conn */ + LDAPRequest *lp; + int looped = 0; + ber_len_t len = srv->lud_dn ? strlen( srv->lud_dn ) : 0; + for ( lp = origreq; lp; ) { + if ( lp->lr_conn == lc + && len == lp->lr_dn.bv_len + && len + && strncmp( srv->lud_dn, lp->lr_dn.bv_val, len ) == 0 ) + { + looped = 1; + break; + } + if ( lp == origreq ) { + lp = lp->lr_child; + } else { + lp = lp->lr_refnext; + } + } + if ( looped ) { + ldap_free_urllist( srv ); + srv = NULL; + ld->ld_errno = LDAP_CLIENT_LOOP; + rc = -1; + continue; + } + + if ( lc->lconn_rebind_inprogress ) { + /* We are already chasing a referral or search reference and a + * bind on that connection is in progress. We must queue + * referrals on that connection, so we don't get a request + * going out before the bind operation completes. This happens + * if two search references come in one behind the other + * for the same server with different contexts. + */ + Debug( LDAP_DEBUG_TRACE, + "ldap_chase_v3referrals: queue referral \"%s\"\n", + refarray[i], 0, 0); + if( lc->lconn_rebind_queue == NULL ) { + /* Create a referral list */ + lc->lconn_rebind_queue = + (char ***) LDAP_MALLOC( sizeof(void *) * 2); + + if( lc->lconn_rebind_queue == NULL) { + ld->ld_errno = LDAP_NO_MEMORY; + rc = -1; + goto done; + } + + lc->lconn_rebind_queue[0] = refarray; + lc->lconn_rebind_queue[1] = NULL; + refarray = NULL; + + } else { + /* Count how many referral arrays we already have */ + for( j = 0; lc->lconn_rebind_queue[j] != NULL; j++) { + /* empty */; + } + + /* Add the new referral to the list */ + lc->lconn_rebind_queue = (char ***) LDAP_REALLOC( + lc->lconn_rebind_queue, sizeof(void *) * (j + 2)); + + if( lc->lconn_rebind_queue == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + rc = -1; + goto done; + } + lc->lconn_rebind_queue[j] = refarray; + lc->lconn_rebind_queue[j+1] = NULL; + refarray = NULL; + } + + /* We have queued the referral/reference, now just return */ + rc = 0; + *hadrefp = 1; + count = 1; /* Pretend we already followed referral */ + goto done; + } + } + /* Re-encode the request with the new starting point of the search. + * Note: In the future we also need to replace the filter if one + * was provided with the search reference + */ + + /* For references we don't want old dn if new dn empty */ + if ( sref && srv->lud_dn == NULL ) { + srv->lud_dn = LDAP_STRDUP( "" ); + } + + LDAP_NEXT_MSGID( ld, id ); + ber = re_encode_request( ld, origreq->lr_ber, id, + sref, srv, &rinfo.ri_request ); + + if( ber == NULL ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + rc = -1; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + "ldap_chase_v3referral: msgid %d, url \"%s\"\n", + lr->lr_msgid, refarray[i], 0); + + /* Send the new request to the server - may require a bind */ + rinfo.ri_msgid = origreq->lr_origid; + rinfo.ri_url = refarray[i]; + rc = ldap_send_server_request( ld, ber, id, + origreq, &srv, NULL, &rinfo, 0, 1 ); + if ( rc < 0 ) { + /* Failure, try next referral in the list */ + Debug( LDAP_DEBUG_ANY, "Unable to chase referral \"%s\" (%d: %s)\n", + refarray[i], ld->ld_errno, ldap_err2string( ld->ld_errno ) ); + unfollowedcnt += ldap_append_referral( ld, &unfollowed, refarray[i] ); + ldap_free_urllist( srv ); + srv = NULL; + ld->ld_errno = LDAP_REFERRAL; + } else { + /* Success, no need to try this referral list further */ + rc = 0; + ++count; + *hadrefp = 1; + + /* check if there is a queue of referrals that came in during bind */ + if ( lc == NULL) { + lc = find_connection( ld, srv, 1 ); + if ( lc == NULL ) { + ld->ld_errno = LDAP_OPERATIONS_ERROR; + rc = -1; + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + goto done; + } + } + + if ( lc->lconn_rebind_queue != NULL ) { + /* Release resources of previous list */ + LDAP_VFREE( refarray ); + refarray = NULL; + ldap_free_urllist( srv ); + srv = NULL; + + /* Pull entries off end of queue so list always null terminated */ + for( j = 0; lc->lconn_rebind_queue[j] != NULL; j++ ) + ; + refarray = lc->lconn_rebind_queue[j - 1]; + lc->lconn_rebind_queue[j-1] = NULL; + /* we pulled off last entry from queue, free queue */ + if ( j == 1 ) { + LDAP_FREE( lc->lconn_rebind_queue ); + lc->lconn_rebind_queue = NULL; + } + /* restart the loop the with new referral list */ + i = -1; + continue; + } + break; /* referral followed, break out of for loop */ + } + } /* end for loop */ +done: + LDAP_VFREE( refarray ); + ldap_free_urllist( srv ); + LDAP_FREE( *errstrp ); + + if( rc == 0 ) { + *errstrp = NULL; + LDAP_FREE( unfollowed ); + return count; + } else { + *errstrp = unfollowed; + return rc; + } +} + +/* + * XXX merging of errors in this routine needs to be improved + * Protected by res_mutex, conn_mutex and req_mutex (try_read1msg) + */ +int +ldap_chase_referrals( LDAP *ld, + LDAPRequest *lr, + char **errstrp, + int sref, + int *hadrefp ) +{ + int rc, count, id; + unsigned len; + char *p, *ref, *unfollowed; + LDAPRequest *origreq; + LDAPURLDesc *srv; + BerElement *ber; + LDAPreqinfo rinfo; + LDAPConn *lc; + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_res_mutex ); + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_req_mutex ); + Debug( LDAP_DEBUG_TRACE, "ldap_chase_referrals\n", 0, 0, 0 ); + + ld->ld_errno = LDAP_SUCCESS; /* optimistic */ + *hadrefp = 0; + + if ( *errstrp == NULL ) { + return( 0 ); + } + + len = strlen( *errstrp ); + for ( p = *errstrp; len >= LDAP_REF_STR_LEN; ++p, --len ) { + if ( strncasecmp( p, LDAP_REF_STR, LDAP_REF_STR_LEN ) == 0 ) { + *p = '\0'; + p += LDAP_REF_STR_LEN; + break; + } + } + + if ( len < LDAP_REF_STR_LEN ) { + return( 0 ); + } + + if ( lr->lr_parentcnt >= ld->ld_refhoplimit ) { + Debug( LDAP_DEBUG_ANY, + "more than %d referral hops (dropping)\n", + ld->ld_refhoplimit, 0, 0 ); + /* XXX report as error in ld->ld_errno? */ + return( 0 ); + } + + /* find original request */ + for ( origreq = lr; origreq->lr_parent != NULL; + origreq = origreq->lr_parent ) { + /* empty */; + } + + unfollowed = NULL; + rc = count = 0; + + /* parse out & follow referrals */ + for ( ref = p; rc == 0 && ref != NULL; ref = p ) { + p = strchr( ref, '\n' ); + if ( p != NULL ) { + *p++ = '\0'; + } + + rc = ldap_url_parse_ext( ref, &srv, LDAP_PVT_URL_PARSE_NOEMPTY_DN ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "ignoring %s referral <%s>\n", + ref, rc == LDAP_URL_ERR_BADSCHEME ? "unknown" : "incorrect", 0 ); + rc = ldap_append_referral( ld, &unfollowed, ref ); + *hadrefp = 1; + continue; + } + + Debug( LDAP_DEBUG_TRACE, + "chasing LDAP referral: <%s>\n", ref, 0, 0 ); + + *hadrefp = 1; + + /* See if we've already been here */ + if (( lc = find_connection( ld, srv, 1 )) != NULL ) { + LDAPRequest *lp; + int looped = 0; + ber_len_t len = srv->lud_dn ? strlen( srv->lud_dn ) : 0; + for ( lp = lr; lp; lp = lp->lr_parent ) { + if ( lp->lr_conn == lc + && len == lp->lr_dn.bv_len ) + { + if ( len && strncmp( srv->lud_dn, lp->lr_dn.bv_val, len ) ) + continue; + looped = 1; + break; + } + } + if ( looped ) { + ldap_free_urllist( srv ); + ld->ld_errno = LDAP_CLIENT_LOOP; + rc = -1; + continue; + } + } + + LDAP_NEXT_MSGID( ld, id ); + ber = re_encode_request( ld, origreq->lr_ber, + id, sref, srv, &rinfo.ri_request ); + + if ( ber == NULL ) { + ldap_free_urllist( srv ); + return -1 ; + } + + /* copy the complete referral for rebind process */ + rinfo.ri_url = LDAP_STRDUP( ref ); + + rinfo.ri_msgid = origreq->lr_origid; + + rc = ldap_send_server_request( ld, ber, id, + lr, &srv, NULL, &rinfo, 0, 1 ); + LDAP_FREE( rinfo.ri_url ); + + if( rc >= 0 ) { + ++count; + } else { + Debug( LDAP_DEBUG_ANY, + "Unable to chase referral \"%s\" (%d: %s)\n", + ref, ld->ld_errno, ldap_err2string( ld->ld_errno ) ); + rc = ldap_append_referral( ld, &unfollowed, ref ); + } + + ldap_free_urllist(srv); + } + + LDAP_FREE( *errstrp ); + *errstrp = unfollowed; + + return(( rc == 0 ) ? count : rc ); +} + + +int +ldap_append_referral( LDAP *ld, char **referralsp, char *s ) +{ + int first; + + if ( *referralsp == NULL ) { + first = 1; + *referralsp = (char *)LDAP_MALLOC( strlen( s ) + LDAP_REF_STR_LEN + + 1 ); + } else { + first = 0; + *referralsp = (char *)LDAP_REALLOC( *referralsp, + strlen( *referralsp ) + strlen( s ) + 2 ); + } + + if ( *referralsp == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return( -1 ); + } + + if ( first ) { + strcpy( *referralsp, LDAP_REF_STR ); + } else { + strcat( *referralsp, "\n" ); + } + strcat( *referralsp, s ); + + return( 0 ); +} + + + +static BerElement * +re_encode_request( LDAP *ld, + BerElement *origber, + ber_int_t msgid, + int sref, + LDAPURLDesc *srv, + int *type ) +{ + /* + * XXX this routine knows way too much about how the lber library works! + */ + ber_int_t along; + ber_tag_t tag; + ber_tag_t rtag; + ber_int_t ver; + ber_int_t scope; + int rc; + BerElement tmpber, *ber; + struct berval dn; + + Debug( LDAP_DEBUG_TRACE, + "re_encode_request: new msgid %ld, new dn <%s>\n", + (long) msgid, + ( srv == NULL || srv->lud_dn == NULL) ? "NONE" : srv->lud_dn, 0 ); + + tmpber = *origber; + + /* + * all LDAP requests are sequences that start with a message id. + * For all except delete, this is followed by a sequence that is + * tagged with the operation code. For delete, the provided DN + * is not wrapped by a sequence. + */ + rtag = ber_scanf( &tmpber, "{it", /*}*/ &along, &tag ); + + if ( rtag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return( NULL ); + } + + assert( tag != 0); + if ( tag == LDAP_REQ_BIND ) { + /* bind requests have a version number before the DN & other stuff */ + rtag = ber_scanf( &tmpber, "{im" /*}*/, &ver, &dn ); + + } else if ( tag == LDAP_REQ_DELETE ) { + /* delete requests don't have a DN wrapping sequence */ + rtag = ber_scanf( &tmpber, "m", &dn ); + + } else if ( tag == LDAP_REQ_SEARCH ) { + /* search requests need to be re-scope-ed */ + rtag = ber_scanf( &tmpber, "{me" /*"}"*/, &dn, &scope ); + + if( srv->lud_scope != LDAP_SCOPE_DEFAULT ) { + /* use the scope provided in reference */ + scope = srv->lud_scope; + + } else if ( sref ) { + /* use scope implied by previous operation + * base -> base + * one -> base + * subtree -> subtree + * subordinate -> subtree + */ + switch( scope ) { + default: + case LDAP_SCOPE_BASE: + case LDAP_SCOPE_ONELEVEL: + scope = LDAP_SCOPE_BASE; + break; + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + scope = LDAP_SCOPE_SUBTREE; + break; + } + } + + } else { + rtag = ber_scanf( &tmpber, "{m" /*}*/, &dn ); + } + + if( rtag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + return NULL; + } + + /* restore character zero'd out by ber_scanf*/ + dn.bv_val[dn.bv_len] = tmpber.ber_tag; + + if (( ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return NULL; + } + + if ( srv->lud_dn ) { + ber_str2bv( srv->lud_dn, 0, 0, &dn ); + } + + if ( tag == LDAP_REQ_BIND ) { + rc = ber_printf( ber, "{it{iO" /*}}*/, msgid, tag, ver, &dn ); + } else if ( tag == LDAP_REQ_DELETE ) { + rc = ber_printf( ber, "{itON}", msgid, tag, &dn ); + } else if ( tag == LDAP_REQ_SEARCH ) { + rc = ber_printf( ber, "{it{Oe" /*}}*/, msgid, tag, &dn, scope ); + } else { + rc = ber_printf( ber, "{it{O" /*}}*/, msgid, tag, &dn ); + } + + if ( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return NULL; + } + + if ( tag != LDAP_REQ_DELETE && ( + ber_write(ber, tmpber.ber_ptr, ( tmpber.ber_end - tmpber.ber_ptr ), 0) + != ( tmpber.ber_end - tmpber.ber_ptr ) || + ber_printf( ber, /*{{*/ "N}N}" ) == -1 ) ) + { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return NULL; + } + +#ifdef LDAP_DEBUG + if ( ldap_debug & LDAP_DEBUG_PACKETS ) { + Debug( LDAP_DEBUG_ANY, "re_encode_request new request is:\n", + 0, 0, 0 ); + ber_log_dump( LDAP_DEBUG_BER, ldap_debug, ber, 0 ); + } +#endif /* LDAP_DEBUG */ + + *type = tag; /* return request type */ + return ber; +} + + +/* protected by req_mutex */ +LDAPRequest * +ldap_find_request_by_msgid( LDAP *ld, ber_int_t msgid ) +{ + LDAPRequest *lr; + + for ( lr = ld->ld_requests; lr != NULL; lr = lr->lr_next ) { + if ( lr->lr_status == LDAP_REQST_COMPLETED ) { + continue; /* Skip completed requests */ + } + if ( msgid == lr->lr_msgid ) { + lr->lr_refcnt++; + break; + } + } + + return( lr ); +} + +/* protected by req_mutex */ +void +ldap_return_request( LDAP *ld, LDAPRequest *lrx, int freeit ) +{ + LDAPRequest *lr; + + for ( lr = ld->ld_requests; lr != NULL; lr = lr->lr_next ) { + if ( lr == lrx ) { + if ( lr->lr_refcnt > 0 ) { + lr->lr_refcnt--; + + } else if ( lr->lr_refcnt < 0 ) { + lr->lr_refcnt++; + if ( lr->lr_refcnt == 0 ) { + lr = NULL; + } + } + break; + } + } + if ( lr == NULL ) { + ldap_free_request_int( ld, lrx ); + + } else if ( freeit ) { + ldap_free_request( ld, lrx ); + } +} diff --git a/libraries/libldap/result.c b/libraries/libldap/result.c new file mode 100644 index 0000000..9966ec8 --- /dev/null +++ b/libraries/libldap/result.c @@ -0,0 +1,1380 @@ +/* result.c - wait for an ldap result */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ +/* This notice applies to changes, created by or for Novell, Inc., + * to preexisting works for which notices appear elsewhere in this file. + * + * Copyright (C) 1999, 2000 Novell, Inc. All Rights Reserved. + * + * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES. + * USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO VERSION + * 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS AVAILABLE AT + * HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" IN THE + * TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION OF THIS + * WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP PUBLIC + * LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT THE + * PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. + *--- + * Modification to OpenLDAP source by Novell, Inc. + * April 2000 sfs Add code to process V3 referrals and search results + *--- + * Note: A verbatim copy of version 2.0.1 of the OpenLDAP Public License + * can be found in the file "build/LICENSE-2.0.1" in this distribution + * of OpenLDAP Software. + */ + +/* + * LDAPv3 (RFC 4511) + * LDAPResult ::= SEQUENCE { + * resultCode ENUMERATED { ... }, + * matchedDN LDAPDN, + * diagnosticMessage LDAPString, + * referral [3] Referral OPTIONAL + * } + * Referral ::= SEQUENCE OF LDAPURL (one or more) + * LDAPURL ::= LDAPString (limited to URL chars) + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include "ldap-int.h" +#include "ldap_log.h" +#include "lutil.h" + +static int ldap_abandoned LDAP_P(( LDAP *ld, ber_int_t msgid )); +static int ldap_mark_abandoned LDAP_P(( LDAP *ld, ber_int_t msgid )); +static int wait4msg LDAP_P(( LDAP *ld, ber_int_t msgid, int all, struct timeval *timeout, + LDAPMessage **result )); +static ber_tag_t try_read1msg LDAP_P(( LDAP *ld, ber_int_t msgid, + int all, LDAPConn *lc, LDAPMessage **result )); +static ber_tag_t build_result_ber LDAP_P(( LDAP *ld, BerElement **bp, LDAPRequest *lr )); +static void merge_error_info LDAP_P(( LDAP *ld, LDAPRequest *parentr, LDAPRequest *lr )); +static LDAPMessage * chkResponseList LDAP_P(( LDAP *ld, int msgid, int all)); + +#define LDAP_MSG_X_KEEP_LOOKING (-2) + + +/* + * ldap_result - wait for an ldap result response to a message from the + * ldap server. If msgid is LDAP_RES_ANY (-1), any message will be + * accepted. If msgid is LDAP_RES_UNSOLICITED (0), any unsolicited + * message is accepted. Otherwise ldap_result will wait for a response + * with msgid. If all is LDAP_MSG_ONE (0) the first message with id + * msgid will be accepted, otherwise, ldap_result will wait for all + * responses with id msgid and then return a pointer to the entire list + * of messages. In general, this is only useful for search responses, + * which can be of three message types (zero or more entries, zero or + * search references, followed by an ldap result). An extension to + * LDAPv3 allows partial extended responses to be returned in response + * to any request. The type of the first message received is returned. + * When waiting, any messages that have been abandoned/discarded are + * discarded. + * + * Example: + * ldap_result( s, msgid, all, timeout, result ) + */ +int +ldap_result( + LDAP *ld, + int msgid, + int all, + struct timeval *timeout, + LDAPMessage **result ) +{ + int rc; + + assert( ld != NULL ); + assert( result != NULL ); + + Debug( LDAP_DEBUG_TRACE, "ldap_result ld %p msgid %d\n", (void *)ld, msgid, 0 ); + + if (ld->ld_errno == LDAP_LOCAL_ERROR || ld->ld_errno == LDAP_SERVER_DOWN) + return -1; + + LDAP_MUTEX_LOCK( &ld->ld_res_mutex ); + rc = wait4msg( ld, msgid, all, timeout, result ); + LDAP_MUTEX_UNLOCK( &ld->ld_res_mutex ); + + return rc; +} + +/* protected by res_mutex */ +static LDAPMessage * +chkResponseList( + LDAP *ld, + int msgid, + int all) +{ + LDAPMessage *lm, **lastlm, *nextlm; + int cnt = 0; + + /* + * Look through the list of responses we have received on + * this association and see if the response we're interested in + * is there. If it is, return it. If not, call wait4msg() to + * wait until it arrives or timeout occurs. + */ + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_res_mutex ); + + Debug( LDAP_DEBUG_TRACE, + "ldap_chkResponseList ld %p msgid %d all %d\n", + (void *)ld, msgid, all ); + + lastlm = &ld->ld_responses; + for ( lm = ld->ld_responses; lm != NULL; lm = nextlm ) { + nextlm = lm->lm_next; + ++cnt; + + if ( ldap_abandoned( ld, lm->lm_msgid ) ) { + Debug( LDAP_DEBUG_ANY, + "response list msg abandoned, " + "msgid %d message type %s\n", + lm->lm_msgid, ldap_int_msgtype2str( lm->lm_msgtype ), 0 ); + + switch ( lm->lm_msgtype ) { + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + case LDAP_RES_INTERMEDIATE: + break; + + default: + /* there's no need to keep the id + * in the abandoned list any longer */ + ldap_mark_abandoned( ld, lm->lm_msgid ); + break; + } + + /* Remove this entry from list */ + *lastlm = nextlm; + + ldap_msgfree( lm ); + + continue; + } + + if ( msgid == LDAP_RES_ANY || lm->lm_msgid == msgid ) { + LDAPMessage *tmp; + + if ( all == LDAP_MSG_ONE || + all == LDAP_MSG_RECEIVED || + msgid == LDAP_RES_UNSOLICITED ) + { + break; + } + + tmp = lm->lm_chain_tail; + if ( tmp->lm_msgtype == LDAP_RES_SEARCH_ENTRY || + tmp->lm_msgtype == LDAP_RES_SEARCH_REFERENCE || + tmp->lm_msgtype == LDAP_RES_INTERMEDIATE ) + { + tmp = NULL; + } + + if ( tmp == NULL ) { + lm = NULL; + } + + break; + } + lastlm = &lm->lm_next; + } + + if ( lm != NULL ) { + /* Found an entry, remove it from the list */ + if ( all == LDAP_MSG_ONE && lm->lm_chain != NULL ) { + *lastlm = lm->lm_chain; + lm->lm_chain->lm_next = lm->lm_next; + lm->lm_chain->lm_chain_tail = ( lm->lm_chain_tail != lm ) ? lm->lm_chain_tail : lm->lm_chain; + lm->lm_chain = NULL; + lm->lm_chain_tail = NULL; + } else { + *lastlm = lm->lm_next; + } + lm->lm_next = NULL; + } + +#ifdef LDAP_DEBUG + if ( lm == NULL) { + Debug( LDAP_DEBUG_TRACE, + "ldap_chkResponseList returns ld %p NULL\n", (void *)ld, 0, 0); + } else { + Debug( LDAP_DEBUG_TRACE, + "ldap_chkResponseList returns ld %p msgid %d, type 0x%02lx\n", + (void *)ld, lm->lm_msgid, (unsigned long)lm->lm_msgtype ); + } +#endif + + return lm; +} + +/* protected by res_mutex */ +static int +wait4msg( + LDAP *ld, + ber_int_t msgid, + int all, + struct timeval *timeout, + LDAPMessage **result ) +{ + int rc; + struct timeval tv = { 0 }, + tv0 = { 0 }, + start_time_tv = { 0 }, + *tvp = NULL; + LDAPConn *lc; + + assert( ld != NULL ); + assert( result != NULL ); + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_res_mutex ); + + if ( timeout == NULL && ld->ld_options.ldo_tm_api.tv_sec >= 0 ) { + tv = ld->ld_options.ldo_tm_api; + timeout = &tv; + } + +#ifdef LDAP_DEBUG + if ( timeout == NULL ) { + Debug( LDAP_DEBUG_TRACE, "wait4msg ld %p msgid %d (infinite timeout)\n", + (void *)ld, msgid, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "wait4msg ld %p msgid %d (timeout %ld usec)\n", + (void *)ld, msgid, (long)timeout->tv_sec * 1000000 + timeout->tv_usec ); + } +#endif /* LDAP_DEBUG */ + + if ( timeout != NULL && timeout->tv_sec != -1 ) { + tv0 = *timeout; + tv = *timeout; + tvp = &tv; +#ifdef HAVE_GETTIMEOFDAY + gettimeofday( &start_time_tv, NULL ); +#else /* ! HAVE_GETTIMEOFDAY */ + start_time_tv.tv_sec = time( NULL ); + start_time_tv.tv_usec = 0; +#endif /* ! HAVE_GETTIMEOFDAY */ + } + + rc = LDAP_MSG_X_KEEP_LOOKING; + while ( rc == LDAP_MSG_X_KEEP_LOOKING ) { +#ifdef LDAP_DEBUG + if ( ldap_debug & LDAP_DEBUG_TRACE ) { + Debug( LDAP_DEBUG_TRACE, "wait4msg continue ld %p msgid %d all %d\n", + (void *)ld, msgid, all ); + ldap_dump_connection( ld, ld->ld_conns, 1 ); + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + ldap_dump_requests_and_responses( ld ); + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + } +#endif /* LDAP_DEBUG */ + + if ( ( *result = chkResponseList( ld, msgid, all ) ) != NULL ) { + rc = (*result)->lm_msgtype; + + } else { + int lc_ready = 0; + + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + for ( lc = ld->ld_conns; lc != NULL; lc = lc->lconn_next ) { + if ( ber_sockbuf_ctrl( lc->lconn_sb, + LBER_SB_OPT_DATA_READY, NULL ) ) + { + lc_ready = 2; /* ready at ber level, not socket level */ + break; + } + } + + if ( !lc_ready ) { + int err; + rc = ldap_int_select( ld, tvp ); + if ( rc == -1 ) { + err = sock_errno(); +#ifdef LDAP_DEBUG + Debug( LDAP_DEBUG_TRACE, + "ldap_int_select returned -1: errno %d\n", + err, 0, 0 ); +#endif + } + + if ( rc == 0 || ( rc == -1 && ( + !LDAP_BOOL_GET(&ld->ld_options, LDAP_BOOL_RESTART) + || err != EINTR ) ) ) + { + ld->ld_errno = (rc == -1 ? LDAP_SERVER_DOWN : + LDAP_TIMEOUT); + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + return( rc ); + } + + if ( rc == -1 ) { + rc = LDAP_MSG_X_KEEP_LOOKING; /* select interrupted: loop */ + + } else { + lc_ready = 1; + } + } + if ( lc_ready ) { + LDAPConn *lnext; + int serviced = 0; + rc = LDAP_MSG_X_KEEP_LOOKING; + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + if ( ld->ld_requests && + ld->ld_requests->lr_status == LDAP_REQST_WRITING && + ldap_is_write_ready( ld, + ld->ld_requests->lr_conn->lconn_sb ) ) + { + serviced = 1; + ldap_int_flush_request( ld, ld->ld_requests ); + } + for ( lc = ld->ld_conns; + rc == LDAP_MSG_X_KEEP_LOOKING && lc != NULL; + lc = lnext ) + { + if ( lc->lconn_status == LDAP_CONNST_CONNECTED && + ldap_is_read_ready( ld, lc->lconn_sb ) ) + { + serviced = 1; + /* Don't let it get freed out from under us */ + ++lc->lconn_refcnt; + rc = try_read1msg( ld, msgid, all, lc, result ); + lnext = lc->lconn_next; + + /* Only take locks if we're really freeing */ + if ( lc->lconn_refcnt <= 1 ) { + ldap_free_connection( ld, lc, 0, 1 ); + } else { + --lc->lconn_refcnt; + } + } else { + lnext = lc->lconn_next; + } + } + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + /* Quit looping if no one handled any socket events */ + if (!serviced && lc_ready == 1) + rc = -1; + } + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + } + + if ( rc == LDAP_MSG_X_KEEP_LOOKING && tvp != NULL ) { + struct timeval curr_time_tv = { 0 }, + delta_time_tv = { 0 }; + +#ifdef HAVE_GETTIMEOFDAY + gettimeofday( &curr_time_tv, NULL ); +#else /* ! HAVE_GETTIMEOFDAY */ + curr_time_tv.tv_sec = time( NULL ); + curr_time_tv.tv_usec = 0; +#endif /* ! HAVE_GETTIMEOFDAY */ + + /* delta_time = tmp_time - start_time */ + delta_time_tv.tv_sec = curr_time_tv.tv_sec - start_time_tv.tv_sec; + delta_time_tv.tv_usec = curr_time_tv.tv_usec - start_time_tv.tv_usec; + if ( delta_time_tv.tv_usec < 0 ) { + delta_time_tv.tv_sec--; + delta_time_tv.tv_usec += 1000000; + } + + /* tv0 < delta_time ? */ + if ( ( tv0.tv_sec < delta_time_tv.tv_sec ) || + ( ( tv0.tv_sec == delta_time_tv.tv_sec ) && ( tv0.tv_usec < delta_time_tv.tv_usec ) ) ) + { + rc = 0; /* timed out */ + ld->ld_errno = LDAP_TIMEOUT; + break; + } + + /* tv0 -= delta_time */ + tv0.tv_sec -= delta_time_tv.tv_sec; + tv0.tv_usec -= delta_time_tv.tv_usec; + if ( tv0.tv_usec < 0 ) { + tv0.tv_sec--; + tv0.tv_usec += 1000000; + } + + tv.tv_sec = tv0.tv_sec; + tv.tv_usec = tv0.tv_usec; + + Debug( LDAP_DEBUG_TRACE, "wait4msg ld %p %ld s %ld us to go\n", + (void *)ld, (long) tv.tv_sec, (long) tv.tv_usec ); + + start_time_tv.tv_sec = curr_time_tv.tv_sec; + start_time_tv.tv_usec = curr_time_tv.tv_usec; + } + } + + return( rc ); +} + + +/* protected by res_mutex, conn_mutex and req_mutex */ +static ber_tag_t +try_read1msg( + LDAP *ld, + ber_int_t msgid, + int all, + LDAPConn *lc, + LDAPMessage **result ) +{ + BerElement *ber; + LDAPMessage *newmsg, *l, *prev; + ber_int_t id; + ber_tag_t tag; + ber_len_t len; + int foundit = 0; + LDAPRequest *lr, *tmplr, dummy_lr = { 0 }; + BerElement tmpber; + int rc, refer_cnt, hadref, simple_request, err; + ber_int_t lderr; + +#ifdef LDAP_CONNECTIONLESS + LDAPMessage *tmp = NULL, *chain_head = NULL; + int moremsgs = 0, isv2 = 0; +#endif + + assert( ld != NULL ); + assert( lc != NULL ); + + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_res_mutex ); + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_conn_mutex ); + LDAP_ASSERT_MUTEX_OWNER( &ld->ld_req_mutex ); + + Debug( LDAP_DEBUG_TRACE, "read1msg: ld %p msgid %d all %d\n", + (void *)ld, msgid, all ); + +retry: + if ( lc->lconn_ber == NULL ) { + lc->lconn_ber = ldap_alloc_ber_with_options( ld ); + + if ( lc->lconn_ber == NULL ) { + return -1; + } + } + + ber = lc->lconn_ber; + assert( LBER_VALID (ber) ); + + /* get the next message */ + sock_errset(0); +#ifdef LDAP_CONNECTIONLESS + if ( LDAP_IS_UDP(ld) ) { + struct sockaddr_storage from; + ber_int_sb_read( lc->lconn_sb, &from, sizeof(struct sockaddr_storage) ); + if ( ld->ld_options.ldo_version == LDAP_VERSION2 ) isv2 = 1; + } +nextresp3: +#endif + tag = ber_get_next( lc->lconn_sb, &len, ber ); + switch ( tag ) { + case LDAP_TAG_MESSAGE: + /* + * We read a complete message. + * The connection should no longer need this ber. + */ + lc->lconn_ber = NULL; + break; + + case LBER_DEFAULT: + err = sock_errno(); +#ifdef LDAP_DEBUG + Debug( LDAP_DEBUG_CONNS, + "ber_get_next failed.\n", 0, 0, 0 ); +#endif + if ( err == EWOULDBLOCK ) return LDAP_MSG_X_KEEP_LOOKING; + if ( err == EAGAIN ) return LDAP_MSG_X_KEEP_LOOKING; + ld->ld_errno = LDAP_SERVER_DOWN; + --lc->lconn_refcnt; + lc->lconn_status = 0; + return -1; + + default: + ld->ld_errno = LDAP_LOCAL_ERROR; + return -1; + } + + /* message id */ + if ( ber_get_int( ber, &id ) == LBER_ERROR ) { + ber_free( ber, 1 ); + ld->ld_errno = LDAP_DECODING_ERROR; + return( -1 ); + } + + /* id == 0 iff unsolicited notification message (RFC 4511) */ + + /* id < 0 is invalid, just toss it. FIXME: should we disconnect? */ + if ( id < 0 ) { + goto retry_ber; + } + + /* if it's been abandoned, toss it */ + if ( id > 0 ) { + if ( ldap_abandoned( ld, id ) ) { + /* the message type */ + tag = ber_peek_tag( ber, &len ); + switch ( tag ) { + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + case LDAP_RES_INTERMEDIATE: + case LBER_ERROR: + break; + + default: + /* there's no need to keep the id + * in the abandoned list any longer */ + ldap_mark_abandoned( ld, id ); + break; + } + + Debug( LDAP_DEBUG_ANY, + "abandoned/discarded ld %p msgid %d message type %s\n", + (void *)ld, id, ldap_int_msgtype2str( tag ) ); + +retry_ber: + ber_free( ber, 1 ); + if ( ber_sockbuf_ctrl( lc->lconn_sb, LBER_SB_OPT_DATA_READY, NULL ) ) { + goto retry; + } + return( LDAP_MSG_X_KEEP_LOOKING ); /* continue looking */ + } + + lr = ldap_find_request_by_msgid( ld, id ); + if ( lr == NULL ) { + const char *msg = "unknown"; + + /* the message type */ + tag = ber_peek_tag( ber, &len ); + switch ( tag ) { + case LBER_ERROR: + break; + + default: + msg = ldap_int_msgtype2str( tag ); + break; + } + + Debug( LDAP_DEBUG_ANY, + "no request for response on ld %p msgid %d message type %s (tossing)\n", + (void *)ld, id, msg ); + + goto retry_ber; + } + +#ifdef LDAP_CONNECTIONLESS + if ( LDAP_IS_UDP(ld) && isv2 ) { + ber_scanf(ber, "x{"); + } +nextresp2: + ; +#endif + } + + /* the message type */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 1 ); + return( -1 ); + } + + Debug( LDAP_DEBUG_TRACE, + "read1msg: ld %p msgid %d message type %s\n", + (void *)ld, id, ldap_int_msgtype2str( tag ) ); + + if ( id == 0 ) { + /* unsolicited notification message (RFC 4511) */ + if ( tag != LDAP_RES_EXTENDED ) { + /* toss it */ + goto retry_ber; + + /* strictly speaking, it's an error; from RFC 4511: + +4.4. Unsolicited Notification + + An unsolicited notification is an LDAPMessage sent from the server to + the client that is not in response to any LDAPMessage received by the + server. It is used to signal an extraordinary condition in the + server or in the LDAP session between the client and the server. The + notification is of an advisory nature, and the server will not expect + any response to be returned from the client. + + The unsolicited notification is structured as an LDAPMessage in which + the messageID is zero and protocolOp is set to the extendedResp + choice using the ExtendedResponse type (See Section 4.12). The + responseName field of the ExtendedResponse always contains an LDAPOID + that is unique for this notification. + + * however, since unsolicited responses + * are of advisory nature, better + * toss it, right now + */ + +#if 0 + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 1 ); + return( -1 ); +#endif + } + + lr = &dummy_lr; + } + + id = lr->lr_origid; + refer_cnt = 0; + hadref = simple_request = 0; + rc = LDAP_MSG_X_KEEP_LOOKING; /* default is to keep looking (no response found) */ + lr->lr_res_msgtype = tag; + + /* + * Check for V3 search reference + */ + if ( tag == LDAP_RES_SEARCH_REFERENCE ) { + if ( ld->ld_version > LDAP_VERSION2 ) { + /* This is a V3 search reference */ + if ( LDAP_BOOL_GET(&ld->ld_options, LDAP_BOOL_REFERRALS) || + lr->lr_parent != NULL ) + { + char **refs = NULL; + tmpber = *ber; + + /* Get the referral list */ + if ( ber_scanf( &tmpber, "{v}", &refs ) == LBER_ERROR ) { + rc = LDAP_DECODING_ERROR; + + } else { + /* Note: refs array is freed by ldap_chase_v3referrals */ + refer_cnt = ldap_chase_v3referrals( ld, lr, refs, + 1, &lr->lr_res_error, &hadref ); + if ( refer_cnt > 0 ) { + /* successfully chased reference */ + /* If haven't got end search, set chasing referrals */ + if ( lr->lr_status != LDAP_REQST_COMPLETED ) { + lr->lr_status = LDAP_REQST_CHASINGREFS; + Debug( LDAP_DEBUG_TRACE, + "read1msg: search ref chased, " + "mark request chasing refs, " + "id = %d\n", + lr->lr_msgid, 0, 0 ); + } + } + } + } + } + + } else if ( tag != LDAP_RES_SEARCH_ENTRY && tag != LDAP_RES_INTERMEDIATE ) { + /* All results that just return a status, i.e. don't return data + * go through the following code. This code also chases V2 referrals + * and checks if all referrals have been chased. + */ + char *lr_res_error = NULL; + + tmpber = *ber; /* struct copy */ + if ( ber_scanf( &tmpber, "{eAA", &lderr, + &lr->lr_res_matched, &lr_res_error ) + != LBER_ERROR ) + { + if ( lr_res_error != NULL ) { + if ( lr->lr_res_error != NULL ) { + (void)ldap_append_referral( ld, &lr->lr_res_error, lr_res_error ); + LDAP_FREE( (char *)lr_res_error ); + + } else { + lr->lr_res_error = lr_res_error; + } + lr_res_error = NULL; + } + + /* Do we need to check for referrals? */ + if ( tag != LDAP_RES_BIND && + ( LDAP_BOOL_GET(&ld->ld_options, LDAP_BOOL_REFERRALS) || + lr->lr_parent != NULL )) + { + char **refs = NULL; + ber_len_t len; + + /* Check if V3 referral */ + if ( ber_peek_tag( &tmpber, &len ) == LDAP_TAG_REFERRAL ) { + if ( ld->ld_version > LDAP_VERSION2 ) { + /* Get the referral list */ + if ( ber_scanf( &tmpber, "{v}", &refs) == LBER_ERROR) { + rc = LDAP_DECODING_ERROR; + lr->lr_status = LDAP_REQST_COMPLETED; + Debug( LDAP_DEBUG_TRACE, + "read1msg: referral decode error, " + "mark request completed, ld %p msgid %d\n", + (void *)ld, lr->lr_msgid, 0 ); + + } else { + /* Chase the referral + * refs array is freed by ldap_chase_v3referrals + */ + refer_cnt = ldap_chase_v3referrals( ld, lr, refs, + 0, &lr->lr_res_error, &hadref ); + lr->lr_status = LDAP_REQST_COMPLETED; + Debug( LDAP_DEBUG_TRACE, + "read1msg: referral %s chased, " + "mark request completed, ld %p msgid %d\n", + refer_cnt > 0 ? "" : "not", + (void *)ld, lr->lr_msgid); + if ( refer_cnt < 0 ) { + refer_cnt = 0; + } + } + } + } else { + switch ( lderr ) { + case LDAP_SUCCESS: + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + break; + + default: + if ( lr->lr_res_error == NULL ) { + break; + } + + /* pedantic, should never happen */ + if ( lr->lr_res_error[ 0 ] == '\0' ) { + LDAP_FREE( lr->lr_res_error ); + lr->lr_res_error = NULL; + break; + } + + /* V2 referrals are in error string */ + refer_cnt = ldap_chase_referrals( ld, lr, + &lr->lr_res_error, -1, &hadref ); + lr->lr_status = LDAP_REQST_COMPLETED; + Debug( LDAP_DEBUG_TRACE, + "read1msg: V2 referral chased, " + "mark request completed, id = %d\n", + lr->lr_msgid, 0, 0 ); + break; + } + } + } + + /* save errno, message, and matched string */ + if ( !hadref || lr->lr_res_error == NULL ) { + lr->lr_res_errno = + lderr == LDAP_PARTIAL_RESULTS + ? LDAP_SUCCESS : lderr; + + } else if ( ld->ld_errno != LDAP_SUCCESS ) { + lr->lr_res_errno = ld->ld_errno; + + } else { + lr->lr_res_errno = LDAP_PARTIAL_RESULTS; + } + } + + /* in any case, don't leave any lr_res_error 'round */ + if ( lr_res_error ) { + LDAP_FREE( lr_res_error ); + } + + Debug( LDAP_DEBUG_TRACE, + "read1msg: ld %p %d new referrals\n", + (void *)ld, refer_cnt, 0 ); + + if ( refer_cnt != 0 ) { /* chasing referrals */ + ber_free( ber, 1 ); + ber = NULL; + if ( refer_cnt < 0 ) { + ldap_return_request( ld, lr, 0 ); + return( -1 ); /* fatal error */ + } + lr->lr_res_errno = LDAP_SUCCESS; /* sucessfully chased referral */ + if ( lr->lr_res_matched ) { + LDAP_FREE( lr->lr_res_matched ); + lr->lr_res_matched = NULL; + } + + } else { + if ( lr->lr_outrefcnt <= 0 && lr->lr_parent == NULL ) { + /* request without any referrals */ + simple_request = ( hadref ? 0 : 1 ); + + } else { + /* request with referrals or child request */ + ber_free( ber, 1 ); + ber = NULL; + } + + lr->lr_status = LDAP_REQST_COMPLETED; /* declare this request done */ + Debug( LDAP_DEBUG_TRACE, + "read1msg: mark request completed, ld %p msgid %d\n", + (void *)ld, lr->lr_msgid, 0); + tmplr = lr; + while ( lr->lr_parent != NULL ) { + merge_error_info( ld, lr->lr_parent, lr ); + + lr = lr->lr_parent; + if ( --lr->lr_outrefcnt > 0 ) { + break; /* not completely done yet */ + } + } + /* ITS#6744: Original lr was refcounted when we retrieved it, + * must release it now that we're working with the parent + */ + if ( tmplr->lr_parent ) { + ldap_return_request( ld, tmplr, 0 ); + } + + /* Check if all requests are finished, lr is now parent */ + tmplr = lr; + if ( tmplr->lr_status == LDAP_REQST_COMPLETED ) { + for ( tmplr = lr->lr_child; + tmplr != NULL; + tmplr = tmplr->lr_refnext ) + { + if ( tmplr->lr_status != LDAP_REQST_COMPLETED ) break; + } + } + + /* This is the parent request if the request has referrals */ + if ( lr->lr_outrefcnt <= 0 && + lr->lr_parent == NULL && + tmplr == NULL ) + { + id = lr->lr_msgid; + tag = lr->lr_res_msgtype; + Debug( LDAP_DEBUG_TRACE, "request done: ld %p msgid %d\n", + (void *)ld, id, 0 ); + Debug( LDAP_DEBUG_TRACE, + "res_errno: %d, res_error: <%s>, " + "res_matched: <%s>\n", + lr->lr_res_errno, + lr->lr_res_error ? lr->lr_res_error : "", + lr->lr_res_matched ? lr->lr_res_matched : "" ); + if ( !simple_request ) { + ber_free( ber, 1 ); + ber = NULL; + if ( build_result_ber( ld, &ber, lr ) + == LBER_ERROR ) + { + rc = -1; /* fatal error */ + } + } + + if ( lr != &dummy_lr ) { + ldap_return_request( ld, lr, 1 ); + } + lr = NULL; + } + + /* + * RFC 4511 unsolicited (id == 0) responses + * shouldn't necessarily end the connection + */ + if ( lc != NULL && id != 0 ) { + --lc->lconn_refcnt; + lc = NULL; + } + } + } + + if ( lr != NULL ) { + if ( lr != &dummy_lr ) { + ldap_return_request( ld, lr, 0 ); + } + lr = NULL; + } + + if ( ber == NULL ) { + return( rc ); + } + + /* try to handle unsolicited responses as appropriate */ + if ( id == 0 && msgid > LDAP_RES_UNSOLICITED ) { + int is_nod = 0; + + tag = ber_peek_tag( &tmpber, &len ); + + /* we have a res oid */ + if ( tag == LDAP_TAG_EXOP_RES_OID ) { + static struct berval bv_nod = BER_BVC( LDAP_NOTICE_OF_DISCONNECTION ); + struct berval resoid = BER_BVNULL; + + if ( ber_scanf( &tmpber, "m", &resoid ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 1 ); + return -1; + } + + assert( !BER_BVISEMPTY( &resoid ) ); + + is_nod = ber_bvcmp( &resoid, &bv_nod ) == 0; + + tag = ber_peek_tag( &tmpber, &len ); + } + +#if 0 /* don't need right now */ + /* we have res data */ + if ( tag == LDAP_TAG_EXOP_RES_VALUE ) { + struct berval resdata; + + if ( ber_scanf( &tmpber, "m", &resdata ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 0 ); + return ld->ld_errno; + } + + /* use it... */ + } +#endif + + /* handle RFC 4511 "Notice of Disconnection" locally */ + + if ( is_nod ) { + if ( tag == LDAP_TAG_EXOP_RES_VALUE ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 1 ); + return -1; + } + + /* get rid of the connection... */ + if ( lc != NULL ) { + --lc->lconn_refcnt; + } + + /* need to return -1, because otherwise + * a valid result is expected */ + ld->ld_errno = lderr; + return -1; + } + } + + /* make a new ldap message */ + newmsg = (LDAPMessage *) LDAP_CALLOC( 1, sizeof(LDAPMessage) ); + if ( newmsg == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return( -1 ); + } + newmsg->lm_msgid = (int)id; + newmsg->lm_msgtype = tag; + newmsg->lm_ber = ber; + newmsg->lm_chain_tail = newmsg; + +#ifdef LDAP_CONNECTIONLESS + /* CLDAP replies all fit in a single datagram. In LDAPv2 RFC1798 + * the responses are all a sequence wrapped in one message. In + * LDAPv3 each response is in its own message. The datagram must + * end with a SearchResult. We can't just parse each response in + * separate calls to try_read1msg because the header info is only + * present at the beginning of the datagram, not at the beginning + * of each response. So parse all the responses at once and queue + * them up, then pull off the first response to return to the + * caller when all parsing is complete. + */ + if ( LDAP_IS_UDP(ld) ) { + /* If not a result, look for more */ + if ( tag != LDAP_RES_SEARCH_RESULT ) { + int ok = 0; + moremsgs = 1; + if (isv2) { + /* LDAPv2: dup the current ber, skip past the current + * response, and see if there are any more after it. + */ + ber = ber_dup( ber ); + ber_scanf( ber, "x" ); + if ( ber_peek_tag( ber, &len ) != LBER_DEFAULT ) { + /* There's more - dup the ber buffer so they can all be + * individually freed by ldap_msgfree. + */ + struct berval bv; + ber_get_option( ber, LBER_OPT_BER_REMAINING_BYTES, &len ); + bv.bv_val = LDAP_MALLOC( len ); + if ( bv.bv_val ) { + ok = 1; + ber_read( ber, bv.bv_val, len ); + bv.bv_len = len; + ber_init2( ber, &bv, ld->ld_lberoptions ); + } + } + } else { + /* LDAPv3: Just allocate a new ber. Since this is a buffered + * datagram, if the sockbuf is readable we still have data + * to parse. + */ + ber = ldap_alloc_ber_with_options( ld ); + if ( ber_sockbuf_ctrl( lc->lconn_sb, LBER_SB_OPT_DATA_READY, NULL ) ) ok = 1; + } + /* set up response chain */ + if ( tmp == NULL ) { + newmsg->lm_next = ld->ld_responses; + ld->ld_responses = newmsg; + chain_head = newmsg; + } else { + tmp->lm_chain = newmsg; + } + chain_head->lm_chain_tail = newmsg; + tmp = newmsg; + /* "ok" means there's more to parse */ + if ( ok ) { + if ( isv2 ) { + goto nextresp2; + + } else { + goto nextresp3; + } + } else { + /* got to end of datagram without a SearchResult. Free + * our dup'd ber, but leave any buffer alone. For v2 case, + * the previous response is still using this buffer. For v3, + * the new ber has no buffer to free yet. + */ + ber_free( ber, 0 ); + return -1; + } + } else if ( moremsgs ) { + /* got search result, and we had multiple responses in 1 datagram. + * stick the result onto the end of the chain, and then pull the + * first response off the head of the chain. + */ + tmp->lm_chain = newmsg; + chain_head->lm_chain_tail = newmsg; + *result = chkResponseList( ld, msgid, all ); + ld->ld_errno = LDAP_SUCCESS; + return( (*result)->lm_msgtype ); + } + } +#endif /* LDAP_CONNECTIONLESS */ + + /* is this the one we're looking for? */ + if ( msgid == LDAP_RES_ANY || id == msgid ) { + if ( all == LDAP_MSG_ONE + || ( newmsg->lm_msgtype != LDAP_RES_SEARCH_RESULT + && newmsg->lm_msgtype != LDAP_RES_SEARCH_ENTRY + && newmsg->lm_msgtype != LDAP_RES_INTERMEDIATE + && newmsg->lm_msgtype != LDAP_RES_SEARCH_REFERENCE ) ) + { + *result = newmsg; + ld->ld_errno = LDAP_SUCCESS; + return( tag ); + + } else if ( newmsg->lm_msgtype == LDAP_RES_SEARCH_RESULT) { + foundit = 1; /* return the chain later */ + } + } + + /* + * if not, we must add it to the list of responses. if + * the msgid is already there, it must be part of an existing + * search response. + */ + + prev = NULL; + for ( l = ld->ld_responses; l != NULL; l = l->lm_next ) { + if ( l->lm_msgid == newmsg->lm_msgid ) { + break; + } + prev = l; + } + + /* not part of an existing search response */ + if ( l == NULL ) { + if ( foundit ) { + *result = newmsg; + goto exit; + } + + newmsg->lm_next = ld->ld_responses; + ld->ld_responses = newmsg; + goto exit; + } + + Debug( LDAP_DEBUG_TRACE, "adding response ld %p msgid %d type %ld:\n", + (void *)ld, newmsg->lm_msgid, (long) newmsg->lm_msgtype ); + + /* part of a search response - add to end of list of entries */ + l->lm_chain_tail->lm_chain = newmsg; + l->lm_chain_tail = newmsg; + + /* return the whole chain if that's what we were looking for */ + if ( foundit ) { + if ( prev == NULL ) { + ld->ld_responses = l->lm_next; + } else { + prev->lm_next = l->lm_next; + } + *result = l; + } + +exit: + if ( foundit ) { + ld->ld_errno = LDAP_SUCCESS; + return( tag ); + } + if ( lc && ber_sockbuf_ctrl( lc->lconn_sb, LBER_SB_OPT_DATA_READY, NULL ) ) { + goto retry; + } + return( LDAP_MSG_X_KEEP_LOOKING ); /* continue looking */ +} + + +static ber_tag_t +build_result_ber( LDAP *ld, BerElement **bp, LDAPRequest *lr ) +{ + ber_len_t len; + ber_tag_t tag; + ber_int_t along; + BerElement *ber; + + *bp = NULL; + ber = ldap_alloc_ber_with_options( ld ); + + if( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return LBER_ERROR; + } + + if ( ber_printf( ber, "{it{ess}}", lr->lr_msgid, + lr->lr_res_msgtype, lr->lr_res_errno, + lr->lr_res_matched ? lr->lr_res_matched : "", + lr->lr_res_error ? lr->lr_res_error : "" ) == -1 ) + { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( LBER_ERROR ); + } + + ber_reset( ber, 1 ); + + if ( ber_skip_tag( ber, &len ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 1 ); + return( LBER_ERROR ); + } + + if ( ber_get_enum( ber, &along ) == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 1 ); + return( LBER_ERROR ); + } + + tag = ber_peek_tag( ber, &len ); + + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_DECODING_ERROR; + ber_free( ber, 1 ); + return( LBER_ERROR ); + } + + *bp = ber; + return tag; +} + + +/* + * Merge error information in "lr" with "parentr" error code and string. + */ +static void +merge_error_info( LDAP *ld, LDAPRequest *parentr, LDAPRequest *lr ) +{ + if ( lr->lr_res_errno == LDAP_PARTIAL_RESULTS ) { + parentr->lr_res_errno = lr->lr_res_errno; + if ( lr->lr_res_error != NULL ) { + (void)ldap_append_referral( ld, &parentr->lr_res_error, + lr->lr_res_error ); + } + + } else if ( lr->lr_res_errno != LDAP_SUCCESS && + parentr->lr_res_errno == LDAP_SUCCESS ) + { + parentr->lr_res_errno = lr->lr_res_errno; + if ( parentr->lr_res_error != NULL ) { + LDAP_FREE( parentr->lr_res_error ); + } + parentr->lr_res_error = lr->lr_res_error; + lr->lr_res_error = NULL; + if ( LDAP_NAME_ERROR( lr->lr_res_errno ) ) { + if ( parentr->lr_res_matched != NULL ) { + LDAP_FREE( parentr->lr_res_matched ); + } + parentr->lr_res_matched = lr->lr_res_matched; + lr->lr_res_matched = NULL; + } + } + + Debug( LDAP_DEBUG_TRACE, "merged parent (id %d) error info: ", + parentr->lr_msgid, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "result errno %d, error <%s>, matched <%s>\n", + parentr->lr_res_errno, + parentr->lr_res_error ? parentr->lr_res_error : "", + parentr->lr_res_matched ? parentr->lr_res_matched : "" ); +} + + + +int +ldap_msgtype( LDAPMessage *lm ) +{ + assert( lm != NULL ); + return ( lm != NULL ) ? (int)lm->lm_msgtype : -1; +} + + +int +ldap_msgid( LDAPMessage *lm ) +{ + assert( lm != NULL ); + + return ( lm != NULL ) ? lm->lm_msgid : -1; +} + + +const char * +ldap_int_msgtype2str( ber_tag_t tag ) +{ + switch( tag ) { + case LDAP_RES_ADD: return "add"; + case LDAP_RES_BIND: return "bind"; + case LDAP_RES_COMPARE: return "compare"; + case LDAP_RES_DELETE: return "delete"; + case LDAP_RES_EXTENDED: return "extended-result"; + case LDAP_RES_INTERMEDIATE: return "intermediate"; + case LDAP_RES_MODIFY: return "modify"; + case LDAP_RES_RENAME: return "rename"; + case LDAP_RES_SEARCH_ENTRY: return "search-entry"; + case LDAP_RES_SEARCH_REFERENCE: return "search-reference"; + case LDAP_RES_SEARCH_RESULT: return "search-result"; + } + return "unknown"; +} + +int +ldap_msgfree( LDAPMessage *lm ) +{ + LDAPMessage *next; + int type = 0; + + Debug( LDAP_DEBUG_TRACE, "ldap_msgfree\n", 0, 0, 0 ); + + for ( ; lm != NULL; lm = next ) { + next = lm->lm_chain; + type = lm->lm_msgtype; + ber_free( lm->lm_ber, 1 ); + LDAP_FREE( (char *) lm ); + } + + return type; +} + +/* + * ldap_msgdelete - delete a message. It returns: + * 0 if the entire message was deleted + * -1 if the message was not found, or only part of it was found + */ +int +ldap_msgdelete( LDAP *ld, int msgid ) +{ + LDAPMessage *lm, *prev; + int rc = 0; + + assert( ld != NULL ); + + Debug( LDAP_DEBUG_TRACE, "ldap_msgdelete ld=%p msgid=%d\n", + (void *)ld, msgid, 0 ); + + LDAP_MUTEX_LOCK( &ld->ld_res_mutex ); + prev = NULL; + for ( lm = ld->ld_responses; lm != NULL; lm = lm->lm_next ) { + if ( lm->lm_msgid == msgid ) { + break; + } + prev = lm; + } + + if ( lm == NULL ) { + rc = -1; + + } else { + if ( prev == NULL ) { + ld->ld_responses = lm->lm_next; + } else { + prev->lm_next = lm->lm_next; + } + } + LDAP_MUTEX_UNLOCK( &ld->ld_res_mutex ); + if ( lm ) { + switch ( ldap_msgfree( lm ) ) { + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + case LDAP_RES_INTERMEDIATE: + rc = -1; + break; + + default: + break; + } + } + + return rc; +} + + +/* + * ldap_abandoned + * + * return the location of the message id in the array of abandoned + * message ids, or -1 + */ +static int +ldap_abandoned( LDAP *ld, ber_int_t msgid ) +{ + int ret, idx; + assert( msgid >= 0 ); + + LDAP_MUTEX_LOCK( &ld->ld_abandon_mutex ); + ret = ldap_int_bisect_find( ld->ld_abandoned, ld->ld_nabandoned, msgid, &idx ); + LDAP_MUTEX_UNLOCK( &ld->ld_abandon_mutex ); + return ret; +} + +/* + * ldap_mark_abandoned + */ +static int +ldap_mark_abandoned( LDAP *ld, ber_int_t msgid ) +{ + int ret, idx; + + assert( msgid >= 0 ); + LDAP_MUTEX_LOCK( &ld->ld_abandon_mutex ); + ret = ldap_int_bisect_find( ld->ld_abandoned, ld->ld_nabandoned, msgid, &idx ); + if (ret <= 0) { /* error or already deleted by another thread */ + LDAP_MUTEX_UNLOCK( &ld->ld_abandon_mutex ); + return ret; + } + /* still in abandoned array, so delete */ + ret = ldap_int_bisect_delete( &ld->ld_abandoned, &ld->ld_nabandoned, + msgid, idx ); + LDAP_MUTEX_UNLOCK( &ld->ld_abandon_mutex ); + return ret; +} diff --git a/libraries/libldap/sasl.c b/libraries/libldap/sasl.c new file mode 100644 index 0000000..16007fe --- /dev/null +++ b/libraries/libldap/sasl.c @@ -0,0 +1,868 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * BindRequest ::= SEQUENCE { + * version INTEGER, + * name DistinguishedName, -- who + * authentication CHOICE { + * simple [0] OCTET STRING -- passwd + * krbv42ldap [1] OCTET STRING -- OBSOLETE + * krbv42dsa [2] OCTET STRING -- OBSOLETE + * sasl [3] SaslCredentials -- LDAPv3 + * } + * } + * + * BindResponse ::= SEQUENCE { + * COMPONENTS OF LDAPResult, + * serverSaslCreds OCTET STRING OPTIONAL -- LDAPv3 + * } + * + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/errno.h> + +#include "ldap-int.h" + +BerElement * +ldap_build_bind_req( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *mechanism, + struct berval *cred, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t *msgidp ) +{ + BerElement *ber; + int rc; + + if( mechanism == LDAP_SASL_SIMPLE ) { + if( dn == NULL && cred != NULL && cred->bv_len ) { + /* use default binddn */ + dn = ld->ld_defbinddn; + } + + } else if( ld->ld_version < LDAP_VERSION3 ) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return( NULL ); + } + + if ( dn == NULL ) { + dn = ""; + } + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + LDAP_NEXT_MSGID( ld, *msgidp ); + if( mechanism == LDAP_SASL_SIMPLE ) { + /* simple bind */ + rc = ber_printf( ber, "{it{istON}" /*}*/, + *msgidp, LDAP_REQ_BIND, + ld->ld_version, dn, LDAP_AUTH_SIMPLE, + cred ); + + } else if ( cred == NULL || cred->bv_val == NULL ) { + /* SASL bind w/o credentials */ + rc = ber_printf( ber, "{it{ist{sN}N}" /*}*/, + *msgidp, LDAP_REQ_BIND, + ld->ld_version, dn, LDAP_AUTH_SASL, + mechanism ); + + } else { + /* SASL bind w/ credentials */ + rc = ber_printf( ber, "{it{ist{sON}N}" /*}*/, + *msgidp, LDAP_REQ_BIND, + ld->ld_version, dn, LDAP_AUTH_SASL, + mechanism, cred ); + } + + if( rc == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +/* + * ldap_sasl_bind - bind to the ldap server (and X.500). + * The dn (usually NULL), mechanism, and credentials are provided. + * The message id of the request initiated is provided upon successful + * (LDAP_SUCCESS) return. + * + * Example: + * ldap_sasl_bind( ld, NULL, "mechanism", + * cred, NULL, NULL, &msgid ) + */ + +int +ldap_sasl_bind( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *mechanism, + struct berval *cred, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + BerElement *ber; + int rc; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_sasl_bind\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( msgidp != NULL ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + ber = ldap_build_bind_req( ld, dn, mechanism, cred, sctrls, cctrls, &id ); + if( !ber ) + return ld->ld_errno; + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_BIND, dn, ber, id ); + + if(*msgidp < 0) + return ld->ld_errno; + + return LDAP_SUCCESS; +} + + +int +ldap_sasl_bind_s( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *mechanism, + struct berval *cred, + LDAPControl **sctrls, + LDAPControl **cctrls, + struct berval **servercredp ) +{ + int rc, msgid; + LDAPMessage *result; + struct berval *scredp = NULL; + + Debug( LDAP_DEBUG_TRACE, "ldap_sasl_bind_s\n", 0, 0, 0 ); + + /* do a quick !LDAPv3 check... ldap_sasl_bind will do the rest. */ + if( servercredp != NULL ) { + if (ld->ld_version < LDAP_VERSION3) { + ld->ld_errno = LDAP_NOT_SUPPORTED; + return ld->ld_errno; + } + *servercredp = NULL; + } + + rc = ldap_sasl_bind( ld, dn, mechanism, cred, sctrls, cctrls, &msgid ); + + if ( rc != LDAP_SUCCESS ) { + return( rc ); + } + +#ifdef LDAP_CONNECTIONLESS + if (LDAP_IS_UDP(ld)) { + return( rc ); + } +#endif + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1 || !result ) { + return( ld->ld_errno ); /* ldap_result sets ld_errno */ + } + + /* parse the results */ + scredp = NULL; + if( servercredp != NULL ) { + rc = ldap_parse_sasl_bind_result( ld, result, &scredp, 0 ); + } + + if ( rc != LDAP_SUCCESS ) { + ldap_msgfree( result ); + return( rc ); + } + + rc = ldap_result2error( ld, result, 1 ); + + if ( rc == LDAP_SUCCESS || rc == LDAP_SASL_BIND_IN_PROGRESS ) { + if( servercredp != NULL ) { + *servercredp = scredp; + scredp = NULL; + } + } + + if ( scredp != NULL ) { + ber_bvfree(scredp); + } + + return rc; +} + + +/* +* Parse BindResponse: +* +* BindResponse ::= [APPLICATION 1] SEQUENCE { +* COMPONENTS OF LDAPResult, +* serverSaslCreds [7] OCTET STRING OPTIONAL } +* +* LDAPResult ::= SEQUENCE { +* resultCode ENUMERATED, +* matchedDN LDAPDN, +* errorMessage LDAPString, +* referral [3] Referral OPTIONAL } +*/ + +int +ldap_parse_sasl_bind_result( + LDAP *ld, + LDAPMessage *res, + struct berval **servercredp, + int freeit ) +{ + ber_int_t errcode; + struct berval* scred; + + ber_tag_t tag; + BerElement *ber; + + Debug( LDAP_DEBUG_TRACE, "ldap_parse_sasl_bind_result\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( res != NULL ); + + if( servercredp != NULL ) { + if( ld->ld_version < LDAP_VERSION2 ) { + return LDAP_NOT_SUPPORTED; + } + *servercredp = NULL; + } + + if( res->lm_msgtype != LDAP_RES_BIND ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + scred = NULL; + + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + ld->ld_error = NULL; + } + if ( ld->ld_matched ) { + LDAP_FREE( ld->ld_matched ); + ld->ld_matched = NULL; + } + + /* parse results */ + + ber = ber_dup( res->lm_ber ); + + if( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + if ( ld->ld_version < LDAP_VERSION2 ) { + tag = ber_scanf( ber, "{iA}", + &errcode, &ld->ld_error ); + + if( tag == LBER_ERROR ) { + ber_free( ber, 0 ); + ld->ld_errno = LDAP_DECODING_ERROR; + return ld->ld_errno; + } + + } else { + ber_len_t len; + + tag = ber_scanf( ber, "{eAA" /*}*/, + &errcode, &ld->ld_matched, &ld->ld_error ); + + if( tag == LBER_ERROR ) { + ber_free( ber, 0 ); + ld->ld_errno = LDAP_DECODING_ERROR; + return ld->ld_errno; + } + + tag = ber_peek_tag(ber, &len); + + if( tag == LDAP_TAG_REFERRAL ) { + /* skip 'em */ + if( ber_scanf( ber, "x" ) == LBER_ERROR ) { + ber_free( ber, 0 ); + ld->ld_errno = LDAP_DECODING_ERROR; + return ld->ld_errno; + } + + tag = ber_peek_tag(ber, &len); + } + + if( tag == LDAP_TAG_SASL_RES_CREDS ) { + if( ber_scanf( ber, "O", &scred ) == LBER_ERROR ) { + ber_free( ber, 0 ); + ld->ld_errno = LDAP_DECODING_ERROR; + return ld->ld_errno; + } + } + } + + ber_free( ber, 0 ); + + if ( servercredp != NULL ) { + *servercredp = scred; + + } else if ( scred != NULL ) { + ber_bvfree( scred ); + } + + ld->ld_errno = errcode; + + if ( freeit ) { + ldap_msgfree( res ); + } + + return( LDAP_SUCCESS ); +} + +int +ldap_pvt_sasl_getmechs ( LDAP *ld, char **pmechlist ) +{ + /* we need to query the server for supported mechs anyway */ + LDAPMessage *res, *e; + char *attrs[] = { "supportedSASLMechanisms", NULL }; + char **values, *mechlist; + int rc; + + Debug( LDAP_DEBUG_TRACE, "ldap_pvt_sasl_getmech\n", 0, 0, 0 ); + + rc = ldap_search_s( ld, "", LDAP_SCOPE_BASE, + NULL, attrs, 0, &res ); + + if ( rc != LDAP_SUCCESS ) { + return ld->ld_errno; + } + + e = ldap_first_entry( ld, res ); + if ( e == NULL ) { + ldap_msgfree( res ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = LDAP_NO_SUCH_OBJECT; + } + return ld->ld_errno; + } + + values = ldap_get_values( ld, e, "supportedSASLMechanisms" ); + if ( values == NULL ) { + ldap_msgfree( res ); + ld->ld_errno = LDAP_NO_SUCH_ATTRIBUTE; + return ld->ld_errno; + } + + mechlist = ldap_charray2str( values, " " ); + if ( mechlist == NULL ) { + LDAP_VFREE( values ); + ldap_msgfree( res ); + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + LDAP_VFREE( values ); + ldap_msgfree( res ); + + *pmechlist = mechlist; + + return LDAP_SUCCESS; +} + +/* + * ldap_sasl_interactive_bind - interactive SASL authentication + * + * This routine uses interactive callbacks. + * + * LDAP_SUCCESS is returned upon success, the ldap error code + * otherwise. LDAP_SASL_BIND_IN_PROGRESS is returned if further + * calls are needed. + */ +int +ldap_sasl_interactive_bind( + LDAP *ld, + LDAP_CONST char *dn, /* usually NULL */ + LDAP_CONST char *mechs, + LDAPControl **serverControls, + LDAPControl **clientControls, + unsigned flags, + LDAP_SASL_INTERACT_PROC *interact, + void *defaults, + LDAPMessage *result, + const char **rmech, + int *msgid ) +{ + char *smechs = NULL; + int rc; + +#ifdef LDAP_CONNECTIONLESS + if( LDAP_IS_UDP(ld) ) { + /* Just force it to simple bind, silly to make the user + * ask all the time. No, we don't ever actually bind, but I'll + * let the final bind handler take care of saving the cdn. + */ + rc = ldap_simple_bind( ld, dn, NULL ); + rc = rc < 0 ? rc : 0; + goto done; + } else +#endif + + /* First time */ + if ( !result ) { + +#ifdef HAVE_CYRUS_SASL + if( mechs == NULL || *mechs == '\0' ) { + mechs = ld->ld_options.ldo_def_sasl_mech; + } +#endif + + if( mechs == NULL || *mechs == '\0' ) { + /* FIXME: this needs to be asynchronous too; + * perhaps NULL should be disallowed for async usage? + */ + rc = ldap_pvt_sasl_getmechs( ld, &smechs ); + if( rc != LDAP_SUCCESS ) { + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + "ldap_sasl_interactive_bind: server supports: %s\n", + smechs, 0, 0 ); + + mechs = smechs; + + } else { + Debug( LDAP_DEBUG_TRACE, + "ldap_sasl_interactive_bind: user selected: %s\n", + mechs, 0, 0 ); + } + } + rc = ldap_int_sasl_bind( ld, dn, mechs, + serverControls, clientControls, + flags, interact, defaults, result, rmech, msgid ); + +done: + if ( smechs ) LDAP_FREE( smechs ); + + return rc; +} + +/* + * ldap_sasl_interactive_bind_s - interactive SASL authentication + * + * This routine uses interactive callbacks. + * + * LDAP_SUCCESS is returned upon success, the ldap error code + * otherwise. + */ +int +ldap_sasl_interactive_bind_s( + LDAP *ld, + LDAP_CONST char *dn, /* usually NULL */ + LDAP_CONST char *mechs, + LDAPControl **serverControls, + LDAPControl **clientControls, + unsigned flags, + LDAP_SASL_INTERACT_PROC *interact, + void *defaults ) +{ + const char *rmech = NULL; + LDAPMessage *result = NULL; + int rc, msgid; + + do { + rc = ldap_sasl_interactive_bind( ld, dn, mechs, + serverControls, clientControls, + flags, interact, defaults, result, &rmech, &msgid ); + + ldap_msgfree( result ); + + if ( rc != LDAP_SASL_BIND_IN_PROGRESS ) + break; + +#ifdef LDAP_CONNECTIONLESS + if (LDAP_IS_UDP(ld)) { + break; + } +#endif + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1 || !result ) { + return( ld->ld_errno ); /* ldap_result sets ld_errno */ + } + } while ( rc == LDAP_SASL_BIND_IN_PROGRESS ); + + return rc; +} + +#ifdef HAVE_CYRUS_SASL + +#ifdef HAVE_SASL_SASL_H +#include <sasl/sasl.h> +#else +#include <sasl.h> +#endif + +#endif /* HAVE_CYRUS_SASL */ + +static int +sb_sasl_generic_remove( Sockbuf_IO_Desc *sbiod ); + +static int +sb_sasl_generic_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct sb_sasl_generic_data *p; + struct sb_sasl_generic_install *i; + + assert( sbiod != NULL ); + + i = (struct sb_sasl_generic_install *)arg; + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) + return -1; + p->ops = i->ops; + p->ops_private = i->ops_private; + p->sbiod = sbiod; + p->flags = 0; + ber_pvt_sb_buf_init( &p->sec_buf_in ); + ber_pvt_sb_buf_init( &p->buf_in ); + ber_pvt_sb_buf_init( &p->buf_out ); + + sbiod->sbiod_pvt = p; + + p->ops->init( p, &p->min_send, &p->max_send, &p->max_recv ); + + if ( ber_pvt_sb_grow_buffer( &p->sec_buf_in, p->min_send ) < 0 ) { + sb_sasl_generic_remove( sbiod ); + sock_errset(ENOMEM); + return -1; + } + + return 0; +} + +static int +sb_sasl_generic_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct sb_sasl_generic_data *p; + + assert( sbiod != NULL ); + + p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt; + + p->ops->fini(p); + + ber_pvt_sb_buf_destroy( &p->sec_buf_in ); + ber_pvt_sb_buf_destroy( &p->buf_in ); + ber_pvt_sb_buf_destroy( &p->buf_out ); + LBER_FREE( p ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static ber_len_t +sb_sasl_generic_pkt_length( + struct sb_sasl_generic_data *p, + const unsigned char *buf, + int debuglevel ) +{ + ber_len_t size; + + assert( buf != NULL ); + + size = buf[0] << 24 + | buf[1] << 16 + | buf[2] << 8 + | buf[3]; + + if ( size > p->max_recv ) { + /* somebody is trying to mess me up. */ + ber_log_printf( LDAP_DEBUG_ANY, debuglevel, + "sb_sasl_generic_pkt_length: " + "received illegal packet length of %lu bytes\n", + (unsigned long)size ); + size = 16; /* this should lead to an error. */ + } + + return size + 4; /* include the size !!! */ +} + +/* Drop a processed packet from the input buffer */ +static void +sb_sasl_generic_drop_packet ( + struct sb_sasl_generic_data *p, + int debuglevel ) +{ + ber_slen_t len; + + len = p->sec_buf_in.buf_ptr - p->sec_buf_in.buf_end; + if ( len > 0 ) + AC_MEMCPY( p->sec_buf_in.buf_base, p->sec_buf_in.buf_base + + p->sec_buf_in.buf_end, len ); + + if ( len >= 4 ) { + p->sec_buf_in.buf_end = sb_sasl_generic_pkt_length(p, + (unsigned char *) p->sec_buf_in.buf_base, debuglevel); + } + else { + p->sec_buf_in.buf_end = 0; + } + p->sec_buf_in.buf_ptr = len; +} + +static ber_slen_t +sb_sasl_generic_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct sb_sasl_generic_data *p; + ber_slen_t ret, bufptr; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt; + + /* Are there anything left in the buffer? */ + ret = ber_pvt_sb_copy_out( &p->buf_in, buf, len ); + bufptr = ret; + len -= ret; + + if ( len == 0 ) + return bufptr; + + p->ops->reset_buf( p, &p->buf_in ); + + /* Read the length of the packet */ + while ( p->sec_buf_in.buf_ptr < 4 ) { + ret = LBER_SBIOD_READ_NEXT( sbiod, p->sec_buf_in.buf_base + + p->sec_buf_in.buf_ptr, + 4 - p->sec_buf_in.buf_ptr ); +#ifdef EINTR + if ( ( ret < 0 ) && ( errno == EINTR ) ) + continue; +#endif + if ( ret <= 0 ) + return bufptr ? bufptr : ret; + + p->sec_buf_in.buf_ptr += ret; + } + + /* The new packet always starts at p->sec_buf_in.buf_base */ + ret = sb_sasl_generic_pkt_length(p, (unsigned char *) p->sec_buf_in.buf_base, + sbiod->sbiod_sb->sb_debug ); + + /* Grow the packet buffer if neccessary */ + if ( ( p->sec_buf_in.buf_size < (ber_len_t) ret ) && + ber_pvt_sb_grow_buffer( &p->sec_buf_in, ret ) < 0 ) + { + sock_errset(ENOMEM); + return -1; + } + p->sec_buf_in.buf_end = ret; + + /* Did we read the whole encrypted packet? */ + while ( p->sec_buf_in.buf_ptr < p->sec_buf_in.buf_end ) { + /* No, we have got only a part of it */ + ret = p->sec_buf_in.buf_end - p->sec_buf_in.buf_ptr; + + ret = LBER_SBIOD_READ_NEXT( sbiod, p->sec_buf_in.buf_base + + p->sec_buf_in.buf_ptr, ret ); +#ifdef EINTR + if ( ( ret < 0 ) && ( errno == EINTR ) ) + continue; +#endif + if ( ret <= 0 ) + return bufptr ? bufptr : ret; + + p->sec_buf_in.buf_ptr += ret; + } + + /* Decode the packet */ + ret = p->ops->decode( p, &p->sec_buf_in, &p->buf_in ); + + /* Drop the packet from the input buffer */ + sb_sasl_generic_drop_packet( p, sbiod->sbiod_sb->sb_debug ); + + if ( ret != 0 ) { + ber_log_printf( LDAP_DEBUG_ANY, sbiod->sbiod_sb->sb_debug, + "sb_sasl_generic_read: failed to decode packet\n" ); + sock_errset(EIO); + return -1; + } + + bufptr += ber_pvt_sb_copy_out( &p->buf_in, (char*) buf + bufptr, len ); + + return bufptr; +} + +static ber_slen_t +sb_sasl_generic_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct sb_sasl_generic_data *p; + int ret; + ber_len_t len2; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt; + + /* Is there anything left in the buffer? */ + if ( p->buf_out.buf_ptr != p->buf_out.buf_end ) { + ret = ber_pvt_sb_do_write( sbiod, &p->buf_out ); + if ( ret < 0 ) return ret; + + /* Still have something left?? */ + if ( p->buf_out.buf_ptr != p->buf_out.buf_end ) { + sock_errset(EAGAIN); + return -1; + } + } + + len2 = p->max_send - 100; /* For safety margin */ + len2 = len > len2 ? len2 : len; + + /* If we're just retrying a partial write, tell the + * caller it's done. Let them call again if there's + * still more left to write. + */ + if ( p->flags & LDAP_PVT_SASL_PARTIAL_WRITE ) { + p->flags ^= LDAP_PVT_SASL_PARTIAL_WRITE; + return len2; + } + + /* now encode the next packet. */ + p->ops->reset_buf( p, &p->buf_out ); + + ret = p->ops->encode( p, buf, len2, &p->buf_out ); + + if ( ret != 0 ) { + ber_log_printf( LDAP_DEBUG_ANY, sbiod->sbiod_sb->sb_debug, + "sb_sasl_generic_write: failed to encode packet\n" ); + sock_errset(EIO); + return -1; + } + + ret = ber_pvt_sb_do_write( sbiod, &p->buf_out ); + + if ( ret < 0 ) { + /* error? */ + int err = sock_errno(); + /* caller can retry this */ + if ( err == EAGAIN || err == EWOULDBLOCK || err == EINTR ) + p->flags |= LDAP_PVT_SASL_PARTIAL_WRITE; + return ret; + } else if ( p->buf_out.buf_ptr != p->buf_out.buf_end ) { + /* partial write? pretend nothing got written */ + p->flags |= LDAP_PVT_SASL_PARTIAL_WRITE; + sock_errset(EAGAIN); + len2 = -1; + } + + /* return number of bytes encoded, not written, to ensure + * no byte is encoded twice (even if only sent once). + */ + return len2; +} + +static int +sb_sasl_generic_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct sb_sasl_generic_data *p; + + p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_DATA_READY ) { + if ( p->buf_in.buf_ptr != p->buf_in.buf_end ) return 1; + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +Sockbuf_IO ldap_pvt_sockbuf_io_sasl_generic = { + sb_sasl_generic_setup, /* sbi_setup */ + sb_sasl_generic_remove, /* sbi_remove */ + sb_sasl_generic_ctrl, /* sbi_ctrl */ + sb_sasl_generic_read, /* sbi_read */ + sb_sasl_generic_write, /* sbi_write */ + NULL /* sbi_close */ +}; + +int ldap_pvt_sasl_generic_install( + Sockbuf *sb, + struct sb_sasl_generic_install *install_arg ) +{ + Debug( LDAP_DEBUG_TRACE, "ldap_pvt_sasl_generic_install\n", + 0, 0, 0 ); + + /* don't install the stuff unless security has been negotiated */ + + if ( !ber_sockbuf_ctrl( sb, LBER_SB_OPT_HAS_IO, + &ldap_pvt_sockbuf_io_sasl_generic ) ) + { +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_APPLICATION, (void *)"sasl_generic_" ); +#endif + ber_sockbuf_add_io( sb, &ldap_pvt_sockbuf_io_sasl_generic, + LBER_SBIOD_LEVEL_APPLICATION, install_arg ); + } + + return LDAP_SUCCESS; +} + +void ldap_pvt_sasl_generic_remove( Sockbuf *sb ) +{ + ber_sockbuf_remove_io( sb, &ldap_pvt_sockbuf_io_sasl_generic, + LBER_SBIOD_LEVEL_APPLICATION ); +#ifdef LDAP_DEBUG + ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_APPLICATION ); +#endif +} diff --git a/libraries/libldap/sbind.c b/libraries/libldap/sbind.c new file mode 100644 index 0000000..ea6dc7b --- /dev/null +++ b/libraries/libldap/sbind.c @@ -0,0 +1,115 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1993 Regents of the University of Michigan. + * All rights reserved. + */ + +/* + * BindRequest ::= SEQUENCE { + * version INTEGER, + * name DistinguishedName, -- who + * authentication CHOICE { + * simple [0] OCTET STRING -- passwd + * krbv42ldap [1] OCTET STRING -- OBSOLETE + * krbv42dsa [2] OCTET STRING -- OBSOLETE + * sasl [3] SaslCredentials -- LDAPv3 + * } + * } + * + * BindResponse ::= SEQUENCE { + * COMPONENTS OF LDAPResult, + * serverSaslCreds OCTET STRING OPTIONAL -- LDAPv3 + * } + * + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * ldap_simple_bind - bind to the ldap server (and X.500). The dn and + * password of the entry to which to bind are supplied. The message id + * of the request initiated is returned. + * + * Example: + * ldap_simple_bind( ld, "cn=manager, o=university of michigan, c=us", + * "secret" ) + */ + +int +ldap_simple_bind( + LDAP *ld, + LDAP_CONST char *dn, + LDAP_CONST char *passwd ) +{ + int rc; + int msgid; + struct berval cred; + + Debug( LDAP_DEBUG_TRACE, "ldap_simple_bind\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + if ( passwd != NULL ) { + cred.bv_val = (char *) passwd; + cred.bv_len = strlen( passwd ); + } else { + cred.bv_val = ""; + cred.bv_len = 0; + } + + rc = ldap_sasl_bind( ld, dn, LDAP_SASL_SIMPLE, &cred, + NULL, NULL, &msgid ); + + return rc == LDAP_SUCCESS ? msgid : -1; +} + +/* + * ldap_simple_bind - bind to the ldap server (and X.500) using simple + * authentication. The dn and password of the entry to which to bind are + * supplied. LDAP_SUCCESS is returned upon success, the ldap error code + * otherwise. + * + * Example: + * ldap_simple_bind_s( ld, "cn=manager, o=university of michigan, c=us", + * "secret" ) + */ + +int +ldap_simple_bind_s( LDAP *ld, LDAP_CONST char *dn, LDAP_CONST char *passwd ) +{ + struct berval cred; + + Debug( LDAP_DEBUG_TRACE, "ldap_simple_bind_s\n", 0, 0, 0 ); + + if ( passwd != NULL ) { + cred.bv_val = (char *) passwd; + cred.bv_len = strlen( passwd ); + } else { + cred.bv_val = ""; + cred.bv_len = 0; + } + + return ldap_sasl_bind_s( ld, dn, LDAP_SASL_SIMPLE, &cred, + NULL, NULL, NULL ); +} diff --git a/libraries/libldap/schema.c b/libraries/libldap/schema.c new file mode 100644 index 0000000..d31616a --- /dev/null +++ b/libraries/libldap/schema.c @@ -0,0 +1,3385 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * schema.c: parsing routines used by servers and clients to process + * schema definitions + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +#include <ldap_schema.h> + +static const char EndOfInput[] = "end of input"; + +static const char * +choose_name( char *names[], const char *fallback ) +{ + return (names != NULL && names[0] != NULL) ? names[0] : fallback; +} + +LDAP_CONST char * +ldap_syntax2name( LDAPSyntax * syn ) +{ + if (!syn) return NULL; + return( syn->syn_oid ); +} + +LDAP_CONST char * +ldap_matchingrule2name( LDAPMatchingRule * mr ) +{ + if (!mr) return NULL; + return( choose_name( mr->mr_names, mr->mr_oid ) ); +} + +LDAP_CONST char * +ldap_matchingruleuse2name( LDAPMatchingRuleUse * mru ) +{ + if (!mru) return NULL; + return( choose_name( mru->mru_names, mru->mru_oid ) ); +} + +LDAP_CONST char * +ldap_attributetype2name( LDAPAttributeType * at ) +{ + if (!at) return NULL; + return( choose_name( at->at_names, at->at_oid ) ); +} + +LDAP_CONST char * +ldap_objectclass2name( LDAPObjectClass * oc ) +{ + if (!oc) return NULL; + return( choose_name( oc->oc_names, oc->oc_oid ) ); +} + +LDAP_CONST char * +ldap_contentrule2name( LDAPContentRule * cr ) +{ + if (!cr) return NULL; + return( choose_name( cr->cr_names, cr->cr_oid ) ); +} + +LDAP_CONST char * +ldap_nameform2name( LDAPNameForm * nf ) +{ + if (!nf) return NULL; + return( choose_name( nf->nf_names, nf->nf_oid ) ); +} + +LDAP_CONST char * +ldap_structurerule2name( LDAPStructureRule * sr ) +{ + if (!sr) return NULL; + return( choose_name( sr->sr_names, NULL ) ); +} + +/* + * When pretty printing the entities we will be appending to a buffer. + * Since checking for overflow, realloc'ing and checking if no error + * is extremely boring, we will use a protection layer that will let + * us blissfully ignore the error until the end. This layer is + * implemented with the help of the next type. + */ + +typedef struct safe_string { + char * val; + ber_len_t size; + ber_len_t pos; + int at_whsp; +} safe_string; + +static safe_string * +new_safe_string(int size) +{ + safe_string * ss; + + ss = LDAP_MALLOC(sizeof(safe_string)); + if ( !ss ) + return(NULL); + + ss->val = LDAP_MALLOC(size); + if ( !ss->val ) { + LDAP_FREE(ss); + return(NULL); + } + + ss->size = size; + ss->pos = 0; + ss->at_whsp = 0; + + return ss; +} + +static void +safe_string_free(safe_string * ss) +{ + if ( !ss ) + return; + LDAP_FREE(ss->val); + LDAP_FREE(ss); +} + +#if 0 /* unused */ +static char * +safe_string_val(safe_string * ss) +{ + ss->val[ss->pos] = '\0'; + return(ss->val); +} +#endif + +static char * +safe_strdup(safe_string * ss) +{ + char *ret = LDAP_MALLOC(ss->pos+1); + if (!ret) + return NULL; + AC_MEMCPY(ret, ss->val, ss->pos); + ret[ss->pos] = '\0'; + return ret; +} + +static int +append_to_safe_string(safe_string * ss, char * s) +{ + int l = strlen(s); + char * temp; + + /* + * Some runaway process is trying to append to a string that + * overflowed and we could not extend. + */ + if ( !ss->val ) + return -1; + + /* We always make sure there is at least one position available */ + if ( ss->pos + l >= ss->size-1 ) { + ss->size *= 2; + if ( ss->pos + l >= ss->size-1 ) { + ss->size = ss->pos + l + 1; + } + + temp = LDAP_REALLOC(ss->val, ss->size); + if ( !temp ) { + /* Trouble, out of memory */ + LDAP_FREE(ss->val); + return -1; + } + ss->val = temp; + } + strncpy(&ss->val[ss->pos], s, l); + ss->pos += l; + if ( ss->pos > 0 && LDAP_SPACE(ss->val[ss->pos-1]) ) + ss->at_whsp = 1; + else + ss->at_whsp = 0; + + return 0; +} + +static int +print_literal(safe_string *ss, char *s) +{ + return(append_to_safe_string(ss,s)); +} + +static int +print_whsp(safe_string *ss) +{ + if ( ss->at_whsp ) + return(append_to_safe_string(ss,"")); + else + return(append_to_safe_string(ss," ")); +} + +static int +print_numericoid(safe_string *ss, char *s) +{ + if ( s ) + return(append_to_safe_string(ss,s)); + else + return(append_to_safe_string(ss,"")); +} + +/* This one is identical to print_qdescr */ +static int +print_qdstring(safe_string *ss, char *s) +{ + print_whsp(ss); + print_literal(ss,"'"); + append_to_safe_string(ss,s); + print_literal(ss,"'"); + return(print_whsp(ss)); +} + +static int +print_qdescr(safe_string *ss, char *s) +{ + print_whsp(ss); + print_literal(ss,"'"); + append_to_safe_string(ss,s); + print_literal(ss,"'"); + return(print_whsp(ss)); +} + +static int +print_qdescrlist(safe_string *ss, char **sa) +{ + char **sp; + int ret = 0; + + for (sp=sa; *sp; sp++) { + ret = print_qdescr(ss,*sp); + } + /* If the list was empty, we return zero that is potentially + * incorrect, but since we will be still appending things, the + * overflow will be detected later. Maybe FIX. + */ + return(ret); +} + +static int +print_qdescrs(safe_string *ss, char **sa) +{ + /* The only way to represent an empty list is as a qdescrlist + * so, if the list is empty we treat it as a long list. + * Really, this is what the syntax mandates. We should not + * be here if the list was empty, but if it happens, a label + * has already been output and we cannot undo it. + */ + if ( !sa[0] || ( sa[0] && sa[1] ) ) { + print_whsp(ss); + print_literal(ss,"("/*)*/); + print_qdescrlist(ss,sa); + print_literal(ss,/*(*/")"); + return(print_whsp(ss)); + } else { + return(print_qdescr(ss,*sa)); + } +} + +static int +print_woid(safe_string *ss, char *s) +{ + print_whsp(ss); + append_to_safe_string(ss,s); + return print_whsp(ss); +} + +static int +print_oidlist(safe_string *ss, char **sa) +{ + char **sp; + + for (sp=sa; *(sp+1); sp++) { + print_woid(ss,*sp); + print_literal(ss,"$"); + } + return(print_woid(ss,*sp)); +} + +static int +print_oids(safe_string *ss, char **sa) +{ + if ( sa[0] && sa[1] ) { + print_literal(ss,"("/*)*/); + print_oidlist(ss,sa); + print_whsp(ss); + return(print_literal(ss,/*(*/")")); + } else { + return(print_woid(ss,*sa)); + } +} + +static int +print_noidlen(safe_string *ss, char *s, int l) +{ + char buf[64]; + int ret; + + ret = print_numericoid(ss,s); + if ( l ) { + snprintf(buf, sizeof buf, "{%d}",l); + ret = print_literal(ss,buf); + } + return(ret); +} + +static int +print_ruleid(safe_string *ss, int rid) +{ + char buf[64]; + snprintf(buf, sizeof buf, "%d", rid); + return print_literal(ss,buf); +} + +static int +print_ruleids(safe_string *ss, int n, int *rids) +{ + int i; + + if( n == 1 ) { + print_ruleid(ss,rids[0]); + return print_whsp(ss); + } else { + print_literal(ss,"("/*)*/); + for( i=0; i<n; i++ ) { + print_whsp(ss); + print_ruleid(ss,rids[i]); + } + print_whsp(ss); + return print_literal(ss,/*(*/")"); + } +} + + +static int +print_extensions(safe_string *ss, LDAPSchemaExtensionItem **extensions) +{ + LDAPSchemaExtensionItem **ext; + + if ( extensions ) { + print_whsp(ss); + for ( ext = extensions; *ext != NULL; ext++ ) { + print_literal(ss, (*ext)->lsei_name); + print_whsp(ss); + /* Should be print_qdstrings */ + print_qdescrs(ss, (*ext)->lsei_values); + print_whsp(ss); + } + } + + return 0; +} + +char * +ldap_syntax2str( LDAPSyntax * syn ) +{ + struct berval bv; + if (ldap_syntax2bv( syn, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_syntax2bv( LDAPSyntax * syn, struct berval *bv ) +{ + safe_string * ss; + + if ( !syn || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("/*)*/); + print_whsp(ss); + + print_numericoid(ss, syn->syn_oid); + print_whsp(ss); + + if ( syn->syn_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,syn->syn_desc); + } + + print_whsp(ss); + + print_extensions(ss, syn->syn_extensions); + + print_literal(ss,/*(*/ ")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + +char * +ldap_matchingrule2str( LDAPMatchingRule * mr ) +{ + struct berval bv; + if (ldap_matchingrule2bv( mr, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_matchingrule2bv( LDAPMatchingRule * mr, struct berval *bv ) +{ + safe_string * ss; + + if ( !mr || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"(" /*)*/); + print_whsp(ss); + + print_numericoid(ss, mr->mr_oid); + print_whsp(ss); + + if ( mr->mr_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,mr->mr_names); + } + + if ( mr->mr_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,mr->mr_desc); + } + + if ( mr->mr_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + if ( mr->mr_syntax_oid ) { + print_literal(ss,"SYNTAX"); + print_whsp(ss); + print_literal(ss, mr->mr_syntax_oid); + print_whsp(ss); + } + + print_whsp(ss); + + print_extensions(ss, mr->mr_extensions); + + print_literal(ss,/*(*/")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + +char * +ldap_matchingruleuse2str( LDAPMatchingRuleUse * mru ) +{ + struct berval bv; + if (ldap_matchingruleuse2bv( mru, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_matchingruleuse2bv( LDAPMatchingRuleUse * mru, struct berval *bv ) +{ + safe_string * ss; + + if ( !mru || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"(" /*)*/); + print_whsp(ss); + + print_numericoid(ss, mru->mru_oid); + print_whsp(ss); + + if ( mru->mru_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,mru->mru_names); + } + + if ( mru->mru_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,mru->mru_desc); + } + + if ( mru->mru_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + if ( mru->mru_applies_oids ) { + print_literal(ss,"APPLIES"); + print_whsp(ss); + print_oids(ss, mru->mru_applies_oids); + print_whsp(ss); + } + + print_whsp(ss); + + print_extensions(ss, mru->mru_extensions); + + print_literal(ss,/*(*/")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + +char * +ldap_objectclass2str( LDAPObjectClass * oc ) +{ + struct berval bv; + if (ldap_objectclass2bv( oc, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_objectclass2bv( LDAPObjectClass * oc, struct berval *bv ) +{ + safe_string * ss; + + if ( !oc || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("/*)*/); + print_whsp(ss); + + print_numericoid(ss, oc->oc_oid); + print_whsp(ss); + + if ( oc->oc_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,oc->oc_names); + } + + if ( oc->oc_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,oc->oc_desc); + } + + if ( oc->oc_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + if ( oc->oc_sup_oids ) { + print_literal(ss,"SUP"); + print_whsp(ss); + print_oids(ss,oc->oc_sup_oids); + print_whsp(ss); + } + + switch (oc->oc_kind) { + case LDAP_SCHEMA_ABSTRACT: + print_literal(ss,"ABSTRACT"); + break; + case LDAP_SCHEMA_STRUCTURAL: + print_literal(ss,"STRUCTURAL"); + break; + case LDAP_SCHEMA_AUXILIARY: + print_literal(ss,"AUXILIARY"); + break; + default: + print_literal(ss,"KIND-UNKNOWN"); + break; + } + print_whsp(ss); + + if ( oc->oc_at_oids_must ) { + print_literal(ss,"MUST"); + print_whsp(ss); + print_oids(ss,oc->oc_at_oids_must); + print_whsp(ss); + } + + if ( oc->oc_at_oids_may ) { + print_literal(ss,"MAY"); + print_whsp(ss); + print_oids(ss,oc->oc_at_oids_may); + print_whsp(ss); + } + + print_whsp(ss); + + print_extensions(ss, oc->oc_extensions); + + print_literal(ss, /*(*/")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + +char * +ldap_contentrule2str( LDAPContentRule * cr ) +{ + struct berval bv; + if (ldap_contentrule2bv( cr, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_contentrule2bv( LDAPContentRule * cr, struct berval *bv ) +{ + safe_string * ss; + + if ( !cr || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("/*)*/); + print_whsp(ss); + + print_numericoid(ss, cr->cr_oid); + print_whsp(ss); + + if ( cr->cr_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,cr->cr_names); + } + + if ( cr->cr_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,cr->cr_desc); + } + + if ( cr->cr_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + if ( cr->cr_oc_oids_aux ) { + print_literal(ss,"AUX"); + print_whsp(ss); + print_oids(ss,cr->cr_oc_oids_aux); + print_whsp(ss); + } + + if ( cr->cr_at_oids_must ) { + print_literal(ss,"MUST"); + print_whsp(ss); + print_oids(ss,cr->cr_at_oids_must); + print_whsp(ss); + } + + if ( cr->cr_at_oids_may ) { + print_literal(ss,"MAY"); + print_whsp(ss); + print_oids(ss,cr->cr_at_oids_may); + print_whsp(ss); + } + + if ( cr->cr_at_oids_not ) { + print_literal(ss,"NOT"); + print_whsp(ss); + print_oids(ss,cr->cr_at_oids_not); + print_whsp(ss); + } + + print_whsp(ss); + print_extensions(ss, cr->cr_extensions); + + print_literal(ss, /*(*/")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + +char * +ldap_structurerule2str( LDAPStructureRule * sr ) +{ + struct berval bv; + if (ldap_structurerule2bv( sr, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_structurerule2bv( LDAPStructureRule * sr, struct berval *bv ) +{ + safe_string * ss; + + if ( !sr || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("/*)*/); + print_whsp(ss); + + print_ruleid(ss, sr->sr_ruleid); + print_whsp(ss); + + if ( sr->sr_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,sr->sr_names); + } + + if ( sr->sr_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,sr->sr_desc); + } + + if ( sr->sr_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + print_literal(ss,"FORM"); + print_whsp(ss); + print_woid(ss,sr->sr_nameform); + print_whsp(ss); + + if ( sr->sr_nsup_ruleids ) { + print_literal(ss,"SUP"); + print_whsp(ss); + print_ruleids(ss,sr->sr_nsup_ruleids,sr->sr_sup_ruleids); + print_whsp(ss); + } + + print_whsp(ss); + print_extensions(ss, sr->sr_extensions); + + print_literal(ss, /*(*/")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + + +char * +ldap_nameform2str( LDAPNameForm * nf ) +{ + struct berval bv; + if (ldap_nameform2bv( nf, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_nameform2bv( LDAPNameForm * nf, struct berval *bv ) +{ + safe_string * ss; + + if ( !nf || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("/*)*/); + print_whsp(ss); + + print_numericoid(ss, nf->nf_oid); + print_whsp(ss); + + if ( nf->nf_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,nf->nf_names); + } + + if ( nf->nf_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,nf->nf_desc); + } + + if ( nf->nf_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + print_literal(ss,"OC"); + print_whsp(ss); + print_woid(ss,nf->nf_objectclass); + print_whsp(ss); + + print_literal(ss,"MUST"); + print_whsp(ss); + print_oids(ss,nf->nf_at_oids_must); + print_whsp(ss); + + + if ( nf->nf_at_oids_may ) { + print_literal(ss,"MAY"); + print_whsp(ss); + print_oids(ss,nf->nf_at_oids_may); + print_whsp(ss); + } + + print_whsp(ss); + print_extensions(ss, nf->nf_extensions); + + print_literal(ss, /*(*/")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + +char * +ldap_attributetype2str( LDAPAttributeType * at ) +{ + struct berval bv; + if (ldap_attributetype2bv( at, &bv )) + return(bv.bv_val); + else + return NULL; +} + +struct berval * +ldap_attributetype2bv( LDAPAttributeType * at, struct berval *bv ) +{ + safe_string * ss; + + if ( !at || !bv ) + return NULL; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("/*)*/); + print_whsp(ss); + + print_numericoid(ss, at->at_oid); + print_whsp(ss); + + if ( at->at_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,at->at_names); + } + + if ( at->at_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,at->at_desc); + } + + if ( at->at_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + if ( at->at_sup_oid ) { + print_literal(ss,"SUP"); + print_woid(ss,at->at_sup_oid); + } + + if ( at->at_equality_oid ) { + print_literal(ss,"EQUALITY"); + print_woid(ss,at->at_equality_oid); + } + + if ( at->at_ordering_oid ) { + print_literal(ss,"ORDERING"); + print_woid(ss,at->at_ordering_oid); + } + + if ( at->at_substr_oid ) { + print_literal(ss,"SUBSTR"); + print_woid(ss,at->at_substr_oid); + } + + if ( at->at_syntax_oid ) { + print_literal(ss,"SYNTAX"); + print_whsp(ss); + print_noidlen(ss,at->at_syntax_oid,at->at_syntax_len); + print_whsp(ss); + } + + if ( at->at_single_value == LDAP_SCHEMA_YES ) { + print_literal(ss,"SINGLE-VALUE"); + print_whsp(ss); + } + + if ( at->at_collective == LDAP_SCHEMA_YES ) { + print_literal(ss,"COLLECTIVE"); + print_whsp(ss); + } + + if ( at->at_no_user_mod == LDAP_SCHEMA_YES ) { + print_literal(ss,"NO-USER-MODIFICATION"); + print_whsp(ss); + } + + if ( at->at_usage != LDAP_SCHEMA_USER_APPLICATIONS ) { + print_literal(ss,"USAGE"); + print_whsp(ss); + switch (at->at_usage) { + case LDAP_SCHEMA_DIRECTORY_OPERATION: + print_literal(ss,"directoryOperation"); + break; + case LDAP_SCHEMA_DISTRIBUTED_OPERATION: + print_literal(ss,"distributedOperation"); + break; + case LDAP_SCHEMA_DSA_OPERATION: + print_literal(ss,"dSAOperation"); + break; + default: + print_literal(ss,"UNKNOWN"); + break; + } + } + + print_whsp(ss); + + print_extensions(ss, at->at_extensions); + + print_literal(ss,/*(*/")"); + + bv->bv_val = safe_strdup(ss); + bv->bv_len = ss->pos; + safe_string_free(ss); + return(bv); +} + +/* + * Now come the parsers. There is one parser for each entity type: + * objectclasses, attributetypes, etc. + * + * Each of them is written as a recursive-descent parser, except that + * none of them is really recursive. But the idea is kept: there + * is one routine per non-terminal that eithers gobbles lexical tokens + * or calls lower-level routines, etc. + * + * The scanner is implemented in the routine get_token. Actually, + * get_token is more than a scanner and will return tokens that are + * in fact non-terminals in the grammar. So you can see the whole + * approach as the combination of a low-level bottom-up recognizer + * combined with a scanner and a number of top-down parsers. Or just + * consider that the real grammars recognized by the parsers are not + * those of the standards. As a matter of fact, our parsers are more + * liberal than the spec when there is no ambiguity. + * + * The difference is pretty academic (modulo bugs or incorrect + * interpretation of the specs). + */ + +typedef enum tk_t { + TK_NOENDQUOTE = -2, + TK_OUTOFMEM = -1, + TK_EOS = 0, + TK_UNEXPCHAR = 1, + TK_BAREWORD = 2, + TK_QDSTRING = 3, + TK_LEFTPAREN = 4, + TK_RIGHTPAREN = 5, + TK_DOLLAR = 6, + TK_QDESCR = TK_QDSTRING +} tk_t; + +static tk_t +get_token( const char ** sp, char ** token_val ) +{ + tk_t kind; + const char * p; + const char * q; + char * res; + + *token_val = NULL; + switch (**sp) { + case '\0': + kind = TK_EOS; + (*sp)++; + break; + case '(': + kind = TK_LEFTPAREN; + (*sp)++; + break; + case ')': + kind = TK_RIGHTPAREN; + (*sp)++; + break; + case '$': + kind = TK_DOLLAR; + (*sp)++; + break; + case '\'': + kind = TK_QDSTRING; + (*sp)++; + p = *sp; + while ( **sp != '\'' && **sp != '\0' ) + (*sp)++; + if ( **sp == '\'' ) { + q = *sp; + res = LDAP_MALLOC(q-p+1); + if ( !res ) { + kind = TK_OUTOFMEM; + } else { + strncpy(res,p,q-p); + res[q-p] = '\0'; + *token_val = res; + } + (*sp)++; + } else { + kind = TK_NOENDQUOTE; + } + break; + default: + kind = TK_BAREWORD; + p = *sp; + while ( !LDAP_SPACE(**sp) && + **sp != '(' && + **sp != ')' && + **sp != '$' && + **sp != '\'' && + /* for suggested minimum upper bound on the number + * of characters (RFC 4517) */ + **sp != '{' && + **sp != '\0' ) + (*sp)++; + q = *sp; + res = LDAP_MALLOC(q-p+1); + if ( !res ) { + kind = TK_OUTOFMEM; + } else { + strncpy(res,p,q-p); + res[q-p] = '\0'; + *token_val = res; + } + break; +/* kind = TK_UNEXPCHAR; */ +/* break; */ + } + + return kind; +} + +/* Gobble optional whitespace */ +static void +parse_whsp(const char **sp) +{ + while (LDAP_SPACE(**sp)) + (*sp)++; +} + +/* TBC:!! + * General note for all parsers: to guarantee the algorithm halts they + * must always advance the pointer even when an error is found. For + * this one is not that important since an error here is fatal at the + * upper layers, but it is a simple strategy that will not get in + * endless loops. + */ + +/* Parse a sequence of dot-separated decimal strings */ +char * +ldap_int_parse_numericoid(const char **sp, int *code, const int flags) +{ + char * res = NULL; + const char * start = *sp; + int len; + int quoted = 0; + + /* Netscape puts the SYNTAX value in quotes (incorrectly) */ + if ( flags & LDAP_SCHEMA_ALLOW_QUOTED && **sp == '\'' ) { + quoted = 1; + (*sp)++; + start++; + } + /* Each iteration of this loop gets one decimal string */ + while (**sp) { + if ( !LDAP_DIGIT(**sp) ) { + /* + * Initial char is not a digit or char after dot is + * not a digit + */ + *code = LDAP_SCHERR_NODIGIT; + return NULL; + } + (*sp)++; + while ( LDAP_DIGIT(**sp) ) + (*sp)++; + if ( **sp != '.' ) + break; + /* Otherwise, gobble the dot and loop again */ + (*sp)++; + } + /* Now *sp points at the char past the numericoid. Perfect. */ + len = *sp - start; + if ( flags & LDAP_SCHEMA_ALLOW_QUOTED && quoted ) { + if ( **sp == '\'' ) { + (*sp)++; + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + return NULL; + } + } + if (flags & LDAP_SCHEMA_SKIP) { + res = (char *)start; + } else { + res = LDAP_MALLOC(len+1); + if (!res) { + *code = LDAP_SCHERR_OUTOFMEM; + return(NULL); + } + strncpy(res,start,len); + res[len] = '\0'; + } + return(res); +} + +/* Parse a sequence of dot-separated decimal strings */ +int +ldap_int_parse_ruleid(const char **sp, int *code, const int flags, int *ruleid) +{ + *ruleid=0; + + if ( !LDAP_DIGIT(**sp) ) { + *code = LDAP_SCHERR_NODIGIT; + return -1; + } + *ruleid = (**sp) - '0'; + (*sp)++; + + while ( LDAP_DIGIT(**sp) ) { + *ruleid *= 10; + *ruleid += (**sp) - '0'; + (*sp)++; + } + + return 0; +} + +/* Parse a qdescr or a list of them enclosed in () */ +static char ** +parse_qdescrs(const char **sp, int *code) +{ + char ** res; + char ** res1; + tk_t kind; + char * sval; + int size; + int pos; + + parse_whsp(sp); + kind = get_token(sp,&sval); + if ( kind == TK_LEFTPAREN ) { + /* Let's presume there will be at least 2 entries */ + size = 3; + res = LDAP_CALLOC(3,sizeof(char *)); + if ( !res ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + pos = 0; + while (1) { + parse_whsp(sp); + kind = get_token(sp,&sval); + if ( kind == TK_RIGHTPAREN ) + break; + if ( kind == TK_QDESCR ) { + if ( pos == size-2 ) { + size++; + res1 = LDAP_REALLOC(res,size*sizeof(char *)); + if ( !res1 ) { + LDAP_VFREE(res); + LDAP_FREE(sval); + *code = LDAP_SCHERR_OUTOFMEM; + return(NULL); + } + res = res1; + } + res[pos++] = sval; + res[pos] = NULL; + parse_whsp(sp); + } else { + LDAP_VFREE(res); + LDAP_FREE(sval); + *code = LDAP_SCHERR_UNEXPTOKEN; + return(NULL); + } + } + parse_whsp(sp); + return(res); + } else if ( kind == TK_QDESCR ) { + res = LDAP_CALLOC(2,sizeof(char *)); + if ( !res ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + res[0] = sval; + res[1] = NULL; + parse_whsp(sp); + return res; + } else { + LDAP_FREE(sval); + *code = LDAP_SCHERR_BADNAME; + return NULL; + } +} + +/* Parse a woid */ +static char * +parse_woid(const char **sp, int *code) +{ + char * sval; + tk_t kind; + + parse_whsp(sp); + kind = get_token(sp, &sval); + if ( kind != TK_BAREWORD ) { + LDAP_FREE(sval); + *code = LDAP_SCHERR_UNEXPTOKEN; + return NULL; + } + parse_whsp(sp); + return sval; +} + +/* Parse a noidlen */ +static char * +parse_noidlen(const char **sp, int *code, int *len, int flags) +{ + char * sval; + const char *savepos; + int quoted = 0; + int allow_quoted = ( flags & LDAP_SCHEMA_ALLOW_QUOTED ); + int allow_oidmacro = ( flags & LDAP_SCHEMA_ALLOW_OID_MACRO ); + + *len = 0; + /* Netscape puts the SYNTAX value in quotes (incorrectly) */ + if ( allow_quoted && **sp == '\'' ) { + quoted = 1; + (*sp)++; + } + savepos = *sp; + sval = ldap_int_parse_numericoid(sp, code, 0); + if ( !sval ) { + if ( allow_oidmacro + && *sp == savepos + && *code == LDAP_SCHERR_NODIGIT ) + { + if ( get_token(sp, &sval) != TK_BAREWORD ) { + if ( sval != NULL ) { + LDAP_FREE(sval); + } + return NULL; + } + } else { + return NULL; + } + } + if ( **sp == '{' /*}*/ ) { + (*sp)++; + *len = atoi(*sp); + while ( LDAP_DIGIT(**sp) ) + (*sp)++; + if ( **sp != /*{*/ '}' ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + LDAP_FREE(sval); + return NULL; + } + (*sp)++; + } + if ( allow_quoted && quoted ) { + if ( **sp == '\'' ) { + (*sp)++; + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + LDAP_FREE(sval); + return NULL; + } + } + return sval; +} + +/* + * Next routine will accept a qdstring in place of an oid if + * allow_quoted is set. This is necessary to interoperate with + * Netscape Directory server that will improperly quote each oid (at + * least those of the descr kind) in the SUP clause. + */ + +/* Parse a woid or a $-separated list of them enclosed in () */ +static char ** +parse_oids(const char **sp, int *code, const int allow_quoted) +{ + char ** res; + char ** res1; + tk_t kind; + char * sval; + int size; + int pos; + + /* + * Strictly speaking, doing this here accepts whsp before the + * ( at the begining of an oidlist, but this is harmless. Also, + * we are very liberal in what we accept as an OID. Maybe + * refine later. + */ + parse_whsp(sp); + kind = get_token(sp,&sval); + if ( kind == TK_LEFTPAREN ) { + /* Let's presume there will be at least 2 entries */ + size = 3; + res = LDAP_CALLOC(3,sizeof(char *)); + if ( !res ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + pos = 0; + parse_whsp(sp); + kind = get_token(sp,&sval); + if ( kind == TK_BAREWORD || + ( allow_quoted && kind == TK_QDSTRING ) ) { + res[pos++] = sval; + res[pos] = NULL; + } else if ( kind == TK_RIGHTPAREN ) { + /* FIXME: be liberal in what we accept... */ + parse_whsp(sp); + LDAP_FREE(res); + return NULL; + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + LDAP_FREE(sval); + LDAP_VFREE(res); + return NULL; + } + parse_whsp(sp); + while (1) { + kind = get_token(sp,&sval); + if ( kind == TK_RIGHTPAREN ) + break; + if ( kind == TK_DOLLAR ) { + parse_whsp(sp); + kind = get_token(sp,&sval); + if ( kind == TK_BAREWORD || + ( allow_quoted && + kind == TK_QDSTRING ) ) { + if ( pos == size-2 ) { + size++; + res1 = LDAP_REALLOC(res,size*sizeof(char *)); + if ( !res1 ) { + LDAP_FREE(sval); + LDAP_VFREE(res); + *code = LDAP_SCHERR_OUTOFMEM; + return(NULL); + } + res = res1; + } + res[pos++] = sval; + res[pos] = NULL; + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + LDAP_FREE(sval); + LDAP_VFREE(res); + return NULL; + } + parse_whsp(sp); + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + LDAP_FREE(sval); + LDAP_VFREE(res); + return NULL; + } + } + parse_whsp(sp); + return(res); + } else if ( kind == TK_BAREWORD || + ( allow_quoted && kind == TK_QDSTRING ) ) { + res = LDAP_CALLOC(2,sizeof(char *)); + if ( !res ) { + LDAP_FREE(sval); + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + res[0] = sval; + res[1] = NULL; + parse_whsp(sp); + return res; + } else { + LDAP_FREE(sval); + *code = LDAP_SCHERR_BADNAME; + return NULL; + } +} + +static int +add_extension(LDAPSchemaExtensionItem ***extensions, + char * name, char ** values) +{ + int n; + LDAPSchemaExtensionItem **tmp, *ext; + + ext = LDAP_CALLOC(1, sizeof(LDAPSchemaExtensionItem)); + if ( !ext ) + return 1; + ext->lsei_name = name; + ext->lsei_values = values; + + if ( !*extensions ) { + *extensions = + LDAP_CALLOC(2, sizeof(LDAPSchemaExtensionItem *)); + if ( !*extensions ) { + LDAP_FREE( ext ); + return 1; + } + n = 0; + } else { + for ( n=0; (*extensions)[n] != NULL; n++ ) + ; + tmp = LDAP_REALLOC(*extensions, + (n+2)*sizeof(LDAPSchemaExtensionItem *)); + if ( !tmp ) { + LDAP_FREE( ext ); + return 1; + } + *extensions = tmp; + } + (*extensions)[n] = ext; + (*extensions)[n+1] = NULL; + return 0; +} + +static void +free_extensions(LDAPSchemaExtensionItem **extensions) +{ + LDAPSchemaExtensionItem **ext; + + if ( extensions ) { + for ( ext = extensions; *ext != NULL; ext++ ) { + LDAP_FREE((*ext)->lsei_name); + LDAP_VFREE((*ext)->lsei_values); + LDAP_FREE(*ext); + } + LDAP_FREE(extensions); + } +} + +void +ldap_syntax_free( LDAPSyntax * syn ) +{ + if ( !syn ) return; + LDAP_FREE(syn->syn_oid); + if (syn->syn_names) LDAP_VFREE(syn->syn_names); + if (syn->syn_desc) LDAP_FREE(syn->syn_desc); + free_extensions(syn->syn_extensions); + LDAP_FREE(syn); +} + +LDAPSyntax * +ldap_str2syntax( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + LDAPSyntax * syn; + char ** ext_vals; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + syn = LDAP_CALLOC(1,sizeof(LDAPSyntax)); + + if ( !syn ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + LDAP_FREE(sval); + *code = LDAP_SCHERR_NOLEFTPAREN; + ldap_syntax_free(syn); + return NULL; + } + + parse_whsp(&ss); + syn->syn_oid = ldap_int_parse_numericoid(&ss,code,0); + if ( !syn->syn_oid ) { + *errp = ss; + ldap_syntax_free(syn); + return NULL; + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal and accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_syntax_free(syn); + return NULL; + case TK_RIGHTPAREN: + return syn; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_syntax_free(syn); + return(NULL); + } + seen_name = 1; + syn->syn_names = parse_qdescrs(&ss,code); + if ( !syn->syn_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_syntax_free(syn); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_syntax_free(syn); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_syntax_free(syn); + return NULL; + } + syn->syn_desc = sval; + parse_whsp(&ss); + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + if ( !ext_vals ) { + *errp = ss; + ldap_syntax_free(syn); + return NULL; + } + if ( add_extension(&syn->syn_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_syntax_free(syn); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_syntax_free(syn); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_syntax_free(syn); + return NULL; + } + } +} + +void +ldap_matchingrule_free( LDAPMatchingRule * mr ) +{ + if (!mr) return; + LDAP_FREE(mr->mr_oid); + if (mr->mr_names) LDAP_VFREE(mr->mr_names); + if (mr->mr_desc) LDAP_FREE(mr->mr_desc); + if (mr->mr_syntax_oid) LDAP_FREE(mr->mr_syntax_oid); + free_extensions(mr->mr_extensions); + LDAP_FREE(mr); +} + +LDAPMatchingRule * +ldap_str2matchingrule( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_syntax = 0; + LDAPMatchingRule * mr; + char ** ext_vals; + const char * savepos; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + mr = LDAP_CALLOC(1,sizeof(LDAPMatchingRule)); + + if ( !mr ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = LDAP_SCHERR_NOLEFTPAREN; + LDAP_FREE(sval); + ldap_matchingrule_free(mr); + return NULL; + } + + parse_whsp(&ss); + savepos = ss; + mr->mr_oid = ldap_int_parse_numericoid(&ss,code,flags); + if ( !mr->mr_oid ) { + if ( flags & LDAP_SCHEMA_ALLOW_NO_OID ) { + /* Backtracking */ + ss = savepos; + kind = get_token(&ss,&sval); + if ( kind == TK_BAREWORD ) { + if ( !strcasecmp(sval, "NAME") || + !strcasecmp(sval, "DESC") || + !strcasecmp(sval, "OBSOLETE") || + !strcasecmp(sval, "SYNTAX") || + !strncasecmp(sval, "X-", 2) ) { + /* Missing OID, backtrack */ + ss = savepos; + } else { + /* Non-numerical OID, ignore */ + } + } + LDAP_FREE(sval); + } else { + *errp = ss; + ldap_matchingrule_free(mr); + return NULL; + } + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal and accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_matchingrule_free(mr); + return NULL; + case TK_RIGHTPAREN: + if( !seen_syntax ) { + *code = LDAP_SCHERR_MISSING; + ldap_matchingrule_free(mr); + return NULL; + } + return mr; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingrule_free(mr); + return(NULL); + } + seen_name = 1; + mr->mr_names = parse_qdescrs(&ss,code); + if ( !mr->mr_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_matchingrule_free(mr); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingrule_free(mr); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingrule_free(mr); + return NULL; + } + mr->mr_desc = sval; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OBSOLETE") ) { + LDAP_FREE(sval); + if ( seen_obsolete ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingrule_free(mr); + return(NULL); + } + seen_obsolete = 1; + mr->mr_obsolete = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"SYNTAX") ) { + LDAP_FREE(sval); + if ( seen_syntax ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingrule_free(mr); + return(NULL); + } + seen_syntax = 1; + parse_whsp(&ss); + mr->mr_syntax_oid = + ldap_int_parse_numericoid(&ss,code,flags); + if ( !mr->mr_syntax_oid ) { + *errp = ss; + ldap_matchingrule_free(mr); + return NULL; + } + parse_whsp(&ss); + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + if ( !ext_vals ) { + *errp = ss; + ldap_matchingrule_free(mr); + return NULL; + } + if ( add_extension(&mr->mr_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingrule_free(mr); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingrule_free(mr); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingrule_free(mr); + return NULL; + } + } +} + +void +ldap_matchingruleuse_free( LDAPMatchingRuleUse * mru ) +{ + if (!mru) return; + LDAP_FREE(mru->mru_oid); + if (mru->mru_names) LDAP_VFREE(mru->mru_names); + if (mru->mru_desc) LDAP_FREE(mru->mru_desc); + if (mru->mru_applies_oids) LDAP_VFREE(mru->mru_applies_oids); + free_extensions(mru->mru_extensions); + LDAP_FREE(mru); +} + +LDAPMatchingRuleUse * +ldap_str2matchingruleuse( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_applies = 0; + LDAPMatchingRuleUse * mru; + char ** ext_vals; + const char * savepos; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + mru = LDAP_CALLOC(1,sizeof(LDAPMatchingRuleUse)); + + if ( !mru ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = LDAP_SCHERR_NOLEFTPAREN; + LDAP_FREE(sval); + ldap_matchingruleuse_free(mru); + return NULL; + } + + parse_whsp(&ss); + savepos = ss; + mru->mru_oid = ldap_int_parse_numericoid(&ss,code,flags); + if ( !mru->mru_oid ) { + if ( flags & LDAP_SCHEMA_ALLOW_NO_OID ) { + /* Backtracking */ + ss = savepos; + kind = get_token(&ss,&sval); + if ( kind == TK_BAREWORD ) { + if ( !strcasecmp(sval, "NAME") || + !strcasecmp(sval, "DESC") || + !strcasecmp(sval, "OBSOLETE") || + !strcasecmp(sval, "APPLIES") || + !strncasecmp(sval, "X-", 2) ) { + /* Missing OID, backtrack */ + ss = savepos; + } else { + /* Non-numerical OID, ignore */ + } + } + LDAP_FREE(sval); + } else { + *errp = ss; + ldap_matchingruleuse_free(mru); + return NULL; + } + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal and accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_matchingruleuse_free(mru); + return NULL; + case TK_RIGHTPAREN: + if( !seen_applies ) { + *code = LDAP_SCHERR_MISSING; + ldap_matchingruleuse_free(mru); + return NULL; + } + return mru; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingruleuse_free(mru); + return(NULL); + } + seen_name = 1; + mru->mru_names = parse_qdescrs(&ss,code); + if ( !mru->mru_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_matchingruleuse_free(mru); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingruleuse_free(mru); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingruleuse_free(mru); + return NULL; + } + mru->mru_desc = sval; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OBSOLETE") ) { + LDAP_FREE(sval); + if ( seen_obsolete ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingruleuse_free(mru); + return(NULL); + } + seen_obsolete = 1; + mru->mru_obsolete = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"APPLIES") ) { + LDAP_FREE(sval); + if ( seen_applies ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_matchingruleuse_free(mru); + return(NULL); + } + seen_applies = 1; + mru->mru_applies_oids = parse_oids(&ss, + code, + flags); + if ( !mru->mru_applies_oids && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_matchingruleuse_free(mru); + return NULL; + } + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + if ( !ext_vals ) { + *errp = ss; + ldap_matchingruleuse_free(mru); + return NULL; + } + if ( add_extension(&mru->mru_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingruleuse_free(mru); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingruleuse_free(mru); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_matchingruleuse_free(mru); + return NULL; + } + } +} + +void +ldap_attributetype_free(LDAPAttributeType * at) +{ + if (!at) return; + LDAP_FREE(at->at_oid); + if (at->at_names) LDAP_VFREE(at->at_names); + if (at->at_desc) LDAP_FREE(at->at_desc); + if (at->at_sup_oid) LDAP_FREE(at->at_sup_oid); + if (at->at_equality_oid) LDAP_FREE(at->at_equality_oid); + if (at->at_ordering_oid) LDAP_FREE(at->at_ordering_oid); + if (at->at_substr_oid) LDAP_FREE(at->at_substr_oid); + if (at->at_syntax_oid) LDAP_FREE(at->at_syntax_oid); + free_extensions(at->at_extensions); + LDAP_FREE(at); +} + +LDAPAttributeType * +ldap_str2attributetype( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_sup = 0; + int seen_equality = 0; + int seen_ordering = 0; + int seen_substr = 0; + int seen_syntax = 0; + int seen_usage = 0; + LDAPAttributeType * at; + char ** ext_vals; + const char * savepos; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + at = LDAP_CALLOC(1,sizeof(LDAPAttributeType)); + + if ( !at ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = LDAP_SCHERR_NOLEFTPAREN; + LDAP_FREE(sval); + ldap_attributetype_free(at); + return NULL; + } + + /* + * Definitions MUST begin with an OID in the numericoid format. + * However, this routine is used by clients to parse the response + * from servers and very well known servers will provide an OID + * in the wrong format or even no OID at all. We do our best to + * extract info from those servers. + */ + parse_whsp(&ss); + savepos = ss; + at->at_oid = ldap_int_parse_numericoid(&ss,code,0); + if ( !at->at_oid ) { + if ( ( flags & ( LDAP_SCHEMA_ALLOW_NO_OID + | LDAP_SCHEMA_ALLOW_OID_MACRO ) ) + && (ss == savepos) ) + { + /* Backtracking */ + ss = savepos; + kind = get_token(&ss,&sval); + if ( kind == TK_BAREWORD ) { + if ( !strcasecmp(sval, "NAME") || + !strcasecmp(sval, "DESC") || + !strcasecmp(sval, "OBSOLETE") || + !strcasecmp(sval, "SUP") || + !strcasecmp(sval, "EQUALITY") || + !strcasecmp(sval, "ORDERING") || + !strcasecmp(sval, "SUBSTR") || + !strcasecmp(sval, "SYNTAX") || + !strcasecmp(sval, "SINGLE-VALUE") || + !strcasecmp(sval, "COLLECTIVE") || + !strcasecmp(sval, "NO-USER-MODIFICATION") || + !strcasecmp(sval, "USAGE") || + !strncasecmp(sval, "X-", 2) ) + { + /* Missing OID, backtrack */ + ss = savepos; + } else if ( flags + & LDAP_SCHEMA_ALLOW_OID_MACRO) + { + /* Non-numerical OID ... */ + int len = ss-savepos; + at->at_oid = LDAP_MALLOC(len+1); + strncpy(at->at_oid, savepos, len); + at->at_oid[len] = 0; + } + } + LDAP_FREE(sval); + } else { + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal and accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_attributetype_free(at); + return NULL; + case TK_RIGHTPAREN: + return at; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_name = 1; + at->at_names = parse_qdescrs(&ss,code); + if ( !at->at_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_attributetype_free(at); + return NULL; + } + at->at_desc = sval; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OBSOLETE") ) { + LDAP_FREE(sval); + if ( seen_obsolete ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_obsolete = 1; + at->at_obsolete = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"SUP") ) { + LDAP_FREE(sval); + if ( seen_sup ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_sup = 1; + at->at_sup_oid = parse_woid(&ss,code); + if ( !at->at_sup_oid ) { + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } else if ( !strcasecmp(sval,"EQUALITY") ) { + LDAP_FREE(sval); + if ( seen_equality ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_equality = 1; + at->at_equality_oid = parse_woid(&ss,code); + if ( !at->at_equality_oid ) { + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } else if ( !strcasecmp(sval,"ORDERING") ) { + LDAP_FREE(sval); + if ( seen_ordering ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_ordering = 1; + at->at_ordering_oid = parse_woid(&ss,code); + if ( !at->at_ordering_oid ) { + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } else if ( !strcasecmp(sval,"SUBSTR") ) { + LDAP_FREE(sval); + if ( seen_substr ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_substr = 1; + at->at_substr_oid = parse_woid(&ss,code); + if ( !at->at_substr_oid ) { + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } else if ( !strcasecmp(sval,"SYNTAX") ) { + LDAP_FREE(sval); + if ( seen_syntax ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_syntax = 1; + parse_whsp(&ss); + savepos = ss; + at->at_syntax_oid = + parse_noidlen(&ss, + code, + &at->at_syntax_len, + flags); + if ( !at->at_syntax_oid ) { + if ( flags & LDAP_SCHEMA_ALLOW_OID_MACRO ) { + kind = get_token(&ss,&sval); + if (kind == TK_BAREWORD) + { + char *sp = strchr(sval, '{'); + at->at_syntax_oid = sval; + if (sp) + { + *sp++ = 0; + at->at_syntax_len = atoi(sp); + while ( LDAP_DIGIT(*sp) ) + sp++; + if ( *sp != '}' ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } + } + } else { + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + } + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"SINGLE-VALUE") ) { + LDAP_FREE(sval); + if ( at->at_single_value ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + at->at_single_value = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"COLLECTIVE") ) { + LDAP_FREE(sval); + if ( at->at_collective ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + at->at_collective = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"NO-USER-MODIFICATION") ) { + LDAP_FREE(sval); + if ( at->at_no_user_mod ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + at->at_no_user_mod = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"USAGE") ) { + LDAP_FREE(sval); + if ( seen_usage ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_attributetype_free(at); + return(NULL); + } + seen_usage = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_BAREWORD ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_attributetype_free(at); + return NULL; + } + if ( !strcasecmp(sval,"userApplications") ) + at->at_usage = + LDAP_SCHEMA_USER_APPLICATIONS; + else if ( !strcasecmp(sval,"directoryOperation") ) + at->at_usage = + LDAP_SCHEMA_DIRECTORY_OPERATION; + else if ( !strcasecmp(sval,"distributedOperation") ) + at->at_usage = + LDAP_SCHEMA_DISTRIBUTED_OPERATION; + else if ( !strcasecmp(sval,"dSAOperation") ) + at->at_usage = + LDAP_SCHEMA_DSA_OPERATION; + else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_attributetype_free(at); + return NULL; + } + LDAP_FREE(sval); + parse_whsp(&ss); + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + if ( !ext_vals ) { + *errp = ss; + ldap_attributetype_free(at); + return NULL; + } + if ( add_extension(&at->at_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_attributetype_free(at); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_attributetype_free(at); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_attributetype_free(at); + return NULL; + } + } +} + +void +ldap_objectclass_free(LDAPObjectClass * oc) +{ + if (!oc) return; + LDAP_FREE(oc->oc_oid); + if (oc->oc_names) LDAP_VFREE(oc->oc_names); + if (oc->oc_desc) LDAP_FREE(oc->oc_desc); + if (oc->oc_sup_oids) LDAP_VFREE(oc->oc_sup_oids); + if (oc->oc_at_oids_must) LDAP_VFREE(oc->oc_at_oids_must); + if (oc->oc_at_oids_may) LDAP_VFREE(oc->oc_at_oids_may); + free_extensions(oc->oc_extensions); + LDAP_FREE(oc); +} + +LDAPObjectClass * +ldap_str2objectclass( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_sup = 0; + int seen_kind = 0; + int seen_must = 0; + int seen_may = 0; + LDAPObjectClass * oc; + char ** ext_vals; + const char * savepos; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + oc = LDAP_CALLOC(1,sizeof(LDAPObjectClass)); + + if ( !oc ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + oc->oc_kind = LDAP_SCHEMA_STRUCTURAL; + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = LDAP_SCHERR_NOLEFTPAREN; + LDAP_FREE(sval); + ldap_objectclass_free(oc); + return NULL; + } + + /* + * Definitions MUST begin with an OID in the numericoid format. + * However, this routine is used by clients to parse the response + * from servers and very well known servers will provide an OID + * in the wrong format or even no OID at all. We do our best to + * extract info from those servers. + */ + parse_whsp(&ss); + savepos = ss; + oc->oc_oid = ldap_int_parse_numericoid(&ss,code,0); + if ( !oc->oc_oid ) { + if ( (flags & LDAP_SCHEMA_ALLOW_ALL) && (ss == savepos) ) { + /* Backtracking */ + ss = savepos; + kind = get_token(&ss,&sval); + if ( kind == TK_BAREWORD ) { + if ( !strcasecmp(sval, "NAME") || + !strcasecmp(sval, "DESC") || + !strcasecmp(sval, "OBSOLETE") || + !strcasecmp(sval, "SUP") || + !strcasecmp(sval, "ABSTRACT") || + !strcasecmp(sval, "STRUCTURAL") || + !strcasecmp(sval, "AUXILIARY") || + !strcasecmp(sval, "MUST") || + !strcasecmp(sval, "MAY") || + !strncasecmp(sval, "X-", 2) ) { + /* Missing OID, backtrack */ + ss = savepos; + } else if ( flags & + LDAP_SCHEMA_ALLOW_OID_MACRO ) { + /* Non-numerical OID, ignore */ + int len = ss-savepos; + oc->oc_oid = LDAP_MALLOC(len+1); + strncpy(oc->oc_oid, savepos, len); + oc->oc_oid[len] = 0; + } + } + LDAP_FREE(sval); + *code = 0; + } else { + *errp = ss; + ldap_objectclass_free(oc); + return NULL; + } + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal an accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_objectclass_free(oc); + return NULL; + case TK_RIGHTPAREN: + return oc; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_name = 1; + oc->oc_names = parse_qdescrs(&ss,code); + if ( !oc->oc_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_objectclass_free(oc); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_objectclass_free(oc); + return NULL; + } + oc->oc_desc = sval; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OBSOLETE") ) { + LDAP_FREE(sval); + if ( seen_obsolete ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_obsolete = 1; + oc->oc_obsolete = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"SUP") ) { + LDAP_FREE(sval); + if ( seen_sup ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_sup = 1; + oc->oc_sup_oids = parse_oids(&ss, + code, + flags); + if ( !oc->oc_sup_oids && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_objectclass_free(oc); + return NULL; + } + *code = 0; + } else if ( !strcasecmp(sval,"ABSTRACT") ) { + LDAP_FREE(sval); + if ( seen_kind ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_kind = 1; + oc->oc_kind = LDAP_SCHEMA_ABSTRACT; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"STRUCTURAL") ) { + LDAP_FREE(sval); + if ( seen_kind ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_kind = 1; + oc->oc_kind = LDAP_SCHEMA_STRUCTURAL; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"AUXILIARY") ) { + LDAP_FREE(sval); + if ( seen_kind ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_kind = 1; + oc->oc_kind = LDAP_SCHEMA_AUXILIARY; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"MUST") ) { + LDAP_FREE(sval); + if ( seen_must ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_must = 1; + oc->oc_at_oids_must = parse_oids(&ss,code,0); + if ( !oc->oc_at_oids_must && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_objectclass_free(oc); + return NULL; + } + *code = 0; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"MAY") ) { + LDAP_FREE(sval); + if ( seen_may ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_objectclass_free(oc); + return(NULL); + } + seen_may = 1; + oc->oc_at_oids_may = parse_oids(&ss,code,0); + if ( !oc->oc_at_oids_may && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_objectclass_free(oc); + return NULL; + } + *code = 0; + parse_whsp(&ss); + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + *code = 0; + if ( !ext_vals ) { + *errp = ss; + ldap_objectclass_free(oc); + return NULL; + } + if ( add_extension(&oc->oc_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_objectclass_free(oc); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_objectclass_free(oc); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_objectclass_free(oc); + return NULL; + } + } +} + +void +ldap_contentrule_free(LDAPContentRule * cr) +{ + if (!cr) return; + LDAP_FREE(cr->cr_oid); + if (cr->cr_names) LDAP_VFREE(cr->cr_names); + if (cr->cr_desc) LDAP_FREE(cr->cr_desc); + if (cr->cr_oc_oids_aux) LDAP_VFREE(cr->cr_oc_oids_aux); + if (cr->cr_at_oids_must) LDAP_VFREE(cr->cr_at_oids_must); + if (cr->cr_at_oids_may) LDAP_VFREE(cr->cr_at_oids_may); + if (cr->cr_at_oids_not) LDAP_VFREE(cr->cr_at_oids_not); + free_extensions(cr->cr_extensions); + LDAP_FREE(cr); +} + +LDAPContentRule * +ldap_str2contentrule( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_aux = 0; + int seen_must = 0; + int seen_may = 0; + int seen_not = 0; + LDAPContentRule * cr; + char ** ext_vals; + const char * savepos; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + cr = LDAP_CALLOC(1,sizeof(LDAPContentRule)); + + if ( !cr ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = LDAP_SCHERR_NOLEFTPAREN; + LDAP_FREE(sval); + ldap_contentrule_free(cr); + return NULL; + } + + /* + * Definitions MUST begin with an OID in the numericoid format. + */ + parse_whsp(&ss); + savepos = ss; + cr->cr_oid = ldap_int_parse_numericoid(&ss,code,0); + if ( !cr->cr_oid ) { + if ( (flags & LDAP_SCHEMA_ALLOW_ALL) && (ss == savepos) ) { + /* Backtracking */ + ss = savepos; + kind = get_token(&ss,&sval); + if ( kind == TK_BAREWORD ) { + if ( !strcasecmp(sval, "NAME") || + !strcasecmp(sval, "DESC") || + !strcasecmp(sval, "OBSOLETE") || + !strcasecmp(sval, "AUX") || + !strcasecmp(sval, "MUST") || + !strcasecmp(sval, "MAY") || + !strcasecmp(sval, "NOT") || + !strncasecmp(sval, "X-", 2) ) { + /* Missing OID, backtrack */ + ss = savepos; + } else if ( flags & + LDAP_SCHEMA_ALLOW_OID_MACRO ) { + /* Non-numerical OID, ignore */ + int len = ss-savepos; + cr->cr_oid = LDAP_MALLOC(len+1); + strncpy(cr->cr_oid, savepos, len); + cr->cr_oid[len] = 0; + } + } + LDAP_FREE(sval); + } else { + *errp = ss; + ldap_contentrule_free(cr); + return NULL; + } + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal an accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_contentrule_free(cr); + return NULL; + case TK_RIGHTPAREN: + return cr; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_contentrule_free(cr); + return(NULL); + } + seen_name = 1; + cr->cr_names = parse_qdescrs(&ss,code); + if ( !cr->cr_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_contentrule_free(cr); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_contentrule_free(cr); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_contentrule_free(cr); + return NULL; + } + cr->cr_desc = sval; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OBSOLETE") ) { + LDAP_FREE(sval); + if ( seen_obsolete ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_contentrule_free(cr); + return(NULL); + } + seen_obsolete = 1; + cr->cr_obsolete = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"AUX") ) { + LDAP_FREE(sval); + if ( seen_aux ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_contentrule_free(cr); + return(NULL); + } + seen_aux = 1; + cr->cr_oc_oids_aux = parse_oids(&ss,code,0); + if ( !cr->cr_oc_oids_aux ) { + *errp = ss; + ldap_contentrule_free(cr); + return NULL; + } + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"MUST") ) { + LDAP_FREE(sval); + if ( seen_must ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_contentrule_free(cr); + return(NULL); + } + seen_must = 1; + cr->cr_at_oids_must = parse_oids(&ss,code,0); + if ( !cr->cr_at_oids_must && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_contentrule_free(cr); + return NULL; + } + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"MAY") ) { + LDAP_FREE(sval); + if ( seen_may ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_contentrule_free(cr); + return(NULL); + } + seen_may = 1; + cr->cr_at_oids_may = parse_oids(&ss,code,0); + if ( !cr->cr_at_oids_may && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_contentrule_free(cr); + return NULL; + } + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"NOT") ) { + LDAP_FREE(sval); + if ( seen_not ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_contentrule_free(cr); + return(NULL); + } + seen_not = 1; + cr->cr_at_oids_not = parse_oids(&ss,code,0); + if ( !cr->cr_at_oids_not && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_contentrule_free(cr); + return NULL; + } + parse_whsp(&ss); + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + if ( !ext_vals ) { + *errp = ss; + ldap_contentrule_free(cr); + return NULL; + } + if ( add_extension(&cr->cr_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_contentrule_free(cr); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_contentrule_free(cr); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_contentrule_free(cr); + return NULL; + } + } +} + +void +ldap_structurerule_free(LDAPStructureRule * sr) +{ + if (!sr) return; + if (sr->sr_names) LDAP_VFREE(sr->sr_names); + if (sr->sr_desc) LDAP_FREE(sr->sr_desc); + if (sr->sr_nameform) LDAP_FREE(sr->sr_nameform); + if (sr->sr_sup_ruleids) LDAP_FREE(sr->sr_sup_ruleids); + free_extensions(sr->sr_extensions); + LDAP_FREE(sr); +} + +LDAPStructureRule * +ldap_str2structurerule( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + int ret; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_nameform = 0; + LDAPStructureRule * sr; + char ** ext_vals; + const char * savepos; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + sr = LDAP_CALLOC(1,sizeof(LDAPStructureRule)); + + if ( !sr ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = LDAP_SCHERR_NOLEFTPAREN; + LDAP_FREE(sval); + ldap_structurerule_free(sr); + return NULL; + } + + /* + * Definitions MUST begin with a ruleid. + */ + parse_whsp(&ss); + savepos = ss; + ret = ldap_int_parse_ruleid(&ss,code,0,&sr->sr_ruleid); + if ( ret ) { + *errp = ss; + ldap_structurerule_free(sr); + return NULL; + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal an accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_structurerule_free(sr); + return NULL; + case TK_RIGHTPAREN: + if( !seen_nameform ) { + *code = LDAP_SCHERR_MISSING; + ldap_structurerule_free(sr); + return NULL; + } + return sr; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_structurerule_free(sr); + return(NULL); + } + seen_name = 1; + sr->sr_names = parse_qdescrs(&ss,code); + if ( !sr->sr_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_structurerule_free(sr); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_structurerule_free(sr); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_structurerule_free(sr); + return NULL; + } + sr->sr_desc = sval; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OBSOLETE") ) { + LDAP_FREE(sval); + if ( seen_obsolete ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_structurerule_free(sr); + return(NULL); + } + seen_obsolete = 1; + sr->sr_obsolete = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"FORM") ) { + LDAP_FREE(sval); + if ( seen_nameform ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_structurerule_free(sr); + return(NULL); + } + seen_nameform = 1; + sr->sr_nameform = parse_woid(&ss,code); + if ( !sr->sr_nameform ) { + *errp = ss; + ldap_structurerule_free(sr); + return NULL; + } + parse_whsp(&ss); + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + if ( !ext_vals ) { + *errp = ss; + ldap_structurerule_free(sr); + return NULL; + } + if ( add_extension(&sr->sr_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_structurerule_free(sr); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_structurerule_free(sr); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_structurerule_free(sr); + return NULL; + } + } +} + +void +ldap_nameform_free(LDAPNameForm * nf) +{ + if (!nf) return; + LDAP_FREE(nf->nf_oid); + if (nf->nf_names) LDAP_VFREE(nf->nf_names); + if (nf->nf_desc) LDAP_FREE(nf->nf_desc); + if (nf->nf_objectclass) LDAP_FREE(nf->nf_objectclass); + if (nf->nf_at_oids_must) LDAP_VFREE(nf->nf_at_oids_must); + if (nf->nf_at_oids_may) LDAP_VFREE(nf->nf_at_oids_may); + free_extensions(nf->nf_extensions); + LDAP_FREE(nf); +} + +LDAPNameForm * +ldap_str2nameform( LDAP_CONST char * s, + int * code, + LDAP_CONST char ** errp, + LDAP_CONST unsigned flags ) +{ + tk_t kind; + const char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_class = 0; + int seen_must = 0; + int seen_may = 0; + LDAPNameForm * nf; + char ** ext_vals; + const char * savepos; + + if ( !s ) { + *code = LDAP_SCHERR_EMPTY; + *errp = ""; + return NULL; + } + + *errp = s; + nf = LDAP_CALLOC(1,sizeof(LDAPNameForm)); + + if ( !nf ) { + *code = LDAP_SCHERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = LDAP_SCHERR_NOLEFTPAREN; + LDAP_FREE(sval); + ldap_nameform_free(nf); + return NULL; + } + + /* + * Definitions MUST begin with an OID in the numericoid format. + * However, this routine is used by clients to parse the response + * from servers and very well known servers will provide an OID + * in the wrong format or even no OID at all. We do our best to + * extract info from those servers. + */ + parse_whsp(&ss); + savepos = ss; + nf->nf_oid = ldap_int_parse_numericoid(&ss,code,0); + if ( !nf->nf_oid ) { + *errp = ss; + ldap_nameform_free(nf); + return NULL; + } + parse_whsp(&ss); + + /* + * Beyond this point we will be liberal an accept the items + * in any order. + */ + while (1) { + kind = get_token(&ss,&sval); + switch (kind) { + case TK_EOS: + *code = LDAP_SCHERR_NORIGHTPAREN; + *errp = EndOfInput; + ldap_nameform_free(nf); + return NULL; + case TK_RIGHTPAREN: + if( !seen_class || !seen_must ) { + *code = LDAP_SCHERR_MISSING; + ldap_nameform_free(nf); + return NULL; + } + return nf; + case TK_BAREWORD: + if ( !strcasecmp(sval,"NAME") ) { + LDAP_FREE(sval); + if ( seen_name ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_nameform_free(nf); + return(NULL); + } + seen_name = 1; + nf->nf_names = parse_qdescrs(&ss,code); + if ( !nf->nf_names ) { + if ( *code != LDAP_SCHERR_OUTOFMEM ) + *code = LDAP_SCHERR_BADNAME; + *errp = ss; + ldap_nameform_free(nf); + return NULL; + } + } else if ( !strcasecmp(sval,"DESC") ) { + LDAP_FREE(sval); + if ( seen_desc ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_nameform_free(nf); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_nameform_free(nf); + return NULL; + } + nf->nf_desc = sval; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OBSOLETE") ) { + LDAP_FREE(sval); + if ( seen_obsolete ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_nameform_free(nf); + return(NULL); + } + seen_obsolete = 1; + nf->nf_obsolete = LDAP_SCHEMA_YES; + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"OC") ) { + LDAP_FREE(sval); + if ( seen_class ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_nameform_free(nf); + return(NULL); + } + seen_class = 1; + nf->nf_objectclass = parse_woid(&ss,code); + if ( !nf->nf_objectclass ) { + *errp = ss; + ldap_nameform_free(nf); + return NULL; + } + } else if ( !strcasecmp(sval,"MUST") ) { + LDAP_FREE(sval); + if ( seen_must ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_nameform_free(nf); + return(NULL); + } + seen_must = 1; + nf->nf_at_oids_must = parse_oids(&ss,code,0); + if ( !nf->nf_at_oids_must && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_nameform_free(nf); + return NULL; + } + parse_whsp(&ss); + } else if ( !strcasecmp(sval,"MAY") ) { + LDAP_FREE(sval); + if ( seen_may ) { + *code = LDAP_SCHERR_DUPOPT; + *errp = ss; + ldap_nameform_free(nf); + return(NULL); + } + seen_may = 1; + nf->nf_at_oids_may = parse_oids(&ss,code,0); + if ( !nf->nf_at_oids_may && *code != LDAP_SUCCESS ) { + *errp = ss; + ldap_nameform_free(nf); + return NULL; + } + parse_whsp(&ss); + } else if ( sval[0] == 'X' && sval[1] == '-' ) { + /* Should be parse_qdstrings */ + ext_vals = parse_qdescrs(&ss, code); + if ( !ext_vals ) { + *errp = ss; + ldap_nameform_free(nf); + return NULL; + } + if ( add_extension(&nf->nf_extensions, + sval, ext_vals) ) { + *code = LDAP_SCHERR_OUTOFMEM; + *errp = ss; + LDAP_FREE(sval); + ldap_nameform_free(nf); + return NULL; + } + } else { + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_nameform_free(nf); + return NULL; + } + break; + default: + *code = LDAP_SCHERR_UNEXPTOKEN; + *errp = ss; + LDAP_FREE(sval); + ldap_nameform_free(nf); + return NULL; + } + } +} + +static char *const err2text[] = { + N_("Success"), + N_("Out of memory"), + N_("Unexpected token"), + N_("Missing opening parenthesis"), + N_("Missing closing parenthesis"), + N_("Expecting digit"), + N_("Expecting a name"), + N_("Bad description"), + N_("Bad superiors"), + N_("Duplicate option"), + N_("Unexpected end of data"), + N_("Missing required field"), + N_("Out of order field") +}; + +char * +ldap_scherr2str(int code) +{ + if ( code < 0 || code >= (int)(sizeof(err2text)/sizeof(char *)) ) { + return _("Unknown error"); + } else { + return _(err2text[code]); + } +} diff --git a/libraries/libldap/search.c b/libraries/libldap/search.c new file mode 100644 index 0000000..d3582dc --- /dev/null +++ b/libraries/libldap/search.c @@ -0,0 +1,545 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +/* + * ldap_search_ext - initiate an ldap search operation. + * + * Parameters: + * + * ld LDAP descriptor + * base DN of the base object + * scope the search scope - one of + * LDAP_SCOPE_BASE (baseObject), + * LDAP_SCOPE_ONELEVEL (oneLevel), + * LDAP_SCOPE_SUBTREE (subtree), or + * LDAP_SCOPE_SUBORDINATE (children) -- OpenLDAP extension + * filter a string containing the search filter + * (e.g., "(|(cn=bob)(sn=bob))") + * attrs list of attribute types to return for matches + * attrsonly 1 => attributes only 0 => attributes and values + * + * Example: + * char *attrs[] = { "mail", "title", 0 }; + * ldap_search_ext( ld, "dc=example,dc=com", LDAP_SCOPE_SUBTREE, "cn~=bob", + * attrs, attrsonly, sctrls, ctrls, timeout, sizelimit, + * &msgid ); + */ +int +ldap_search_ext( + LDAP *ld, + LDAP_CONST char *base, + int scope, + LDAP_CONST char *filter, + char **attrs, + int attrsonly, + LDAPControl **sctrls, + LDAPControl **cctrls, + struct timeval *timeout, + int sizelimit, + int *msgidp ) +{ + return ldap_pvt_search( ld, base, scope, filter, attrs, + attrsonly, sctrls, cctrls, timeout, sizelimit, -1, msgidp ); +} + +int +ldap_pvt_search( + LDAP *ld, + LDAP_CONST char *base, + int scope, + LDAP_CONST char *filter, + char **attrs, + int attrsonly, + LDAPControl **sctrls, + LDAPControl **cctrls, + struct timeval *timeout, + int sizelimit, + int deref, + int *msgidp ) +{ + int rc; + BerElement *ber; + int timelimit; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_search_ext\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + /* + * if timeout is provided, both tv_sec and tv_usec must + * not be zero + */ + if( timeout != NULL ) { + if( timeout->tv_sec == 0 && timeout->tv_usec == 0 ) { + return LDAP_PARAM_ERROR; + } + + /* timelimit must be non-zero if timeout is provided */ + timelimit = timeout->tv_sec != 0 ? timeout->tv_sec : 1; + + } else { + /* no timeout, no timelimit */ + timelimit = -1; + } + + ber = ldap_build_search_req( ld, base, scope, filter, attrs, + attrsonly, sctrls, cctrls, timelimit, sizelimit, deref, &id ); + + if ( ber == NULL ) { + return ld->ld_errno; + } + + + /* send the message */ + *msgidp = ldap_send_initial_request( ld, LDAP_REQ_SEARCH, base, ber, id ); + + if( *msgidp < 0 ) + return ld->ld_errno; + + return LDAP_SUCCESS; +} + +int +ldap_search_ext_s( + LDAP *ld, + LDAP_CONST char *base, + int scope, + LDAP_CONST char *filter, + char **attrs, + int attrsonly, + LDAPControl **sctrls, + LDAPControl **cctrls, + struct timeval *timeout, + int sizelimit, + LDAPMessage **res ) +{ + return ldap_pvt_search_s( ld, base, scope, filter, attrs, + attrsonly, sctrls, cctrls, timeout, sizelimit, -1, res ); +} + +int +ldap_pvt_search_s( + LDAP *ld, + LDAP_CONST char *base, + int scope, + LDAP_CONST char *filter, + char **attrs, + int attrsonly, + LDAPControl **sctrls, + LDAPControl **cctrls, + struct timeval *timeout, + int sizelimit, + int deref, + LDAPMessage **res ) +{ + int rc; + int msgid; + + *res = NULL; + + rc = ldap_pvt_search( ld, base, scope, filter, attrs, attrsonly, + sctrls, cctrls, timeout, sizelimit, deref, &msgid ); + + if ( rc != LDAP_SUCCESS ) { + return( rc ); + } + + rc = ldap_result( ld, msgid, LDAP_MSG_ALL, timeout, res ); + + if( rc <= 0 ) { + /* error(-1) or timeout(0) */ + if ( ld->ld_errno == LDAP_TIMEOUT ) { + /* cleanup request */ + (void) ldap_abandon( ld, msgid ); + ld->ld_errno = LDAP_TIMEOUT; + } + return( ld->ld_errno ); + } + + if( rc == LDAP_RES_SEARCH_REFERENCE || rc == LDAP_RES_INTERMEDIATE ) { + return( ld->ld_errno ); + } + + return( ldap_result2error( ld, *res, 0 ) ); +} + +/* + * ldap_search - initiate an ldap search operation. + * + * Parameters: + * + * ld LDAP descriptor + * base DN of the base object + * scope the search scope - one of + * LDAP_SCOPE_BASE (baseObject), + * LDAP_SCOPE_ONELEVEL (oneLevel), + * LDAP_SCOPE_SUBTREE (subtree), or + * LDAP_SCOPE_SUBORDINATE (children) -- OpenLDAP extension + * filter a string containing the search filter + * (e.g., "(|(cn=bob)(sn=bob))") + * attrs list of attribute types to return for matches + * attrsonly 1 => attributes only 0 => attributes and values + * + * Example: + * char *attrs[] = { "mail", "title", 0 }; + * msgid = ldap_search( ld, "dc=example,dc=com", LDAP_SCOPE_SUBTREE, "cn~=bob", + * attrs, attrsonly ); + */ +int +ldap_search( + LDAP *ld, LDAP_CONST char *base, int scope, LDAP_CONST char *filter, + char **attrs, int attrsonly ) +{ + BerElement *ber; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_search\n", 0, 0, 0 ); + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + ber = ldap_build_search_req( ld, base, scope, filter, attrs, + attrsonly, NULL, NULL, -1, -1, -1, &id ); + + if ( ber == NULL ) { + return( -1 ); + } + + + /* send the message */ + return ( ldap_send_initial_request( ld, LDAP_REQ_SEARCH, base, ber, id )); +} + + +BerElement * +ldap_build_search_req( + LDAP *ld, + LDAP_CONST char *base, + ber_int_t scope, + LDAP_CONST char *filter, + char **attrs, + ber_int_t attrsonly, + LDAPControl **sctrls, + LDAPControl **cctrls, + ber_int_t timelimit, + ber_int_t sizelimit, + ber_int_t deref, + ber_int_t *idp) +{ + BerElement *ber; + int err; + + /* + * Create the search request. It looks like this: + * SearchRequest := [APPLICATION 3] SEQUENCE { + * baseObject DistinguishedName, + * scope ENUMERATED { + * baseObject (0), + * singleLevel (1), + * wholeSubtree (2) + * }, + * derefAliases ENUMERATED { + * neverDerefaliases (0), + * derefInSearching (1), + * derefFindingBaseObj (2), + * alwaysDerefAliases (3) + * }, + * sizelimit INTEGER (0 .. 65535), + * timelimit INTEGER (0 .. 65535), + * attrsOnly BOOLEAN, + * filter Filter, + * attributes SEQUENCE OF AttributeType + * } + * wrapped in an ldap message. + */ + + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( NULL ); + } + + if ( base == NULL ) { + /* no base provided, use session default base */ + base = ld->ld_options.ldo_defbase; + + if ( base == NULL ) { + /* no session default base, use top */ + base = ""; + } + } + + LDAP_NEXT_MSGID( ld, *idp ); +#ifdef LDAP_CONNECTIONLESS + if ( LDAP_IS_UDP(ld) ) { + struct sockaddr_storage sa = {0}; + /* dummy, filled with ldo_peer in request.c */ + err = ber_write( ber, (char *) &sa, sizeof( sa ), 0 ); + } + if ( LDAP_IS_UDP(ld) && ld->ld_options.ldo_version == LDAP_VERSION2) { + char *dn = ld->ld_options.ldo_cldapdn; + if (!dn) dn = ""; + err = ber_printf( ber, "{ist{seeiib", *idp, dn, + LDAP_REQ_SEARCH, base, (ber_int_t) scope, + (deref < 0) ? ld->ld_deref : deref, + (sizelimit < 0) ? ld->ld_sizelimit : sizelimit, + (timelimit < 0) ? ld->ld_timelimit : timelimit, + attrsonly ); + } else +#endif + { + err = ber_printf( ber, "{it{seeiib", *idp, + LDAP_REQ_SEARCH, base, (ber_int_t) scope, + (deref < 0) ? ld->ld_deref : deref, + (sizelimit < 0) ? ld->ld_sizelimit : sizelimit, + (timelimit < 0) ? ld->ld_timelimit : timelimit, + attrsonly ); + } + + if ( err == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + if( filter == NULL ) { + filter = "(objectclass=*)"; + } + + err = ldap_pvt_put_filter( ber, filter ); + + if ( err == -1 ) { + ld->ld_errno = LDAP_FILTER_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + +#ifdef LDAP_DEBUG + if ( ldap_debug & LDAP_DEBUG_ARGS ) { + char buf[ BUFSIZ ], *ptr = " *"; + + if ( attrs != NULL ) { + int i, len, rest = sizeof( buf ); + + for ( i = 0; attrs[ i ] != NULL && rest > 0; i++ ) { + ptr = &buf[ sizeof( buf ) - rest ]; + len = snprintf( ptr, rest, " %s", attrs[ i ] ); + rest -= (len >= 0 ? len : (int) sizeof( buf )); + } + + if ( rest <= 0 ) { + AC_MEMCPY( &buf[ sizeof( buf ) - STRLENOF( "...(truncated)" ) - 1 ], + "...(truncated)", STRLENOF( "...(truncated)" ) + 1 ); + } + ptr = buf; + } + + Debug( LDAP_DEBUG_ARGS, "ldap_build_search_req ATTRS:%s\n", ptr, 0,0 ); + } +#endif /* LDAP_DEBUG */ + + if ( ber_printf( ber, /*{*/ "{v}N}", attrs ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return( NULL ); + } + + if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( NULL ); + } + + return( ber ); +} + +int +ldap_search_st( + LDAP *ld, LDAP_CONST char *base, int scope, + LDAP_CONST char *filter, char **attrs, + int attrsonly, struct timeval *timeout, LDAPMessage **res ) +{ + int msgid; + + *res = NULL; + + if ( (msgid = ldap_search( ld, base, scope, filter, attrs, attrsonly )) + == -1 ) + return( ld->ld_errno ); + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, timeout, res ) == -1 || !*res ) + return( ld->ld_errno ); + + if ( ld->ld_errno == LDAP_TIMEOUT ) { + (void) ldap_abandon( ld, msgid ); + ld->ld_errno = LDAP_TIMEOUT; + return( ld->ld_errno ); + } + + return( ldap_result2error( ld, *res, 0 ) ); +} + +int +ldap_search_s( + LDAP *ld, + LDAP_CONST char *base, + int scope, + LDAP_CONST char *filter, + char **attrs, + int attrsonly, + LDAPMessage **res ) +{ + int msgid; + + *res = NULL; + + if ( (msgid = ldap_search( ld, base, scope, filter, attrs, attrsonly )) + == -1 ) + return( ld->ld_errno ); + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, res ) == -1 || !*res ) + return( ld->ld_errno ); + + return( ldap_result2error( ld, *res, 0 ) ); +} + +static char escape[128] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 +}; +#define NEEDFLTESCAPE(c) ((c) & 0x80 || escape[ (unsigned)(c) ]) + +/* + * compute the length of the escaped value + */ +ber_len_t +ldap_bv2escaped_filter_value_len( struct berval *in ) +{ + ber_len_t i, l; + + assert( in != NULL ); + + if ( in->bv_len == 0 ) { + return 0; + } + + for( l = 0, i = 0; i < in->bv_len; l++, i++ ) { + char c = in->bv_val[ i ]; + if ( NEEDFLTESCAPE( c ) ) { + l += 2; + } + } + + return l; +} + +int +ldap_bv2escaped_filter_value( struct berval *in, struct berval *out ) +{ + return ldap_bv2escaped_filter_value_x( in, out, 0, NULL ); +} + +int +ldap_bv2escaped_filter_value_x( struct berval *in, struct berval *out, int inplace, void *ctx ) +{ + ber_len_t i, l; + + assert( in != NULL ); + assert( out != NULL ); + + BER_BVZERO( out ); + + if ( in->bv_len == 0 ) { + return 0; + } + + /* assume we'll escape everything */ + l = ldap_bv2escaped_filter_value_len( in ); + if ( l == in->bv_len ) { + if ( inplace ) { + *out = *in; + } else { + ber_dupbv( out, in ); + } + return 0; + } + out->bv_val = LDAP_MALLOCX( l + 1, ctx ); + if ( out->bv_val == NULL ) { + return -1; + } + + for ( i = 0; i < in->bv_len; i++ ) { + char c = in->bv_val[ i ]; + if ( NEEDFLTESCAPE( c ) ) { + assert( out->bv_len < l - 2 ); + out->bv_val[out->bv_len++] = '\\'; + out->bv_val[out->bv_len++] = "0123456789ABCDEF"[0x0f & (c>>4)]; + out->bv_val[out->bv_len++] = "0123456789ABCDEF"[0x0f & c]; + + } else { + assert( out->bv_len < l ); + out->bv_val[out->bv_len++] = c; + } + } + + out->bv_val[out->bv_len] = '\0'; + + return 0; +} + diff --git a/libraries/libldap/sort.c b/libraries/libldap/sort.c new file mode 100644 index 0000000..9c4a74e --- /dev/null +++ b/libraries/libldap/sort.c @@ -0,0 +1,183 @@ +/* sort.c -- LDAP library entry and value sort routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1994 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/time.h> + + +#include "ldap-int.h" + +struct entrything { + char **et_vals; + LDAPMessage *et_msg; + int (*et_cmp_fn) LDAP_P((const char *a, const char *b)); +}; + +static int et_cmp LDAP_P(( const void *aa, const void *bb)); + + +int +ldap_sort_strcasecmp( + LDAP_CONST void *a, + LDAP_CONST void *b +) +{ + return( strcasecmp( *(char *const *)a, *(char *const *)b ) ); +} + +static int +et_cmp( + const void *aa, + const void *bb +) +{ + int i, rc; + const struct entrything *a = (const struct entrything *)aa; + const struct entrything *b = (const struct entrything *)bb; + + if ( a->et_vals == NULL && b->et_vals == NULL ) + return( 0 ); + if ( a->et_vals == NULL ) + return( -1 ); + if ( b->et_vals == NULL ) + return( 1 ); + + for ( i = 0; a->et_vals[i] && b->et_vals[i]; i++ ) { + if ( (rc = a->et_cmp_fn( a->et_vals[i], b->et_vals[i] )) != 0 ) { + return( rc ); + } + } + + if ( a->et_vals[i] == NULL && b->et_vals[i] == NULL ) + return( 0 ); + if ( a->et_vals[i] == NULL ) + return( -1 ); + return( 1 ); +} + +int +ldap_sort_entries( + LDAP *ld, + LDAPMessage **chain, + LDAP_CONST char *attr, /* NULL => sort by DN */ + int (*cmp) (LDAP_CONST char *, LDAP_CONST char *) +) +{ + int i, count = 0; + struct entrything *et; + LDAPMessage *e, *ehead = NULL, *etail = NULL; + LDAPMessage *ohead = NULL, *otail = NULL; + LDAPMessage **ep; + + assert( ld != NULL ); + + /* Separate entries from non-entries */ + for ( e = *chain; e; e=e->lm_chain ) { + if ( e->lm_msgtype == LDAP_RES_SEARCH_ENTRY ) { + count++; + if ( !ehead ) ehead = e; + if ( etail ) etail->lm_chain = e; + etail = e; + } else { + if ( !ohead ) ohead = e; + if ( otail ) otail->lm_chain = e; + otail = e; + } + } + + if ( count < 2 ) { + /* zero or one entries -- already sorted! */ + if ( ehead ) { + etail->lm_chain = ohead; + *chain = ehead; + } else { + *chain = ohead; + } + return 0; + } + + if ( (et = (struct entrything *) LDAP_MALLOC( count * + sizeof(struct entrything) )) == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return( -1 ); + } + + e = ehead; + for ( i = 0; i < count; i++ ) { + et[i].et_cmp_fn = cmp; + et[i].et_msg = e; + if ( attr == NULL ) { + char *dn; + + dn = ldap_get_dn( ld, e ); + et[i].et_vals = ldap_explode_dn( dn, 1 ); + LDAP_FREE( dn ); + } else { + et[i].et_vals = ldap_get_values( ld, e, attr ); + } + + e = e->lm_chain; + } + + qsort( et, count, sizeof(struct entrything), et_cmp ); + + ep = chain; + for ( i = 0; i < count; i++ ) { + *ep = et[i].et_msg; + ep = &(*ep)->lm_chain; + + LDAP_VFREE( et[i].et_vals ); + } + *ep = ohead; + (*chain)->lm_chain_tail = otail ? otail : etail; + + LDAP_FREE( (char *) et ); + + return( 0 ); +} + +int +ldap_sort_values( + LDAP *ld, + char **vals, + int (*cmp) (LDAP_CONST void *, LDAP_CONST void *) +) +{ + int nel; + + for ( nel = 0; vals[nel] != NULL; nel++ ) + ; /* NULL */ + + qsort( vals, nel, sizeof(char *), cmp ); + + return( 0 ); +} diff --git a/libraries/libldap/sortctrl.c b/libraries/libldap/sortctrl.c new file mode 100644 index 0000000..e345f64 --- /dev/null +++ b/libraries/libldap/sortctrl.c @@ -0,0 +1,552 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (C) 1999, 2000 Novell, Inc. All Rights Reserved. + * + * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND + * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT + * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS + * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" + * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION + * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP + * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT + * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. + */ +/* Note: A verbatim copy of version 2.0.1 of the OpenLDAP Public License + * can be found in the file "build/LICENSE-2.0.1" in this distribution + * of OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +#define LDAP_MATCHRULE_IDENTIFIER 0x80L +#define LDAP_REVERSEORDER_IDENTIFIER 0x81L +#define LDAP_ATTRTYPES_IDENTIFIER 0x80L + + + +/* --------------------------------------------------------------------------- + countKeys + + Internal function to determine the number of keys in the string. + + keyString (IN) String of items separated by whitespace. + ---------------------------------------------------------------------------*/ + +static int countKeys(char *keyString) +{ + char *p = keyString; + int count = 0; + + for (;;) + { + while (LDAP_SPACE(*p)) /* Skip leading whitespace */ + p++; + + if (*p == '\0') /* End of string? */ + return count; + + count++; /* Found start of a key */ + + while (!LDAP_SPACE(*p)) /* Skip till next space or end of string. */ + if (*p++ == '\0') + return count; + } +} + + +/* --------------------------------------------------------------------------- + readNextKey + + Internal function to parse the next sort key in the string. + Allocate an LDAPSortKey structure and initialize it with + attribute name, reverse flag, and matching rule OID. + + Each sort key in the string has the format: + [whitespace][-]attribute[:[OID]] + + pNextKey (IN/OUT) Points to the next key in the sortkey string to parse. + The pointer is updated to point to the next character + after the sortkey being parsed. + + key (OUT) Points to the address of an LDAPSortKey stucture + which has been allocated by this routine and + initialized with information from the next sortkey. + ---------------------------------------------------------------------------*/ + +static int readNextKey( char **pNextKey, LDAPSortKey **key) +{ + char *p = *pNextKey; + int rev = 0; + char *attrStart; + int attrLen; + char *oidStart = NULL; + int oidLen = 0; + + /* Skip leading white space. */ + while (LDAP_SPACE(*p)) + p++; + + if (*p == '-') /* Check if the reverse flag is present. */ + { + rev=1; + p++; + } + + /* We're now positioned at the start of the attribute. */ + attrStart = p; + + /* Get the length of the attribute until the next whitespace or ":". */ + attrLen = strcspn(p, " \t:"); + p += attrLen; + + if (attrLen == 0) /* If no attribute name was present, quit. */ + return LDAP_PARAM_ERROR; + + if (*p == ':') + { + oidStart = ++p; /* Start of the OID, after the colon */ + oidLen = strcspn(p, " \t"); /* Get length of OID till next whitespace */ + p += oidLen; + } + + *pNextKey = p; /* Update argument to point to next key */ + + /* Allocate an LDAPSortKey structure */ + *key = LDAP_MALLOC(sizeof(LDAPSortKey)); + if (*key == NULL) return LDAP_NO_MEMORY; + + /* Allocate memory for the attribute and copy to it. */ + (*key)->attributeType = LDAP_MALLOC(attrLen+1); + if ((*key)->attributeType == NULL) { + LDAP_FREE(*key); + return LDAP_NO_MEMORY; + } + + strncpy((*key)->attributeType, attrStart, attrLen); + (*key)->attributeType[attrLen] = 0; + + /* If present, allocate memory for the OID and copy to it. */ + if (oidLen) { + (*key)->orderingRule = LDAP_MALLOC(oidLen+1); + if ((*key)->orderingRule == NULL) { + LDAP_FREE((*key)->attributeType); + LDAP_FREE(*key); + return LDAP_NO_MEMORY; + } + strncpy((*key)->orderingRule, oidStart, oidLen); + (*key)->orderingRule[oidLen] = 0; + + } else { + (*key)->orderingRule = NULL; + } + + (*key)->reverseOrder = rev; + + return LDAP_SUCCESS; +} + + +/* --------------------------------------------------------------------------- + ldap_create_sort_keylist + + Create an array of pointers to LDAPSortKey structures, containing the + information specified by the string representation of one or more + sort keys. + + sortKeyList (OUT) Points to a null-terminated array of pointers to + LDAPSortKey structures allocated by this routine. + This memory SHOULD be freed by the calling program + using ldap_free_sort_keylist(). + + keyString (IN) Points to a string of one or more sort keys. + + ---------------------------------------------------------------------------*/ + +int +ldap_create_sort_keylist ( LDAPSortKey ***sortKeyList, char *keyString ) +{ + int numKeys, rc, i; + char *nextKey; + LDAPSortKey **keyList = NULL; + + assert( sortKeyList != NULL ); + assert( keyString != NULL ); + + *sortKeyList = NULL; + + /* Determine the number of sort keys so we can allocate memory. */ + if (( numKeys = countKeys(keyString)) == 0) { + return LDAP_PARAM_ERROR; + } + + /* Allocate the array of pointers. Initialize to NULL. */ + keyList=(LDAPSortKey**)LBER_CALLOC(numKeys+1, sizeof(LDAPSortKey*)); + if ( keyList == NULL) return LDAP_NO_MEMORY; + + /* For each sort key in the string, create an LDAPSortKey structure + and add it to the list. + */ + nextKey = keyString; /* Points to the next key in the string */ + for (i=0; i < numKeys; i++) { + rc = readNextKey(&nextKey, &keyList[i]); + + if (rc != LDAP_SUCCESS) { + ldap_free_sort_keylist(keyList); + return rc; + } + } + + *sortKeyList = keyList; + return LDAP_SUCCESS; +} + + +/* --------------------------------------------------------------------------- + ldap_free_sort_keylist + + Frees the sort key structures created by ldap_create_sort_keylist(). + Frees the memory referenced by the LDAPSortKey structures, + the LDAPSortKey structures themselves, and the array of pointers + to the structures. + + keyList (IN) Points to an array of pointers to LDAPSortKey structures. + ---------------------------------------------------------------------------*/ + +void +ldap_free_sort_keylist ( LDAPSortKey **keyList ) +{ + int i; + LDAPSortKey *nextKeyp; + + if (keyList == NULL) return; + + i=0; + while ( 0 != (nextKeyp = keyList[i++]) ) { + if (nextKeyp->attributeType) { + LBER_FREE(nextKeyp->attributeType); + } + + if (nextKeyp->orderingRule != NULL) { + LBER_FREE(nextKeyp->orderingRule); + } + + LBER_FREE(nextKeyp); + } + + LBER_FREE(keyList); +} + + +/* --------------------------------------------------------------------------- + ldap_create_sort_control_value + + Create and encode the value of the server-side sort control. + + ld (IN) An LDAP session handle, as obtained from a call to + ldap_init(). + + keyList (IN) Points to a null-terminated array of pointers to + LDAPSortKey structures, containing a description of + each of the sort keys to be used. The description + consists of an attribute name, ascending/descending flag, + and an optional matching rule (OID) to use. + + value (OUT) Contains the control value; the bv_val member of the berval structure + SHOULD be freed by calling ldap_memfree() when done. + + + Ber encoding + + SortKeyList ::= SEQUENCE OF SEQUENCE { + attributeType AttributeDescription, + orderingRule [0] MatchingRuleId OPTIONAL, + reverseOrder [1] BOOLEAN DEFAULT FALSE } + + ---------------------------------------------------------------------------*/ + +int +ldap_create_sort_control_value( + LDAP *ld, + LDAPSortKey **keyList, + struct berval *value ) +{ + int i; + BerElement *ber = NULL; + ber_tag_t tag; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + if ( ld == NULL ) return LDAP_PARAM_ERROR; + if ( keyList == NULL || value == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return LDAP_PARAM_ERROR; + } + + value->bv_val = NULL; + value->bv_len = 0; + ld->ld_errno = LDAP_SUCCESS; + + ber = ldap_alloc_ber_with_options( ld ); + if ( ber == NULL) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + tag = ber_printf( ber, "{" /*}*/ ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + + for ( i = 0; keyList[i] != NULL; i++ ) { + tag = ber_printf( ber, "{s" /*}*/, keyList[i]->attributeType ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + + if ( keyList[i]->orderingRule != NULL ) { + tag = ber_printf( ber, "ts", + LDAP_MATCHRULE_IDENTIFIER, + keyList[i]->orderingRule ); + + if ( tag == LBER_ERROR ) { + goto error_return; + } + } + + if ( keyList[i]->reverseOrder ) { + tag = ber_printf( ber, "tb", + LDAP_REVERSEORDER_IDENTIFIER, + keyList[i]->reverseOrder ); + + if ( tag == LBER_ERROR ) { + goto error_return; + } + } + + tag = ber_printf( ber, /*{*/ "N}" ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + } + + tag = ber_printf( ber, /*{*/ "N}" ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + + if ( ber_flatten2( ber, value, 1 ) == -1 ) { + ld->ld_errno = LDAP_NO_MEMORY; + } + + if ( 0 ) { +error_return:; + ld->ld_errno = LDAP_ENCODING_ERROR; + } + + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + return ld->ld_errno; +} + + +/* --------------------------------------------------------------------------- + ldap_create_sort_control + + Create and encode the server-side sort control. + + ld (IN) An LDAP session handle, as obtained from a call to + ldap_init(). + + keyList (IN) Points to a null-terminated array of pointers to + LDAPSortKey structures, containing a description of + each of the sort keys to be used. The description + consists of an attribute name, ascending/descending flag, + and an optional matching rule (OID) to use. + + isCritical (IN) 0 - Indicates the control is not critical to the operation. + non-zero - The control is critical to the operation. + + ctrlp (OUT) Returns a pointer to the LDAPControl created. This control + SHOULD be freed by calling ldap_control_free() when done. + + + Ber encoding + + SortKeyList ::= SEQUENCE OF SEQUENCE { + attributeType AttributeDescription, + orderingRule [0] MatchingRuleId OPTIONAL, + reverseOrder [1] BOOLEAN DEFAULT FALSE } + + ---------------------------------------------------------------------------*/ + +int +ldap_create_sort_control( + LDAP *ld, + LDAPSortKey **keyList, + int isCritical, + LDAPControl **ctrlp ) +{ + struct berval value; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + if ( ld == NULL ) { + return LDAP_PARAM_ERROR; + } + + if ( ctrlp == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + ld->ld_errno = ldap_create_sort_control_value( ld, keyList, &value ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = ldap_control_create( LDAP_CONTROL_SORTREQUEST, + isCritical, &value, 0, ctrlp ); + if ( ld->ld_errno != LDAP_SUCCESS ) { + LDAP_FREE( value.bv_val ); + } + } + + return ld->ld_errno; +} + + +/* --------------------------------------------------------------------------- + ldap_parse_sortedresult_control + + Decode the server-side sort control return information. + + ld (IN) An LDAP session handle, as obtained from a call to + ldap_init(). + + ctrl (IN) The address of the LDAP Control Structure. + + returnCode (OUT) This result parameter is filled in with the sort control + result code. This parameter MUST not be NULL. + + attribute (OUT) If an error occured the server may return a string + indicating the first attribute in the sortkey list + that was in error. If a string is returned, the memory + should be freed with ldap_memfree. If this parameter is + NULL, no string is returned. + + + Ber encoding for sort control + + SortResult ::= SEQUENCE { + sortResult ENUMERATED { + success (0), -- results are sorted + operationsError (1), -- server internal failure + timeLimitExceeded (3), -- timelimit reached before + -- sorting was completed + strongAuthRequired (8), -- refused to return sorted + -- results via insecure + -- protocol + adminLimitExceeded (11), -- too many matching entries + -- for the server to sort + noSuchAttribute (16), -- unrecognized attribute + -- type in sort key + inappropriateMatching (18), -- unrecognized or inappro- + -- priate matching rule in + -- sort key + insufficientAccessRights (50), -- refused to return sorted + -- results to this client + busy (51), -- too busy to process + unwillingToPerform (53), -- unable to sort + other (80) + }, + attributeType [0] AttributeDescription OPTIONAL } + ---------------------------------------------------------------------------*/ + +int +ldap_parse_sortresponse_control( + LDAP *ld, + LDAPControl *ctrl, + ber_int_t *returnCode, + char **attribute ) +{ + BerElement *ber; + ber_tag_t tag, berTag; + ber_len_t berLen; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + if (ld == NULL) { + return LDAP_PARAM_ERROR; + } + + if (ctrl == NULL) { + ld->ld_errno = LDAP_PARAM_ERROR; + return(ld->ld_errno); + } + + if (attribute) { + *attribute = NULL; + } + + if ( strcmp(LDAP_CONTROL_SORTRESPONSE, ctrl->ldctl_oid) != 0 ) { + /* Not sort result control */ + ld->ld_errno = LDAP_CONTROL_NOT_FOUND; + return(ld->ld_errno); + } + + /* Create a BerElement from the berval returned in the control. */ + ber = ber_init(&ctrl->ldctl_value); + + if (ber == NULL) { + ld->ld_errno = LDAP_NO_MEMORY; + return(ld->ld_errno); + } + + /* Extract the result code from the control. */ + tag = ber_scanf(ber, "{e" /*}*/, returnCode); + + if( tag == LBER_ERROR ) { + ber_free(ber, 1); + ld->ld_errno = LDAP_DECODING_ERROR; + return(ld->ld_errno); + } + + /* If caller wants the attribute name, and if it's present in the control, + extract the attribute name which caused the error. */ + if (attribute && (LDAP_ATTRTYPES_IDENTIFIER == ber_peek_tag(ber, &berLen))) + { + tag = ber_scanf(ber, "ta", &berTag, attribute); + + if (tag == LBER_ERROR ) { + ber_free(ber, 1); + ld->ld_errno = LDAP_DECODING_ERROR; + return(ld->ld_errno); + } + } + + ber_free(ber,1); + + ld->ld_errno = LDAP_SUCCESS; + return(ld->ld_errno); +} diff --git a/libraries/libldap/stctrl.c b/libraries/libldap/stctrl.c new file mode 100644 index 0000000..d4a384e --- /dev/null +++ b/libraries/libldap/stctrl.c @@ -0,0 +1,302 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * Portions Copyright 2007 Pierangelo Masarati. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was developed by Pierangelo Masarati for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +#ifdef LDAP_CONTROL_X_SESSION_TRACKING + +/* + * Client-side of <draft-wahl-ldap-session-03> + */ + +int +ldap_create_session_tracking_value( + LDAP *ld, + char *sessionSourceIp, + char *sessionSourceName, + char *formatOID, + struct berval *sessionTrackingIdentifier, + struct berval *value ) +{ + BerElement *ber = NULL; + ber_tag_t tag; + + struct berval ip, name, oid, id; + + if ( ld == NULL || + formatOID == NULL || + value == NULL ) + { +param_error:; + if ( ld ) { + ld->ld_errno = LDAP_PARAM_ERROR; + } + + return LDAP_PARAM_ERROR; + } + + assert( LDAP_VALID( ld ) ); + ld->ld_errno = LDAP_SUCCESS; + + /* check sizes according to I.D. */ + if ( sessionSourceIp == NULL ) { + BER_BVSTR( &ip, "" ); + + } else { + ber_str2bv( sessionSourceIp, 0, 0, &ip ); + /* NOTE: we're strict because we don't want + * to send out bad data */ + if ( ip.bv_len > 128 ) goto param_error; + } + + if ( sessionSourceName == NULL ) { + BER_BVSTR( &name, "" ); + + } else { + ber_str2bv( sessionSourceName, 0, 0, &name ); + /* NOTE: we're strict because we don't want + * to send out bad data */ + if ( name.bv_len > 65536 ) goto param_error; + } + + ber_str2bv( formatOID, 0, 0, &oid ); + /* NOTE: we're strict because we don't want + * to send out bad data */ + if ( oid.bv_len > 1024 ) goto param_error; + + if ( sessionTrackingIdentifier == NULL || + sessionTrackingIdentifier->bv_val == NULL ) + { + BER_BVSTR( &id, "" ); + + } else { + id = *sessionTrackingIdentifier; + } + + /* prepare value */ + value->bv_val = NULL; + value->bv_len = 0; + + ber = ldap_alloc_ber_with_options( ld ); + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + tag = ber_printf( ber, "{OOOO}", &ip, &name, &oid, &id ); + if ( tag == LBER_ERROR ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + goto done; + } + + if ( ber_flatten2( ber, value, 1 ) == -1 ) { + ld->ld_errno = LDAP_NO_MEMORY; + } + +done:; + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + return ld->ld_errno; +} + +/* + * NOTE: this API is bad; it could be much more efficient... + */ +int +ldap_create_session_tracking_control( + LDAP *ld, + char *sessionSourceIp, + char *sessionSourceName, + char *formatOID, + struct berval *sessionTrackingIdentifier, + LDAPControl **ctrlp ) +{ + struct berval value; + + if ( ctrlp == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + ld->ld_errno = ldap_create_session_tracking_value( ld, + sessionSourceIp, sessionSourceName, formatOID, + sessionTrackingIdentifier, &value ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + ld->ld_errno = ldap_control_create( LDAP_CONTROL_X_SESSION_TRACKING, + 0, &value, 0, ctrlp ); + if ( ld->ld_errno != LDAP_SUCCESS ) { + LDAP_FREE( value.bv_val ); + } + } + + return ld->ld_errno; +} + +int +ldap_parse_session_tracking_control( + LDAP *ld, + LDAPControl *ctrl, + struct berval *ip, + struct berval *name, + struct berval *oid, + struct berval *id ) +{ + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + + if ( ld == NULL || + ctrl == NULL || + ip == NULL || + name == NULL || + oid == NULL || + id == NULL ) + { + if ( ld ) { + ld->ld_errno = LDAP_PARAM_ERROR; + } + + /* NOTE: we want the caller to get all or nothing; + * we could allow some of the pointers to be NULL, + * if one does not want part of the data */ + return LDAP_PARAM_ERROR; + } + + BER_BVZERO( ip ); + BER_BVZERO( name ); + BER_BVZERO( oid ); + BER_BVZERO( id ); + + ber = ber_init( &ctrl->ldctl_value ); + + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + tag = ber_skip_tag( ber, &len ); + if ( tag != LBER_SEQUENCE ) { + tag = LBER_ERROR; + goto error; + } + + /* sessionSourceIp */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + tag = ber_skip_tag( ber, &len ); + + } else { + if ( len > 128 ) { + /* should be LDAP_DECODING_ERROR, + * but we're liberal in what we accept */ + } + tag = ber_scanf( ber, "o", ip ); + } + + /* sessionSourceName */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + tag = ber_skip_tag( ber, &len ); + + } else { + if ( len > 65536 ) { + /* should be LDAP_DECODING_ERROR, + * but we're liberal in what we accept */ + } + tag = ber_scanf( ber, "o", name ); + } + + /* formatOID */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + ld->ld_errno = LDAP_DECODING_ERROR; + goto error; + + } else { + if ( len > 1024 ) { + /* should be LDAP_DECODING_ERROR, + * but we're liberal in what we accept */ + } + tag = ber_scanf( ber, "o", oid ); + } + + /* FIXME: should check if it is an OID... leave it to the caller */ + + /* sessionTrackingIdentifier */ + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + tag = LBER_ERROR; + goto error; + } + + if ( len == 0 ) { + tag = ber_skip_tag( ber, &len ); + + } else { +#if 0 + if ( len > 65536 ) { + /* should be LDAP_DECODING_ERROR, + * but we're liberal in what we accept */ + } +#endif + tag = ber_scanf( ber, "o", id ); + } + + /* closure */ + tag = ber_skip_tag( ber, &len ); + if ( tag == LBER_DEFAULT && len == 0 ) { + tag = 0; + } + +error:; + (void)ber_free( ber, 1 ); + + if ( tag == LBER_ERROR ) { + return LDAP_DECODING_ERROR; + } + + return ld->ld_errno; +} + +#endif /* LDAP_CONTROL_X_SESSION_TRACKING */ diff --git a/libraries/libldap/string.c b/libraries/libldap/string.c new file mode 100644 index 0000000..b42c50b --- /dev/null +++ b/libraries/libldap/string.c @@ -0,0 +1,177 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * Locale-specific 1-byte character versions + * See utf-8.c for UTF-8 versions + */ + +#include "portable.h" + +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/ctype.h> + +#include "ldap-int.h" + + +#if defined ( HAVE_STRSPN ) +#define int_strspn strspn +#else +static int int_strspn( const char *str, const char *delim ) +{ + int pos; + const char *p=delim; + + for( pos=0; (*str) ; pos++,str++) { + if (*str!=*p) { + for( p=delim; (*p) ; p++ ) { + if (*str==*p) { + break; + } + } + } + + if (*p=='\0') { + return pos; + } + } + return pos; +} +#endif + +#if defined( HAVE_STRPBRK ) +#define int_strpbrk strpbrk +#else +static char *(int_strpbrk)( const char *str, const char *accept ) +{ + const char *p; + + for( ; (*str) ; str++ ) { + for( p=accept; (*p) ; p++) { + if (*str==*p) { + return str; + } + } + } + + return NULL; +} +#endif + +char *(ldap_pvt_strtok)( char *str, const char *delim, char **pos ) +{ + char *p; + + if (pos==NULL) { + return NULL; + } + + if (str==NULL) { + if (*pos==NULL) { + return NULL; + } + + str=*pos; + } + + /* skip any initial delimiters */ + str += int_strspn( str, delim ); + if (*str == '\0') { + return NULL; + } + + p = int_strpbrk( str, delim ); + if (p==NULL) { + *pos = NULL; + + } else { + *p ='\0'; + *pos = p+1; + } + + return str; +} + +char * +ldap_pvt_str2upper( char *str ) +{ + char *s; + + /* to upper */ + if ( str ) { + for ( s = str; *s; s++ ) { + *s = TOUPPER( (unsigned char) *s ); + } + } + + return( str ); +} + +struct berval * +ldap_pvt_str2upperbv( char *str, struct berval *bv ) +{ + char *s = NULL; + + assert( bv != NULL ); + + /* to upper */ + if ( str ) { + for ( s = str; *s; s++ ) { + *s = TOUPPER( (unsigned char) *s ); + } + } + + bv->bv_val = str; + bv->bv_len = (ber_len_t)(s - str); + + return( bv ); +} + +char * +ldap_pvt_str2lower( char *str ) +{ + char *s; + + /* to lower */ + if ( str ) { + for ( s = str; *s; s++ ) { + *s = TOLOWER( (unsigned char) *s ); + } + } + + return( str ); +} + +struct berval * +ldap_pvt_str2lowerbv( char *str, struct berval *bv ) +{ + char *s = NULL; + + assert( bv != NULL ); + + /* to lower */ + if ( str ) { + for ( s = str; *s; s++ ) { + *s = TOLOWER( (unsigned char) *s ); + } + } + + bv->bv_val = str; + bv->bv_len = (ber_len_t)(s - str); + + return( bv ); +} diff --git a/libraries/libldap/t61.c b/libraries/libldap/t61.c new file mode 100644 index 0000000..5019d02 --- /dev/null +++ b/libraries/libldap/t61.c @@ -0,0 +1,692 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2002-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +/* + * Basic T.61 <-> UTF-8 conversion + * + * These routines will perform a lossless translation from T.61 to UTF-8 + * and a lossy translation from UTF-8 to T.61. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_utf8.h" + +#include "ldap_defaults.h" + +/* + * T.61 is somewhat braindead; even in the 7-bit space it is not + * completely equivalent to 7-bit US-ASCII. Our definition of the + * character set comes from RFC 1345 with a slightly more readable + * rendition at http://std.dkuug.dk/i18n/charmaps/T.61-8BIT. + * + * Even though '#' and '$' are present in the 7-bit US-ASCII space, + * (x23 and x24, resp.) in T.61 they are mapped to 8-bit characters + * xA6 and xA4. + * + * Also T.61 lacks + * backslash \ (x5C) + * caret ^ (x5E) + * backquote ` (x60) + * left brace { (x7B) + * right brace } (x7D) + * tilde ~ (x7E) + * + * In T.61, the codes xC1 to xCF (excluding xC9, unused) are non-spacing + * accents of some form or another. There are predefined combinations + * for certain characters, but they can also be used arbitrarily. The + * table at dkuug.dk maps these accents to the E000 "private use" range + * of the Unicode space, but I believe they more properly belong in the + * 0300 range (non-spacing accents). The transformation is complicated + * slightly because Unicode wants the non-spacing character to follow + * the base character, while T.61 has the non-spacing character leading. + * Also, T.61 specifically recognizes certain combined pairs as "characters" + * but doesn't specify how to treat unrecognized pairs. This code will + * always attempt to combine pairs when a known Unicode composite exists. + */ + +static const wchar_t t61_tab[] = { + 0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, + 0x008, 0x009, 0x00a, 0x00b, 0x00c, 0x00d, 0x00e, 0x00f, + 0x010, 0x011, 0x012, 0x013, 0x014, 0x015, 0x016, 0x017, + 0x018, 0x019, 0x01a, 0x01b, 0x01c, 0x01d, 0x01e, 0x01f, + 0x020, 0x021, 0x022, 0x000, 0x000, 0x025, 0x026, 0x027, + 0x028, 0x029, 0x02a, 0x02b, 0x02c, 0x02d, 0x02e, 0x02f, + 0x030, 0x031, 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, + 0x038, 0x039, 0x03a, 0x03b, 0x03c, 0x03d, 0x03e, 0x03f, + 0x040, 0x041, 0x042, 0x043, 0x044, 0x045, 0x046, 0x047, + 0x048, 0x049, 0x04a, 0x04b, 0x04c, 0x04d, 0x04e, 0x04f, + 0x050, 0x051, 0x052, 0x053, 0x054, 0x055, 0x056, 0x057, + 0x058, 0x059, 0x05a, 0x05b, 0x000, 0x05d, 0x000, 0x05f, + 0x000, 0x061, 0x062, 0x063, 0x064, 0x065, 0x066, 0x067, + 0x068, 0x069, 0x06a, 0x06b, 0x06c, 0x06d, 0x06e, 0x06f, + 0x070, 0x071, 0x072, 0x073, 0x074, 0x075, 0x076, 0x077, + 0x078, 0x079, 0x07a, 0x000, 0x07c, 0x000, 0x000, 0x07f, + 0x080, 0x081, 0x082, 0x083, 0x084, 0x085, 0x086, 0x087, + 0x088, 0x089, 0x08a, 0x08b, 0x08c, 0x08d, 0x08e, 0x08f, + 0x090, 0x091, 0x092, 0x093, 0x094, 0x095, 0x096, 0x097, + 0x098, 0x099, 0x09a, 0x09b, 0x09c, 0x09d, 0x09e, 0x09f, + 0x0a0, 0x0a1, 0x0a2, 0x0a3, 0x024, 0x0a5, 0x023, 0x0a7, + 0x0a4, 0x000, 0x000, 0x0ab, 0x000, 0x000, 0x000, 0x000, + 0x0b0, 0x0b1, 0x0b2, 0x0b3, 0x0d7, 0x0b5, 0x0b6, 0x0b7, + 0x0f7, 0x000, 0x000, 0x0bb, 0x0bc, 0x0bd, 0x0be, 0x0bf, + 0x000, 0x300, 0x301, 0x302, 0x303, 0x304, 0x306, 0x307, + 0x308, 0x000, 0x30a, 0x327, 0x332, 0x30b, 0x328, 0x30c, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x2126, 0xc6, 0x0d0, 0x0aa, 0x126, 0x000, 0x132, 0x13f, + 0x141, 0x0d8, 0x152, 0x0ba, 0x0de, 0x166, 0x14a, 0x149, + 0x138, 0x0e6, 0x111, 0x0f0, 0x127, 0x131, 0x133, 0x140, + 0x142, 0x0f8, 0x153, 0x0df, 0x0fe, 0x167, 0x14b, 0x000 +}; + +typedef wchar_t wvec16[16]; +typedef wchar_t wvec32[32]; +typedef wchar_t wvec64[64]; + +/* Substitutions when 0xc1-0xcf appears by itself or with space 0x20 */ +static const wvec16 accents = { + 0x000, 0x060, 0x0b4, 0x05e, 0x07e, 0x0af, 0x2d8, 0x2d9, + 0x0a8, 0x000, 0x2da, 0x0b8, 0x000, 0x2dd, 0x2db, 0x2c7}; + +/* In the following tables, base characters commented in (parentheses) + * are not defined by T.61 but are mapped anyway since their Unicode + * composite exists. + */ + +/* Grave accented chars AEIOU (NWY) */ +static const wvec32 c1_vec1 = { + /* Upper case */ + 0, 0xc0, 0, 0, 0, 0xc8, 0, 0, 0, 0xcc, 0, 0, 0, 0, 0x1f8, 0xd2, + 0, 0, 0, 0, 0, 0xd9, 0, 0x1e80, 0, 0x1ef2, 0, 0, 0, 0, 0, 0}; +static const wvec32 c1_vec2 = { + /* Lower case */ + 0, 0xe0, 0, 0, 0, 0xe8, 0, 0, 0, 0xec, 0, 0, 0, 0, 0x1f9, 0xf2, + 0, 0, 0, 0, 0, 0xf9, 0, 0x1e81, 0, 0x1ef3, 0, 0, 0, 0, 0, 0}; + +static const wvec32 *c1_grave[] = { + NULL, NULL, &c1_vec1, &c1_vec2, NULL, NULL, NULL, NULL +}; + +/* Acute accented chars AEIOUYCLNRSZ (GKMPW) */ +static const wvec32 c2_vec1 = { + /* Upper case */ + 0, 0xc1, 0, 0x106, 0, 0xc9, 0, 0x1f4, + 0, 0xcd, 0, 0x1e30, 0x139, 0x1e3e, 0x143, 0xd3, + 0x1e54, 0, 0x154, 0x15a, 0, 0xda, 0, 0x1e82, + 0, 0xdd, 0x179, 0, 0, 0, 0, 0}; +static const wvec32 c2_vec2 = { + /* Lower case */ + 0, 0xe1, 0, 0x107, 0, 0xe9, 0, 0x1f5, + 0, 0xed, 0, 0x1e31, 0x13a, 0x1e3f, 0x144, 0xf3, + 0x1e55, 0, 0x155, 0x15b, 0, 0xfa, 0, 0x1e83, + 0, 0xfd, 0x17a, 0, 0, 0, 0, 0}; +static const wvec32 c2_vec3 = { + /* (AE and ae) */ + 0, 0x1fc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x1fd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static const wvec32 *c2_acute[] = { + NULL, NULL, &c2_vec1, &c2_vec2, NULL, NULL, NULL, &c2_vec3 +}; + +/* Circumflex AEIOUYCGHJSW (Z) */ +static const wvec32 c3_vec1 = { + /* Upper case */ + 0, 0xc2, 0, 0x108, 0, 0xca, 0, 0x11c, + 0x124, 0xce, 0x134, 0, 0, 0, 0, 0xd4, + 0, 0, 0, 0x15c, 0, 0xdb, 0, 0x174, + 0, 0x176, 0x1e90, 0, 0, 0, 0, 0}; +static const wvec32 c3_vec2 = { + /* Lower case */ + 0, 0xe2, 0, 0x109, 0, 0xea, 0, 0x11d, + 0x125, 0xee, 0x135, 0, 0, 0, 0, 0xf4, + 0, 0, 0, 0x15d, 0, 0xfb, 0, 0x175, + 0, 0x177, 0x1e91, 0, 0, 0, 0, 0}; +static const wvec32 *c3_circumflex[] = { + NULL, NULL, &c3_vec1, &c3_vec2, NULL, NULL, NULL, NULL +}; + +/* Tilde AIOUN (EVY) */ +static const wvec32 c4_vec1 = { + /* Upper case */ + 0, 0xc3, 0, 0, 0, 0x1ebc, 0, 0, 0, 0x128, 0, 0, 0, 0, 0xd1, 0xd5, + 0, 0, 0, 0, 0, 0x168, 0x1e7c, 0, 0, 0x1ef8, 0, 0, 0, 0, 0, 0}; +static const wvec32 c4_vec2 = { + /* Lower case */ + 0, 0xe3, 0, 0, 0, 0x1ebd, 0, 0, 0, 0x129, 0, 0, 0, 0, 0xf1, 0xf5, + 0, 0, 0, 0, 0, 0x169, 0x1e7d, 0, 0, 0x1ef9, 0, 0, 0, 0, 0, 0}; +static const wvec32 *c4_tilde[] = { + NULL, NULL, &c4_vec1, &c4_vec2, NULL, NULL, NULL, NULL +}; + +/* Macron AEIOU (YG) */ +static const wvec32 c5_vec1 = { + /* Upper case */ + 0, 0x100, 0, 0, 0, 0x112, 0, 0x1e20, 0, 0x12a, 0, 0, 0, 0, 0, 0x14c, + 0, 0, 0, 0, 0, 0x16a, 0, 0, 0, 0x232, 0, 0, 0, 0, 0, 0}; +static const wvec32 c5_vec2 = { + /* Lower case */ + 0, 0x101, 0, 0, 0, 0x113, 0, 0x1e21, 0, 0x12b, 0, 0, 0, 0, 0, 0x14d, + 0, 0, 0, 0, 0, 0x16b, 0, 0, 0, 0x233, 0, 0, 0, 0, 0, 0}; +static const wvec32 c5_vec3 = { + /* (AE and ae) */ + 0, 0x1e2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x1e3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 *c5_macron[] = { + NULL, NULL, &c5_vec1, &c5_vec2, NULL, NULL, NULL, &c5_vec3 +}; + +/* Breve AUG (EIO) */ +static const wvec32 c6_vec1 = { + /* Upper case */ + 0, 0x102, 0, 0, 0, 0x114, 0, 0x11e, 0, 0x12c, 0, 0, 0, 0, 0, 0x14e, + 0, 0, 0, 0, 0, 0x16c, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 c6_vec2 = { + /* Lower case */ + 0, 0x103, 0, 0, 0, 0x115, 0, 0x11f, 0, 0x12d, 0, 0, 0, 0, 0, 0x14f, + 0, 0, 0, 0, 0, 0x16d, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 *c6_breve[] = { + NULL, NULL, &c6_vec1, &c6_vec2, NULL, NULL, NULL, NULL +}; + +/* Dot Above CEGIZ (AOBDFHMNPRSTWXY) */ +static const wvec32 c7_vec1 = { + /* Upper case */ + 0, 0x226, 0x1e02, 0x10a, 0x1e0a, 0x116, 0x1e1e, 0x120, + 0x1e22, 0x130, 0, 0, 0, 0x1e40, 0x1e44, 0x22e, + 0x1e56, 0, 0x1e58, 0x1e60, 0x1e6a, 0, 0, 0x1e86, + 0x1e8a, 0x1e8e, 0x17b, 0, 0, 0, 0, 0}; +static const wvec32 c7_vec2 = { + /* Lower case */ + 0, 0x227, 0x1e03, 0x10b, 0x1e0b, 0x117, 0x1e1f, 0x121, + 0x1e23, 0, 0, 0, 0, 0x1e41, 0x1e45, 0x22f, + 0x1e57, 0, 0x1e59, 0x1e61, 0x1e6b, 0, 0, 0x1e87, + 0x1e8b, 0x1e8f, 0x17c, 0, 0, 0, 0, 0}; +static const wvec32 *c7_dotabove[] = { + NULL, NULL, &c7_vec1, &c7_vec2, NULL, NULL, NULL, NULL +}; + +/* Diaeresis AEIOUY (HWXt) */ +static const wvec32 c8_vec1 = { + /* Upper case */ + 0, 0xc4, 0, 0, 0, 0xcb, 0, 0, 0x1e26, 0xcf, 0, 0, 0, 0, 0, 0xd6, + 0, 0, 0, 0, 0, 0xdc, 0, 0x1e84, 0x1e8c, 0x178, 0, 0, 0, 0, 0, 0}; +static const wvec32 c8_vec2 = { + /* Lower case */ + 0, 0xe4, 0, 0, 0, 0xeb, 0, 0, 0x1e27, 0xef, 0, 0, 0, 0, 0, 0xf6, + 0, 0, 0, 0, 0x1e97, 0xfc, 0, 0x1e85, 0x1e8d, 0xff, 0, 0, 0, 0, 0, 0}; +static const wvec32 *c8_diaeresis[] = { + NULL, NULL, &c8_vec1, &c8_vec2, NULL, NULL, NULL, NULL +}; + +/* Ring Above AU (wy) */ +static const wvec32 ca_vec1 = { + /* Upper case */ + 0, 0xc5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0x16e, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 ca_vec2 = { + /* Lower case */ + 0, 0xe5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0x16f, 0, 0x1e98, 0, 0x1e99, 0, 0, 0, 0, 0, 0}; +static const wvec32 *ca_ringabove[] = { + NULL, NULL, &ca_vec1, &ca_vec2, NULL, NULL, NULL, NULL +}; + +/* Cedilla CGKLNRST (EDH) */ +static const wvec32 cb_vec1 = { + /* Upper case */ + 0, 0, 0, 0xc7, 0x1e10, 0x228, 0, 0x122, + 0x1e28, 0, 0, 0x136, 0x13b, 0, 0x145, 0, + 0, 0, 0x156, 0x15e, 0x162, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 cb_vec2 = { + /* Lower case */ + 0, 0, 0, 0xe7, 0x1e11, 0x229, 0, 0x123, + 0x1e29, 0, 0, 0x137, 0x13c, 0, 0x146, 0, + 0, 0, 0x157, 0x15f, 0x163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 *cb_cedilla[] = { + NULL, NULL, &cb_vec1, &cb_vec2, NULL, NULL, NULL, NULL +}; + +/* Double Acute Accent OU */ +static const wvec32 cd_vec1 = { + /* Upper case */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x150, + 0, 0, 0, 0, 0, 0x170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 cd_vec2 = { + /* Lower case */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x151, + 0, 0, 0, 0, 0, 0x171, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 *cd_doubleacute[] = { + NULL, NULL, &cd_vec1, &cd_vec2, NULL, NULL, NULL, NULL +}; + +/* Ogonek AEIU (O) */ +static const wvec32 ce_vec1 = { + /* Upper case */ + 0, 0x104, 0, 0, 0, 0x118, 0, 0, 0, 0x12e, 0, 0, 0, 0, 0, 0x1ea, + 0, 0, 0, 0, 0, 0x172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 ce_vec2 = { + /* Lower case */ + 0, 0x105, 0, 0, 0, 0x119, 0, 0, 0, 0x12f, 0, 0, 0, 0, 0, 0x1eb, + 0, 0, 0, 0, 0, 0x173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static const wvec32 *ce_ogonek[] = { + NULL, NULL, &ce_vec1, &ce_vec2, NULL, NULL, NULL, NULL +}; + +/* Caron CDELNRSTZ (AIOUGKjH) */ +static const wvec32 cf_vec1 = { + /* Upper case */ + 0, 0x1cd, 0, 0x10c, 0x10e, 0x11a, 0, 0x1e6, + 0x21e, 0x1cf, 0, 0x1e8, 0x13d, 0, 0x147, 0x1d1, + 0, 0, 0x158, 0x160, 0x164, 0x1d3, 0, 0, + 0, 0, 0x17d, 0, 0, 0, 0, 0}; +static const wvec32 cf_vec2 = { + /* Lower case */ + 0, 0x1ce, 0, 0x10d, 0x10f, 0x11b, 0, 0x1e7, + 0x21f, 0x1d0, 0x1f0, 0x1e9, 0x13e, 0, 0x148, 0x1d2, + 0, 0, 0x159, 0x161, 0x165, 0x1d4, 0, 0, + 0, 0, 0x17e, 0, 0, 0, 0, 0}; +static const wvec32 *cf_caron[] = { + NULL, NULL, &cf_vec1, &cf_vec2, NULL, NULL, NULL, NULL +}; + +static const wvec32 **cx_tab[] = { + NULL, c1_grave, c2_acute, c3_circumflex, c4_tilde, c5_macron, + c6_breve, c7_dotabove, c8_diaeresis, NULL, ca_ringabove, + cb_cedilla, NULL, cd_doubleacute, ce_ogonek, cf_caron }; + +int ldap_t61s_valid( struct berval *str ) +{ + unsigned char *c = (unsigned char *)str->bv_val; + int i; + + for (i=0; i < str->bv_len; c++,i++) + if (!t61_tab[*c]) + return 0; + return 1; +} + +/* Transform a T.61 string to UTF-8. + */ +int ldap_t61s_to_utf8s( struct berval *src, struct berval *dst ) +{ + unsigned char *c; + char *d; + int i, wlen = 0; + + /* Just count the length of the UTF-8 result first */ + for (i=0,c=(unsigned char *)src->bv_val; i < src->bv_len; c++,i++) { + /* Invalid T.61 characters? */ + if (!t61_tab[*c]) + return LDAP_INVALID_SYNTAX; + if ((*c & 0xf0) == 0xc0) { + int j = *c & 0x0f; + /* If this is the end of the string, or if the base + * character is just a space, treat this as a regular + * spacing character. + */ + if ((!c[1] || c[1] == 0x20) && accents[j]) { + wlen += ldap_x_wc_to_utf8(NULL, accents[j], 0); + } else if (cx_tab[j] && cx_tab[j][c[1]>>5] && + /* We have a composite mapping for this pair */ + (*cx_tab[j][c[1]>>5])[c[1]&0x1f]) { + wlen += ldap_x_wc_to_utf8( NULL, + (*cx_tab[j][c[1]>>5])[c[1]&0x1f], 0); + } else { + /* No mapping, just swap it around so the base + * character comes first. + */ + wlen += ldap_x_wc_to_utf8(NULL, c[1], 0); + wlen += ldap_x_wc_to_utf8(NULL, + t61_tab[*c], 0); + } + c++; i++; + continue; + } else { + wlen += ldap_x_wc_to_utf8(NULL, t61_tab[*c], 0); + } + } + + /* Now transform the string */ + dst->bv_len = wlen; + dst->bv_val = LDAP_MALLOC( wlen+1 ); + d = dst->bv_val; + if (!d) + return LDAP_NO_MEMORY; + + for (i=0,c=(unsigned char *)src->bv_val; i < src->bv_len; c++,i++) { + if ((*c & 0xf0) == 0xc0) { + int j = *c & 0x0f; + /* If this is the end of the string, or if the base + * character is just a space, treat this as a regular + * spacing character. + */ + if ((!c[1] || c[1] == 0x20) && accents[j]) { + d += ldap_x_wc_to_utf8(d, accents[j], 6); + } else if (cx_tab[j] && cx_tab[j][c[1]>>5] && + /* We have a composite mapping for this pair */ + (*cx_tab[j][c[1]>>5])[c[1]&0x1f]) { + d += ldap_x_wc_to_utf8(d, + (*cx_tab[j][c[1]>>5])[c[1]&0x1f], 6); + } else { + /* No mapping, just swap it around so the base + * character comes first. + */ + d += ldap_x_wc_to_utf8(d, c[1], 6); + d += ldap_x_wc_to_utf8(d, t61_tab[*c], 6); + } + c++; i++; + continue; + } else { + d += ldap_x_wc_to_utf8(d, t61_tab[*c], 6); + } + } + *d = '\0'; + return LDAP_SUCCESS; +} + +/* For the reverse mapping, we just pay attention to the Latin-oriented + * code blocks. These are + * 0000 - 007f Basic Latin + * 0080 - 00ff Latin-1 Supplement + * 0100 - 017f Latin Extended-A + * 0180 - 024f Latin Extended-B + * 1e00 - 1eff Latin Extended Additional + * + * We have a special case to map Ohm U2126 back to T.61 0xe0. All other + * unrecognized characters are replaced with '?' 0x3f. + */ + +static const wvec64 u000 = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x00a6, 0x00a4, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f}; + +/* In this range, we've mapped caret to xc3/x20, backquote to xc1/x20, + * and tilde to xc4/x20. T.61 (stupidly!) doesn't define these characters + * on their own, even though it provides them as combiners for other + * letters. T.61 doesn't define these pairings either, so this may just + * have to be replaced with '?' 0x3f if other software can't cope with it. + */ +static const wvec64 u001 = { + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x003f, 0x005d, 0xc320, 0x005f, + 0xc120, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x003f, 0x007c, 0x003f, 0xc420, 0x007f}; + +static const wvec64 u002 = { + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a8, 0x00a5, 0x003f, 0x00a7, + 0xc820, 0x003f, 0x00e3, 0x00ab, 0x003f, 0x003f, 0x003f, 0xc520, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0xc220, 0x00b5, 0x00b6, 0x00b7, + 0xcb20, 0x003f, 0x00eb, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf}; + +static const wvec64 u003 = { + 0xc141, 0xc241, 0xc341, 0xc441, 0xc841, 0xca41, 0x00e1, 0xcb43, + 0xc145, 0xc245, 0xc345, 0xc845, 0xc149, 0xc249, 0xc349, 0xc849, + 0x00e2, 0xc44e, 0xc14f, 0xc24f, 0xc34f, 0xc44f, 0xc84f, 0x00b4, + 0x00e9, 0xc155, 0xc255, 0xc355, 0xc855, 0xc259, 0x00ec, 0x00fb, + 0xc161, 0xc261, 0xc361, 0xc461, 0xc861, 0xca61, 0x00f1, 0xcb63, + 0xc165, 0xc265, 0xc365, 0xc865, 0xc169, 0xc269, 0xc369, 0xc869, + 0x00f3, 0xc46e, 0xc16f, 0xc26f, 0xc36f, 0xc46f, 0xc86f, 0x00b8, + 0x00f9, 0xc175, 0xc275, 0xc375, 0xc875, 0xc279, 0x00fc, 0xc879}; + +/* These codes are used here but not defined by T.61: + * x114 = xc6/x45, x115 = xc6/x65, x12c = xc6/x49, x12d = xc6/x69 + */ +static const wvec64 u010 = { + 0xc541, 0xc561, 0xc641, 0xc661, 0xce41, 0xce61, 0xc243, 0xc263, + 0xc343, 0xc363, 0xc743, 0xc763, 0xcf43, 0xcf63, 0xcf44, 0xcf64, + 0x003f, 0x00f2, 0xc545, 0xc565, 0xc645, 0xc665, 0xc745, 0xc765, + 0xce45, 0xce65, 0xcf45, 0xcf65, 0xc347, 0xc367, 0xc647, 0xc667, + 0xc747, 0xc767, 0xcb47, 0xcb67, 0xc348, 0xc368, 0x00e4, 0x00f4, + 0xc449, 0xc469, 0xc549, 0xc569, 0xc649, 0xc669, 0xce49, 0xce69, + 0xc749, 0x00f5, 0x00e6, 0x00f6, 0xc34a, 0xc36a, 0xcb4b, 0xcb6b, + 0x00f0, 0xc24c, 0xc26c, 0xcb4c, 0xcb6c, 0xcf4c, 0xcf6c, 0x00e7}; + +/* These codes are used here but not defined by T.61: + * x14e = xc6/x4f, x14f = xc6/x6f + */ +static const wvec64 u011 = { + 0x00f7, 0x00e8, 0x00f8, 0xc24e, 0xc26e, 0xcb4e, 0xcb6e, 0xcf4e, + 0xcf6e, 0x00ef, 0x00ee, 0x00fe, 0xc54f, 0xc56f, 0xc64f, 0xc66f, + 0xcd4f, 0xcd6f, 0x00ea, 0x00fa, 0xc252, 0xc272, 0xcb52, 0xcb72, + 0xcf52, 0xcf72, 0xc253, 0xc273, 0xc353, 0xc373, 0xcb53, 0xcb73, + 0xcf53, 0xcf73, 0xcb54, 0xcb74, 0xcf54, 0xcf74, 0x00ed, 0x00fd, + 0xc455, 0xc475, 0xc555, 0xc575, 0xc655, 0xc675, 0xca55, 0xca75, + 0xcd55, 0xcd75, 0xce55, 0xce75, 0xc357, 0xc377, 0xc359, 0xc379, + 0xc859, 0xc25a, 0xc27a, 0xc75a, 0xc77a, 0xcf5a, 0xcf7a, 0x003f}; + +/* All of the codes in this block are undefined in T.61. + */ +static const wvec64 u013 = { + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0xcf41, 0xcf61, 0xcf49, + 0xcf69, 0xcf4f, 0xcf6f, 0xcf55, 0xcf75, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0xc5e1, 0xc5f1, 0x003f, 0x003f, 0xcf47, 0xcf67, + 0xcf4b, 0xcf6b, 0xce4f, 0xce6f, 0x003f, 0x003f, 0x003f, 0x003f, + 0xcf6a, 0x003f, 0x003f, 0x003f, 0xc247, 0xc267, 0x003f, 0x003f, + 0xc14e, 0xc16e, 0x003f, 0x003f, 0xc2e1, 0xc2f1, 0x003f, 0x003f}; + +/* All of the codes in this block are undefined in T.61. + */ +static const wvec64 u020 = { + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0xcf48, 0xcf68, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0xc741, 0xc761, + 0xcb45, 0xcb65, 0x003f, 0x003f, 0x003f, 0x003f, 0xc74f, 0xc76f, + 0x003f, 0x003f, 0xc559, 0xc579, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f}; + +static const wvec64 u023 = { + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0xcf20, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0xc620, 0xc720, 0xca20, 0xce20, 0x003f, 0xcd20, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f}; + +/* These are the non-spacing characters by themselves. They should + * never appear by themselves in actual text. + */ +static const wvec64 u030 = { + 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x003f, 0x00c6, 0x00c7, + 0x00c8, 0x003f, 0x00ca, 0x00cd, 0x00cf, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x00cb, + 0x00ce, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x00cc, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f}; + +/* None of the following blocks are defined in T.61. + */ +static const wvec64 u1e0 = { + 0x003f, 0x003f, 0xc742, 0xc762, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0xc744, 0xc764, 0x003f, 0x003f, 0x003f, 0x003f, + 0xcb44, 0xcb64, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0xc746, 0xc766, + 0xc547, 0xc567, 0xc748, 0xc768, 0x003f, 0x003f, 0xc848, 0xc868, + 0xcb48, 0xcb68, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0xc24b, 0xc26b, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0xc24d, 0xc26d, +}; + +static const wvec64 u1e1 = { + 0xc74d, 0xc76d, 0x003f, 0x003f, 0xc74e, 0xc76e, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0xc250, 0xc270, 0xc750, 0xc770, + 0xc752, 0xc772, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0xc753, 0xc773, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0xc754, 0xc774, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0xc456, 0xc476, 0x003f, 0x003f, +}; + +static const wvec64 u1e2 = { + 0xc157, 0xc177, 0xc257, 0xc277, 0xc857, 0xc877, 0xc757, 0xc777, + 0x003f, 0x003f, 0xc758, 0xc778, 0xc858, 0xc878, 0xc759, 0xc779, + 0xc35a, 0xc37a, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0xc874, + 0xca77, 0xca79, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0xc445, 0xc465, 0x003f, 0x003f, +}; + +static const wvec64 u1e3 = { + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, + 0x003f, 0x003f, 0xc159, 0xc179, 0x003f, 0x003f, 0x003f, 0x003f, + 0xc459, 0xc479, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, 0x003f, +}; + +static const wvec64 *wc00[] = { + &u000, &u001, &u002, &u003, + &u010, &u011, NULL, &u013, + &u020, NULL, NULL, &u023, + &u030, NULL, NULL, NULL}; + +static const wvec64 *wc1e[] = { + &u1e0, &u1e1, &u1e2, &u1e3}; + + +int ldap_utf8s_to_t61s( struct berval *src, struct berval *dst ) +{ + char *c, *d; + wchar_t tmp; + int i, j, tlen = 0; + + /* Just count the length of the T.61 result first */ + for (i=0,c=src->bv_val; i < src->bv_len;) { + j = ldap_x_utf8_to_wc( &tmp, c ); + if (j == -1) + return LDAP_INVALID_SYNTAX; + switch (tmp >> 8) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + if (wc00[tmp >> 6] && + ((*wc00[tmp >> 6])[tmp & 0x3f] & 0xff00)) { + tlen++; + } + tlen++; + break; + case 0x1e: + if ((*wc1e[(tmp >> 6) & 3])[tmp & 0x3f] & 0xff00) { + tlen++; + } + case 0x21: + default: + tlen ++; + break; + } + i += j; + c += j; + } + dst->bv_len = tlen; + dst->bv_val = LDAP_MALLOC( tlen+1 ); + if (!dst->bv_val) + return LDAP_NO_MEMORY; + + d = dst->bv_val; + for (i=0,c=src->bv_val; i < src->bv_len;) { + j = ldap_x_utf8_to_wc( &tmp, c ); + switch (tmp >> 8) { + case 0x00: + case 0x01: + case 0x02: + if (wc00[tmp >> 6]) { + tmp = (*wc00[tmp >> 6])[tmp & 0x3f]; + if (tmp & 0xff00) + *d++ = (tmp >> 8); + *d++ = tmp & 0xff; + } else { + *d++ = 0x3f; + } + break; + case 0x03: + /* swap order of non-spacing characters */ + if (wc00[tmp >> 6]) { + wchar_t t2 = (*wc00[tmp >> 6])[tmp & 0x3f]; + if (t2 != 0x3f) { + d[0] = d[-1]; + d[-1] = t2; + d++; + } else { + *d++ = 0x3f; + } + } else { + *d++ = 0x3f; + } + break; + case 0x1e: + tmp = (*wc1e[(tmp >> 6) & 3])[tmp & 0x3f]; + if (tmp & 0xff00) + *d++ = (tmp >> 8); + *d++ = tmp & 0xff; + break; + case 0x21: + if (tmp == 0x2126) { + *d++ = 0xe0; + break; + } + /* FALLTHRU */ + default: + *d++ = 0x3f; + break; + } + i += j; + c += j; + } + *d = '\0'; + return LDAP_SUCCESS; +} diff --git a/libraries/libldap/test.c b/libraries/libldap/test.c new file mode 100644 index 0000000..6f73324 --- /dev/null +++ b/libraries/libldap/test.c @@ -0,0 +1,807 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/ctype.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include <sys/stat.h> + +#ifdef HAVE_SYS_FILE_H +#include <sys/file.h> +#endif +#ifdef HAVE_IO_H +#include <io.h> +#endif + +#include <fcntl.h> + +/* including the "internal" defs is legit and nec. since this test routine has + * a-priori knowledge of libldap internal workings. + * hodges@stanford.edu 5-Feb-96 + */ +#include "ldap-int.h" + +/* local functions */ +static char *get_line LDAP_P(( char *line, int len, FILE *fp, const char *prompt )); +static char **get_list LDAP_P(( const char *prompt )); +static int file_read LDAP_P(( const char *path, struct berval *bv )); +static LDAPMod **get_modlist LDAP_P(( const char *prompt1, + const char *prompt2, const char *prompt3 )); +static void handle_result LDAP_P(( LDAP *ld, LDAPMessage *lm )); +static void print_ldap_result LDAP_P(( LDAP *ld, LDAPMessage *lm, + const char *s )); +static void print_search_entry LDAP_P(( LDAP *ld, LDAPMessage *res )); +static void free_list LDAP_P(( char **list )); + +static char *dnsuffix; + +static char * +get_line( char *line, int len, FILE *fp, const char *prompt ) +{ + fputs(prompt, stdout); + + if ( fgets( line, len, fp ) == NULL ) + return( NULL ); + + line[ strlen( line ) - 1 ] = '\0'; + + return( line ); +} + +static char ** +get_list( const char *prompt ) +{ + static char buf[256]; + int num; + char **result; + + num = 0; + result = (char **) 0; + while ( 1 ) { + get_line( buf, sizeof(buf), stdin, prompt ); + + if ( *buf == '\0' ) + break; + + if ( result == (char **) 0 ) + result = (char **) malloc( sizeof(char *) ); + else + result = (char **) realloc( result, + sizeof(char *) * (num + 1) ); + + result[num++] = (char *) strdup( buf ); + } + if ( result == (char **) 0 ) + return( NULL ); + result = (char **) realloc( result, sizeof(char *) * (num + 1) ); + result[num] = NULL; + + return( result ); +} + + +static void +free_list( char **list ) +{ + int i; + + if ( list != NULL ) { + for ( i = 0; list[ i ] != NULL; ++i ) { + free( list[ i ] ); + } + free( (char *)list ); + } +} + + +static int +file_read( const char *path, struct berval *bv ) +{ + FILE *fp; + ber_slen_t rlen; + int eof; + + if (( fp = fopen( path, "r" )) == NULL ) { + perror( path ); + return( -1 ); + } + + if ( fseek( fp, 0L, SEEK_END ) != 0 ) { + perror( path ); + fclose( fp ); + return( -1 ); + } + + bv->bv_len = ftell( fp ); + + if (( bv->bv_val = (char *)malloc( bv->bv_len )) == NULL ) { + perror( "malloc" ); + fclose( fp ); + return( -1 ); + } + + if ( fseek( fp, 0L, SEEK_SET ) != 0 ) { + perror( path ); + fclose( fp ); + return( -1 ); + } + + rlen = fread( bv->bv_val, 1, bv->bv_len, fp ); + eof = feof( fp ); + fclose( fp ); + + if ( (ber_len_t) rlen != bv->bv_len ) { + perror( path ); + free( bv->bv_val ); + return( -1 ); + } + + return( bv->bv_len ); +} + + +static LDAPMod ** +get_modlist( + const char *prompt1, + const char *prompt2, + const char *prompt3 ) +{ + static char buf[256]; + int num; + LDAPMod tmp = { 0 }; + LDAPMod **result; + struct berval **bvals; + + num = 0; + result = NULL; + while ( 1 ) { + if ( prompt1 ) { + get_line( buf, sizeof(buf), stdin, prompt1 ); + tmp.mod_op = atoi( buf ); + + if ( tmp.mod_op == -1 || buf[0] == '\0' ) + break; + } + + get_line( buf, sizeof(buf), stdin, prompt2 ); + if ( buf[0] == '\0' ) + break; + tmp.mod_type = strdup( buf ); + + tmp.mod_values = get_list( prompt3 ); + + if ( tmp.mod_values != NULL ) { + int i; + + for ( i = 0; tmp.mod_values[i] != NULL; ++i ) + ; + bvals = (struct berval **)calloc( i + 1, + sizeof( struct berval *)); + for ( i = 0; tmp.mod_values[i] != NULL; ++i ) { + bvals[i] = (struct berval *)malloc( + sizeof( struct berval )); + if ( strncmp( tmp.mod_values[i], "{FILE}", + 6 ) == 0 ) { + if ( file_read( tmp.mod_values[i] + 6, + bvals[i] ) < 0 ) { + free( bvals ); + for ( i = 0; i<num; i++ ) + free( result[ i ] ); + free( result ); + return( NULL ); + } + } else { + bvals[i]->bv_val = tmp.mod_values[i]; + bvals[i]->bv_len = + strlen( tmp.mod_values[i] ); + } + } + tmp.mod_bvalues = bvals; + tmp.mod_op |= LDAP_MOD_BVALUES; + } + + if ( result == NULL ) + result = (LDAPMod **) malloc( sizeof(LDAPMod *) ); + else + result = (LDAPMod **) realloc( result, + sizeof(LDAPMod *) * (num + 1) ); + + result[num] = (LDAPMod *) malloc( sizeof(LDAPMod) ); + *(result[num]) = tmp; /* struct copy */ + num++; + } + if ( result == NULL ) + return( NULL ); + result = (LDAPMod **) realloc( result, sizeof(LDAPMod *) * (num + 1) ); + result[num] = NULL; + + return( result ); +} + + +static int +bind_prompt( LDAP *ld, + LDAP_CONST char *url, + ber_tag_t request, ber_int_t msgid, + void *params ) +{ + static char dn[256], passwd[256]; + int authmethod; + + printf("rebind for request=%ld msgid=%ld url=%s\n", + request, (long) msgid, url ); + + authmethod = LDAP_AUTH_SIMPLE; + + get_line( dn, sizeof(dn), stdin, "re-bind dn? " ); + strcat( dn, dnsuffix ); + + if ( authmethod == LDAP_AUTH_SIMPLE && dn[0] != '\0' ) { + get_line( passwd, sizeof(passwd), stdin, + "re-bind password? " ); + } else { + passwd[0] = '\0'; + } + + return ldap_bind_s( ld, dn, passwd, authmethod); +} + + +int +main( int argc, char **argv ) +{ + LDAP *ld = NULL; + int i, c, port, errflg, method, id, msgtype; + char line[256], command1, command2, command3; + char passwd[64], dn[256], rdn[64], attr[64], value[256]; + char filter[256], *host, **types; + char **exdn; + static const char usage[] = + "usage: %s [-u] [-h host] [-d level] [-s dnsuffix] [-p port] [-t file] [-T file]\n"; + int bound, all, scope, attrsonly; + LDAPMessage *res; + LDAPMod **mods, **attrs; + struct timeval timeout; + char *copyfname = NULL; + int copyoptions = 0; + LDAPURLDesc *ludp; + + host = NULL; + port = LDAP_PORT; + dnsuffix = ""; + errflg = 0; + + while (( c = getopt( argc, argv, "h:d:s:p:t:T:" )) != -1 ) { + switch( c ) { + case 'd': +#ifdef LDAP_DEBUG + ldap_debug = atoi( optarg ); +#ifdef LBER_DEBUG + if ( ldap_debug & LDAP_DEBUG_PACKETS ) { + ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &ldap_debug ); + } +#endif +#else + printf( "Compile with -DLDAP_DEBUG for debugging\n" ); +#endif + break; + + case 'h': + host = optarg; + break; + + case 's': + dnsuffix = optarg; + break; + + case 'p': + port = atoi( optarg ); + break; + + case 't': /* copy ber's to given file */ + copyfname = strdup( optarg ); +/* copyoptions = LBER_TO_FILE; */ + break; + + case 'T': /* only output ber's to given file */ + copyfname = strdup( optarg ); +/* copyoptions = (LBER_TO_FILE | LBER_TO_FILE_ONLY); */ + break; + + default: + ++errflg; + } + } + + if ( host == NULL && optind == argc - 1 ) { + host = argv[ optind ]; + ++optind; + } + + if ( errflg || optind < argc - 1 ) { + fprintf( stderr, usage, argv[ 0 ] ); + exit( EXIT_FAILURE ); + } + + printf( "ldap_init( %s, %d )\n", + host == NULL ? "(null)" : host, port ); + + ld = ldap_init( host, port ); + + if ( ld == NULL ) { + perror( "ldap_init" ); + exit( EXIT_FAILURE ); + } + + if ( copyfname != NULL ) { + if ( ( ld->ld_sb->sb_fd = open( copyfname, O_WRONLY|O_CREAT|O_EXCL, + 0600 )) == -1 ) { + perror( copyfname ); + exit ( EXIT_FAILURE ); + } + ld->ld_sb->sb_options = copyoptions; + } + + bound = 0; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + (void) memset( line, '\0', sizeof(line) ); + while ( get_line( line, sizeof(line), stdin, "\ncommand? " ) != NULL ) { + command1 = line[0]; + command2 = line[1]; + command3 = line[2]; + + switch ( command1 ) { + case 'a': /* add or abandon */ + switch ( command2 ) { + case 'd': /* add */ + get_line( dn, sizeof(dn), stdin, "dn? " ); + strcat( dn, dnsuffix ); + if ( (attrs = get_modlist( NULL, "attr? ", + "value? " )) == NULL ) + break; + if ( (id = ldap_add( ld, dn, attrs )) == -1 ) + ldap_perror( ld, "ldap_add" ); + else + printf( "Add initiated with id %d\n", + id ); + break; + + case 'b': /* abandon */ + get_line( line, sizeof(line), stdin, "msgid? " ); + id = atoi( line ); + if ( ldap_abandon( ld, id ) != 0 ) + ldap_perror( ld, "ldap_abandon" ); + else + printf( "Abandon successful\n" ); + break; + default: + printf( "Possibilities: [ad]d, [ab]ort\n" ); + } + break; + + case 'b': /* asynch bind */ + method = LDAP_AUTH_SIMPLE; + get_line( dn, sizeof(dn), stdin, "dn? " ); + strcat( dn, dnsuffix ); + + if ( method == LDAP_AUTH_SIMPLE && dn[0] != '\0' ) + get_line( passwd, sizeof(passwd), stdin, + "password? " ); + else + passwd[0] = '\0'; + + if ( ldap_bind( ld, dn, passwd, method ) == -1 ) { + fprintf( stderr, "ldap_bind failed\n" ); + ldap_perror( ld, "ldap_bind" ); + } else { + printf( "Bind initiated\n" ); + bound = 1; + } + break; + + case 'B': /* synch bind */ + method = LDAP_AUTH_SIMPLE; + get_line( dn, sizeof(dn), stdin, "dn? " ); + strcat( dn, dnsuffix ); + + if ( dn[0] != '\0' ) + get_line( passwd, sizeof(passwd), stdin, + "password? " ); + else + passwd[0] = '\0'; + + if ( ldap_bind_s( ld, dn, passwd, method ) != + LDAP_SUCCESS ) { + fprintf( stderr, "ldap_bind_s failed\n" ); + ldap_perror( ld, "ldap_bind_s" ); + } else { + printf( "Bind successful\n" ); + bound = 1; + } + break; + + case 'c': /* compare */ + get_line( dn, sizeof(dn), stdin, "dn? " ); + strcat( dn, dnsuffix ); + get_line( attr, sizeof(attr), stdin, "attr? " ); + get_line( value, sizeof(value), stdin, "value? " ); + + if ( (id = ldap_compare( ld, dn, attr, value )) == -1 ) + ldap_perror( ld, "ldap_compare" ); + else + printf( "Compare initiated with id %d\n", id ); + break; + + case 'd': /* turn on debugging */ +#ifdef LDAP_DEBUG + get_line( line, sizeof(line), stdin, "debug level? " ); + ldap_debug = atoi( line ); +#ifdef LBER_DEBUG + if ( ldap_debug & LDAP_DEBUG_PACKETS ) { + ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &ldap_debug ); + } +#endif +#else + printf( "Compile with -DLDAP_DEBUG for debugging\n" ); +#endif + break; + + case 'E': /* explode a dn */ + get_line( line, sizeof(line), stdin, "dn? " ); + exdn = ldap_explode_dn( line, 0 ); + for ( i = 0; exdn != NULL && exdn[i] != NULL; i++ ) { + printf( "\t%s\n", exdn[i] ); + } + break; + + case 'g': /* set next msgid */ + get_line( line, sizeof(line), stdin, "msgid? " ); + ld->ld_msgid = atoi( line ); + break; + + case 'v': /* set version number */ + get_line( line, sizeof(line), stdin, "version? " ); + ld->ld_version = atoi( line ); + break; + + case 'm': /* modify or modifyrdn */ + if ( strncmp( line, "modify", 4 ) == 0 ) { + get_line( dn, sizeof(dn), stdin, "dn? " ); + strcat( dn, dnsuffix ); + if ( (mods = get_modlist( + "mod (0=>add, 1=>delete, 2=>replace -1=>done)? ", + "attribute type? ", "attribute value? " )) + == NULL ) + break; + if ( (id = ldap_modify( ld, dn, mods )) == -1 ) + ldap_perror( ld, "ldap_modify" ); + else + printf( "Modify initiated with id %d\n", + id ); + } else if ( strncmp( line, "modrdn", 4 ) == 0 ) { + get_line( dn, sizeof(dn), stdin, "dn? " ); + strcat( dn, dnsuffix ); + get_line( rdn, sizeof(rdn), stdin, "newrdn? " ); + if ( (id = ldap_modrdn( ld, dn, rdn )) == -1 ) + ldap_perror( ld, "ldap_modrdn" ); + else + printf( "Modrdn initiated with id %d\n", + id ); + } else { + printf( "Possibilities: [modi]fy, [modr]dn\n" ); + } + break; + + case 'q': /* quit */ + ldap_unbind( ld ); + exit( EXIT_SUCCESS ); + break; + + case 'r': /* result or remove */ + switch ( command3 ) { + case 's': /* result */ + get_line( line, sizeof(line), stdin, + "msgid (-1=>any)? " ); + if ( line[0] == '\0' ) + id = -1; + else + id = atoi( line ); + get_line( line, sizeof(line), stdin, + "all (0=>any, 1=>all)? " ); + if ( line[0] == '\0' ) + all = 1; + else + all = atoi( line ); + if (( msgtype = ldap_result( ld, id, all, + &timeout, &res )) < 1 ) { + ldap_perror( ld, "ldap_result" ); + break; + } + printf( "\nresult: msgtype %d msgid %d\n", + msgtype, res->lm_msgid ); + handle_result( ld, res ); + res = NULL; + break; + + case 'm': /* remove */ + get_line( dn, sizeof(dn), stdin, "dn? " ); + strcat( dn, dnsuffix ); + if ( (id = ldap_delete( ld, dn )) == -1 ) + ldap_perror( ld, "ldap_delete" ); + else + printf( "Remove initiated with id %d\n", + id ); + break; + + default: + printf( "Possibilities: [rem]ove, [res]ult\n" ); + break; + } + break; + + case 's': /* search */ + get_line( dn, sizeof(dn), stdin, "searchbase? " ); + strcat( dn, dnsuffix ); + get_line( line, sizeof(line), stdin, + "scope (0=baseObject, 1=oneLevel, 2=subtree, 3=children)? " ); + scope = atoi( line ); + get_line( filter, sizeof(filter), stdin, + "search filter (e.g. sn=jones)? " ); + types = get_list( "attrs to return? " ); + get_line( line, sizeof(line), stdin, + "attrsonly (0=attrs&values, 1=attrs only)? " ); + attrsonly = atoi( line ); + + if (( id = ldap_search( ld, dn, scope, filter, + types, attrsonly )) == -1 ) { + ldap_perror( ld, "ldap_search" ); + } else { + printf( "Search initiated with id %d\n", id ); + } + free_list( types ); + break; + + case 't': /* set timeout value */ + get_line( line, sizeof(line), stdin, "timeout? " ); + timeout.tv_sec = atoi( line ); + break; + + case 'p': /* parse LDAP URL */ + get_line( line, sizeof(line), stdin, "LDAP URL? " ); + if (( i = ldap_url_parse( line, &ludp )) != 0 ) { + fprintf( stderr, "ldap_url_parse: error %d\n", i ); + } else { + printf( "\t host: " ); + if ( ludp->lud_host == NULL ) { + printf( "DEFAULT\n" ); + } else { + printf( "<%s>\n", ludp->lud_host ); + } + printf( "\t port: " ); + if ( ludp->lud_port == 0 ) { + printf( "DEFAULT\n" ); + } else { + printf( "%d\n", ludp->lud_port ); + } + printf( "\t dn: <%s>\n", ludp->lud_dn ); + printf( "\t attrs:" ); + if ( ludp->lud_attrs == NULL ) { + printf( " ALL" ); + } else { + for ( i = 0; ludp->lud_attrs[ i ] != NULL; ++i ) { + printf( " <%s>", ludp->lud_attrs[ i ] ); + } + } + printf( "\n\t scope: %s\n", + ludp->lud_scope == LDAP_SCOPE_BASE ? "baseObject" + : ludp->lud_scope == LDAP_SCOPE_ONELEVEL ? "oneLevel" + : ludp->lud_scope == LDAP_SCOPE_SUBTREE ? "subtree" +#ifdef LDAP_SCOPE_SUBORDINATE + : ludp->lud_scope == LDAP_SCOPE_SUBORDINATE ? "children" +#endif + : "**invalid**" ); + printf( "\tfilter: <%s>\n", ludp->lud_filter ); + ldap_free_urldesc( ludp ); + } + break; + + case 'n': /* set dn suffix, for convenience */ + get_line( line, sizeof(line), stdin, "DN suffix? " ); + strcpy( dnsuffix, line ); + break; + + case 'o': /* set ldap options */ + get_line( line, sizeof(line), stdin, "alias deref (0=never, 1=searching, 2=finding, 3=always)?" ); + ld->ld_deref = atoi( line ); + get_line( line, sizeof(line), stdin, "timelimit?" ); + ld->ld_timelimit = atoi( line ); + get_line( line, sizeof(line), stdin, "sizelimit?" ); + ld->ld_sizelimit = atoi( line ); + + LDAP_BOOL_ZERO(&ld->ld_options); + + get_line( line, sizeof(line), stdin, + "Recognize and chase referrals (0=no, 1=yes)?" ); + if ( atoi( line ) != 0 ) { + LDAP_BOOL_SET(&ld->ld_options, LDAP_BOOL_REFERRALS); + get_line( line, sizeof(line), stdin, + "Prompt for bind credentials when chasing referrals (0=no, 1=yes)?" ); + if ( atoi( line ) != 0 ) { + ldap_set_rebind_proc( ld, bind_prompt, NULL ); + } + } + break; + + case '?': /* help */ + printf( +"Commands: [ad]d [ab]andon [b]ind\n" +" [B]ind async [c]ompare\n" +" [modi]fy [modr]dn [rem]ove\n" +" [res]ult [s]earch [q]uit/unbind\n\n" +" [d]ebug set ms[g]id\n" +" d[n]suffix [t]imeout [v]ersion\n" +" [?]help [o]ptions" +" [E]xplode dn [p]arse LDAP URL\n" ); + break; + + default: + printf( "Invalid command. Type ? for help.\n" ); + break; + } + + (void) memset( line, '\0', sizeof(line) ); + } + + return( 0 ); +} + +static void +handle_result( LDAP *ld, LDAPMessage *lm ) +{ + switch ( lm->lm_msgtype ) { + case LDAP_RES_COMPARE: + printf( "Compare result\n" ); + print_ldap_result( ld, lm, "compare" ); + break; + + case LDAP_RES_SEARCH_RESULT: + printf( "Search result\n" ); + print_ldap_result( ld, lm, "search" ); + break; + + case LDAP_RES_SEARCH_ENTRY: + printf( "Search entry\n" ); + print_search_entry( ld, lm ); + break; + + case LDAP_RES_ADD: + printf( "Add result\n" ); + print_ldap_result( ld, lm, "add" ); + break; + + case LDAP_RES_DELETE: + printf( "Delete result\n" ); + print_ldap_result( ld, lm, "delete" ); + break; + + case LDAP_RES_MODRDN: + printf( "ModRDN result\n" ); + print_ldap_result( ld, lm, "modrdn" ); + break; + + case LDAP_RES_BIND: + printf( "Bind result\n" ); + print_ldap_result( ld, lm, "bind" ); + break; + + default: + printf( "Unknown result type 0x%lx\n", + (unsigned long) lm->lm_msgtype ); + print_ldap_result( ld, lm, "unknown" ); + } +} + +static void +print_ldap_result( LDAP *ld, LDAPMessage *lm, const char *s ) +{ + ldap_result2error( ld, lm, 1 ); + ldap_perror( ld, s ); +/* + if ( ld->ld_error != NULL && *ld->ld_error != '\0' ) + fprintf( stderr, "Additional info: %s\n", ld->ld_error ); + if ( LDAP_NAME_ERROR( ld->ld_errno ) && ld->ld_matched != NULL ) + fprintf( stderr, "Matched DN: %s\n", ld->ld_matched ); +*/ +} + +static void +print_search_entry( LDAP *ld, LDAPMessage *res ) +{ + LDAPMessage *e; + + for ( e = ldap_first_entry( ld, res ); e != NULL; + e = ldap_next_entry( ld, e ) ) + { + BerElement *ber = NULL; + char *a, *dn, *ufn; + + if ( e->lm_msgtype == LDAP_RES_SEARCH_RESULT ) + break; + + dn = ldap_get_dn( ld, e ); + printf( "\tDN: %s\n", dn ); + + ufn = ldap_dn2ufn( dn ); + printf( "\tUFN: %s\n", ufn ); + + free( dn ); + free( ufn ); + + for ( a = ldap_first_attribute( ld, e, &ber ); a != NULL; + a = ldap_next_attribute( ld, e, ber ) ) + { + struct berval **vals; + + printf( "\t\tATTR: %s\n", a ); + if ( (vals = ldap_get_values_len( ld, e, a )) + == NULL ) { + printf( "\t\t\t(no values)\n" ); + } else { + int i; + for ( i = 0; vals[i] != NULL; i++ ) { + int j, nonascii; + + nonascii = 0; + for ( j = 0; (ber_len_t) j < vals[i]->bv_len; j++ ) + if ( !isascii( vals[i]->bv_val[j] ) ) { + nonascii = 1; + break; + } + + if ( nonascii ) { + printf( "\t\t\tlength (%ld) (not ascii)\n", vals[i]->bv_len ); +#ifdef BPRINT_NONASCII + ber_bprint( vals[i]->bv_val, + vals[i]->bv_len ); +#endif /* BPRINT_NONASCII */ + continue; + } + printf( "\t\t\tlength (%ld) %s\n", + vals[i]->bv_len, vals[i]->bv_val ); + } + ber_bvecfree( vals ); + } + } + + if(ber != NULL) { + ber_free( ber, 0 ); + } + } + + if ( res->lm_msgtype == LDAP_RES_SEARCH_RESULT + || res->lm_chain != NULL ) + print_ldap_result( ld, res, "search" ); +} diff --git a/libraries/libldap/tls2.c b/libraries/libldap/tls2.c new file mode 100644 index 0000000..d25c190 --- /dev/null +++ b/libraries/libldap/tls2.c @@ -0,0 +1,1371 @@ +/* tls.c - Handle tls/ssl. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: restructured by Howard Chu. + */ + +#include "portable.h" +#include "ldap_config.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> + +#include "ldap-int.h" + +#ifdef HAVE_TLS + +#include "ldap-tls.h" + +static tls_impl *tls_imp = &ldap_int_tls_impl; +#define HAS_TLS( sb ) ber_sockbuf_ctrl( sb, LBER_SB_OPT_HAS_IO, \ + (void *)tls_imp->ti_sbio ) + +#endif /* HAVE_TLS */ + +#ifdef LDAP_DEVEL +#define LDAP_USE_NON_BLOCKING_TLS +#endif /* LDAP_DEVEL */ + +/* RFC2459 minimum required set of supported attribute types + * in a certificate DN + */ +typedef struct oid_name { + struct berval oid; + struct berval name; +} oid_name; + +static oid_name oids[] = { + { BER_BVC("2.5.4.3"), BER_BVC("cn") }, + { BER_BVC("2.5.4.4"), BER_BVC("sn") }, + { BER_BVC("2.5.4.6"), BER_BVC("c") }, + { BER_BVC("2.5.4.7"), BER_BVC("l") }, + { BER_BVC("2.5.4.8"), BER_BVC("st") }, + { BER_BVC("2.5.4.10"), BER_BVC("o") }, + { BER_BVC("2.5.4.11"), BER_BVC("ou") }, + { BER_BVC("2.5.4.12"), BER_BVC("title") }, + { BER_BVC("2.5.4.41"), BER_BVC("name") }, + { BER_BVC("2.5.4.42"), BER_BVC("givenName") }, + { BER_BVC("2.5.4.43"), BER_BVC("initials") }, + { BER_BVC("2.5.4.44"), BER_BVC("generationQualifier") }, + { BER_BVC("2.5.4.46"), BER_BVC("dnQualifier") }, + { BER_BVC("1.2.840.113549.1.9.1"), BER_BVC("email") }, + { BER_BVC("0.9.2342.19200300.100.1.25"), BER_BVC("dc") }, + { BER_BVNULL, BER_BVNULL } +}; + +#ifdef HAVE_TLS + +void +ldap_pvt_tls_ctx_free ( void *c ) +{ + if ( !c ) return; + tls_imp->ti_ctx_free( c ); +} + +static void +tls_ctx_ref( tls_ctx *ctx ) +{ + if ( !ctx ) return; + + tls_imp->ti_ctx_ref( ctx ); +} + +#ifdef LDAP_R_COMPILE +/* + * an extra mutex for the default ctx. + */ +static ldap_pvt_thread_mutex_t tls_def_ctx_mutex; +#endif + +void +ldap_int_tls_destroy( struct ldapoptions *lo ) +{ + if ( lo->ldo_tls_ctx ) { + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + } + + if ( lo->ldo_tls_certfile ) { + LDAP_FREE( lo->ldo_tls_certfile ); + lo->ldo_tls_certfile = NULL; + } + if ( lo->ldo_tls_keyfile ) { + LDAP_FREE( lo->ldo_tls_keyfile ); + lo->ldo_tls_keyfile = NULL; + } + if ( lo->ldo_tls_dhfile ) { + LDAP_FREE( lo->ldo_tls_dhfile ); + lo->ldo_tls_dhfile = NULL; + } + if ( lo->ldo_tls_cacertfile ) { + LDAP_FREE( lo->ldo_tls_cacertfile ); + lo->ldo_tls_cacertfile = NULL; + } + if ( lo->ldo_tls_cacertdir ) { + LDAP_FREE( lo->ldo_tls_cacertdir ); + lo->ldo_tls_cacertdir = NULL; + } + if ( lo->ldo_tls_ciphersuite ) { + LDAP_FREE( lo->ldo_tls_ciphersuite ); + lo->ldo_tls_ciphersuite = NULL; + } + if ( lo->ldo_tls_crlfile ) { + LDAP_FREE( lo->ldo_tls_crlfile ); + lo->ldo_tls_crlfile = NULL; + } +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +void +ldap_pvt_tls_destroy( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + + ldap_int_tls_destroy( lo ); + + tls_imp->ti_tls_destroy(); +} + +/* + * Initialize a particular TLS implementation. + * Called once per implementation. + */ +static int +tls_init(tls_impl *impl ) +{ + static int tls_initialized = 0; + + if ( !tls_initialized++ ) { +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &tls_def_ctx_mutex ); +#endif + } + + if ( impl->ti_inited++ ) return 0; + +#ifdef LDAP_R_COMPILE + impl->ti_thr_init(); +#endif + return impl->ti_tls_init(); +} + +/* + * Initialize TLS subsystem. Called once per implementation. + */ +int +ldap_pvt_tls_init( void ) +{ + return tls_init( tls_imp ); +} + +/* + * initialize a new TLS context + */ +static int +ldap_int_tls_init_ctx( struct ldapoptions *lo, int is_server ) +{ + int rc = 0; + tls_impl *ti = tls_imp; + struct ldaptls lts = lo->ldo_tls_info; + + if ( lo->ldo_tls_ctx ) + return 0; + + tls_init( ti ); + + if ( is_server && !lts.lt_certfile && !lts.lt_keyfile && + !lts.lt_cacertfile && !lts.lt_cacertdir ) { + /* minimum configuration not provided */ + return LDAP_NOT_SUPPORTED; + } + +#ifdef HAVE_EBCDIC + /* This ASCII/EBCDIC handling is a real pain! */ + if ( lts.lt_ciphersuite ) { + lts.lt_ciphersuite = LDAP_STRDUP( lts.lt_ciphersuite ); + __atoe( lts.lt_ciphersuite ); + } + if ( lts.lt_cacertfile ) { + lts.lt_cacertfile = LDAP_STRDUP( lts.lt_cacertfile ); + __atoe( lts.lt_cacertfile ); + } + if ( lts.lt_certfile ) { + lts.lt_certfile = LDAP_STRDUP( lts.lt_certfile ); + __atoe( lts.lt_certfile ); + } + if ( lts.lt_keyfile ) { + lts.lt_keyfile = LDAP_STRDUP( lts.lt_keyfile ); + __atoe( lts.lt_keyfile ); + } + if ( lts.lt_crlfile ) { + lts.lt_crlfile = LDAP_STRDUP( lts.lt_crlfile ); + __atoe( lts.lt_crlfile ); + } + if ( lts.lt_cacertdir ) { + lts.lt_cacertdir = LDAP_STRDUP( lts.lt_cacertdir ); + __atoe( lts.lt_cacertdir ); + } + if ( lts.lt_dhfile ) { + lts.lt_dhfile = LDAP_STRDUP( lts.lt_dhfile ); + __atoe( lts.lt_dhfile ); + } +#endif + lo->ldo_tls_ctx = ti->ti_ctx_new( lo ); + if ( lo->ldo_tls_ctx == NULL ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not allocate default ctx.\n", + 0,0,0); + rc = -1; + goto error_exit; + } + + rc = ti->ti_ctx_init( lo, <s, is_server ); + +error_exit: + if ( rc < 0 && lo->ldo_tls_ctx != NULL ) { + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + } +#ifdef HAVE_EBCDIC + LDAP_FREE( lts.lt_ciphersuite ); + LDAP_FREE( lts.lt_cacertfile ); + LDAP_FREE( lts.lt_certfile ); + LDAP_FREE( lts.lt_keyfile ); + LDAP_FREE( lts.lt_crlfile ); + LDAP_FREE( lts.lt_cacertdir ); + LDAP_FREE( lts.lt_dhfile ); +#endif + return rc; +} + +/* + * initialize the default context + */ +int +ldap_pvt_tls_init_def_ctx( int is_server ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + int rc; + LDAP_MUTEX_LOCK( &tls_def_ctx_mutex ); + rc = ldap_int_tls_init_ctx( lo, is_server ); + LDAP_MUTEX_UNLOCK( &tls_def_ctx_mutex ); + return rc; +} + +static tls_session * +alloc_handle( void *ctx_arg, int is_server ) +{ + tls_ctx *ctx; + tls_session *ssl; + + if ( ctx_arg ) { + ctx = ctx_arg; + } else { + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + if ( ldap_pvt_tls_init_def_ctx( is_server ) < 0 ) return NULL; + ctx = lo->ldo_tls_ctx; + } + + ssl = tls_imp->ti_session_new( ctx, is_server ); + if ( ssl == NULL ) { + Debug( LDAP_DEBUG_ANY,"TLS: can't create ssl handle.\n",0,0,0); + return NULL; + } + return ssl; +} + +static int +update_flags( Sockbuf *sb, tls_session * ssl, int rc ) +{ + sb->sb_trans_needs_read = 0; + sb->sb_trans_needs_write = 0; + + return tls_imp->ti_session_upflags( sb, ssl, rc ); +} + +/* + * Call this to do a TLS connect on a sockbuf. ctx_arg can be + * a SSL_CTX * or NULL, in which case the default ctx is used. + * + * Return value: + * + * 0 - Success. Connection is ready for communication. + * <0 - Error. Can't create a TLS stream. + * >0 - Partial success. + * Do a select (using information from lber_pvt_sb_needs_{read,write} + * and call again. + */ + +static int +ldap_int_tls_connect( LDAP *ld, LDAPConn *conn, const char *host ) +{ + Sockbuf *sb = conn->lconn_sb; + int err; + tls_session *ssl = NULL; + + if ( HAS_TLS( sb )) { + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); + } else { + struct ldapoptions *lo; + tls_ctx *ctx; + + ctx = ld->ld_options.ldo_tls_ctx; + + ssl = alloc_handle( ctx, 0 ); + + if ( ssl == NULL ) return -1; + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)"tls_" ); +#endif + ber_sockbuf_add_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); + + lo = LDAP_INT_GLOBAL_OPT(); + if( ctx == NULL ) { + ctx = lo->ldo_tls_ctx; + ld->ld_options.ldo_tls_ctx = ctx; + tls_ctx_ref( ctx ); + } + if ( ld->ld_options.ldo_tls_connect_cb ) + ld->ld_options.ldo_tls_connect_cb( ld, ssl, ctx, + ld->ld_options.ldo_tls_connect_arg ); + if ( lo && lo->ldo_tls_connect_cb && lo->ldo_tls_connect_cb != + ld->ld_options.ldo_tls_connect_cb ) + lo->ldo_tls_connect_cb( ld, ssl, ctx, lo->ldo_tls_connect_arg ); + } + + err = tls_imp->ti_session_connect( ld, ssl ); + +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + + if ( err == 0 ) { + err = ldap_pvt_tls_check_hostname( ld, ssl, host ); + } + + if ( err < 0 ) + { + char buf[256], *msg; + if ( update_flags( sb, ssl, err )) { + return 1; + } + + msg = tls_imp->ti_session_errmsg( ssl, err, buf, sizeof(buf) ); + if ( msg ) { + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( msg ); +#ifdef HAVE_EBCDIC + if ( ld->ld_error ) __etoa(ld->ld_error); +#endif + } + + Debug( LDAP_DEBUG_ANY,"TLS: can't connect: %s.\n", + ld->ld_error ? ld->ld_error : "" ,0,0); + + ber_sockbuf_remove_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT ); +#ifdef LDAP_DEBUG + ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT ); +#endif + return -1; + } + + return 0; +} + +/* + * Call this to do a TLS accept on a sockbuf. + * Everything else is the same as with tls_connect. + */ +int +ldap_pvt_tls_accept( Sockbuf *sb, void *ctx_arg ) +{ + int err; + tls_session *ssl = NULL; + + if ( HAS_TLS( sb )) { + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&ssl ); + } else { + ssl = alloc_handle( ctx_arg, 1 ); + if ( ssl == NULL ) return -1; + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)"tls_" ); +#endif + ber_sockbuf_add_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT, (void *)ssl ); + } + + err = tls_imp->ti_session_accept( ssl ); + +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + + if ( err < 0 ) + { + if ( update_flags( sb, ssl, err )) return 1; + + if ( DebugTest( LDAP_DEBUG_ANY ) ) { + char buf[256], *msg; + msg = tls_imp->ti_session_errmsg( ssl, err, buf, sizeof(buf) ); + Debug( LDAP_DEBUG_ANY,"TLS: can't accept: %s.\n", + msg ? msg : "(unknown)", 0, 0 ); + } + + ber_sockbuf_remove_io( sb, tls_imp->ti_sbio, + LBER_SBIOD_LEVEL_TRANSPORT ); +#ifdef LDAP_DEBUG + ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_TRANSPORT ); +#endif + return -1; + } + return 0; +} + +int +ldap_pvt_tls_inplace ( Sockbuf *sb ) +{ + return HAS_TLS( sb ) ? 1 : 0; +} + +int +ldap_tls_inplace( LDAP *ld ) +{ + Sockbuf *sb = NULL; + + if ( ld->ld_defconn && ld->ld_defconn->lconn_sb ) { + sb = ld->ld_defconn->lconn_sb; + + } else if ( ld->ld_sb ) { + sb = ld->ld_sb; + + } else { + return 0; + } + + return ldap_pvt_tls_inplace( sb ); +} + +int +ldap_pvt_tls_get_peer_dn( void *s, struct berval *dn, + LDAPDN_rewrite_dummy *func, unsigned flags ) +{ + tls_session *session = s; + struct berval bvdn; + int rc; + + rc = tls_imp->ti_session_peer_dn( session, &bvdn ); + if ( rc ) return rc; + + rc = ldap_X509dn2bv( &bvdn, dn, + (LDAPDN_rewrite_func *)func, flags); + return rc; +} + +int +ldap_pvt_tls_check_hostname( LDAP *ld, void *s, const char *name_in ) +{ + tls_session *session = s; + + if (ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_NEVER && + ld->ld_options.ldo_tls_require_cert != LDAP_OPT_X_TLS_ALLOW) { + ld->ld_errno = tls_imp->ti_session_chkhost( ld, session, name_in ); + if (ld->ld_errno != LDAP_SUCCESS) { + return ld->ld_errno; + } + } + + return LDAP_SUCCESS; +} + +int +ldap_int_tls_config( LDAP *ld, int option, const char *arg ) +{ + int i; + + switch( option ) { + case LDAP_OPT_X_TLS_CACERTFILE: + case LDAP_OPT_X_TLS_CACERTDIR: + case LDAP_OPT_X_TLS_CERTFILE: + case LDAP_OPT_X_TLS_KEYFILE: + case LDAP_OPT_X_TLS_RANDOM_FILE: + case LDAP_OPT_X_TLS_CIPHER_SUITE: + case LDAP_OPT_X_TLS_DHFILE: + case LDAP_OPT_X_TLS_CRLFILE: /* GnuTLS only */ + return ldap_pvt_tls_set_option( ld, option, (void *) arg ); + + case LDAP_OPT_X_TLS_REQUIRE_CERT: + case LDAP_OPT_X_TLS: + i = -1; + if ( strcasecmp( arg, "never" ) == 0 ) { + i = LDAP_OPT_X_TLS_NEVER ; + + } else if ( strcasecmp( arg, "demand" ) == 0 ) { + i = LDAP_OPT_X_TLS_DEMAND ; + + } else if ( strcasecmp( arg, "allow" ) == 0 ) { + i = LDAP_OPT_X_TLS_ALLOW ; + + } else if ( strcasecmp( arg, "try" ) == 0 ) { + i = LDAP_OPT_X_TLS_TRY ; + + } else if ( ( strcasecmp( arg, "hard" ) == 0 ) || + ( strcasecmp( arg, "on" ) == 0 ) || + ( strcasecmp( arg, "yes" ) == 0) || + ( strcasecmp( arg, "true" ) == 0 ) ) + { + i = LDAP_OPT_X_TLS_HARD ; + } + + if (i >= 0) { + return ldap_pvt_tls_set_option( ld, option, &i ); + } + return -1; + case LDAP_OPT_X_TLS_PROTOCOL_MIN: { + char *next; + long l; + l = strtol( arg, &next, 10 ); + if ( l < 0 || l > 0xff || next == arg || + ( *next != '\0' && *next != '.' ) ) + return -1; + i = l << 8; + if (*next == '.') { + arg = next + 1; + l = strtol( arg, &next, 10 ); + if ( l < 0 || l > 0xff || next == arg || *next != '\0' ) + return -1; + i += l; + } + return ldap_pvt_tls_set_option( ld, option, &i ); + } +#ifdef HAVE_OPENSSL_CRL + case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ + i = -1; + if ( strcasecmp( arg, "none" ) == 0 ) { + i = LDAP_OPT_X_TLS_CRL_NONE ; + } else if ( strcasecmp( arg, "peer" ) == 0 ) { + i = LDAP_OPT_X_TLS_CRL_PEER ; + } else if ( strcasecmp( arg, "all" ) == 0 ) { + i = LDAP_OPT_X_TLS_CRL_ALL ; + } + if (i >= 0) { + return ldap_pvt_tls_set_option( ld, option, &i ); + } + return -1; +#endif + } + return -1; +} + +int +ldap_pvt_tls_get_option( LDAP *ld, int option, void *arg ) +{ + struct ldapoptions *lo; + + if( option == LDAP_OPT_X_TLS_PACKAGE ) { + *(char **)arg = LDAP_STRDUP( tls_imp->ti_name ); + return 0; + } + + if( ld != NULL ) { + assert( LDAP_VALID( ld ) ); + + if( !LDAP_VALID( ld ) ) { + return LDAP_OPT_ERROR; + } + + lo = &ld->ld_options; + + } else { + /* Get pointer to global option structure */ + lo = LDAP_INT_GLOBAL_OPT(); + if ( lo == NULL ) { + return LDAP_NO_MEMORY; + } + } + + switch( option ) { + case LDAP_OPT_X_TLS: + *(int *)arg = lo->ldo_tls_mode; + break; + case LDAP_OPT_X_TLS_CTX: + *(void **)arg = lo->ldo_tls_ctx; + if ( lo->ldo_tls_ctx ) { + tls_ctx_ref( lo->ldo_tls_ctx ); + } + break; + case LDAP_OPT_X_TLS_CACERTFILE: + *(char **)arg = lo->ldo_tls_cacertfile ? + LDAP_STRDUP( lo->ldo_tls_cacertfile ) : NULL; + break; + case LDAP_OPT_X_TLS_CACERTDIR: + *(char **)arg = lo->ldo_tls_cacertdir ? + LDAP_STRDUP( lo->ldo_tls_cacertdir ) : NULL; + break; + case LDAP_OPT_X_TLS_CERTFILE: + *(char **)arg = lo->ldo_tls_certfile ? + LDAP_STRDUP( lo->ldo_tls_certfile ) : NULL; + break; + case LDAP_OPT_X_TLS_KEYFILE: + *(char **)arg = lo->ldo_tls_keyfile ? + LDAP_STRDUP( lo->ldo_tls_keyfile ) : NULL; + break; + case LDAP_OPT_X_TLS_DHFILE: + *(char **)arg = lo->ldo_tls_dhfile ? + LDAP_STRDUP( lo->ldo_tls_dhfile ) : NULL; + break; + case LDAP_OPT_X_TLS_CRLFILE: /* GnuTLS only */ + *(char **)arg = lo->ldo_tls_crlfile ? + LDAP_STRDUP( lo->ldo_tls_crlfile ) : NULL; + break; + case LDAP_OPT_X_TLS_REQUIRE_CERT: + *(int *)arg = lo->ldo_tls_require_cert; + break; +#ifdef HAVE_OPENSSL_CRL + case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ + *(int *)arg = lo->ldo_tls_crlcheck; + break; +#endif + case LDAP_OPT_X_TLS_CIPHER_SUITE: + *(char **)arg = lo->ldo_tls_ciphersuite ? + LDAP_STRDUP( lo->ldo_tls_ciphersuite ) : NULL; + break; + case LDAP_OPT_X_TLS_PROTOCOL_MIN: + *(int *)arg = lo->ldo_tls_protocol_min; + break; + case LDAP_OPT_X_TLS_RANDOM_FILE: + *(char **)arg = lo->ldo_tls_randfile ? + LDAP_STRDUP( lo->ldo_tls_randfile ) : NULL; + break; + case LDAP_OPT_X_TLS_SSL_CTX: { + void *retval = 0; + if ( ld != NULL ) { + LDAPConn *conn = ld->ld_defconn; + if ( conn != NULL ) { + Sockbuf *sb = conn->lconn_sb; + retval = ldap_pvt_tls_sb_ctx( sb ); + } + } + *(void **)arg = retval; + break; + } + case LDAP_OPT_X_TLS_CONNECT_CB: + *(LDAP_TLS_CONNECT_CB **)arg = lo->ldo_tls_connect_cb; + break; + case LDAP_OPT_X_TLS_CONNECT_ARG: + *(void **)arg = lo->ldo_tls_connect_arg; + break; + default: + return -1; + } + return 0; +} + +int +ldap_pvt_tls_set_option( LDAP *ld, int option, void *arg ) +{ + struct ldapoptions *lo; + + if( ld != NULL ) { + assert( LDAP_VALID( ld ) ); + + if( !LDAP_VALID( ld ) ) { + return LDAP_OPT_ERROR; + } + + lo = &ld->ld_options; + + } else { + /* Get pointer to global option structure */ + lo = LDAP_INT_GLOBAL_OPT(); + if ( lo == NULL ) { + return LDAP_NO_MEMORY; + } + } + + switch( option ) { + case LDAP_OPT_X_TLS: + if ( !arg ) return -1; + + switch( *(int *) arg ) { + case LDAP_OPT_X_TLS_NEVER: + case LDAP_OPT_X_TLS_DEMAND: + case LDAP_OPT_X_TLS_ALLOW: + case LDAP_OPT_X_TLS_TRY: + case LDAP_OPT_X_TLS_HARD: + if (lo != NULL) { + lo->ldo_tls_mode = *(int *)arg; + } + + return 0; + } + return -1; + + case LDAP_OPT_X_TLS_CTX: + if ( lo->ldo_tls_ctx ) + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = arg; + tls_ctx_ref( lo->ldo_tls_ctx ); + return 0; + case LDAP_OPT_X_TLS_CONNECT_CB: + lo->ldo_tls_connect_cb = (LDAP_TLS_CONNECT_CB *)arg; + return 0; + case LDAP_OPT_X_TLS_CONNECT_ARG: + lo->ldo_tls_connect_arg = arg; + return 0; + case LDAP_OPT_X_TLS_CACERTFILE: + if ( lo->ldo_tls_cacertfile ) LDAP_FREE( lo->ldo_tls_cacertfile ); + lo->ldo_tls_cacertfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_CACERTDIR: + if ( lo->ldo_tls_cacertdir ) LDAP_FREE( lo->ldo_tls_cacertdir ); + lo->ldo_tls_cacertdir = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_CERTFILE: + if ( lo->ldo_tls_certfile ) LDAP_FREE( lo->ldo_tls_certfile ); + lo->ldo_tls_certfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_KEYFILE: + if ( lo->ldo_tls_keyfile ) LDAP_FREE( lo->ldo_tls_keyfile ); + lo->ldo_tls_keyfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_DHFILE: + if ( lo->ldo_tls_dhfile ) LDAP_FREE( lo->ldo_tls_dhfile ); + lo->ldo_tls_dhfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_CRLFILE: /* GnuTLS only */ + if ( lo->ldo_tls_crlfile ) LDAP_FREE( lo->ldo_tls_crlfile ); + lo->ldo_tls_crlfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + case LDAP_OPT_X_TLS_REQUIRE_CERT: + if ( !arg ) return -1; + switch( *(int *) arg ) { + case LDAP_OPT_X_TLS_NEVER: + case LDAP_OPT_X_TLS_DEMAND: + case LDAP_OPT_X_TLS_ALLOW: + case LDAP_OPT_X_TLS_TRY: + case LDAP_OPT_X_TLS_HARD: + lo->ldo_tls_require_cert = * (int *) arg; + return 0; + } + return -1; +#ifdef HAVE_OPENSSL_CRL + case LDAP_OPT_X_TLS_CRLCHECK: /* OpenSSL only */ + if ( !arg ) return -1; + switch( *(int *) arg ) { + case LDAP_OPT_X_TLS_CRL_NONE: + case LDAP_OPT_X_TLS_CRL_PEER: + case LDAP_OPT_X_TLS_CRL_ALL: + lo->ldo_tls_crlcheck = * (int *) arg; + return 0; + } + return -1; +#endif + case LDAP_OPT_X_TLS_CIPHER_SUITE: + if ( lo->ldo_tls_ciphersuite ) LDAP_FREE( lo->ldo_tls_ciphersuite ); + lo->ldo_tls_ciphersuite = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + return 0; + + case LDAP_OPT_X_TLS_PROTOCOL_MIN: + if ( !arg ) return -1; + lo->ldo_tls_protocol_min = *(int *)arg; + return 0; + case LDAP_OPT_X_TLS_RANDOM_FILE: + if ( ld != NULL ) + return -1; + if ( lo->ldo_tls_randfile ) LDAP_FREE (lo->ldo_tls_randfile ); + lo->ldo_tls_randfile = arg ? LDAP_STRDUP( (char *) arg ) : NULL; + break; + case LDAP_OPT_X_TLS_NEWCTX: + if ( !arg ) return -1; + if ( lo->ldo_tls_ctx ) + ldap_pvt_tls_ctx_free( lo->ldo_tls_ctx ); + lo->ldo_tls_ctx = NULL; + return ldap_int_tls_init_ctx( lo, *(int *)arg ); + default: + return -1; + } + return 0; +} + +int +ldap_int_tls_start ( LDAP *ld, LDAPConn *conn, LDAPURLDesc *srv ) +{ + Sockbuf *sb; + char *host; + void *ssl; + int ret; +#ifdef LDAP_USE_NON_BLOCKING_TLS + struct timeval start_time_tv, tv, tv0; + ber_socket_t sd = AC_SOCKET_ERROR; +#endif /* LDAP_USE_NON_BLOCKING_TLS */ + + if ( !conn ) + return LDAP_PARAM_ERROR; + + sb = conn->lconn_sb; + if( srv ) { + host = srv->lud_host; + } else { + host = conn->lconn_server->lud_host; + } + + /* avoid NULL host */ + if( host == NULL ) { + host = "localhost"; + } + + (void) tls_init( tls_imp ); + +#ifdef LDAP_USE_NON_BLOCKING_TLS + /* + * Use non-blocking io during SSL Handshake when a timeout is configured + */ + if ( ld->ld_options.ldo_tm_net.tv_sec >= 0 ) { + ber_sockbuf_ctrl( sb, LBER_SB_OPT_SET_NONBLOCK, (void*)1 ); + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_FD, &sd ); + tv = ld->ld_options.ldo_tm_net; + tv0 = tv; +#ifdef HAVE_GETTIMEOFDAY + gettimeofday( &start_time_tv, NULL ); +#else /* ! HAVE_GETTIMEOFDAY */ + time( &start_time_tv.tv_sec ); + start_time_tv.tv_usec = 0; +#endif /* ! HAVE_GETTIMEOFDAY */ + } + +#endif /* LDAP_USE_NON_BLOCKING_TLS */ + + ld->ld_errno = LDAP_SUCCESS; + ret = ldap_int_tls_connect( ld, conn, host ); + +#ifdef LDAP_USE_NON_BLOCKING_TLS + while ( ret > 0 ) { /* this should only happen for non-blocking io */ + int wr=0; + + if ( sb->sb_trans_needs_read ) { + wr=0; + } else if ( sb->sb_trans_needs_write ) { + wr=1; + } + Debug( LDAP_DEBUG_TRACE, "ldap_int_tls_start: ldap_int_tls_connect needs %s\n", + wr ? "write": "read", 0, 0); + + ret = ldap_int_poll( ld, sd, &tv, wr); + if ( ret < 0 ) { + ld->ld_errno = LDAP_TIMEOUT; + break; + } else { + /* ldap_int_poll called ldap_pvt_ndelay_off */ + ber_sockbuf_ctrl( sb, LBER_SB_OPT_SET_NONBLOCK, (void*)1 ); + ret = ldap_int_tls_connect( ld, conn, host ); + if ( ret > 0 ) { /* need to call tls_connect once more */ + struct timeval curr_time_tv, delta_tv; + + /* This is mostly copied from result.c:wait4msg(), should + * probably be moved into a separate function */ +#ifdef HAVE_GETTIMEOFDAY + gettimeofday( &curr_time_tv, NULL ); +#else /* ! HAVE_GETTIMEOFDAY */ + time( &curr_time_tv.tv_sec ); + curr_time_tv.tv_usec = 0; +#endif /* ! HAVE_GETTIMEOFDAY */ + + /* delta = curr - start */ + delta_tv.tv_sec = curr_time_tv.tv_sec - start_time_tv.tv_sec; + delta_tv.tv_usec = curr_time_tv.tv_usec - start_time_tv.tv_usec; + if ( delta_tv.tv_usec < 0 ) { + delta_tv.tv_sec--; + delta_tv.tv_usec += 1000000; + } + + /* tv0 < delta ? */ + if ( ( tv0.tv_sec < delta_tv.tv_sec ) || + ( ( tv0.tv_sec == delta_tv.tv_sec ) && + ( tv0.tv_usec < delta_tv.tv_usec ) ) ) + { + ret = -1; + ld->ld_errno = LDAP_TIMEOUT; + break; + } else { + /* timeout -= delta_time */ + tv0.tv_sec -= delta_tv.tv_sec; + tv0.tv_usec -= delta_tv.tv_usec; + if ( tv0.tv_usec < 0 ) { + tv0.tv_sec--; + tv0.tv_usec += 1000000; + } + start_time_tv.tv_sec = curr_time_tv.tv_sec; + start_time_tv.tv_usec = curr_time_tv.tv_usec; + } + tv = tv0; + Debug( LDAP_DEBUG_TRACE, "ldap_int_tls_start: ld %p %ld s %ld us to go\n", + (void *)ld, (long) tv.tv_sec, (long) tv.tv_usec ); + } + } + } + if ( ld->ld_options.ldo_tm_net.tv_sec >= 0 ) { + ber_sockbuf_ctrl( sb, LBER_SB_OPT_SET_NONBLOCK, NULL ); + } +#endif /* LDAP_USE_NON_BLOCKING_TLS */ + + if ( ret < 0 ) { + if ( ld->ld_errno == LDAP_SUCCESS ) + ld->ld_errno = LDAP_CONNECT_ERROR; + return (ld->ld_errno); + } + + return LDAP_SUCCESS; +} + +void * +ldap_pvt_tls_sb_ctx( Sockbuf *sb ) +{ + void *p = NULL; + + ber_sockbuf_ctrl( sb, LBER_SB_OPT_GET_SSL, (void *)&p ); + return p; +} + +int +ldap_pvt_tls_get_strength( void *s ) +{ + tls_session *session = s; + + return tls_imp->ti_session_strength( session ); +} + +int +ldap_pvt_tls_get_my_dn( void *s, struct berval *dn, LDAPDN_rewrite_dummy *func, unsigned flags ) +{ + tls_session *session = s; + struct berval der_dn; + int rc; + + rc = tls_imp->ti_session_my_dn( session, &der_dn ); + if ( rc == LDAP_SUCCESS ) + rc = ldap_X509dn2bv(&der_dn, dn, (LDAPDN_rewrite_func *)func, flags ); + return rc; +} +#endif /* HAVE_TLS */ + +int +ldap_start_tls( LDAP *ld, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int *msgidp ) +{ + return ldap_extended_operation( ld, LDAP_EXOP_START_TLS, + NULL, serverctrls, clientctrls, msgidp ); +} + +int +ldap_install_tls( LDAP *ld ) +{ +#ifndef HAVE_TLS + return LDAP_NOT_SUPPORTED; +#else + if ( ldap_tls_inplace( ld ) ) { + return LDAP_LOCAL_ERROR; + } + + return ldap_int_tls_start( ld, ld->ld_defconn, NULL ); +#endif +} + +int +ldap_start_tls_s ( LDAP *ld, + LDAPControl **serverctrls, + LDAPControl **clientctrls ) +{ +#ifndef HAVE_TLS + return LDAP_NOT_SUPPORTED; +#else + int rc; + char *rspoid = NULL; + struct berval *rspdata = NULL; + + /* XXYYZ: this initiates operation only on default connection! */ + + if ( ldap_tls_inplace( ld ) ) { + return LDAP_LOCAL_ERROR; + } + + rc = ldap_extended_operation_s( ld, LDAP_EXOP_START_TLS, + NULL, serverctrls, clientctrls, &rspoid, &rspdata ); + + if ( rspoid != NULL ) { + LDAP_FREE(rspoid); + } + + if ( rspdata != NULL ) { + ber_bvfree( rspdata ); + } + + if ( rc == LDAP_SUCCESS ) { + rc = ldap_int_tls_start( ld, ld->ld_defconn, NULL ); + } + + return rc; +#endif +} + +/* These tags probably all belong in lber.h, but they're + * not normally encountered when processing LDAP, so maybe + * they belong somewhere else instead. + */ + +#define LBER_TAG_OID ((ber_tag_t) 0x06UL) + +/* Tags for string types used in a DirectoryString. + * + * Note that IA5string is not one of the defined choices for + * DirectoryString in X.520, but it gets used for email AVAs. + */ +#define LBER_TAG_UTF8 ((ber_tag_t) 0x0cUL) +#define LBER_TAG_PRINTABLE ((ber_tag_t) 0x13UL) +#define LBER_TAG_TELETEX ((ber_tag_t) 0x14UL) +#define LBER_TAG_IA5 ((ber_tag_t) 0x16UL) +#define LBER_TAG_UNIVERSAL ((ber_tag_t) 0x1cUL) +#define LBER_TAG_BMP ((ber_tag_t) 0x1eUL) + +static oid_name * +find_oid( struct berval *oid ) +{ + int i; + + for ( i=0; !BER_BVISNULL( &oids[i].oid ); i++ ) { + if ( oids[i].oid.bv_len != oid->bv_len ) continue; + if ( !strcmp( oids[i].oid.bv_val, oid->bv_val )) + return &oids[i]; + } + return NULL; +} + +/* Converts BER Bitstring value to LDAP BitString value (RFC4517) + * + * berValue : IN + * rfc4517Value: OUT + * + * berValue and ldapValue should not be NULL + */ + +#define BITS_PER_BYTE 8 +#define SQUOTE_LENGTH 1 +#define B_CHAR_LENGTH 1 +#define STR_OVERHEAD (2*SQUOTE_LENGTH + B_CHAR_LENGTH) + +static int +der_to_ldap_BitString (struct berval *berValue, + struct berval *ldapValue) +{ + ber_len_t bitPadding=0; + ber_len_t bits, maxBits; + char *tmpStr; + unsigned char byte; + ber_len_t bitLength; + ber_len_t valLen; + unsigned char* valPtr; + + ldapValue->bv_len=0; + ldapValue->bv_val=NULL; + + /* Gets padding and points to binary data */ + valLen=berValue->bv_len; + valPtr=(unsigned char*)berValue->bv_val; + if (valLen) { + bitPadding=(ber_len_t)(valPtr[0]); + valLen--; + valPtr++; + } + /* If Block is non DER encoding fixes to DER encoding */ + if (bitPadding >= BITS_PER_BYTE) { + if (valLen*BITS_PER_BYTE > bitPadding ) { + valLen-=(bitPadding/BITS_PER_BYTE); + bitPadding%=BITS_PER_BYTE; + } else { + valLen=0; + bitPadding=0; + } + } + /* Just in case bad encoding */ + if (valLen*BITS_PER_BYTE < bitPadding ) { + bitPadding=0; + valLen=0; + } + + /* Gets buffer to hold RFC4517 Bit String format */ + bitLength=valLen*BITS_PER_BYTE-bitPadding; + tmpStr=LDAP_MALLOC(bitLength + STR_OVERHEAD + 1); + + if (!tmpStr) + return LDAP_NO_MEMORY; + + ldapValue->bv_val=tmpStr; + ldapValue->bv_len=bitLength + STR_OVERHEAD; + + /* Formatting in '*binary-digit'B format */ + maxBits=BITS_PER_BYTE; + *tmpStr++ ='\''; + while(valLen) { + byte=*valPtr; + if (valLen==1) + maxBits-=bitPadding; + for (bits=0; bits<maxBits; bits++) { + if (0x80 & byte) + *tmpStr='1'; + else + *tmpStr='0'; + tmpStr++; + byte<<=1; + } + valPtr++; + valLen--; + } + *tmpStr++ ='\''; + *tmpStr++ ='B'; + *tmpStr=0; + + return LDAP_SUCCESS; +} + +/* Convert a structured DN from an X.509 certificate into an LDAPV3 DN. + * x509_name must be raw DER. If func is non-NULL, the + * constructed DN will use numeric OIDs to identify attributeTypes, + * and the func() will be invoked to rewrite the DN with the given + * flags. + * + * Otherwise the DN will use shortNames from a hardcoded table. + */ +int +ldap_X509dn2bv( void *x509_name, struct berval *bv, LDAPDN_rewrite_func *func, + unsigned flags ) +{ + LDAPDN newDN; + LDAPRDN newRDN; + LDAPAVA *newAVA, *baseAVA; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + char oids[8192], *oidptr = oids, *oidbuf = NULL; + void *ptrs[2048]; + char *dn_end, *rdn_end; + int i, navas, nrdns, rc = LDAP_SUCCESS; + size_t dnsize, oidrem = sizeof(oids), oidsize = 0; + int csize; + ber_tag_t tag; + ber_len_t len; + oid_name *oidname; + + struct berval Oid, Val, oid2, *in = x509_name; + + assert( bv != NULL ); + + bv->bv_len = 0; + bv->bv_val = NULL; + + navas = 0; + nrdns = 0; + + /* A DN is a SEQUENCE of RDNs. An RDN is a SET of AVAs. + * An AVA is a SEQUENCE of attr and value. + * Count the number of AVAs and RDNs + */ + ber_init2( ber, in, LBER_USE_DER ); + tag = ber_peek_tag( ber, &len ); + if ( tag != LBER_SEQUENCE ) + return LDAP_DECODING_ERROR; + + for ( tag = ber_first_element( ber, &len, &dn_end ); + tag == LBER_SET; + tag = ber_next_element( ber, &len, dn_end )) { + nrdns++; + for ( tag = ber_first_element( ber, &len, &rdn_end ); + tag == LBER_SEQUENCE; + tag = ber_next_element( ber, &len, rdn_end )) { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + navas++; + } + } + + /* Allocate the DN/RDN/AVA stuff as a single block */ + dnsize = sizeof(LDAPRDN) * (nrdns+1); + dnsize += sizeof(LDAPAVA *) * (navas+nrdns); + dnsize += sizeof(LDAPAVA) * navas; + if (dnsize > sizeof(ptrs)) { + newDN = (LDAPDN)LDAP_MALLOC( dnsize ); + if ( newDN == NULL ) + return LDAP_NO_MEMORY; + } else { + newDN = (LDAPDN)(char *)ptrs; + } + + newDN[nrdns] = NULL; + newRDN = (LDAPRDN)(newDN + nrdns+1); + newAVA = (LDAPAVA *)(newRDN + navas + nrdns); + baseAVA = newAVA; + + /* Rewind and start extracting */ + ber_rewind( ber ); + + tag = ber_first_element( ber, &len, &dn_end ); + for ( i = nrdns - 1; i >= 0; i-- ) { + newDN[i] = newRDN; + + for ( tag = ber_first_element( ber, &len, &rdn_end ); + tag == LBER_SEQUENCE; + tag = ber_next_element( ber, &len, rdn_end )) { + + *newRDN++ = newAVA; + tag = ber_skip_tag( ber, &len ); + tag = ber_get_stringbv( ber, &Oid, LBER_BV_NOTERM ); + if ( tag != LBER_TAG_OID ) { + rc = LDAP_DECODING_ERROR; + goto nomem; + } + + oid2.bv_val = oidptr; + oid2.bv_len = oidrem; + if ( ber_decode_oid( &Oid, &oid2 ) < 0 ) { + rc = LDAP_DECODING_ERROR; + goto nomem; + } + oidname = find_oid( &oid2 ); + if ( !oidname ) { + newAVA->la_attr = oid2; + oidptr += oid2.bv_len + 1; + oidrem -= oid2.bv_len + 1; + + /* Running out of OID buffer space? */ + if (oidrem < 128) { + if ( oidsize == 0 ) { + oidsize = sizeof(oids) * 2; + oidrem = oidsize; + oidbuf = LDAP_MALLOC( oidsize ); + if ( oidbuf == NULL ) goto nomem; + oidptr = oidbuf; + } else { + char *old = oidbuf; + oidbuf = LDAP_REALLOC( oidbuf, oidsize*2 ); + if ( oidbuf == NULL ) goto nomem; + /* Buffer moved! Fix AVA pointers */ + if ( old != oidbuf ) { + LDAPAVA *a; + long dif = oidbuf - old; + + for (a=baseAVA; a<=newAVA; a++){ + if (a->la_attr.bv_val >= old && + a->la_attr.bv_val <= (old + oidsize)) + a->la_attr.bv_val += dif; + } + } + oidptr = oidbuf + oidsize - oidrem; + oidrem += oidsize; + oidsize *= 2; + } + } + } else { + if ( func ) { + newAVA->la_attr = oidname->oid; + } else { + newAVA->la_attr = oidname->name; + } + } + newAVA->la_private = NULL; + newAVA->la_flags = LDAP_AVA_STRING; + tag = ber_get_stringbv( ber, &Val, LBER_BV_NOTERM ); + switch(tag) { + case LBER_TAG_UNIVERSAL: + /* This uses 32-bit ISO 10646-1 */ + csize = 4; goto to_utf8; + case LBER_TAG_BMP: + /* This uses 16-bit ISO 10646-1 */ + csize = 2; goto to_utf8; + case LBER_TAG_TELETEX: + /* This uses 8-bit, assume ISO 8859-1 */ + csize = 1; +to_utf8: rc = ldap_ucs_to_utf8s( &Val, csize, &newAVA->la_value ); + newAVA->la_flags |= LDAP_AVA_NONPRINTABLE; +allocd: + newAVA->la_flags |= LDAP_AVA_FREE_VALUE; + if (rc != LDAP_SUCCESS) goto nomem; + break; + case LBER_TAG_UTF8: + newAVA->la_flags |= LDAP_AVA_NONPRINTABLE; + /* This is already in UTF-8 encoding */ + case LBER_TAG_IA5: + case LBER_TAG_PRINTABLE: + /* These are always 7-bit strings */ + newAVA->la_value = Val; + break; + case LBER_BITSTRING: + /* X.690 bitString value converted to RFC4517 Bit String */ + rc = der_to_ldap_BitString( &Val, &newAVA->la_value ); + goto allocd; + default: + /* Not a string type at all */ + newAVA->la_flags = 0; + newAVA->la_value = Val; + break; + } + newAVA++; + } + *newRDN++ = NULL; + tag = ber_next_element( ber, &len, dn_end ); + } + + if ( func ) { + rc = func( newDN, flags, NULL ); + if ( rc != LDAP_SUCCESS ) + goto nomem; + } + + rc = ldap_dn2bv_x( newDN, bv, LDAP_DN_FORMAT_LDAPV3, NULL ); + +nomem: + for (;baseAVA < newAVA; baseAVA++) { + if (baseAVA->la_flags & LDAP_AVA_FREE_ATTR) + LDAP_FREE( baseAVA->la_attr.bv_val ); + if (baseAVA->la_flags & LDAP_AVA_FREE_VALUE) + LDAP_FREE( baseAVA->la_value.bv_val ); + } + + if ( oidsize != 0 ) + LDAP_FREE( oidbuf ); + if ( newDN != (LDAPDN)(char *) ptrs ) + LDAP_FREE( newDN ); + return rc; +} + diff --git a/libraries/libldap/tls_g.c b/libraries/libldap/tls_g.c new file mode 100644 index 0000000..0266ff1 --- /dev/null +++ b/libraries/libldap/tls_g.c @@ -0,0 +1,940 @@ +/* tls_g.c - Handle tls/ssl using GNUTLS. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: GNUTLS support written by Howard Chu and + * Emily Backes; sponsored by The Written Word (thewrittenword.com) + * and Stanford University (stanford.edu). + */ + +#include "portable.h" + +#ifdef HAVE_GNUTLS + +#include "ldap_config.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "ldap-int.h" +#include "ldap-tls.h" + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +typedef struct tlsg_ctx { + gnutls_certificate_credentials_t cred; + gnutls_dh_params_t dh_params; + unsigned long verify_depth; + int refcount; + int reqcert; + gnutls_priority_t prios; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t ref_mutex; +#endif +} tlsg_ctx; + +typedef struct tlsg_session { + gnutls_session_t session; + tlsg_ctx *ctx; + struct berval peer_der_dn; +} tlsg_session; + +static int tlsg_parse_ciphers( tlsg_ctx *ctx, char *suites ); +static int tlsg_cert_verify( tlsg_session *s ); + +#ifdef LDAP_R_COMPILE + +static int +tlsg_mutex_init( void **priv ) +{ + int err = 0; + ldap_pvt_thread_mutex_t *lock = LDAP_MALLOC( sizeof( ldap_pvt_thread_mutex_t )); + + if ( !lock ) + err = ENOMEM; + if ( !err ) { + err = ldap_pvt_thread_mutex_init( lock ); + if ( err ) + LDAP_FREE( lock ); + else + *priv = lock; + } + return err; +} + +static int +tlsg_mutex_destroy( void **lock ) +{ + int err = ldap_pvt_thread_mutex_destroy( *lock ); + LDAP_FREE( *lock ); + return err; +} + +static int +tlsg_mutex_lock( void **lock ) +{ + return ldap_pvt_thread_mutex_lock( *lock ); +} + +static int +tlsg_mutex_unlock( void **lock ) +{ + return ldap_pvt_thread_mutex_unlock( *lock ); +} + +static void +tlsg_thr_init( void ) +{ + gnutls_global_set_mutex (tlsg_mutex_init, + tlsg_mutex_destroy, + tlsg_mutex_lock, + tlsg_mutex_unlock); +} +#endif /* LDAP_R_COMPILE */ + +/* + * Initialize TLS subsystem. Should be called only once. + */ +static int +tlsg_init( void ) +{ + gnutls_global_init(); + return 0; +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +static void +tlsg_destroy( void ) +{ + gnutls_global_deinit(); +} + +static tls_ctx * +tlsg_ctx_new ( struct ldapoptions *lo ) +{ + tlsg_ctx *ctx; + + ctx = ber_memcalloc ( 1, sizeof (*ctx) ); + if ( ctx ) { + if ( gnutls_certificate_allocate_credentials( &ctx->cred )) { + ber_memfree( ctx ); + return NULL; + } + ctx->refcount = 1; + gnutls_priority_init( &ctx->prios, "NORMAL", NULL ); +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &ctx->ref_mutex ); +#endif + } + return (tls_ctx *)ctx; +} + +static void +tlsg_ctx_ref( tls_ctx *ctx ) +{ + tlsg_ctx *c = (tlsg_ctx *)ctx; + LDAP_MUTEX_LOCK( &c->ref_mutex ); + c->refcount++; + LDAP_MUTEX_UNLOCK( &c->ref_mutex ); +} + +static void +tlsg_ctx_free ( tls_ctx *ctx ) +{ + tlsg_ctx *c = (tlsg_ctx *)ctx; + int refcount; + + if ( !c ) return; + + LDAP_MUTEX_LOCK( &c->ref_mutex ); + refcount = --c->refcount; + LDAP_MUTEX_UNLOCK( &c->ref_mutex ); + if ( refcount ) + return; + gnutls_priority_deinit( c->prios ); + gnutls_certificate_free_credentials( c->cred ); + if ( c->dh_params ) + gnutls_dh_params_deinit( c->dh_params ); + ber_memfree ( c ); +} + +static int +tlsg_getfile( const char *path, gnutls_datum_t *buf ) +{ + int rc = -1, fd; + struct stat st; + + fd = open( path, O_RDONLY ); + if ( fd >= 0 && fstat( fd, &st ) == 0 ) { + buf->size = st.st_size; + buf->data = LDAP_MALLOC( st.st_size + 1 ); + if ( buf->data ) { + rc = read( fd, buf->data, st.st_size ); + close( fd ); + if ( rc < st.st_size ) + rc = -1; + else + rc = 0; + } + } + return rc; +} + +/* This is the GnuTLS default */ +#define VERIFY_DEPTH 6 + +/* + * initialize a new TLS context + */ +static int +tlsg_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) +{ + tlsg_ctx *ctx = lo->ldo_tls_ctx; + int rc; + + if ( lo->ldo_tls_ciphersuite && + tlsg_parse_ciphers( ctx, lt->lt_ciphersuite )) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lo->ldo_tls_ciphersuite, 0, 0 ); + return -1; + } + + if (lo->ldo_tls_cacertdir != NULL) { + Debug( LDAP_DEBUG_ANY, + "TLS: warning: cacertdir not implemented for gnutls\n", + NULL, NULL, NULL ); + } + + if (lo->ldo_tls_cacertfile != NULL) { + rc = gnutls_certificate_set_x509_trust_file( + ctx->cred, + lt->lt_cacertfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) return -1; + } + + if ( lo->ldo_tls_certfile && lo->ldo_tls_keyfile ) { + gnutls_x509_privkey_t key; + gnutls_datum_t buf; + gnutls_x509_crt_t certs[VERIFY_DEPTH]; + unsigned int max = VERIFY_DEPTH; + + rc = gnutls_x509_privkey_init( &key ); + if ( rc ) return -1; + + /* OpenSSL builds the cert chain for us, but GnuTLS + * expects it to be present in the certfile. If it's + * not, we have to build it ourselves. So we have to + * do some special checks here... + */ + rc = tlsg_getfile( lt->lt_keyfile, &buf ); + if ( rc ) return -1; + rc = gnutls_x509_privkey_import( key, &buf, + GNUTLS_X509_FMT_PEM ); + LDAP_FREE( buf.data ); + if ( rc < 0 ) return rc; + + rc = tlsg_getfile( lt->lt_certfile, &buf ); + if ( rc ) return -1; + rc = gnutls_x509_crt_list_import( certs, &max, &buf, + GNUTLS_X509_FMT_PEM, 0 ); + LDAP_FREE( buf.data ); + if ( rc < 0 ) return rc; + + /* If there's only one cert and it's not self-signed, + * then we have to build the cert chain. + */ + if ( max == 1 && !gnutls_x509_crt_check_issuer( certs[0], certs[0] )) { + unsigned int i; + for ( i = 1; i<VERIFY_DEPTH; i++ ) { + if ( gnutls_certificate_get_issuer( ctx->cred, certs[i-1], &certs[i], 0 )) + break; + max++; + /* If this CA is self-signed, we're done */ + if ( gnutls_x509_crt_check_issuer( certs[i], certs[i] )) + break; + } + } + rc = gnutls_certificate_set_x509_key( ctx->cred, certs, max, key ); + if ( rc ) return -1; + } else if ( lo->ldo_tls_certfile || lo->ldo_tls_keyfile ) { + Debug( LDAP_DEBUG_ANY, + "TLS: only one of certfile and keyfile specified\n", + NULL, NULL, NULL ); + return -1; + } + + if ( lo->ldo_tls_crlfile ) { + rc = gnutls_certificate_set_x509_crl_file( + ctx->cred, + lt->lt_crlfile, + GNUTLS_X509_FMT_PEM ); + if ( rc < 0 ) return -1; + rc = 0; + } + + /* FIXME: ITS#5992 - this should be configurable, + * and V1 CA certs should be phased out ASAP. + */ + gnutls_certificate_set_verify_flags( ctx->cred, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT ); + + if ( is_server && lo->ldo_tls_dhfile ) { + gnutls_datum_t buf; + rc = tlsg_getfile( lo->ldo_tls_dhfile, &buf ); + if ( rc ) return -1; + rc = gnutls_dh_params_init( &ctx->dh_params ); + if ( rc == 0 ) + rc = gnutls_dh_params_import_pkcs3( ctx->dh_params, &buf, + GNUTLS_X509_FMT_PEM ); + LDAP_FREE( buf.data ); + if ( rc ) return -1; + gnutls_certificate_set_dh_params( ctx->cred, ctx->dh_params ); + } + + ctx->reqcert = lo->ldo_tls_require_cert; + + return 0; +} + +static tls_session * +tlsg_session_new ( tls_ctx * ctx, int is_server ) +{ + tlsg_ctx *c = (tlsg_ctx *)ctx; + tlsg_session *session; + + session = ber_memcalloc ( 1, sizeof (*session) ); + if ( !session ) + return NULL; + + session->ctx = c; + gnutls_init( &session->session, is_server ? GNUTLS_SERVER : GNUTLS_CLIENT ); + gnutls_priority_set( session->session, c->prios ); + if ( c->cred ) + gnutls_credentials_set( session->session, GNUTLS_CRD_CERTIFICATE, c->cred ); + + if ( is_server ) { + int flag = 0; + if ( c->reqcert ) { + flag = GNUTLS_CERT_REQUEST; + if ( c->reqcert == LDAP_OPT_X_TLS_DEMAND || + c->reqcert == LDAP_OPT_X_TLS_HARD ) + flag = GNUTLS_CERT_REQUIRE; + gnutls_certificate_server_set_request( session->session, flag ); + } + } + return (tls_session *)session; +} + +static int +tlsg_session_accept( tls_session *session ) +{ + tlsg_session *s = (tlsg_session *)session; + int rc; + + rc = gnutls_handshake( s->session ); + if ( rc == 0 && s->ctx->reqcert != LDAP_OPT_X_TLS_NEVER ) { + const gnutls_datum_t *peer_cert_list; + unsigned int list_size; + + peer_cert_list = gnutls_certificate_get_peers( s->session, + &list_size ); + if ( !peer_cert_list && s->ctx->reqcert == LDAP_OPT_X_TLS_TRY ) + rc = 0; + else { + rc = tlsg_cert_verify( s ); + if ( rc && s->ctx->reqcert == LDAP_OPT_X_TLS_ALLOW ) + rc = 0; + } + } + return rc; +} + +static int +tlsg_session_connect( LDAP *ld, tls_session *session ) +{ + return tlsg_session_accept( session); +} + +static int +tlsg_session_upflags( Sockbuf *sb, tls_session *session, int rc ) +{ + tlsg_session *s = (tlsg_session *)session; + + if ( rc != GNUTLS_E_INTERRUPTED && rc != GNUTLS_E_AGAIN ) + return 0; + + switch (gnutls_record_get_direction (s->session)) { + case 0: + sb->sb_trans_needs_read = 1; + return 1; + case 1: + sb->sb_trans_needs_write = 1; + return 1; + } + return 0; +} + +static char * +tlsg_session_errmsg( tls_session *sess, int rc, char *buf, size_t len ) +{ + return (char *)gnutls_strerror( rc ); +} + +static void +tlsg_x509_cert_dn( struct berval *cert, struct berval *dn, int get_subject ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_len_t len; + ber_int_t i; + + ber_init2( ber, cert, LBER_USE_DER ); + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_skip_tag( ber, &len ); /* Sequence */ + tag = ber_peek_tag( ber, &len ); /* Context + Constructed (version) */ + if ( tag == 0xa0 ) { /* Version is optional */ + tag = ber_skip_tag( ber, &len ); + tag = ber_get_int( ber, &i ); /* Int: Version */ + } + tag = ber_skip_tag( ber, &len ); /* Int: Serial (can be longer than ber_int_t) */ + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Sequence: Signature */ + ber_skip_data( ber, len ); + if ( !get_subject ) { + tag = ber_peek_tag( ber, &len ); /* Sequence: Issuer DN */ + } else { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + tag = ber_skip_tag( ber, &len ); /* Sequence: Validity */ + ber_skip_data( ber, len ); + tag = ber_peek_tag( ber, &len ); /* Sequence: Subject DN */ + } + len = ber_ptrlen( ber ); + dn->bv_val = cert->bv_val + len; + dn->bv_len = cert->bv_len - len; +} + +static int +tlsg_session_my_dn( tls_session *session, struct berval *der_dn ) +{ + tlsg_session *s = (tlsg_session *)session; + const gnutls_datum_t *x; + struct berval bv; + + x = gnutls_certificate_get_ours( s->session ); + + if (!x) return LDAP_INVALID_CREDENTIALS; + + bv.bv_val = (char *) x->data; + bv.bv_len = x->size; + + tlsg_x509_cert_dn( &bv, der_dn, 1 ); + return 0; +} + +static int +tlsg_session_peer_dn( tls_session *session, struct berval *der_dn ) +{ + tlsg_session *s = (tlsg_session *)session; + if ( !s->peer_der_dn.bv_val ) { + const gnutls_datum_t *peer_cert_list; + unsigned int list_size; + struct berval bv; + + peer_cert_list = gnutls_certificate_get_peers( s->session, + &list_size ); + if ( !peer_cert_list ) return LDAP_INVALID_CREDENTIALS; + + bv.bv_len = peer_cert_list->size; + bv.bv_val = (char *) peer_cert_list->data; + + tlsg_x509_cert_dn( &bv, &s->peer_der_dn, 1 ); + } + *der_dn = s->peer_der_dn; + return 0; +} + +/* what kind of hostname were we given? */ +#define IS_DNS 0 +#define IS_IP4 1 +#define IS_IP6 2 + +#define CN_OID "2.5.4.3" + +static int +tlsg_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) +{ + tlsg_session *s = (tlsg_session *)session; + int i, ret; + const gnutls_datum_t *peer_cert_list; + unsigned int list_size; + char altname[NI_MAXHOST]; + size_t altnamesize; + + gnutls_x509_crt_t cert; + const char *name; + char *ptr; + char *domain = NULL; +#ifdef LDAP_PF_INET6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + int len1 = 0, len2 = 0; + int ntype = IS_DNS; + + if( ldap_int_hostname && + ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) + { + name = ldap_int_hostname; + } else { + name = name_in; + } + + peer_cert_list = gnutls_certificate_get_peers( s->session, + &list_size ); + if ( !peer_cert_list ) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get peer certificate.\n", + 0, 0, 0 ); + /* If this was a fatal condition, things would have + * aborted long before now. + */ + return LDAP_SUCCESS; + } + ret = gnutls_x509_crt_init( &cert ); + if ( ret < 0 ) + return LDAP_LOCAL_ERROR; + ret = gnutls_x509_crt_import( cert, peer_cert_list, GNUTLS_X509_FMT_DER ); + if ( ret ) { + gnutls_x509_crt_deinit( cert ); + return LDAP_LOCAL_ERROR; + } + +#ifdef LDAP_PF_INET6 + if (inet_pton(AF_INET6, name, &addr)) { + ntype = IS_IP6; + } else +#endif + if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { + if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; + } + + if (ntype == IS_DNS) { + len1 = strlen(name); + domain = strchr(name, '.'); + if (domain) { + len2 = len1 - (domain-name); + } + } + + for ( i=0, ret=0; ret >= 0; i++ ) { + altnamesize = sizeof(altname); + ret = gnutls_x509_crt_get_subject_alt_name( cert, i, + altname, &altnamesize, NULL ); + if ( ret < 0 ) break; + + /* ignore empty */ + if ( altnamesize == 0 ) continue; + + if ( ret == GNUTLS_SAN_DNSNAME ) { + if (ntype != IS_DNS) continue; + + /* Is this an exact match? */ + if ((len1 == altnamesize) && !strncasecmp(name, altname, len1)) { + break; + } + + /* Is this a wildcard match? */ + if (domain && (altname[0] == '*') && (altname[1] == '.') && + (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) + { + break; + } + } else if ( ret == GNUTLS_SAN_IPADDRESS ) { + if (ntype == IS_DNS) continue; + +#ifdef LDAP_PF_INET6 + if (ntype == IS_IP6 && altnamesize != sizeof(struct in6_addr)) { + continue; + } else +#endif + if (ntype == IS_IP4 && altnamesize != sizeof(struct in_addr)) { + continue; + } + if (!memcmp(altname, &addr, altnamesize)) { + break; + } + } + } + if ( ret >= 0 ) { + ret = LDAP_SUCCESS; + } else { + /* find the last CN */ + i=0; + do { + altnamesize = 0; + ret = gnutls_x509_crt_get_dn_by_oid( cert, CN_OID, + i, 1, altname, &altnamesize ); + if ( ret == GNUTLS_E_SHORT_MEMORY_BUFFER ) + i++; + else + break; + } while ( 1 ); + + if ( i ) { + altnamesize = sizeof(altname); + ret = gnutls_x509_crt_get_dn_by_oid( cert, CN_OID, + i-1, 0, altname, &altnamesize ); + } + + if ( ret < 0 ) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get common name from peer certificate.\n", + 0, 0, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: unable to get CN from peer certificate")); + + } else { + ret = LDAP_LOCAL_ERROR; + if ( !len1 ) len1 = strlen( name ); + if ( len1 == altnamesize && strncasecmp(name, altname, altnamesize) == 0 ) { + ret = LDAP_SUCCESS; + + } else if (( altname[0] == '*' ) && ( altname[1] == '.' )) { + /* Is this a wildcard match? */ + if( domain && + (len2 == altnamesize-1) && !strncasecmp(domain, &altname[1], len2)) { + ret = LDAP_SUCCESS; + } + } + } + + if( ret == LDAP_LOCAL_ERROR ) { + altname[altnamesize] = '\0'; + Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " + "common name in certificate (%s).\n", + name, altname, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: hostname does not match CN in peer certificate")); + } + } + gnutls_x509_crt_deinit( cert ); + return ret; +} + +static int +tlsg_session_strength( tls_session *session ) +{ + tlsg_session *s = (tlsg_session *)session; + gnutls_cipher_algorithm_t c; + + c = gnutls_cipher_get( s->session ); + return gnutls_cipher_get_key_size( c ) * 8; +} + +/* suites is a string of colon-separated cipher suite names. */ +static int +tlsg_parse_ciphers( tlsg_ctx *ctx, char *suites ) +{ + const char *err; + int rc = gnutls_priority_init( &ctx->prios, suites, &err ); + if ( rc ) + ctx->prios = NULL; + return rc; +} + +/* + * TLS support for LBER Sockbufs + */ + +struct tls_data { + tlsg_session *session; + Sockbuf_IO_Desc *sbiod; +}; + +static ssize_t +tlsg_recv( gnutls_transport_ptr_t ptr, void *buf, size_t len ) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); +} + +static ssize_t +tlsg_send( gnutls_transport_ptr_t ptr, const void *buf, size_t len ) +{ + struct tls_data *p; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)ptr; + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + return LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); +} + +static int +tlsg_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct tls_data *p; + tlsg_session *session = arg; + + assert( sbiod != NULL ); + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) { + return -1; + } + + gnutls_transport_set_ptr( session->session, (gnutls_transport_ptr)p ); + gnutls_transport_set_pull_function( session->session, tlsg_recv ); + gnutls_transport_set_push_function( session->session, tlsg_send ); + p->session = session; + p->sbiod = sbiod; + sbiod->sbiod_pvt = p; + return 0; +} + +static int +tlsg_sb_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + gnutls_deinit ( p->session->session ); + LBER_FREE( p->session ); + LBER_FREE( sbiod->sbiod_pvt ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static int +tlsg_sb_close( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + gnutls_bye ( p->session->session, GNUTLS_SHUT_WR ); + return 0; +} + +static int +tlsg_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_GET_SSL ) { + *((tlsg_session **)arg) = p->session; + return 1; + + } else if ( opt == LBER_SB_OPT_DATA_READY ) { + if( gnutls_record_check_pending( p->session->session ) > 0 ) { + return 1; + } + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +static ber_slen_t +tlsg_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = gnutls_record_recv ( p->session->session, buf, len ); + switch (ret) { + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_AGAIN: + sbiod->sbiod_sb->sb_trans_needs_read = 1; + sock_errset(EWOULDBLOCK); + ret = 0; + break; + case GNUTLS_E_REHANDSHAKE: + for ( ret = gnutls_handshake ( p->session->session ); + ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN; + ret = gnutls_handshake ( p->session->session ) ); + sbiod->sbiod_sb->sb_trans_needs_read = 1; + ret = 0; + break; + default: + sbiod->sbiod_sb->sb_trans_needs_read = 0; + } + return ret; +} + +static ber_slen_t +tlsg_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = gnutls_record_send ( p->session->session, (char *)buf, len ); + + if ( ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + ret = 0; + } else { + sbiod->sbiod_sb->sb_trans_needs_write = 0; + } + return ret; +} + +static Sockbuf_IO tlsg_sbio = +{ + tlsg_sb_setup, /* sbi_setup */ + tlsg_sb_remove, /* sbi_remove */ + tlsg_sb_ctrl, /* sbi_ctrl */ + tlsg_sb_read, /* sbi_read */ + tlsg_sb_write, /* sbi_write */ + tlsg_sb_close /* sbi_close */ +}; + +/* Certs are not automatically varified during the handshake */ +static int +tlsg_cert_verify( tlsg_session *ssl ) +{ + unsigned int status = 0; + int err; + time_t now = time(0); + time_t peertime; + + err = gnutls_certificate_verify_peers2( ssl->session, &status ); + if ( err < 0 ) { + Debug( LDAP_DEBUG_ANY,"TLS: gnutls_certificate_verify_peers2 failed %d\n", + err,0,0 ); + return -1; + } + if ( status ) { + Debug( LDAP_DEBUG_TRACE,"TLS: peer cert untrusted or revoked (0x%x)\n", + status, 0,0 ); + return -1; + } + peertime = gnutls_certificate_expiration_time_peers( ssl->session ); + if ( peertime == (time_t) -1 ) { + Debug( LDAP_DEBUG_ANY, "TLS: gnutls_certificate_expiration_time_peers failed\n", + 0, 0, 0 ); + return -1; + } + if ( peertime < now ) { + Debug( LDAP_DEBUG_ANY, "TLS: peer certificate is expired\n", + 0, 0, 0 ); + return -1; + } + peertime = gnutls_certificate_activation_time_peers( ssl->session ); + if ( peertime == (time_t) -1 ) { + Debug( LDAP_DEBUG_ANY, "TLS: gnutls_certificate_activation_time_peers failed\n", + 0, 0, 0 ); + return -1; + } + if ( peertime > now ) { + Debug( LDAP_DEBUG_ANY, "TLS: peer certificate not yet active\n", + 0, 0, 0 ); + return -1; + } + return 0; +} + +tls_impl ldap_int_tls_impl = { + "GnuTLS", + + tlsg_init, + tlsg_destroy, + + tlsg_ctx_new, + tlsg_ctx_ref, + tlsg_ctx_free, + tlsg_ctx_init, + + tlsg_session_new, + tlsg_session_connect, + tlsg_session_accept, + tlsg_session_upflags, + tlsg_session_errmsg, + tlsg_session_my_dn, + tlsg_session_peer_dn, + tlsg_session_chkhost, + tlsg_session_strength, + + &tlsg_sbio, + +#ifdef LDAP_R_COMPILE + tlsg_thr_init, +#else + NULL, +#endif + + 0 +}; + +#endif /* HAVE_GNUTLS */ diff --git a/libraries/libldap/tls_m.c b/libraries/libldap/tls_m.c new file mode 100644 index 0000000..fb5554e --- /dev/null +++ b/libraries/libldap/tls_m.c @@ -0,0 +1,3324 @@ +/* tls_m.c - Handle tls/ssl using Mozilla NSS. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: Initial version written by Howard Chu. + * Additional support by Rich Megginson. + */ + +#include "portable.h" + +#ifdef HAVE_MOZNSS + +#include "ldap_config.h" + +#include <stdio.h> + +#if defined( HAVE_FCNTL_H ) +#include <fcntl.h> +#endif + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> +#include <ac/regex.h> + +#include "ldap-int.h" +#include "ldap-tls.h" + +#define READ_PASSWORD_FROM_STDIN +#define READ_PASSWORD_FROM_FILE + +#ifdef READ_PASSWORD_FROM_STDIN +#include <termios.h> /* for echo on/off */ +#endif + +#include <nspr/nspr.h> +#include <nspr/private/pprio.h> +#include <nss/nss.h> +#include <nss/ssl.h> +#include <nss/sslerr.h> +#include <nss/sslproto.h> +#include <nss/pk11pub.h> +#include <nss/secerr.h> +#include <nss/keyhi.h> +#include <nss/secmod.h> +#include <nss/cert.h> + +#undef NSS_VERSION_INT +#define NSS_VERSION_INT ((NSS_VMAJOR << 24) | (NSS_VMINOR << 16) | \ + (NSS_VPATCH << 8) | NSS_VBUILD) + +/* NSS 3.12.5 and later have NSS_InitContext */ +#if NSS_VERSION_INT >= 0x030c0500 +#define HAVE_NSS_INITCONTEXT 1 +#endif + +/* NSS 3.12.9 and later have SECMOD_RestartModules */ +#if NSS_VERSION_INT >= 0x030c0900 +#define HAVE_SECMOD_RESTARTMODULES 1 +#endif + +/* InitContext does not currently work in server mode */ +/* #define INITCONTEXT_HACK 1 */ + +typedef struct tlsm_ctx { + PRFileDesc *tc_model; + int tc_refcnt; + int tc_unique; /* unique number associated with this ctx */ + PRBool tc_verify_cert; + CERTCertDBHandle *tc_certdb; + PK11SlotInfo *tc_certdb_slot; + CERTCertificate *tc_certificate; + SECKEYPrivateKey *tc_private_key; + char *tc_pin_file; + struct ldaptls *tc_config; + int tc_is_server; + int tc_require_cert; + PRCallOnceType tc_callonce; + PRBool tc_using_pem; +#ifdef HAVE_NSS_INITCONTEXT + NSSInitContext *tc_initctx; /* the NSS context */ +#endif + PK11GenericObject **tc_pem_objs; /* array of objects to free */ + int tc_n_pem_objs; /* number of objects */ + PRBool tc_warn_only; /* only warn of errors in validation */ +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_t tc_refmutex; +#endif +} tlsm_ctx; + +typedef PRFileDesc tlsm_session; + +static int tlsm_ctx_count; +#define TLSM_CERTDB_DESC_FMT "ldap(%d)" + +static PRDescIdentity tlsm_layer_id; + +static const PRIOMethods tlsm_PR_methods; + +#define CERTDB_NONE NULL +#define PREFIX_NONE NULL + +#define PEM_LIBRARY "nsspem" +#define PEM_MODULE "PEM" +#define PEM_CA_HASH_FILE_REGEX "^[0-9a-f]{8}\\.[0-9]+$" + +static SECMODModule *pem_module; + +#define DEFAULT_TOKEN_NAME "default" +#define TLSM_PEM_SLOT_CACERTS "PEM Token #0" +#define TLSM_PEM_SLOT_CERTS "PEM Token #1" + +#define PK11_SETATTRS(x,id,v,l) (x).type = (id); \ + (x).pValue=(v); (x).ulValueLen = (l); + +/* forward declaration */ +static int tlsm_init( void ); + +#ifdef LDAP_R_COMPILE + +/* it doesn't seem guaranteed that a client will call + tlsm_thr_init in a non-threaded context - so we have + to wrap the mutex creation in a prcallonce +*/ +static ldap_pvt_thread_mutex_t tlsm_ctx_count_mutex; +static ldap_pvt_thread_mutex_t tlsm_init_mutex; +static ldap_pvt_thread_mutex_t tlsm_pem_mutex; +static PRCallOnceType tlsm_init_mutex_callonce = {0,0}; + +static PRStatus PR_CALLBACK +tlsm_thr_init_callonce( void ) +{ + if ( ldap_pvt_thread_mutex_init( &tlsm_ctx_count_mutex ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not create mutex for context counter: %d\n", errno, 0, 0 ); + return PR_FAILURE; + } + + if ( ldap_pvt_thread_mutex_init( &tlsm_init_mutex ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not create mutex for moznss initialization: %d\n", errno, 0, 0 ); + return PR_FAILURE; + } + + if ( ldap_pvt_thread_mutex_init( &tlsm_pem_mutex ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not create mutex for PEM module: %d\n", errno, 0, 0 ); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static void +tlsm_thr_init( void ) +{ + ( void )PR_CallOnce( &tlsm_init_mutex_callonce, tlsm_thr_init_callonce ); +} + +#endif /* LDAP_R_COMPILE */ + +static const char * +tlsm_dump_cipher_info(PRFileDesc *fd) +{ + PRUint16 ii; + + for (ii = 0; ii < SSL_NumImplementedCiphers; ++ii) { + PRInt32 cipher = (PRInt32)SSL_ImplementedCiphers[ii]; + PRBool enabled = PR_FALSE; + PRInt32 policy = 0; + SSLCipherSuiteInfo info; + + if (fd) { + SSL_CipherPrefGet(fd, cipher, &enabled); + } else { + SSL_CipherPrefGetDefault(cipher, &enabled); + } + SSL_CipherPolicyGet(cipher, &policy); + SSL_GetCipherSuiteInfo(cipher, &info, (PRUintn)sizeof(info)); + Debug( LDAP_DEBUG_TRACE, + "TLS: cipher: %d - %s, enabled: %d, ", + info.cipherSuite, info.cipherSuiteName, enabled ); + Debug( LDAP_DEBUG_TRACE, + "policy: %d\n", policy, 0, 0 ); + } + + return ""; +} + +/* Cipher definitions */ +typedef struct { + char *ossl_name; /* The OpenSSL cipher name */ + int num; /* The cipher id */ + int attr; /* cipher attributes: algorithms, etc */ + int version; /* protocol version valid for this cipher */ + int bits; /* bits of strength */ + int alg_bits; /* bits of the algorithm */ + int strength; /* LOW, MEDIUM, HIGH */ + int enabled; /* Enabled by default? */ +} cipher_properties; + +/* cipher attributes */ +#define SSL_kRSA 0x00000001L +#define SSL_aRSA 0x00000002L +#define SSL_aDSS 0x00000004L +#define SSL_DSS SSL_aDSS +#define SSL_eNULL 0x00000008L +#define SSL_DES 0x00000010L +#define SSL_3DES 0x00000020L +#define SSL_RC4 0x00000040L +#define SSL_RC2 0x00000080L +#define SSL_AES 0x00000100L +#define SSL_MD5 0x00000200L +#define SSL_SHA1 0x00000400L +#define SSL_SHA SSL_SHA1 +#define SSL_RSA (SSL_kRSA|SSL_aRSA) + +/* cipher strength */ +#define SSL_NULL 0x00000001L +#define SSL_EXPORT40 0x00000002L +#define SSL_EXPORT56 0x00000004L +#define SSL_LOW 0x00000008L +#define SSL_MEDIUM 0x00000010L +#define SSL_HIGH 0x00000020L + +#define SSL2 0x00000001L +#define SSL3 0x00000002L +/* OpenSSL treats SSL3 and TLSv1 the same */ +#define TLS1 SSL3 + +/* Cipher translation */ +static cipher_properties ciphers_def[] = { + /* SSL 2 ciphers */ + {"DES-CBC3-MD5", SSL_EN_DES_192_EDE3_CBC_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_3DES|SSL_MD5, SSL2, 168, 168, SSL_HIGH, SSL_ALLOWED}, + {"RC2-CBC-MD5", SSL_EN_RC2_128_CBC_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC2|SSL_MD5, SSL2, 128, 128, SSL_MEDIUM, SSL_ALLOWED}, + {"RC4-MD5", SSL_EN_RC4_128_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL2, 128, 128, SSL_MEDIUM, SSL_ALLOWED}, + {"DES-CBC-MD5", SSL_EN_DES_64_CBC_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_DES|SSL_MD5, SSL2, 56, 56, SSL_LOW, SSL_ALLOWED}, + {"EXP-RC2-CBC-MD5", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC2|SSL_MD5, SSL2, 40, 128, SSL_EXPORT40, SSL_ALLOWED}, + {"EXP-RC4-MD5", SSL_EN_RC4_128_EXPORT40_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL2, 40, 128, SSL_EXPORT40, SSL_ALLOWED}, + + /* SSL3 ciphers */ + {"RC4-MD5", SSL_RSA_WITH_RC4_128_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL3, 128, 128, SSL_MEDIUM, SSL_ALLOWED}, + {"RC4-SHA", SSL_RSA_WITH_RC4_128_SHA, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_SHA1, SSL3, 128, 128, SSL_MEDIUM, SSL_ALLOWED}, + {"DES-CBC3-SHA", SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_3DES|SSL_SHA1, SSL3, 168, 168, SSL_HIGH, SSL_ALLOWED}, + {"DES-CBC-SHA", SSL_RSA_WITH_DES_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_DES|SSL_SHA1, SSL3, 56, 56, SSL_LOW, SSL_ALLOWED}, + {"EXP-RC4-MD5", SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL3, 40, 128, SSL_EXPORT40, SSL_ALLOWED}, + {"EXP-RC2-CBC-MD5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, SSL_kRSA|SSL_aRSA|SSL_RC2|SSL_MD5, SSL3, 0, 0, SSL_EXPORT40, SSL_ALLOWED}, + {"NULL-MD5", SSL_RSA_WITH_NULL_MD5, SSL_kRSA|SSL_aRSA|SSL_eNULL|SSL_MD5, SSL3, 0, 0, SSL_NULL, SSL_NOT_ALLOWED}, + {"NULL-SHA", SSL_RSA_WITH_NULL_SHA, SSL_kRSA|SSL_aRSA|SSL_eNULL|SSL_SHA1, SSL3, 0, 0, SSL_NULL, SSL_NOT_ALLOWED}, + + /* TLSv1 ciphers */ + {"EXP1024-DES-CBC-SHA", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_DES|SSL_SHA, TLS1, 56, 56, SSL_EXPORT56, SSL_ALLOWED}, + {"EXP1024-RC4-SHA", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_SHA, TLS1, 56, 56, SSL_EXPORT56, SSL_ALLOWED}, + {"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_AES|SSL_SHA, TLS1, 128, 128, SSL_HIGH, SSL_ALLOWED}, + {"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_AES|SSL_SHA, TLS1, 256, 256, SSL_HIGH, SSL_ALLOWED}, +}; + +#define ciphernum (sizeof(ciphers_def)/sizeof(cipher_properties)) + +/* given err which is the current errno, calls PR_SetError with + the corresponding NSPR error code */ +static void +tlsm_map_error(int err) +{ + PRErrorCode prError; + + switch ( err ) { + case EACCES: + prError = PR_NO_ACCESS_RIGHTS_ERROR; + break; + case EADDRINUSE: + prError = PR_ADDRESS_IN_USE_ERROR; + break; + case EADDRNOTAVAIL: + prError = PR_ADDRESS_NOT_AVAILABLE_ERROR; + break; + case EAFNOSUPPORT: + prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; + break; + case EAGAIN: + prError = PR_WOULD_BLOCK_ERROR; + break; + /* + * On QNX and Neutrino, EALREADY is defined as EBUSY. + */ +#if EALREADY != EBUSY + case EALREADY: + prError = PR_ALREADY_INITIATED_ERROR; + break; +#endif + case EBADF: + prError = PR_BAD_DESCRIPTOR_ERROR; + break; +#ifdef EBADMSG + case EBADMSG: + prError = PR_IO_ERROR; + break; +#endif + case EBUSY: + prError = PR_FILESYSTEM_MOUNTED_ERROR; + break; + case ECONNABORTED: + prError = PR_CONNECT_ABORTED_ERROR; + break; + case ECONNREFUSED: + prError = PR_CONNECT_REFUSED_ERROR; + break; + case ECONNRESET: + prError = PR_CONNECT_RESET_ERROR; + break; + case EDEADLK: + prError = PR_DEADLOCK_ERROR; + break; +#ifdef EDIRCORRUPTED + case EDIRCORRUPTED: + prError = PR_DIRECTORY_CORRUPTED_ERROR; + break; +#endif +#ifdef EDQUOT + case EDQUOT: + prError = PR_NO_DEVICE_SPACE_ERROR; + break; +#endif + case EEXIST: + prError = PR_FILE_EXISTS_ERROR; + break; + case EFAULT: + prError = PR_ACCESS_FAULT_ERROR; + break; + case EFBIG: + prError = PR_FILE_TOO_BIG_ERROR; + break; + case EHOSTUNREACH: + prError = PR_HOST_UNREACHABLE_ERROR; + break; + case EINPROGRESS: + prError = PR_IN_PROGRESS_ERROR; + break; + case EINTR: + prError = PR_PENDING_INTERRUPT_ERROR; + break; + case EINVAL: + prError = PR_INVALID_ARGUMENT_ERROR; + break; + case EIO: + prError = PR_IO_ERROR; + break; + case EISCONN: + prError = PR_IS_CONNECTED_ERROR; + break; + case EISDIR: + prError = PR_IS_DIRECTORY_ERROR; + break; + case ELOOP: + prError = PR_LOOP_ERROR; + break; + case EMFILE: + prError = PR_PROC_DESC_TABLE_FULL_ERROR; + break; + case EMLINK: + prError = PR_MAX_DIRECTORY_ENTRIES_ERROR; + break; + case EMSGSIZE: + prError = PR_INVALID_ARGUMENT_ERROR; + break; +#ifdef EMULTIHOP + case EMULTIHOP: + prError = PR_REMOTE_FILE_ERROR; + break; +#endif + case ENAMETOOLONG: + prError = PR_NAME_TOO_LONG_ERROR; + break; + case ENETUNREACH: + prError = PR_NETWORK_UNREACHABLE_ERROR; + break; + case ENFILE: + prError = PR_SYS_DESC_TABLE_FULL_ERROR; + break; + /* + * On SCO OpenServer 5, ENOBUFS is defined as ENOSR. + */ +#if defined(ENOBUFS) && (ENOBUFS != ENOSR) + case ENOBUFS: + prError = PR_INSUFFICIENT_RESOURCES_ERROR; + break; +#endif + case ENODEV: + prError = PR_FILE_NOT_FOUND_ERROR; + break; + case ENOENT: + prError = PR_FILE_NOT_FOUND_ERROR; + break; + case ENOLCK: + prError = PR_FILE_IS_LOCKED_ERROR; + break; +#ifdef ENOLINK + case ENOLINK: + prError = PR_REMOTE_FILE_ERROR; + break; +#endif + case ENOMEM: + prError = PR_OUT_OF_MEMORY_ERROR; + break; + case ENOPROTOOPT: + prError = PR_INVALID_ARGUMENT_ERROR; + break; + case ENOSPC: + prError = PR_NO_DEVICE_SPACE_ERROR; + break; +#ifdef ENOSR + case ENOSR: + prError = PR_INSUFFICIENT_RESOURCES_ERROR; + break; +#endif + case ENOTCONN: + prError = PR_NOT_CONNECTED_ERROR; + break; + case ENOTDIR: + prError = PR_NOT_DIRECTORY_ERROR; + break; + case ENOTSOCK: + prError = PR_NOT_SOCKET_ERROR; + break; + case ENXIO: + prError = PR_FILE_NOT_FOUND_ERROR; + break; + case EOPNOTSUPP: + prError = PR_NOT_TCP_SOCKET_ERROR; + break; +#ifdef EOVERFLOW + case EOVERFLOW: + prError = PR_BUFFER_OVERFLOW_ERROR; + break; +#endif + case EPERM: + prError = PR_NO_ACCESS_RIGHTS_ERROR; + break; + case EPIPE: + prError = PR_CONNECT_RESET_ERROR; + break; +#ifdef EPROTO + case EPROTO: + prError = PR_IO_ERROR; + break; +#endif + case EPROTONOSUPPORT: + prError = PR_PROTOCOL_NOT_SUPPORTED_ERROR; + break; + case EPROTOTYPE: + prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; + break; + case ERANGE: + prError = PR_INVALID_METHOD_ERROR; + break; + case EROFS: + prError = PR_READ_ONLY_FILESYSTEM_ERROR; + break; + case ESPIPE: + prError = PR_INVALID_METHOD_ERROR; + break; + case ETIMEDOUT: + prError = PR_IO_TIMEOUT_ERROR; + break; +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: + prError = PR_WOULD_BLOCK_ERROR; + break; +#endif + case EXDEV: + prError = PR_NOT_SAME_DEVICE_ERROR; + break; + default: + prError = PR_UNKNOWN_ERROR; + break; + } + PR_SetError( prError, err ); +} + +/* + * cipher_list is an integer array with the following values: + * -1: never enable this cipher + * 0: cipher disabled + * 1: cipher enabled + */ +static int +nss_parse_ciphers(const char *cipherstr, int cipher_list[ciphernum]) +{ + int i; + char *cipher; + char *ciphers; + char *ciphertip; + int action; + int rv; + + /* All disabled to start */ + for (i=0; i<ciphernum; i++) + cipher_list[i] = 0; + + ciphertip = strdup(cipherstr); + cipher = ciphers = ciphertip; + + while (ciphers && (strlen(ciphers))) { + while ((*cipher) && (isspace(*cipher))) + ++cipher; + + action = 1; + switch(*cipher) { + case '+': /* Add something */ + action = 1; + cipher++; + break; + case '-': /* Subtract something */ + action = 0; + cipher++; + break; + case '!': /* Disable something */ + action = -1; + cipher++; + break; + default: + /* do nothing */ + break; + } + + if ((ciphers = strchr(cipher, ':'))) { + *ciphers++ = '\0'; + } + + /* Do the easy one first */ + if (!strcmp(cipher, "ALL")) { + for (i=0; i<ciphernum; i++) { + if (!(ciphers_def[i].attr & SSL_eNULL)) + cipher_list[i] = action; + } + } else if (!strcmp(cipher, "COMPLEMENTOFALL")) { + for (i=0; i<ciphernum; i++) { + if ((ciphers_def[i].attr & SSL_eNULL)) + cipher_list[i] = action; + } + } else if (!strcmp(cipher, "DEFAULT")) { + for (i=0; i<ciphernum; i++) { + cipher_list[i] = ciphers_def[i].enabled == SSL_ALLOWED ? 1 : 0; + } + } else { + int mask = 0; + int strength = 0; + int protocol = 0; + char *c; + + c = cipher; + while (c && (strlen(c))) { + + if ((c = strchr(cipher, '+'))) { + *c++ = '\0'; + } + + if (!strcmp(cipher, "RSA")) { + mask |= SSL_RSA; + } else if ((!strcmp(cipher, "NULL")) || (!strcmp(cipher, "eNULL"))) { + mask |= SSL_eNULL; + } else if (!strcmp(cipher, "AES")) { + mask |= SSL_AES; + } else if (!strcmp(cipher, "3DES")) { + mask |= SSL_3DES; + } else if (!strcmp(cipher, "DES")) { + mask |= SSL_DES; + } else if (!strcmp(cipher, "RC4")) { + mask |= SSL_RC4; + } else if (!strcmp(cipher, "RC2")) { + mask |= SSL_RC2; + } else if (!strcmp(cipher, "MD5")) { + mask |= SSL_MD5; + } else if ((!strcmp(cipher, "SHA")) || (!strcmp(cipher, "SHA1"))) { + mask |= SSL_SHA1; + } else if (!strcmp(cipher, "SSLv2")) { + protocol |= SSL2; + } else if (!strcmp(cipher, "SSLv3")) { + protocol |= SSL3; + } else if (!strcmp(cipher, "TLSv1")) { + protocol |= TLS1; + } else if (!strcmp(cipher, "HIGH")) { + strength |= SSL_HIGH; + } else if (!strcmp(cipher, "MEDIUM")) { + strength |= SSL_MEDIUM; + } else if (!strcmp(cipher, "LOW")) { + strength |= SSL_LOW; + } else if ((!strcmp(cipher, "EXPORT")) || (!strcmp(cipher, "EXP"))) { + strength |= SSL_EXPORT40|SSL_EXPORT56; + } else if (!strcmp(cipher, "EXPORT40")) { + strength |= SSL_EXPORT40; + } else if (!strcmp(cipher, "EXPORT56")) { + strength |= SSL_EXPORT56; + } + + if (c) + cipher = c; + + } /* while */ + + /* If we have a mask, apply it. If not then perhaps they provided + * a specific cipher to enable. + */ + if (mask || strength || protocol) { + for (i=0; i<ciphernum; i++) { + if (((ciphers_def[i].attr & mask) || + (ciphers_def[i].strength & strength) || + (ciphers_def[i].version & protocol)) && + (cipher_list[i] != -1)) { + /* Enable the NULL ciphers only if explicity + * requested */ + if (ciphers_def[i].attr & SSL_eNULL) { + if (mask & SSL_eNULL) + cipher_list[i] = action; + } else + cipher_list[i] = action; + } + } + } else { + for (i=0; i<ciphernum; i++) { + if (!strcmp(ciphers_def[i].ossl_name, cipher) && + cipher_list[i] != -1) + cipher_list[i] = action; + } + } + } + + if (ciphers) + cipher = ciphers; + } + + /* See if any ciphers were enabled */ + rv = 0; + for (i=0; i<ciphernum; i++) { + if (cipher_list[i] == 1) + rv = 1; + } + + free(ciphertip); + + return rv; +} + +static int +tlsm_parse_ciphers(tlsm_ctx *ctx, const char *str) +{ + int cipher_state[ciphernum]; + int rv, i; + + if (!ctx) + return 0; + + rv = nss_parse_ciphers(str, cipher_state); + + if (rv) { + /* First disable everything */ + for (i = 0; i < SSL_NumImplementedCiphers; i++) + SSL_CipherPrefSet(ctx->tc_model, SSL_ImplementedCiphers[i], SSL_NOT_ALLOWED); + + /* Now enable what was requested */ + for (i=0; i<ciphernum; i++) { + SSLCipherSuiteInfo suite; + PRBool enabled; + + if (SSL_GetCipherSuiteInfo(ciphers_def[i].num, &suite, sizeof suite) + == SECSuccess) { + enabled = cipher_state[i] < 0 ? 0 : cipher_state[i]; + if (enabled == SSL_ALLOWED) { + if (PK11_IsFIPS() && !suite.isFIPS) + enabled = SSL_NOT_ALLOWED; + } + SSL_CipherPrefSet(ctx->tc_model, ciphers_def[i].num, enabled); + } + } + } + + return rv == 1 ? 0 : -1; +} + +static SECStatus +tlsm_bad_cert_handler(void *arg, PRFileDesc *ssl) +{ + SECStatus success = SECSuccess; + PRErrorCode err; + tlsm_ctx *ctx = (tlsm_ctx *)arg; + + if (!ssl || !ctx) { + return SECFailure; + } + + err = PORT_GetError(); + + switch (err) { + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + if (ctx->tc_verify_cert) { + success = SECFailure; + } + break; + /* we bypass NSS's hostname checks and do our own */ + case SSL_ERROR_BAD_CERT_DOMAIN: + break; + default: + success = SECFailure; + break; + } + + return success; +} + +static const char * +tlsm_dump_security_status(PRFileDesc *fd) +{ + char * cp; /* bulk cipher name */ + char * ip; /* cert issuer DN */ + char * sp; /* cert subject DN */ + int op; /* High, Low, Off */ + int kp0; /* total key bits */ + int kp1; /* secret key bits */ + SSL3Statistics * ssl3stats = SSL_GetStatistics(); + + SSL_SecurityStatus( fd, &op, &cp, &kp0, &kp1, &ip, &sp ); + Debug( LDAP_DEBUG_TRACE, + "TLS certificate verification: subject: %s, issuer: %s, cipher: %s, ", + sp ? sp : "-unknown-", ip ? ip : "-unknown-", cp ? cp : "-unknown-" ); + PR_Free(cp); + PR_Free(ip); + PR_Free(sp); + Debug( LDAP_DEBUG_TRACE, + "security level: %s, secret key bits: %d, total key bits: %d, ", + ((op == SSL_SECURITY_STATUS_ON_HIGH) ? "high" : + ((op == SSL_SECURITY_STATUS_ON_LOW) ? "low" : "off")), + kp1, kp0 ); + + Debug( LDAP_DEBUG_TRACE, + "cache hits: %ld, cache misses: %ld, cache not reusable: %ld\n", + ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses, + ssl3stats->hch_sid_cache_not_ok ); + + return ""; +} + +static void +tlsm_handshake_complete_cb( PRFileDesc *fd, void *client_data ) +{ + tlsm_dump_security_status( fd ); +} + +#ifdef READ_PASSWORD_FROM_FILE +static char * +tlsm_get_pin_from_file(const char *token_name, tlsm_ctx *ctx) +{ + char *pwdstr = NULL; + char *contents = NULL; + char *lasts = NULL; + char *line = NULL; + char *candidate = NULL; + PRFileInfo file_info; + PRFileDesc *pwd_fileptr = PR_Open( ctx->tc_pin_file, PR_RDONLY, 00400 ); + + /* open the password file */ + if ( !pwd_fileptr ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not open security pin file %s - error %d:%s.\n", + ctx->tc_pin_file, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + goto done; + } + + /* get the file size */ + if ( PR_SUCCESS != PR_GetFileInfo( ctx->tc_pin_file, &file_info ) ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not get file info from pin file %s - error %d:%s.\n", + ctx->tc_pin_file, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + goto done; + } + + /* create a buffer to hold the file contents */ + if ( !( contents = PR_CALLOC( file_info.size + 1 ) ) ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not alloc a buffer for contents of pin file %s - error %d:%s.\n", + ctx->tc_pin_file, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + goto done; + } + + /* read file into the buffer */ + if( PR_Read( pwd_fileptr, contents, file_info.size ) <= 0 ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not read the file contents from pin file %s - error %d:%s.\n", + ctx->tc_pin_file, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + goto done; + } + + /* format is [tokenname:]password EOL [tokenname:]password EOL ... */ + /* if you want to use a password containing a colon character, use + the special tokenname "default" */ + for ( line = PL_strtok_r( contents, "\r\n", &lasts ); line; + line = PL_strtok_r( NULL, "\r\n", &lasts ) ) { + char *colon; + + if ( !*line ) { + continue; /* skip blank lines */ + } + colon = PL_strchr( line, ':' ); + if ( colon ) { + if ( *(colon + 1) && token_name && + !PL_strncmp( token_name, line, colon-line ) ) { + candidate = colon + 1; /* found a definite match */ + break; + } else if ( !PL_strncmp( DEFAULT_TOKEN_NAME, line, colon-line ) ) { + candidate = colon + 1; /* found possible match */ + } + } else { /* no token name */ + candidate = line; + } + } +done: + if ( pwd_fileptr ) { + PR_Close( pwd_fileptr ); + } + if ( candidate ) { + pwdstr = PL_strdup( candidate ); + } + PL_strfree( contents ); + + return pwdstr; +} +#endif /* READ_PASSWORD_FROM_FILE */ + +#ifdef READ_PASSWORD_FROM_STDIN +/* + * Turn the echoing off on a tty. + */ +static void +echoOff(int fd) +{ + if ( isatty( fd ) ) { + struct termios tio; + tcgetattr( fd, &tio ); + tio.c_lflag &= ~ECHO; + tcsetattr( fd, TCSAFLUSH, &tio ); + } +} + +/* + * Turn the echoing on on a tty. + */ +static void +echoOn(int fd) +{ + if ( isatty( fd ) ) { + struct termios tio; + tcgetattr( fd, &tio ); + tio.c_lflag |= ECHO; + tcsetattr( fd, TCSAFLUSH, &tio ); + tcsetattr( fd, TCSAFLUSH, &tio ); + } +} +#endif /* READ_PASSWORD_FROM_STDIN */ + +/* + * This does the actual work of reading the pin/password/pass phrase + */ +static char * +tlsm_get_pin(PK11SlotInfo *slot, PRBool retry, tlsm_ctx *ctx) +{ + char *token_name = NULL; + char *pwdstr = NULL; + + token_name = PK11_GetTokenName( slot ); +#ifdef READ_PASSWORD_FROM_FILE + /* Try to get the passwords from the password file if it exists. + * THIS IS UNSAFE and is provided for convenience only. Without this + * capability the server would have to be started in foreground mode + * if using an encrypted key. + */ + if ( ctx && ctx->tc_pin_file ) { + pwdstr = tlsm_get_pin_from_file( token_name, ctx ); + if ( retry && pwdstr != NULL ) + return NULL; + } +#endif /* RETRIEVE_PASSWORD_FROM_FILE */ +#ifdef READ_PASSWORD_FROM_STDIN + if ( !pwdstr ) { + int infd = PR_FileDesc2NativeHandle( PR_STDIN ); + int isTTY = isatty( infd ); + unsigned char phrase[200]; + /* Prompt for password */ + if ( isTTY ) { + fprintf( stdout, + "Please enter pin, password, or pass phrase for security token '%s': ", + token_name ? token_name : DEFAULT_TOKEN_NAME ); + echoOff( infd ); + } + fgets( (char*)phrase, sizeof(phrase), stdin ); + if ( isTTY ) { + fprintf( stdout, "\n" ); + echoOn( infd ); + } + /* stomp on newline */ + phrase[strlen((char*)phrase)-1] = 0; + + pwdstr = PL_strdup( (char*)phrase ); + } + +#endif /* READ_PASSWORD_FROM_STDIN */ + return pwdstr; +} + +/* + * PKCS11 devices (including the internal softokn cert/key database) + * may be protected by a pin or password or even pass phrase + * MozNSS needs a way for the user to provide that + */ +static char * +tlsm_pin_prompt(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + tlsm_ctx *ctx = (tlsm_ctx *)arg; + + return tlsm_get_pin( slot, retry, ctx ); +} + +static char * +tlsm_ctx_subject_name(tlsm_ctx *ctx) +{ + if ( !ctx || !ctx->tc_certificate ) + return "(unknown)"; + + return ctx->tc_certificate->subjectName; +} + +static SECStatus +tlsm_get_basic_constraint_extension( CERTCertificate *cert, + CERTBasicConstraints *cbcval ) +{ + SECItem encodedVal = { 0, NULL }; + SECStatus rc; + + rc = CERT_FindCertExtension( cert, SEC_OID_X509_BASIC_CONSTRAINTS, + &encodedVal); + if ( rc != SECSuccess ) { + return rc; + } + + rc = CERT_DecodeBasicConstraintValue( cbcval, &encodedVal ); + + /* free the raw extension data */ + PORT_Free( encodedVal.data ); + + return rc; +} + +static PRBool +tlsm_cert_is_self_issued( CERTCertificate *cert ) +{ + /* A cert is self-issued if its subject and issuer are equal and + * both are of non-zero length. + */ + PRBool is_self_issued = cert && + (PRBool)SECITEM_ItemsAreEqual( &cert->derIssuer, + &cert->derSubject ) && + cert->derSubject.len > 0; + return is_self_issued; +} + +/* + * The private key for used certificate can be already unlocked by other + * thread or library. Find the unlocked key if possible. + */ +static SECKEYPrivateKey * +tlsm_find_unlocked_key( tlsm_ctx *ctx, void *pin_arg ) +{ + SECKEYPrivateKey *result = NULL; + + PK11SlotList *slots = PK11_GetAllSlotsForCert( ctx->tc_certificate, NULL ); + if ( !slots ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: cannot get all slots for certificate '%s' (error %d: %s)", + tlsm_ctx_subject_name( ctx ), errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return result; + } + + PK11SlotListElement *le; + for ( le = slots->head; le; le = le->next ) { + PK11SlotInfo *slot = le->slot; + if ( PK11_IsLoggedIn( slot, NULL ) ) { + result = PK11_FindKeyByDERCert( slot, ctx->tc_certificate, pin_arg ); + break; + } + } + + PK11_FreeSlotList( slots ); + return result; +} + +static SECStatus +tlsm_verify_cert(CERTCertDBHandle *handle, CERTCertificate *cert, void *pinarg, + PRBool checksig, SECCertificateUsage certUsage, PRBool warn_only, + PRBool ignore_issuer ) +{ + CERTVerifyLog verifylog; + SECStatus ret = SECSuccess; + const char *name; + int debug_level = LDAP_DEBUG_ANY; + + if ( warn_only ) { + debug_level = LDAP_DEBUG_TRACE; + } + + /* the log captures information about every cert in the chain, so we can tell + which cert caused the problem and what the problem was */ + memset( &verifylog, 0, sizeof( verifylog ) ); + verifylog.arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE ); + if ( verifylog.arena == NULL ) { + Debug( LDAP_DEBUG_ANY, + "TLS certificate verification: Out of memory for certificate verification logger\n", + 0, 0, 0 ); + return SECFailure; + } + ret = CERT_VerifyCertificate( handle, cert, checksig, certUsage, PR_Now(), pinarg, &verifylog, + NULL ); + if ( ( name = cert->subjectName ) == NULL ) { + name = cert->nickname; + } + if ( verifylog.head == NULL ) { + /* it is possible for CERT_VerifyCertificate return with an error with no logging */ + if ( ret != SECSuccess ) { + PRErrorCode errcode = PR_GetError(); + Debug( debug_level, + "TLS: certificate [%s] is not valid - error %d:%s.\n", + name ? name : "(unknown)", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + } + } else { + const char *name; + CERTVerifyLogNode *node; + + ret = SECSuccess; /* reset */ + node = verifylog.head; + while ( node ) { + if ( ( name = node->cert->subjectName ) == NULL ) { + name = node->cert->nickname; + } + if ( node->error ) { + /* NSS does not like CA certs that have the basic constraints extension + with the CA flag set to FALSE - openssl doesn't check if the cert + is self issued */ + if ( ( node->error == SEC_ERROR_CA_CERT_INVALID ) && + tlsm_cert_is_self_issued( node->cert ) ) { + + PRErrorCode orig_error = PR_GetError(); + PRInt32 orig_oserror = PR_GetOSError(); + + CERTBasicConstraints basicConstraint; + SECStatus rv = tlsm_get_basic_constraint_extension( node->cert, &basicConstraint ); + if ( ( rv == SECSuccess ) && ( basicConstraint.isCA == PR_FALSE ) ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: certificate [%s] is not correct because it is a CA cert and the " + "BasicConstraint CA flag is set to FALSE - allowing for now, but " + "please fix your certs if possible\n", name, 0, 0 ); + } else { /* does not have basicconstraint, or some other error */ + ret = SECFailure; + Debug( debug_level, + "TLS: certificate [%s] is not valid - CA cert is not valid\n", + name, 0, 0 ); + } + + PR_SetError( orig_error, orig_oserror ); + + } else if ( warn_only || ( ignore_issuer && ( + node->error == SEC_ERROR_UNKNOWN_ISSUER || + node->error == SEC_ERROR_UNTRUSTED_ISSUER ) + ) ) { + ret = SECSuccess; + Debug( debug_level, + "TLS: Warning: ignoring error for certificate [%s] - error %ld:%s.\n", + name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) ); + } else { + ret = SECFailure; + Debug( debug_level, + "TLS: certificate [%s] is not valid - error %ld:%s.\n", + name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) ); + } + } + CERT_DestroyCertificate( node->cert ); + node = node->next; + } + } + + PORT_FreeArena( verifylog.arena, PR_FALSE ); + + if ( ret == SECSuccess ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: certificate [%s] is valid\n", name, 0, 0 ); + } + + return ret; +} + +static SECStatus +tlsm_auth_cert_handler(void *arg, PRFileDesc *fd, + PRBool checksig, PRBool isServer) +{ + SECCertificateUsage certUsage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer; + SECStatus ret = SECSuccess; + CERTCertificate *peercert = SSL_PeerCertificate( fd ); + tlsm_ctx *ctx = (tlsm_ctx *)arg; + + ret = tlsm_verify_cert( ctx->tc_certdb, peercert, + SSL_RevealPinArg( fd ), + checksig, certUsage, ctx->tc_warn_only, PR_FALSE ); + CERT_DestroyCertificate( peercert ); + + return ret; +} + +static PRCallOnceType tlsm_register_shutdown_callonce = {0,0}; + +static SECStatus +tlsm_nss_shutdown_cb( void *appData, void *nssData ) +{ + SECStatus rc = SECSuccess; + + SSL_ShutdownServerSessionIDCache(); + + if ( pem_module ) { + SECMOD_UnloadUserModule( pem_module ); + SECMOD_DestroyModule( pem_module ); + pem_module = NULL; + } + + /* init callonce so it can be armed again for cases like persistent daemon with LDAP_OPT_X_TLS_NEWCTX */ + tlsm_register_shutdown_callonce.initialized = 0; + tlsm_register_shutdown_callonce.inProgress = 0; + tlsm_register_shutdown_callonce.status = 0; + + return rc; +} + +static PRStatus PR_CALLBACK +tlsm_register_nss_shutdown_cb( void ) +{ + if ( SECSuccess == NSS_RegisterShutdown( tlsm_nss_shutdown_cb, + NULL ) ) { + return PR_SUCCESS; + } + return PR_FAILURE; +} + +static PRStatus +tlsm_register_nss_shutdown( void ) +{ + return PR_CallOnce( &tlsm_register_shutdown_callonce, + tlsm_register_nss_shutdown_cb ); +} + +static int +tlsm_init_pem_module( void ) +{ + int rc = 0; + char *fullname = NULL; + char *configstring = NULL; + + if ( pem_module ) { + return rc; + } + + /* not loaded - load it */ + /* get the system dependent library name */ + fullname = PR_GetLibraryName( NULL, PEM_LIBRARY ); + /* Load our PKCS#11 module */ + configstring = PR_smprintf( "library=%s name=" PEM_MODULE " parameters=\"\"", fullname ); + PL_strfree( fullname ); + + pem_module = SECMOD_LoadUserModule( configstring, NULL, PR_FALSE ); + PR_smprintf_free( configstring ); + + if ( !pem_module || !pem_module->loaded ) { + if ( pem_module ) { + SECMOD_DestroyModule( pem_module ); + pem_module = NULL; + } + rc = -1; + } + + return rc; +} + +static void +tlsm_add_pem_obj( tlsm_ctx *ctx, PK11GenericObject *obj ) +{ + int idx = ctx->tc_n_pem_objs; + ctx->tc_n_pem_objs++; + ctx->tc_pem_objs = (PK11GenericObject **) + PORT_Realloc( ctx->tc_pem_objs, ctx->tc_n_pem_objs * sizeof( PK11GenericObject * ) ); + ctx->tc_pem_objs[idx] = obj; +} + +static void +tlsm_free_pem_objs( tlsm_ctx *ctx ) +{ + /* free in reverse order of allocation */ + while ( ctx->tc_n_pem_objs-- ) { + PK11_DestroyGenericObject( ctx->tc_pem_objs[ctx->tc_n_pem_objs] ); + ctx->tc_pem_objs[ctx->tc_n_pem_objs] = NULL; + } + PORT_Free(ctx->tc_pem_objs); + ctx->tc_pem_objs = NULL; + ctx->tc_n_pem_objs = 0; +} + +static int +tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca ) +{ + PK11SlotInfo *slot; + PK11GenericObject *cert; + CK_ATTRIBUTE attrs[4]; + CK_BBOOL cktrue = CK_TRUE; + CK_BBOOL ckfalse = CK_FALSE; + CK_OBJECT_CLASS objClass = CKO_CERTIFICATE; + char *slotname; + PRFileInfo fi; + PRStatus status; + SECItem certDER = { 0, NULL, 0 }; + + memset( &fi, 0, sizeof(fi) ); + status = PR_GetFileInfo( filename, &fi ); + if ( PR_SUCCESS != status) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not read certificate file %s - error %d:%s.\n", + filename, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + if ( fi.type != PR_FILE_FILE ) { + PR_SetError(PR_IS_DIRECTORY_ERROR, 0); + Debug( LDAP_DEBUG_ANY, + "TLS: error: the certificate file %s is not a file.\n", + filename, 0 ,0 ); + return -1; + } + + slotname = isca ? TLSM_PEM_SLOT_CACERTS : TLSM_PEM_SLOT_CERTS; + slot = PK11_FindSlotByName( slotname ); + + if ( !slot ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not find the slot for the certificate '%s' - error %d:%s.\n", + filename, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + PK11_SETATTRS( attrs[0], CKA_CLASS, &objClass, sizeof( objClass ) ); + PK11_SETATTRS( attrs[1], CKA_TOKEN, &cktrue, sizeof( CK_BBOOL ) ); + PK11_SETATTRS( attrs[2], CKA_LABEL, (unsigned char *) filename, strlen( filename ) + 1 ); + PK11_SETATTRS( attrs[3], CKA_TRUST, isca ? &cktrue : &ckfalse, sizeof( CK_BBOOL ) ); + + cert = PK11_CreateGenericObject( slot, attrs, 4, PR_FALSE /* isPerm */ ); + + if ( !cert ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not add the certificate '%s' - error %d:%s.\n", + filename, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + PK11_FreeSlot( slot ); + return -1; + } + + /* if not CA, we store the certificate in ctx->tc_certificate */ + if ( !isca ) { + if ( PK11_ReadRawAttribute( PK11_TypeGeneric, cert, CKA_VALUE, &certDER ) != SECSuccess ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not get DER of the '%s' certificate - error %d:%s.\n", + filename, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + PK11_DestroyGenericObject( cert ); + PK11_FreeSlot( slot ); + return -1; + } + + ctx->tc_certificate = PK11_FindCertFromDERCertItem( slot, &certDER, NULL ); + SECITEM_FreeItem( &certDER, PR_FALSE ); + + if ( !ctx->tc_certificate ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not get certificate '%s' using DER - error %d:%s.\n", + filename, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + PK11_DestroyGenericObject( cert ); + PK11_FreeSlot( slot ); + return -1; + } + } + + tlsm_add_pem_obj( ctx, cert ); + + PK11_FreeSlot( slot ); + + return 0; +} + +static int +tlsm_ctx_load_private_key( tlsm_ctx *ctx ) +{ + if ( !ctx->tc_certificate ) + return -1; + + if ( ctx->tc_private_key ) + return 0; + + void *pin_arg = SSL_RevealPinArg( ctx->tc_model ); + + SECKEYPrivateKey *unlocked_key = tlsm_find_unlocked_key( ctx, pin_arg ); + Debug( LDAP_DEBUG_ANY, + "TLS: %s unlocked certificate for certificate '%s'.\n", + unlocked_key ? "found" : "no", tlsm_ctx_subject_name( ctx ), 0 ); + + /* prefer unlocked key, then key from opened certdb, then any other */ + if ( unlocked_key ) + ctx->tc_private_key = unlocked_key; + else if ( ctx->tc_certdb_slot && !ctx->tc_using_pem ) + ctx->tc_private_key = PK11_FindKeyByDERCert( ctx->tc_certdb_slot, ctx->tc_certificate, pin_arg ); + else + ctx->tc_private_key = PK11_FindKeyByAnyCert( ctx->tc_certificate, pin_arg ); + + if ( !ctx->tc_private_key ) { + PRErrorCode errcode = PR_GetError(); + Debug(LDAP_DEBUG_ANY, + "TLS: cannot find private key for certificate '%s' (error %d: %s)", + tlsm_ctx_subject_name( ctx ), errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + return 0; +} + +static int +tlsm_add_key_from_file( tlsm_ctx *ctx, const char *filename ) +{ + PK11SlotInfo * slot = NULL; + PK11GenericObject *key; + CK_ATTRIBUTE attrs[3]; + CK_BBOOL cktrue = CK_TRUE; + CK_OBJECT_CLASS objClass = CKO_PRIVATE_KEY; + int retcode = 0; + PRFileInfo fi; + PRStatus status; + + memset( &fi, 0, sizeof(fi) ); + status = PR_GetFileInfo( filename, &fi ); + if ( PR_SUCCESS != status) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not read key file %s - error %d:%s.\n", + filename, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + if ( fi.type != PR_FILE_FILE ) { + PR_SetError(PR_IS_DIRECTORY_ERROR, 0); + Debug( LDAP_DEBUG_ANY, + "TLS: error: the key file %s is not a file.\n", + filename, 0 ,0 ); + return -1; + } + + slot = PK11_FindSlotByName( TLSM_PEM_SLOT_CERTS ); + + if ( !slot ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not find the slot for the private key '%s' - error %d:%s.\n", + filename, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + PK11_SETATTRS( attrs[0], CKA_CLASS, &objClass, sizeof( objClass ) ); + PK11_SETATTRS( attrs[1], CKA_TOKEN, &cktrue, sizeof( CK_BBOOL ) ); + PK11_SETATTRS( attrs[2], CKA_LABEL, (unsigned char *)filename, strlen( filename ) + 1 ); + + key = PK11_CreateGenericObject( slot, attrs, 3, PR_FALSE /* isPerm */ ); + + if ( !key ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not add the private key '%s' - error %d:%s.\n", + filename, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + retcode = -1; + } else { + tlsm_add_pem_obj( ctx, key ); + retcode = 0; + + /* When adding an encrypted key the PKCS#11 will be set as removed */ + /* This will force the token to be seen as re-inserted */ + SECMOD_WaitForAnyTokenEvent( pem_module, 0, 0 ); + PK11_IsPresent( slot ); + } + + PK11_FreeSlot( slot ); + + return retcode; +} + +static int +tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir ) +{ + PRBool isca = PR_TRUE; + PRStatus status = PR_SUCCESS; + PRErrorCode errcode = PR_SUCCESS; + + if ( !cacertfile && !cacertdir ) { + /* no checking - not good, but allowed */ + return 0; + } + + if ( cacertfile ) { + int rc = tlsm_add_cert_from_file( ctx, cacertfile, isca ); + if ( rc ) { + errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: %s is not a valid CA certificate file - error %d:%s.\n", + cacertfile, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + /* failure with cacertfile is a hard failure even if cacertdir is + also specified and contains valid CA cert files */ + status = PR_FAILURE; + } else { + Debug( LDAP_DEBUG_TRACE, + "TLS: loaded CA certificate file %s.\n", + cacertfile, 0, 0 ); + } + } + + /* if cacertfile above failed, we will return failure, even + if there is a valid CA cert in cacertdir - but we still + process cacertdir in case the user has enabled trace level + debugging so they can see the processing for cacertdir too */ + /* any cacertdir failures are "soft" failures - if the user specifies + no cert checking, then we allow the tls/ssl to continue, no matter + what was specified for cacertdir, or the contents of the directory + - this is different behavior than that of cacertfile */ + if ( cacertdir ) { + PRFileInfo fi; + PRDir *dir; + PRDirEntry *entry; + PRStatus fistatus = PR_FAILURE; + regex_t hashfile_re; + + memset( &fi, 0, sizeof(fi) ); + fistatus = PR_GetFileInfo( cacertdir, &fi ); + if ( PR_SUCCESS != fistatus) { + errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not get info about the CA certificate directory %s - error %d:%s.\n", + cacertdir, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + goto done; + } + + if ( fi.type != PR_FILE_DIRECTORY ) { + Debug( LDAP_DEBUG_ANY, + "TLS: error: the CA certificate directory %s is not a directory.\n", + cacertdir, 0 ,0 ); + goto done; + } + + dir = PR_OpenDir( cacertdir ); + if ( NULL == dir ) { + errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not open the CA certificate directory %s - error %d:%s.\n", + cacertdir, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + goto done; + } + + if ( regcomp( &hashfile_re, PEM_CA_HASH_FILE_REGEX, REG_NOSUB|REG_EXTENDED ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "TLS: cannot compile regex for CA hash files matching\n", 0, 0, 0 ); + goto done; + } + + do { + entry = PR_ReadDir( dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN ); + if ( ( NULL != entry ) && ( NULL != entry->name ) ) { + char *fullpath = NULL; + int match; + + match = regexec( &hashfile_re, entry->name, 0, NULL, 0 ); + if ( match == REG_NOMATCH ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: skipping '%s' - filename does not have expected format " + "(certificate hash with numeric suffix)\n", entry->name, 0, 0 ); + continue; + } else if ( match != 0 ) { + Debug( LDAP_DEBUG_ANY, + "TLS: cannot execute regex for CA hash file matching (%d).\n", + match, 0, 0 ); + continue; + } + + fullpath = PR_smprintf( "%s/%s", cacertdir, entry->name ); + if ( !tlsm_add_cert_from_file( ctx, fullpath, isca ) ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: loaded CA certificate file %s from CA certificate directory %s.\n", + fullpath, cacertdir, 0 ); + } else { + errcode = PR_GetError(); + Debug( LDAP_DEBUG_TRACE, + "TLS: %s is not a valid CA certificate file - error %d:%s.\n", + fullpath, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + } + PR_smprintf_free( fullpath ); + } + } while ( NULL != entry ); + regfree ( &hashfile_re ); + PR_CloseDir( dir ); + } +done: + if ( status != PR_SUCCESS ) { + return -1; + } + + return 0; +} + +/* + * NSS supports having multiple cert/key databases in the same + * directory, each one having a unique string prefix e.g. + * slapd-01-cert8.db - the prefix here is "slapd-01-" + * this function examines the given certdir - if it looks like + * /path/to/directory/prefix it will return the + * /path/to/directory part in realcertdir, and the prefix in prefix + */ +static void +tlsm_get_certdb_prefix( const char *certdir, char **realcertdir, char **prefix ) +{ + char sep = PR_GetDirectorySeparator(); + char *ptr = NULL; + struct PRFileInfo prfi; + PRStatus prc; + + *realcertdir = (char *)certdir; /* default is the one passed in */ + + /* if certdir is not given, just return */ + if ( !certdir ) { + return; + } + + prc = PR_GetFileInfo( certdir, &prfi ); + /* if certdir exists (file or directory) then it cannot specify a prefix */ + if ( prc == PR_SUCCESS ) { + return; + } + + /* if certdir was given, and there is a '/' in certdir, see if there + is anything after the last '/' - if so, assume it is the prefix */ + if ( ( ( ptr = strrchr( certdir, sep ) ) ) && *(ptr+1) ) { + *realcertdir = PL_strndup( certdir, ptr-certdir ); + *prefix = PL_strdup( ptr+1 ); + } + + return; +} + +/* + * Currently mutiple MozNSS contexts share one certificate storage. When the + * certdb is being opened, only new certificates are added to the storage. + * When different databases are used, conflicting nicknames make the + * certificate lookup by the nickname impossible. In addition a token + * description might be prepended in certain conditions. + * + * In order to make the certificate lookup by nickname possible, we explicitly + * open each database using SECMOD_OpenUserDB and assign it the token + * description. The token description is generated using ctx->tc_unique value, + * which is unique for each context. + */ +static PK11SlotInfo * +tlsm_init_open_certdb( tlsm_ctx *ctx, const char *dbdir, const char *prefix ) +{ + PK11SlotInfo *slot = NULL; + char *token_desc = NULL; + char *config = NULL; + + token_desc = PR_smprintf( TLSM_CERTDB_DESC_FMT, ctx->tc_unique ); + config = PR_smprintf( "configDir='%s' tokenDescription='%s' certPrefix='%s' keyPrefix='%s' flags=readOnly", + dbdir, token_desc, prefix, prefix ); + Debug( LDAP_DEBUG_TRACE, "TLS: certdb config: %s\n", config, 0, 0 ); + + slot = SECMOD_OpenUserDB( config ); + if ( !slot ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_TRACE, "TLS: cannot open certdb '%s', error %d:%s\n", dbdir, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + } + + if ( token_desc ) + PR_smprintf_free( token_desc ); + if ( config ) + PR_smprintf_free( config ); + + return slot; +} + +/* + * This is the part of the init we defer until we get the + * actual security configuration information. This is + * only called once, protected by a PRCallOnce + * NOTE: This must be done before the first call to SSL_ImportFD, + * especially the setting of the policy + * NOTE: This must be called after fork() + */ +static int +tlsm_deferred_init( void *arg ) +{ + tlsm_ctx *ctx = (tlsm_ctx *)arg; + struct ldaptls *lt = ctx->tc_config; + const char *securitydirs[3]; + int ii; + int nn; + PRErrorCode errcode = 1; +#ifdef HAVE_NSS_INITCONTEXT + NSSInitParameters initParams; + NSSInitContext *initctx = NULL; + PK11SlotInfo *certdb_slot = NULL; +#endif + SECStatus rc; + int done = 0; + +#ifdef HAVE_SECMOD_RESTARTMODULES + /* NSS enforces the pkcs11 requirement that modules should be unloaded after + a fork() - since there is no portable way to determine if NSS has been + already initialized in a parent process, we just call SECMOD_RestartModules + with force == FALSE - if the module has been unloaded due to a fork, it will + be reloaded, otherwise, it is a no-op */ + if ( SECFailure == ( rc = SECMOD_RestartModules(PR_FALSE /* do not force */) ) ) { + errcode = PORT_GetError(); + if ( errcode != SEC_ERROR_NOT_INITIALIZED ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: could not restart the security modules: %d:%s\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + } else { + errcode = 1; + } + } +#endif + +#ifdef HAVE_NSS_INITCONTEXT + memset( &initParams, 0, sizeof( initParams ) ); + initParams.length = sizeof( initParams ); +#endif /* HAVE_NSS_INITCONTEXT */ + +#ifdef LDAP_R_COMPILE + if ( PR_CallOnce( &tlsm_init_mutex_callonce, tlsm_thr_init_callonce ) ) { + return -1; + } +#endif /* LDAP_R_COMPILE */ + +#ifndef HAVE_NSS_INITCONTEXT + if ( !NSS_IsInitialized() ) { +#endif /* HAVE_NSS_INITCONTEXT */ + /* + MOZNSS_DIR will override everything else - you can + always set MOZNSS_DIR to force the use of this + directory + If using MOZNSS, specify the location of the moznss db dir + in the cacertdir directive of the OpenLDAP configuration. + DEFAULT_MOZNSS_DIR will only be used if the code cannot + find a security dir to use based on the current + settings + */ + nn = 0; + securitydirs[nn++] = PR_GetEnv( "MOZNSS_DIR" ); + securitydirs[nn++] = lt->lt_cacertdir; + securitydirs[nn++] = PR_GetEnv( "DEFAULT_MOZNSS_DIR" ); + for ( ii = 0; !done && ( ii < nn ); ++ii ) { + char *realcertdir = NULL; + const char *defprefix = ""; + char *prefix = (char *)defprefix; + const char *securitydir = securitydirs[ii]; + if ( NULL == securitydir ) { + continue; + } + + tlsm_get_certdb_prefix( securitydir, &realcertdir, &prefix ); + + /* initialize only moddb; certdb will be initialized explicitly */ +#ifdef HAVE_NSS_INITCONTEXT +#ifdef INITCONTEXT_HACK + if ( !NSS_IsInitialized() && ctx->tc_is_server ) { + rc = NSS_Initialize( realcertdir, prefix, prefix, SECMOD_DB, NSS_INIT_READONLY ); + } else { + initctx = NSS_InitContext( realcertdir, prefix, prefix, SECMOD_DB, + &initParams, NSS_INIT_READONLY|NSS_INIT_NOCERTDB ); + } +#else + initctx = NSS_InitContext( realcertdir, prefix, prefix, SECMOD_DB, + &initParams, NSS_INIT_READONLY|NSS_INIT_NOCERTDB ); +#endif + rc = SECFailure; + + if ( initctx != NULL ) { + certdb_slot = tlsm_init_open_certdb( ctx, realcertdir, prefix ); + if ( certdb_slot ) { + rc = SECSuccess; + ctx->tc_initctx = initctx; + ctx->tc_certdb_slot = certdb_slot; + } else { + NSS_ShutdownContext( initctx ); + initctx = NULL; + } + } +#else + rc = NSS_Initialize( realcertdir, prefix, prefix, SECMOD_DB, NSS_INIT_READONLY ); +#endif + + if ( rc != SECSuccess ) { + errcode = PORT_GetError(); + if ( securitydirs[ii] != lt->lt_cacertdir) { + Debug( LDAP_DEBUG_TRACE, + "TLS: could not initialize moznss using security dir %s prefix %s - error %d.\n", + realcertdir, prefix, errcode ); + } + } else { + /* success */ + Debug( LDAP_DEBUG_TRACE, "TLS: using moznss security dir %s prefix %s.\n", + realcertdir, prefix, 0 ); + errcode = 0; + done = 1; + } + if ( realcertdir != securitydir ) { + PL_strfree( realcertdir ); + } + if ( prefix != defprefix ) { + PL_strfree( prefix ); + } + } + + if ( errcode ) { /* no moznss db found, or not using moznss db */ +#ifdef HAVE_NSS_INITCONTEXT + int flags = NSS_INIT_READONLY|NSS_INIT_NOCERTDB|NSS_INIT_NOMODDB; +#ifdef INITCONTEXT_HACK + if ( !NSS_IsInitialized() && ctx->tc_is_server ) { + rc = NSS_NoDB_Init( NULL ); + } else { + initctx = NSS_InitContext( CERTDB_NONE, PREFIX_NONE, PREFIX_NONE, SECMOD_DB, + &initParams, flags ); + rc = (initctx == NULL) ? SECFailure : SECSuccess; + } +#else + initctx = NSS_InitContext( CERTDB_NONE, PREFIX_NONE, PREFIX_NONE, SECMOD_DB, + &initParams, flags ); + if ( initctx ) { + ctx->tc_initctx = initctx; + rc = SECSuccess; + } else { + rc = SECFailure; + } +#endif +#else + rc = NSS_NoDB_Init( NULL ); +#endif + if ( rc != SECSuccess ) { + errcode = PORT_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not initialize moznss - error %d:%s.\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + return -1; + } + } + + if ( errcode || lt->lt_cacertfile ) { + /* initialize the PEM module */ + if ( tlsm_init_pem_module() ) { + int pem_errcode = PORT_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not initialize moznss PEM module - error %d:%s.\n", + pem_errcode, PR_ErrorToString( pem_errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + + if ( errcode ) /* PEM is required */ + return -1; + + } else if ( !errcode ) { + tlsm_init_ca_certs( ctx, lt->lt_cacertfile, NULL ); + } + } + + if ( errcode ) { + if ( tlsm_init_ca_certs( ctx, lt->lt_cacertfile, lt->lt_cacertdir ) ) { + /* if we tried to use lt->lt_cacertdir as an NSS key/cert db, errcode + will be a value other than 1 - print an error message so that the + user will know that failed too */ + if ( ( errcode != 1 ) && ( lt->lt_cacertdir ) ) { + char *realcertdir = NULL; + char *prefix = NULL; + tlsm_get_certdb_prefix( lt->lt_cacertdir, &realcertdir, &prefix ); + Debug( LDAP_DEBUG_TRACE, + "TLS: could not initialize moznss using security dir %s prefix %s - error %d.\n", + realcertdir, prefix ? prefix : "", errcode ); + if ( realcertdir != lt->lt_cacertdir ) { + PL_strfree( realcertdir ); + } + PL_strfree( prefix ); + } + return -1; + } + } + + NSS_SetDomesticPolicy(); + + PK11_SetPasswordFunc( tlsm_pin_prompt ); + + /* register cleanup function */ + if ( tlsm_register_nss_shutdown() ) { + errcode = PORT_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not register NSS shutdown function: %d:%s\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + return -1; + } + + if ( ctx->tc_is_server ) { + /* 0 means use the defaults here */ + SSL_ConfigServerSessionIDCache( 0, 0, 0, NULL ); + } + +#ifndef HAVE_NSS_INITCONTEXT + } +#endif /* HAVE_NSS_INITCONTEXT */ + + return 0; +} + +/* + * Find and verify the certificate. + * The key is loaded and stored in ctx->tc_private_key + */ +static int +tlsm_find_and_verify_cert_key( tlsm_ctx *ctx ) +{ + SECCertificateUsage certUsage; + PRBool checkSig; + SECStatus status; + void *pin_arg; + + if ( tlsm_ctx_load_private_key( ctx ) ) + return -1; + + pin_arg = SSL_RevealPinArg( ctx->tc_model ); + certUsage = ctx->tc_is_server ? certificateUsageSSLServer : certificateUsageSSLClient; + checkSig = ctx->tc_verify_cert ? PR_TRUE : PR_FALSE; + + status = tlsm_verify_cert( ctx->tc_certdb, ctx->tc_certificate, pin_arg, + checkSig, certUsage, ctx->tc_warn_only, PR_TRUE ); + + return status == SECSuccess ? 0 : -1; +} + +static int +tlsm_get_client_auth_data( void *arg, PRFileDesc *fd, + CERTDistNames *caNames, CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey ) +{ + tlsm_ctx *ctx = (tlsm_ctx *)arg; + + if ( pRetCert ) + *pRetCert = CERT_DupCertificate( ctx->tc_certificate ); + + if ( pRetKey ) + *pRetKey = SECKEY_CopyPrivateKey( ctx->tc_private_key ); + + return SECSuccess; +} + +/* + * ctx must have a tc_model that is valid +*/ +static int +tlsm_clientauth_init( tlsm_ctx *ctx ) +{ + SECStatus status = SECFailure; + int rc; + PRBool saveval; + + saveval = ctx->tc_warn_only; + ctx->tc_warn_only = PR_TRUE; + rc = tlsm_find_and_verify_cert_key(ctx); + ctx->tc_warn_only = saveval; + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "TLS: error: unable to set up client certificate authentication for " + "certificate named %s\n", tlsm_ctx_subject_name(ctx), 0, 0 ); + return -1; + } + + status = SSL_GetClientAuthDataHook( ctx->tc_model, + tlsm_get_client_auth_data, + (void *)ctx ); + + return ( status == SECSuccess ? 0 : -1 ); +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +static void +tlsm_destroy( void ) +{ +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_destroy( &tlsm_ctx_count_mutex ); + ldap_pvt_thread_mutex_destroy( &tlsm_init_mutex ); + ldap_pvt_thread_mutex_destroy( &tlsm_pem_mutex ); +#endif +} + +static struct ldaptls * +tlsm_copy_config ( const struct ldaptls *config ) +{ + struct ldaptls *copy; + + assert( config ); + + copy = LDAP_MALLOC( sizeof( *copy ) ); + if ( !copy ) + return NULL; + + memset( copy, 0, sizeof( *copy ) ); + + if ( config->lt_certfile ) + copy->lt_certfile = LDAP_STRDUP( config->lt_certfile ); + if ( config->lt_keyfile ) + copy->lt_keyfile = LDAP_STRDUP( config->lt_keyfile ); + if ( config->lt_dhfile ) + copy->lt_dhfile = LDAP_STRDUP( config->lt_dhfile ); + if ( config->lt_cacertfile ) + copy->lt_cacertfile = LDAP_STRDUP( config->lt_cacertfile ); + if ( config->lt_cacertdir ) + copy->lt_cacertdir = LDAP_STRDUP( config->lt_cacertdir ); + if ( config->lt_ciphersuite ) + copy->lt_ciphersuite = LDAP_STRDUP( config->lt_ciphersuite ); + if ( config->lt_crlfile ) + copy->lt_crlfile = LDAP_STRDUP( config->lt_crlfile ); + if ( config->lt_randfile ) + copy->lt_randfile = LDAP_STRDUP( config->lt_randfile ); + + copy->lt_protocol_min = config->lt_protocol_min; + + return copy; +} + +static void +tlsm_free_config ( struct ldaptls *config ) +{ + assert( config ); + + if ( config->lt_certfile ) + LDAP_FREE( config->lt_certfile ); + if ( config->lt_keyfile ) + LDAP_FREE( config->lt_keyfile ); + if ( config->lt_dhfile ) + LDAP_FREE( config->lt_dhfile ); + if ( config->lt_cacertfile ) + LDAP_FREE( config->lt_cacertfile ); + if ( config->lt_cacertdir ) + LDAP_FREE( config->lt_cacertdir ); + if ( config->lt_ciphersuite ) + LDAP_FREE( config->lt_ciphersuite ); + if ( config->lt_crlfile ) + LDAP_FREE( config->lt_crlfile ); + if ( config->lt_randfile ) + LDAP_FREE( config->lt_randfile ); + + LDAP_FREE( config ); +} + +static tls_ctx * +tlsm_ctx_new ( struct ldapoptions *lo ) +{ + tlsm_ctx *ctx; + + ctx = LDAP_MALLOC( sizeof (*ctx) ); + if ( ctx ) { + ctx->tc_refcnt = 1; +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_init( &ctx->tc_refmutex ); +#endif + LDAP_MUTEX_LOCK( &tlsm_ctx_count_mutex ); + ctx->tc_unique = tlsm_ctx_count++; + LDAP_MUTEX_UNLOCK( &tlsm_ctx_count_mutex ); + ctx->tc_config = NULL; /* populated later by tlsm_ctx_init */ + ctx->tc_certdb = NULL; + ctx->tc_certdb_slot = NULL; + ctx->tc_certificate = NULL; + ctx->tc_private_key = NULL; + ctx->tc_pin_file = NULL; + ctx->tc_model = NULL; + memset(&ctx->tc_callonce, 0, sizeof(ctx->tc_callonce)); + ctx->tc_require_cert = lo->ldo_tls_require_cert; + ctx->tc_verify_cert = PR_FALSE; + ctx->tc_using_pem = PR_FALSE; +#ifdef HAVE_NSS_INITCONTEXT + ctx->tc_initctx = NULL; +#endif /* HAVE_NSS_INITCONTEXT */ + ctx->tc_pem_objs = NULL; + ctx->tc_n_pem_objs = 0; + ctx->tc_warn_only = PR_FALSE; + } + return (tls_ctx *)ctx; +} + +static void +tlsm_ctx_ref( tls_ctx *ctx ) +{ + tlsm_ctx *c = (tlsm_ctx *)ctx; + LDAP_MUTEX_LOCK( &c->tc_refmutex ); + c->tc_refcnt++; + LDAP_MUTEX_UNLOCK( &c->tc_refmutex ); +} + +static void +tlsm_ctx_free ( tls_ctx *ctx ) +{ + tlsm_ctx *c = (tlsm_ctx *)ctx; + int refcount; + + if ( !c ) return; + + LDAP_MUTEX_LOCK( &c->tc_refmutex ); + refcount = --c->tc_refcnt; + LDAP_MUTEX_UNLOCK( &c->tc_refmutex ); + if ( refcount ) + return; + + LDAP_MUTEX_LOCK( &tlsm_init_mutex ); + if ( c->tc_model ) + PR_Close( c->tc_model ); + if ( c->tc_certificate ) + CERT_DestroyCertificate( c->tc_certificate ); + if ( c->tc_private_key ) + SECKEY_DestroyPrivateKey( c->tc_private_key ); + c->tc_certdb = NULL; /* if not the default, may have to clean up */ + if ( c->tc_certdb_slot ) { + if ( SECMOD_CloseUserDB( c->tc_certdb_slot ) ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not close certdb slot - error %d:%s.\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + } + } + if ( c->tc_pin_file ) { + PL_strfree( c->tc_pin_file ); + c->tc_pin_file = NULL; + } + tlsm_free_pem_objs( c ); +#ifdef HAVE_NSS_INITCONTEXT + if ( c->tc_initctx ) { + if ( NSS_ShutdownContext( c->tc_initctx ) ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not shutdown NSS - error %d:%s.\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + } + } + c->tc_initctx = NULL; +#endif /* HAVE_NSS_INITCONTEXT */ + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_destroy( &c->tc_refmutex ); +#endif + + if ( c->tc_config ) + tlsm_free_config( c->tc_config ); + + LDAP_FREE( c ); +} + +/* + * initialize a new TLS context + */ +static int +tlsm_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) +{ + tlsm_ctx *ctx = (tlsm_ctx *)lo->ldo_tls_ctx; + ctx->tc_config = tlsm_copy_config( lt ); + ctx->tc_is_server = is_server; + + return 0; +} + +/* returns true if the given string looks like + "tokenname" ":" "certnickname" + This is true if there is a ':' colon character + in the string and the colon is not the first + or the last character in the string +*/ +static int +tlsm_is_tokenname_certnick( const char *certfile ) +{ + if ( certfile ) { + const char *ptr = PL_strchr( certfile, ':' ); + return ptr && (ptr != certfile) && (*(ptr+1)); + } + return 0; +} + +static int +tlsm_deferred_ctx_init( void *arg ) +{ + tlsm_ctx *ctx = (tlsm_ctx *)arg; + PRBool sslv2 = PR_FALSE; + PRBool sslv3 = PR_TRUE; + PRBool tlsv1 = PR_TRUE; + PRBool request_cert = PR_FALSE; + PRInt32 require_cert = PR_FALSE; + PRFileDesc *fd; + struct ldaptls *lt; + + if ( tlsm_deferred_init( ctx ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not perform TLS system initialization.\n", + 0, 0, 0 ); + return -1; + } + + ctx->tc_certdb = CERT_GetDefaultCertDB(); /* If there is ever a per-context db, change this */ + + fd = PR_CreateIOLayerStub( tlsm_layer_id, &tlsm_PR_methods ); + if ( fd ) { + ctx->tc_model = SSL_ImportFD( NULL, fd ); + } + + if ( !ctx->tc_model ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could perform TLS socket I/O layer initialization - error %d:%s.\n", + err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL ); + + if ( fd ) { + PR_Close( fd ); + } + return -1; + } + + if ( SSL_SetPKCS11PinArg(ctx->tc_model, ctx) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set pin prompt argument\n", 0, 0, 0); + return -1; + } + + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_SECURITY, PR_TRUE ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set secure mode on.\n", + 0, 0, 0 ); + return -1; + } + + lt = ctx->tc_config; + + /* default is sslv3 and tlsv1 */ + if ( lt->lt_protocol_min ) { + if ( lt->lt_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL3 ) { + sslv3 = PR_FALSE; + } else if ( lt->lt_protocol_min <= LDAP_OPT_X_TLS_PROTOCOL_SSL2 ) { + sslv2 = PR_TRUE; + Debug( LDAP_DEBUG_ANY, + "TLS: warning: minimum TLS protocol level set to " + "include SSLv2 - SSLv2 is insecure - do not use\n", 0, 0, 0 ); + } + } + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_ENABLE_SSL2, sslv2 ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set SSLv2 mode on.\n", + 0, 0, 0 ); + return -1; + } + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_ENABLE_SSL3, sslv3 ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set SSLv3 mode on.\n", + 0, 0, 0 ); + return -1; + } + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_ENABLE_TLS, tlsv1 ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set TLSv1 mode on.\n", + 0, 0, 0 ); + return -1; + } + + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_HANDSHAKE_AS_CLIENT, !ctx->tc_is_server ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set handshake as client.\n", + 0, 0, 0 ); + return -1; + } + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_HANDSHAKE_AS_SERVER, ctx->tc_is_server ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set handshake as server.\n", + 0, 0, 0 ); + return -1; + } + + if ( lt->lt_ciphersuite ) { + if ( tlsm_parse_ciphers( ctx, lt->lt_ciphersuite ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lt->lt_ciphersuite, 0, 0 ); + return -1; + } + } else if ( tlsm_parse_ciphers( ctx, "DEFAULT" ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list DEFAULT.\n", + 0, 0, 0 ); + return -1; + } + + if ( !ctx->tc_require_cert ) { + ctx->tc_verify_cert = PR_FALSE; + } else if ( !ctx->tc_is_server ) { + request_cert = PR_TRUE; + require_cert = SSL_REQUIRE_NO_ERROR; + if ( ctx->tc_require_cert == LDAP_OPT_X_TLS_DEMAND || + ctx->tc_require_cert == LDAP_OPT_X_TLS_HARD ) { + require_cert = SSL_REQUIRE_ALWAYS; + } + if ( ctx->tc_require_cert != LDAP_OPT_X_TLS_ALLOW ) + ctx->tc_verify_cert = PR_TRUE; + } else { /* server */ + /* server does not request certs by default */ + /* if allow - client may send cert, server will ignore if errors */ + /* if try - client may send cert, server will error if bad cert */ + /* if hard or demand - client must send cert, server will error if bad cert */ + request_cert = PR_TRUE; + require_cert = SSL_REQUIRE_NO_ERROR; + if ( ctx->tc_require_cert == LDAP_OPT_X_TLS_DEMAND || + ctx->tc_require_cert == LDAP_OPT_X_TLS_HARD ) { + require_cert = SSL_REQUIRE_ALWAYS; + } + if ( ctx->tc_require_cert != LDAP_OPT_X_TLS_ALLOW ) { + ctx->tc_verify_cert = PR_TRUE; + } else { + ctx->tc_warn_only = PR_TRUE; + } + } + + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_REQUEST_CERTIFICATE, request_cert ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set request certificate mode.\n", + 0, 0, 0 ); + return -1; + } + + if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_REQUIRE_CERTIFICATE, require_cert ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set require certificate mode.\n", + 0, 0, 0 ); + return -1; + } + + /* set up our cert and key, if any */ + if ( lt->lt_certfile ) { + + /* first search in certdb (lt_certfile is nickname) */ + if ( ctx->tc_certdb ) { + char *tmp_certname; + + if ( tlsm_is_tokenname_certnick( lt->lt_certfile )) { + /* assume already in form tokenname:certnickname */ + tmp_certname = PL_strdup( lt->lt_certfile ); + } else if ( ctx->tc_certdb_slot ) { + tmp_certname = PR_smprintf( TLSM_CERTDB_DESC_FMT ":%s", ctx->tc_unique, lt->lt_certfile ); + } else { + tmp_certname = PR_smprintf( "%s", lt->lt_certfile ); + } + + ctx->tc_certificate = PK11_FindCertFromNickname( tmp_certname, SSL_RevealPinArg( ctx->tc_model ) ); + PR_smprintf_free( tmp_certname ); + + if ( !ctx->tc_certificate ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: the certificate '%s' could not be found in the database - error %d:%s.\n", + lt->lt_certfile, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + } + } + + /* fallback to PEM module (lt_certfile is filename) */ + if ( !ctx->tc_certificate ) { + if ( !pem_module && tlsm_init_pem_module() ) { + int pem_errcode = PORT_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: fallback to PEM impossible, module cannot be loaded - error %d:%s.\n", + pem_errcode, PR_ErrorToString( pem_errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + return -1; + } + + /* this sets ctx->tc_certificate to the correct value */ + if ( !tlsm_add_cert_from_file( ctx, lt->lt_certfile, PR_FALSE ) ) { + ctx->tc_using_pem = PR_TRUE; + } + } + + if ( ctx->tc_certificate ) { + Debug( LDAP_DEBUG_ANY, + "TLS: certificate '%s' successfully loaded from %s.\n", lt->lt_certfile, + ctx->tc_using_pem ? "PEM file" : "moznss database", 0); + } else { + return -1; + } + } + + if ( lt->lt_keyfile ) { + /* if using the PEM module, load the PEM file specified by lt_keyfile */ + /* otherwise, assume this is the pininfo for the key */ + if ( ctx->tc_using_pem ) { + int rc = tlsm_add_key_from_file( ctx, lt->lt_keyfile ); + if ( rc ) { + return rc; + } + } else { + if ( ctx->tc_pin_file ) + PL_strfree( ctx->tc_pin_file ); + ctx->tc_pin_file = PL_strdup( lt->lt_keyfile ); + } + } + + /* Set up callbacks for use by clients */ + if ( !ctx->tc_is_server ) { + if ( SSL_OptionSet( ctx->tc_model, SSL_NO_CACHE, PR_TRUE ) != SECSuccess ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: could not set nocache option for moznss - error %d:%s\n", + err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL ); + return -1; + } + + if ( SSL_BadCertHook( ctx->tc_model, tlsm_bad_cert_handler, ctx ) != SECSuccess ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: could not set bad cert handler for moznss - error %d:%s\n", + err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL ); + return -1; + } + + /* + since a cert has been specified, assume the client wants to do cert auth + */ + if ( ctx->tc_certificate ) { + if ( tlsm_clientauth_init( ctx ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: error: unable to set up client certificate authentication using '%s'\n", + tlsm_ctx_subject_name(ctx), 0, 0 ); + return -1; + } + } + } else { /* set up secure server */ + SSLKEAType certKEA; + SECStatus status; + + /* must have a certificate for the server to use */ + if ( !ctx->tc_certificate ) { + Debug( LDAP_DEBUG_ANY, + "TLS: error: no server certificate: must specify a certificate for the server to use\n", + 0, 0, 0 ); + return -1; + } + + if ( tlsm_find_and_verify_cert_key( ctx ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: error: unable to find and verify server's cert and key for certificate %s\n", + tlsm_ctx_subject_name(ctx), 0, 0 ); + return -1; + } + + /* configure the socket to be a secure server socket */ + certKEA = NSS_FindCertKEAType( ctx->tc_certificate ); + status = SSL_ConfigSecureServer( ctx->tc_model, ctx->tc_certificate, ctx->tc_private_key, certKEA ); + + if ( SECSuccess != status ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: unable to configure secure server using certificate '%s' - error %d:%s\n", + tlsm_ctx_subject_name(ctx), err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + } + + /* Callback for authenticating certificate */ + if ( SSL_AuthCertificateHook( ctx->tc_model, tlsm_auth_cert_handler, + ctx ) != SECSuccess ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: could not set auth cert handler for moznss - error %d:%s\n", + err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL ); + return -1; + } + + if ( SSL_HandshakeCallback( ctx->tc_model, tlsm_handshake_complete_cb, ctx ) ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: could not set handshake callback for moznss - error %d:%s\n", + err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL ); + return -1; + } + + tlsm_free_config( ctx->tc_config ); + ctx->tc_config = NULL; + + return 0; +} + +struct tls_data { + tlsm_session *session; + Sockbuf_IO_Desc *sbiod; + /* there seems to be no portable way to determine if the + sockbuf sd has been set to nonblocking mode - the + call to ber_pvt_socket_set_nonblock() takes place + before the tls socket is set up, so we cannot + intercept that call either. + On systems where fcntl is available, we can just + F_GETFL and test for O_NONBLOCK. On other systems, + we will just see if the IO op returns EAGAIN or EWOULDBLOCK, + and just set this flag */ + PRBool nonblock; + /* + * NSS tries hard to be backwards compatible with SSLv2 clients, or + * clients that send an SSLv2 client hello. This message is not + * tagged in any way, so NSS has no way to know if the incoming + * message is a valid SSLv2 client hello or just some bogus data + * (or cleartext LDAP). We store the first byte read from the + * client here. The most common case will be a client sending + * LDAP data instead of SSL encrypted LDAP data. This can happen, + * for example, if using ldapsearch -Z - if the starttls fails, + * the client will fallback to plain cleartext LDAP. So if we + * see that the firstbyte is a valid LDAP tag, we can be + * pretty sure this is happening. + */ + ber_tag_t firsttag; + /* + * NSS doesn't return SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, etc. + * when it is blocked, so we have to set a flag in the wrapped send + * and recv calls that tells us what operation NSS was last blocked + * on + */ +#define TLSM_READ 1 +#define TLSM_WRITE 2 + int io_flag; +}; + +static struct tls_data * +tlsm_get_pvt_tls_data( PRFileDesc *fd ) +{ + struct tls_data *p; + PRFileDesc *myfd; + + if ( !fd ) { + return NULL; + } + + myfd = PR_GetIdentitiesLayer( fd, tlsm_layer_id ); + + if ( !myfd ) { + return NULL; + } + + p = (struct tls_data *)myfd->secret; + + return p; +} + +static int +tlsm_is_non_ssl_message( PRFileDesc *fd, ber_tag_t *thebyte ) +{ + struct tls_data *p; + + if ( thebyte ) { + *thebyte = LBER_DEFAULT; + } + + p = tlsm_get_pvt_tls_data( fd ); + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + if ( p->firsttag == LBER_SEQUENCE ) { + if ( thebyte ) { + *thebyte = p->firsttag; + } + return 1; + } + + return 0; +} + +static tls_session * +tlsm_session_new ( tls_ctx * ctx, int is_server ) +{ + tlsm_ctx *c = (tlsm_ctx *)ctx; + tlsm_session *session; + PRFileDesc *fd; + PRStatus status; + int rc; + + c->tc_is_server = is_server; + LDAP_MUTEX_LOCK( &tlsm_init_mutex ); + status = PR_CallOnceWithArg( &c->tc_callonce, tlsm_deferred_ctx_init, c ); + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); + if ( PR_SUCCESS != status ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: could not initialize moznss security context - error %d:%s\n", + err, PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT), NULL ); + return NULL; + } + + fd = PR_CreateIOLayerStub( tlsm_layer_id, &tlsm_PR_methods ); + if ( !fd ) { + return NULL; + } + + session = SSL_ImportFD( c->tc_model, fd ); + if ( !session ) { + PR_DELETE( fd ); + return NULL; + } + + rc = SSL_ResetHandshake( session, is_server ); + if ( rc ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_TRACE, + "TLS: error: new session - reset handshake failure %d - error %d:%s\n", + rc, err, + err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); + PR_DELETE( fd ); + PR_Close( session ); + session = NULL; + } + + return (tls_session *)session; +} + +static int +tlsm_session_accept_or_connect( tls_session *session, int is_accept ) +{ + tlsm_session *s = (tlsm_session *)session; + int rc; + const char *op = is_accept ? "accept" : "connect"; + + if ( pem_module ) { + LDAP_MUTEX_LOCK( &tlsm_pem_mutex ); + } + rc = SSL_ForceHandshake( s ); + if ( pem_module ) { + LDAP_MUTEX_UNLOCK( &tlsm_pem_mutex ); + } + if ( rc ) { + PRErrorCode err = PR_GetError(); + rc = -1; + if ( err == PR_WOULD_BLOCK_ERROR ) { + ber_tag_t thetag = LBER_DEFAULT; + /* see if we are blocked because of a bogus packet */ + if ( tlsm_is_non_ssl_message( s, &thetag ) ) { /* see if we received a non-SSL message */ + Debug( LDAP_DEBUG_ANY, + "TLS: error: %s - error - received non-SSL message [0x%x]\n", + op, (unsigned int)thetag, 0 ); + /* reset error to something more descriptive */ + PR_SetError( SSL_ERROR_RX_MALFORMED_HELLO_REQUEST, EPROTO ); + } + } else { + Debug( LDAP_DEBUG_ANY, + "TLS: error: %s - force handshake failure: errno %d - moznss error %d\n", + op, errno, err ); + } + } + + return rc; +} +static int +tlsm_session_accept( tls_session *session ) +{ + return tlsm_session_accept_or_connect( session, 1 ); +} + +static int +tlsm_session_connect( LDAP *ld, tls_session *session ) +{ + return tlsm_session_accept_or_connect( session, 0 ); +} + +static int +tlsm_session_upflags( Sockbuf *sb, tls_session *session, int rc ) +{ + int prerror = PR_GetError(); + + if ( ( prerror == PR_PENDING_INTERRUPT_ERROR ) || ( prerror == PR_WOULD_BLOCK_ERROR ) ) { + tlsm_session *s = (tlsm_session *)session; + struct tls_data *p = tlsm_get_pvt_tls_data( s ); + + if ( p && ( p->io_flag == TLSM_READ ) ) { + sb->sb_trans_needs_read = 1; + return 1; + } else if ( p && ( p->io_flag == TLSM_WRITE ) ) { + sb->sb_trans_needs_write = 1; + return 1; + } + } + + return 0; +} + +static char * +tlsm_session_errmsg( tls_session *sess, int rc, char *buf, size_t len ) +{ + int i; + int prerror = PR_GetError(); + + i = PR_GetErrorTextLength(); + if ( i > len ) { + char *msg = LDAP_MALLOC( i+1 ); + PR_GetErrorText( msg ); + memcpy( buf, msg, len ); + LDAP_FREE( msg ); + } else if ( i ) { + PR_GetErrorText( buf ); + } else if ( prerror ) { + i = PR_snprintf( buf, len, "TLS error %d:%s", + prerror, PR_ErrorToString( prerror, PR_LANGUAGE_I_DEFAULT ) ); + } + + return ( i > 0 ) ? buf : NULL; +} + +static int +tlsm_session_my_dn( tls_session *session, struct berval *der_dn ) +{ + tlsm_session *s = (tlsm_session *)session; + CERTCertificate *cert; + + cert = SSL_LocalCertificate( s ); + if (!cert) return LDAP_INVALID_CREDENTIALS; + + der_dn->bv_val = (char *)cert->derSubject.data; + der_dn->bv_len = cert->derSubject.len; + CERT_DestroyCertificate( cert ); + return 0; +} + +static int +tlsm_session_peer_dn( tls_session *session, struct berval *der_dn ) +{ + tlsm_session *s = (tlsm_session *)session; + CERTCertificate *cert; + + cert = SSL_PeerCertificate( s ); + if (!cert) return LDAP_INVALID_CREDENTIALS; + + der_dn->bv_val = (char *)cert->derSubject.data; + der_dn->bv_len = cert->derSubject.len; + CERT_DestroyCertificate( cert ); + return 0; +} + +/* what kind of hostname were we given? */ +#define IS_DNS 0 +#define IS_IP4 1 +#define IS_IP6 2 + +static int +tlsm_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) +{ + tlsm_session *s = (tlsm_session *)session; + CERTCertificate *cert; + const char *name, *domain = NULL, *ptr; + int ret, ntype = IS_DNS, nlen, dlen; +#ifdef LDAP_PF_INET6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + SECItem altname; + SECStatus rv; + + if( ldap_int_hostname && + ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) + { + name = ldap_int_hostname; + } else { + name = name_in; + } + nlen = strlen( name ); + + cert = SSL_PeerCertificate( s ); + if (!cert) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get peer certificate.\n", + 0, 0, 0 ); + /* if this was a fatal condition, things would have + * aborted long before now. + */ + return LDAP_SUCCESS; + } + +#ifdef LDAP_PF_INET6 + if (inet_pton(AF_INET6, name, &addr)) { + ntype = IS_IP6; + } else +#endif + if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { + if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; + } + if (ntype == IS_DNS ) { + domain = strchr( name, '.' ); + if ( domain ) + dlen = nlen - ( domain - name ); + } + + ret = LDAP_LOCAL_ERROR; + + rv = CERT_FindCertExtension( cert, SEC_OID_X509_SUBJECT_ALT_NAME, + &altname ); + if ( rv == SECSuccess && altname.data ) { + PRArenaPool *arena; + CERTGeneralName *names, *cur; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( !arena ) { + ret = LDAP_NO_MEMORY; + goto fail; + } + + names = cur = CERT_DecodeAltNameExtension(arena, &altname); + if ( !cur ) + goto altfail; + + do { + char *host; + int hlen; + + /* ignore empty */ + if ( !cur->name.other.len ) continue; + + host = (char *)cur->name.other.data; + hlen = cur->name.other.len; + + if ( cur->type == certDNSName ) { + if ( ntype != IS_DNS ) continue; + + /* is this an exact match? */ + if ( nlen == hlen && !strncasecmp( name, host, nlen )) { + ret = LDAP_SUCCESS; + break; + } + + /* is this a wildcard match? */ + if ( domain && host[0] == '*' && host[1] == '.' && + dlen == hlen-1 && !strncasecmp( domain, host+1, dlen )) { + ret = LDAP_SUCCESS; + break; + } + } else if ( cur->type == certIPAddress ) { + if ( ntype == IS_DNS ) continue; + +#ifdef LDAP_PF_INET6 + if (ntype == IS_IP6 && hlen != sizeof(struct in6_addr)) { + continue; + } else +#endif + if (ntype == IS_IP4 && hlen != sizeof(struct in_addr)) { + continue; + } + if (!memcmp(host, &addr, hlen)) { + ret = LDAP_SUCCESS; + break; + } + } + } while (( cur = CERT_GetNextGeneralName( cur )) != names ); +altfail: + PORT_FreeArena( arena, PR_FALSE ); + SECITEM_FreeItem( &altname, PR_FALSE ); + } + /* no altnames matched, try the CN */ + if ( ret != LDAP_SUCCESS ) { + /* find the last CN */ + CERTRDN *rdn, **rdns; + CERTAVA *lastava = NULL; + char buf[2048]; + + buf[0] = '\0'; + rdns = cert->subject.rdns; + while ( rdns && ( rdn = *rdns++ )) { + CERTAVA *ava, **avas = rdn->avas; + while ( avas && ( ava = *avas++ )) { + if ( CERT_GetAVATag( ava ) == SEC_OID_AVA_COMMON_NAME ) + lastava = ava; + } + } + if ( lastava ) { + SECItem *av = CERT_DecodeAVAValue( &lastava->value ); + if ( av ) { + if ( av->len == nlen && !strncasecmp( name, (char *)av->data, nlen )) { + ret = LDAP_SUCCESS; + } else if ( av->data[0] == '*' && av->data[1] == '.' && + domain && dlen == av->len - 1 && !strncasecmp( domain, + (char *)(av->data+1), dlen )) { + ret = LDAP_SUCCESS; + } else { + int len = av->len; + if ( len >= sizeof(buf) ) + len = sizeof(buf)-1; + memcpy( buf, av->data, len ); + buf[len] = '\0'; + } + SECITEM_FreeItem( av, PR_TRUE ); + } + } + if ( ret != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " + "common name in certificate (%s).\n", + name, buf, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: hostname does not match CN in peer certificate")); + } + } + +fail: + CERT_DestroyCertificate( cert ); + return ret; +} + +static int +tlsm_session_strength( tls_session *session ) +{ + tlsm_session *s = (tlsm_session *)session; + int rc, keySize; + + rc = SSL_SecurityStatus( s, NULL, NULL, NULL, &keySize, + NULL, NULL ); + return rc ? 0 : keySize; +} + +/* + * TLS support for LBER Sockbufs + */ + +static PRStatus PR_CALLBACK +tlsm_PR_Close(PRFileDesc *fd) +{ + int rc = PR_SUCCESS; + + /* we don't need to actually close anything here, just + pop our io layer off the stack */ + fd->secret = NULL; /* must have been freed before calling PR_Close */ + if ( fd->lower ) { + fd = PR_PopIOLayer( fd, tlsm_layer_id ); + /* if we are not the last layer, pass the close along */ + if ( fd ) { + if ( fd->dtor ) { + fd->dtor( fd ); + } + rc = fd->methods->close( fd ); + } + } else { + /* we are the last layer - just call our dtor */ + fd->dtor(fd); + } + + return rc; +} + +static PRStatus PR_CALLBACK +tlsm_PR_Shutdown(PRFileDesc *fd, PRShutdownHow how) +{ + int rc = PR_SUCCESS; + + if ( fd->lower ) { + rc = PR_Shutdown( fd->lower, how ); + } + + return rc; +} + +static int PR_CALLBACK +tlsm_PR_Recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags, + PRIntervalTime timeout) +{ + struct tls_data *p; + int rc; + + if ( buf == NULL || len <= 0 ) return 0; + + p = tlsm_get_pvt_tls_data( fd ); + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + rc = LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); + if (rc <= 0) { + tlsm_map_error( errno ); + if ( errno == EAGAIN || errno == EWOULDBLOCK ) { + p->nonblock = PR_TRUE; /* fd is using non-blocking io */ + } else if ( errno ) { /* real error */ + Debug( LDAP_DEBUG_TRACE, + "TLS: error: tlsm_PR_Recv returned %d - error %d:%s\n", + rc, errno, STRERROR(errno) ); + } + } else if ( ( rc > 0 ) && ( len > 0 ) && ( p->firsttag == LBER_DEFAULT ) ) { + p->firsttag = (ber_tag_t)*((char *)buf); + } + p->io_flag = TLSM_READ; + + return rc; +} + +static int PR_CALLBACK +tlsm_PR_Send(PRFileDesc *fd, const void *buf, PRInt32 len, PRIntn flags, + PRIntervalTime timeout) +{ + struct tls_data *p; + int rc; + + if ( buf == NULL || len <= 0 ) return 0; + + p = tlsm_get_pvt_tls_data( fd ); + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + rc = LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); + if (rc <= 0) { + tlsm_map_error( errno ); + if ( errno == EAGAIN || errno == EWOULDBLOCK ) { + p->nonblock = PR_TRUE; + } else if ( errno ) { /* real error */ + Debug( LDAP_DEBUG_TRACE, + "TLS: error: tlsm_PR_Send returned %d - error %d:%s\n", + rc, errno, STRERROR(errno) ); + } + } + p->io_flag = TLSM_WRITE; + + return rc; +} + +static int PR_CALLBACK +tlsm_PR_Read(PRFileDesc *fd, void *buf, PRInt32 len) +{ + return tlsm_PR_Recv( fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); +} + +static int PR_CALLBACK +tlsm_PR_Write(PRFileDesc *fd, const void *buf, PRInt32 len) +{ + return tlsm_PR_Send( fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); +} + +static PRStatus PR_CALLBACK +tlsm_PR_GetPeerName(PRFileDesc *fd, PRNetAddr *addr) +{ + struct tls_data *p; + ber_socklen_t len; + + p = tlsm_get_pvt_tls_data( fd ); + + if ( p == NULL || p->sbiod == NULL ) { + return PR_FAILURE; + } + len = sizeof(PRNetAddr); + return getpeername( p->sbiod->sbiod_sb->sb_fd, (struct sockaddr *)addr, &len ); +} + +static PRStatus PR_CALLBACK +tlsm_PR_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data) +{ + struct tls_data *p; + p = tlsm_get_pvt_tls_data( fd ); + + if ( p == NULL || data == NULL ) { + return PR_FAILURE; + } + + /* only the nonblocking option is supported at this time + MozNSS SSL code needs it */ + if ( data->option != PR_SockOpt_Nonblocking ) { + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return PR_FAILURE; + } +#ifdef HAVE_FCNTL + int flags = fcntl( p->sbiod->sbiod_sb->sb_fd, F_GETFL ); + data->value.non_blocking = (flags & O_NONBLOCK) ? PR_TRUE : PR_FALSE; +#else /* punt :P */ + data->value.non_blocking = p->nonblock; +#endif + return PR_SUCCESS; +} + +static PRStatus PR_CALLBACK +tlsm_PR_prs_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return PR_FAILURE; +} + +static PRFileDesc * PR_CALLBACK +tlsm_PR_pfd_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return NULL; +} + +static PRInt16 PR_CALLBACK +tlsm_PR_i16_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return SECFailure; +} + +static PRInt32 PR_CALLBACK +tlsm_PR_i32_unimp() +{ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return SECFailure; +} + +static PRInt64 PR_CALLBACK +tlsm_PR_i64_unimp() +{ + PRInt64 res; + + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + LL_I2L(res, -1L); + return res; +} + +static const PRIOMethods tlsm_PR_methods = { + PR_DESC_LAYERED, + tlsm_PR_Close, /* close */ + tlsm_PR_Read, /* read */ + tlsm_PR_Write, /* write */ + tlsm_PR_i32_unimp, /* available */ + tlsm_PR_i64_unimp, /* available64 */ + tlsm_PR_prs_unimp, /* fsync */ + tlsm_PR_i32_unimp, /* seek */ + tlsm_PR_i64_unimp, /* seek64 */ + tlsm_PR_prs_unimp, /* fileInfo */ + tlsm_PR_prs_unimp, /* fileInfo64 */ + tlsm_PR_i32_unimp, /* writev */ + tlsm_PR_prs_unimp, /* connect */ + tlsm_PR_pfd_unimp, /* accept */ + tlsm_PR_prs_unimp, /* bind */ + tlsm_PR_prs_unimp, /* listen */ + (PRShutdownFN)tlsm_PR_Shutdown, /* shutdown */ + tlsm_PR_Recv, /* recv */ + tlsm_PR_Send, /* send */ + tlsm_PR_i32_unimp, /* recvfrom */ + tlsm_PR_i32_unimp, /* sendto */ + (PRPollFN)tlsm_PR_i16_unimp, /* poll */ + tlsm_PR_i32_unimp, /* acceptread */ + tlsm_PR_i32_unimp, /* transmitfile */ + tlsm_PR_prs_unimp, /* getsockname */ + tlsm_PR_GetPeerName, /* getpeername */ + tlsm_PR_i32_unimp, /* getsockopt OBSOLETE */ + tlsm_PR_i32_unimp, /* setsockopt OBSOLETE */ + tlsm_PR_GetSocketOption, /* getsocketoption */ + tlsm_PR_i32_unimp, /* setsocketoption */ + tlsm_PR_i32_unimp, /* Send a (partial) file with header/trailer*/ + (PRConnectcontinueFN)tlsm_PR_prs_unimp, /* connectcontinue */ + tlsm_PR_i32_unimp, /* reserved for future use */ + tlsm_PR_i32_unimp, /* reserved for future use */ + tlsm_PR_i32_unimp, /* reserved for future use */ + tlsm_PR_i32_unimp /* reserved for future use */ +}; + +/* + * Initialize TLS subsystem. Should be called only once. + * See tlsm_deferred_init for the bulk of the init process + */ +static int +tlsm_init( void ) +{ + char *nofork = PR_GetEnv( "NSS_STRICT_NOFORK" ); + + PR_Init(0, 0, 0); + + tlsm_layer_id = PR_GetUniqueIdentity( "OpenLDAP" ); + + /* + * There are some applications that acquire a crypto context in the parent process + * and expect that crypto context to work after a fork(). This does not work + * with NSS using strict PKCS11 compliance mode. We set this environment + * variable here to tell the software encryption module/token to allow crypto + * contexts to persist across a fork(). However, if you are using some other + * module or encryption device that supports and expects full PKCS11 semantics, + * the only recourse is to rewrite the application with atfork() handlers to save + * the crypto context in the parent and restore (and SECMOD_RestartModules) the + * context in the child. + */ + if ( !nofork ) { + /* will leak one time */ + char *noforkenvvar = PL_strdup( "NSS_STRICT_NOFORK=DISABLED" ); + PR_SetEnv( noforkenvvar ); + } + + return 0; +} + +static int +tlsm_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct tls_data *p; + tlsm_session *session = arg; + PRFileDesc *fd; + + assert( sbiod != NULL ); + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) { + return -1; + } + + fd = PR_GetIdentitiesLayer( session, tlsm_layer_id ); + if ( !fd ) { + LBER_FREE( p ); + return -1; + } + + fd->secret = (PRFilePrivate *)p; + p->session = session; + p->sbiod = sbiod; + p->firsttag = LBER_DEFAULT; + sbiod->sbiod_pvt = p; + return 0; +} + +static int +tlsm_sb_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + PR_Close( p->session ); + LBER_FREE( sbiod->sbiod_pvt ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static int +tlsm_sb_close( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + PR_Shutdown( p->session, PR_SHUTDOWN_BOTH ); + return 0; +} + +static int +tlsm_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_GET_SSL ) { + *((tlsm_session **)arg) = p->session; + return 1; + + } else if ( opt == LBER_SB_OPT_DATA_READY ) { + if ( p && ( SSL_DataPending( p->session ) > 0 ) ) { + return 1; + } + + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +static ber_slen_t +tlsm_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = PR_Recv( p->session, buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); + if ( ret < 0 ) { + err = PR_GetError(); + if ( err == PR_PENDING_INTERRUPT_ERROR || err == PR_WOULD_BLOCK_ERROR ) { + sbiod->sbiod_sb->sb_trans_needs_read = 1; + sock_errset(EWOULDBLOCK); + } + } else { + sbiod->sbiod_sb->sb_trans_needs_read = 0; + } + return ret; +} + +static ber_slen_t +tlsm_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = PR_Send( p->session, (char *)buf, len, 0, PR_INTERVAL_NO_TIMEOUT ); + if ( ret < 0 ) { + err = PR_GetError(); + if ( err == PR_PENDING_INTERRUPT_ERROR || err == PR_WOULD_BLOCK_ERROR ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + ret = 0; + } + } else { + sbiod->sbiod_sb->sb_trans_needs_write = 0; + } + return ret; +} + +static Sockbuf_IO tlsm_sbio = +{ + tlsm_sb_setup, /* sbi_setup */ + tlsm_sb_remove, /* sbi_remove */ + tlsm_sb_ctrl, /* sbi_ctrl */ + tlsm_sb_read, /* sbi_read */ + tlsm_sb_write, /* sbi_write */ + tlsm_sb_close /* sbi_close */ +}; + +tls_impl ldap_int_tls_impl = { + "MozNSS", + + tlsm_init, + tlsm_destroy, + + tlsm_ctx_new, + tlsm_ctx_ref, + tlsm_ctx_free, + tlsm_ctx_init, + + tlsm_session_new, + tlsm_session_connect, + tlsm_session_accept, + tlsm_session_upflags, + tlsm_session_errmsg, + tlsm_session_my_dn, + tlsm_session_peer_dn, + tlsm_session_chkhost, + tlsm_session_strength, + + &tlsm_sbio, + +#ifdef LDAP_R_COMPILE + tlsm_thr_init, +#else + NULL, +#endif + + 0 +}; + +#endif /* HAVE_MOZNSS */ +/* + emacs settings + Local Variables: + indent-tabs-mode: t + tab-width: 4 + End: +*/ diff --git a/libraries/libldap/tls_o.c b/libraries/libldap/tls_o.c new file mode 100644 index 0000000..b0277df --- /dev/null +++ b/libraries/libldap/tls_o.c @@ -0,0 +1,1291 @@ +/* tls_o.c - Handle tls/ssl using OpenSSL */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: Rewritten by Howard Chu + */ + +#include "portable.h" + +#ifdef HAVE_OPENSSL + +#include "ldap_config.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/time.h> +#include <ac/unistd.h> +#include <ac/param.h> +#include <ac/dirent.h> + +#include "ldap-int.h" +#include "ldap-tls.h" + +#ifdef HAVE_OPENSSL_SSL_H +#include <openssl/ssl.h> +#include <openssl/x509v3.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/safestack.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/dh.h> +#elif defined( HAVE_SSL_H ) +#include <ssl.h> +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 +#define ASN1_STRING_data(x) ASN1_STRING_get0_data(x) +#endif + +typedef SSL_CTX tlso_ctx; +typedef SSL tlso_session; + +static BIO_METHOD * tlso_bio_method = NULL; +static BIO_METHOD * tlso_bio_setup( void ); + +static int tlso_opt_trace = 1; + +static void tlso_report_error( void ); + +static void tlso_info_cb( const SSL *ssl, int where, int ret ); +static int tlso_verify_cb( int ok, X509_STORE_CTX *ctx ); +static int tlso_verify_ok( int ok, X509_STORE_CTX *ctx ); +static int tlso_seed_PRNG( const char *randfile ); +#if OPENSSL_VERSION_NUMBER < 0x10100000 +/* + * OpenSSL 1.1 API and later has new locking code +*/ +static RSA * tlso_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ); + +#ifdef LDAP_R_COMPILE +/* + * provide mutexes for the OpenSSL library. + */ +static ldap_pvt_thread_mutex_t tlso_mutexes[CRYPTO_NUM_LOCKS]; + +static void tlso_locking_cb( int mode, int type, const char *file, int line ) +{ + if ( mode & CRYPTO_LOCK ) { + ldap_pvt_thread_mutex_lock( &tlso_mutexes[type] ); + } else { + ldap_pvt_thread_mutex_unlock( &tlso_mutexes[type] ); + } +} + +static unsigned long tlso_thread_self( void ) +{ + /* FIXME: CRYPTO_set_id_callback only works when ldap_pvt_thread_t + * is an integral type that fits in an unsigned long + */ + + /* force an error if the ldap_pvt_thread_t type is too large */ + enum { ok = sizeof( ldap_pvt_thread_t ) <= sizeof( unsigned long ) }; + typedef struct { int dummy: ok ? 1 : -1; } Check[ok ? 1 : -1]; + + return (unsigned long) ldap_pvt_thread_self(); +} + +static void tlso_thr_init( void ) +{ + int i; + + for( i=0; i< CRYPTO_NUM_LOCKS ; i++ ) { + ldap_pvt_thread_mutex_init( &tlso_mutexes[i] ); + } + CRYPTO_set_locking_callback( tlso_locking_cb ); + CRYPTO_set_id_callback( tlso_thread_self ); +} +#endif /* LDAP_R_COMPILE */ +#else +#ifdef LDAP_R_COMPILE +static void tlso_thr_init( void ) {} +#endif +#endif /* OpenSSL 1.1 */ + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +/* + * OpenSSL 1.1 API and later makes the BIO method concrete types internal. + */ + +static BIO_METHOD * +BIO_meth_new( int type, const char *name ) +{ + BIO_METHOD *method = LDAP_MALLOC( sizeof(BIO_METHOD) ); + memset( method, 0, sizeof(BIO_METHOD) ); + + method->type = type; + method->name = name; + + return method; +} + +static void +BIO_meth_free( BIO_METHOD *meth ) +{ + if ( meth == NULL ) { + return; + } + + LDAP_FREE( meth ); +} + +#define BIO_meth_set_write(m, f) (m)->bwrite = (f) +#define BIO_meth_set_read(m, f) (m)->bread = (f) +#define BIO_meth_set_puts(m, f) (m)->bputs = (f) +#define BIO_meth_set_gets(m, f) (m)->bgets = (f) +#define BIO_meth_set_ctrl(m, f) (m)->ctrl = (f) +#define BIO_meth_set_create(m, f) (m)->create = (f) +#define BIO_meth_set_destroy(m, f) (m)->destroy = (f) + +#endif /* OpenSSL 1.1 */ + +static STACK_OF(X509_NAME) * +tlso_ca_list( char * bundle, char * dir ) +{ + STACK_OF(X509_NAME) *ca_list = NULL; + + if ( bundle ) { + ca_list = SSL_load_client_CA_file( bundle ); + } +#if defined(HAVE_DIRENT_H) || defined(dirent) + if ( dir ) { + int freeit = 0; + + if ( !ca_list ) { + ca_list = sk_X509_NAME_new_null(); + freeit = 1; + } + if ( !SSL_add_dir_cert_subjects_to_stack( ca_list, dir ) && + freeit ) { + sk_X509_NAME_free( ca_list ); + ca_list = NULL; + } + } +#endif + return ca_list; +} + +/* + * Initialize TLS subsystem. Should be called only once. + */ +static int +tlso_init( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); +#ifdef HAVE_EBCDIC + { + char *file = LDAP_STRDUP( lo->ldo_tls_randfile ); + if ( file ) __atoe( file ); + (void) tlso_seed_PRNG( file ); + LDAP_FREE( file ); + } +#else + (void) tlso_seed_PRNG( lo->ldo_tls_randfile ); +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_digests(); +#else + OPENSSL_init_ssl(0, NULL); +#endif + + /* FIXME: mod_ssl does this */ + X509V3_add_standard_extensions(); + + tlso_bio_method = tlso_bio_setup(); + + return 0; +} + +/* + * Tear down the TLS subsystem. Should only be called once. + */ +static void +tlso_destroy( void ) +{ + struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); + + BIO_meth_free( tlso_bio_method ); + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + EVP_cleanup(); +#if OPENSSL_VERSION_NUMBER < 0x10000000 + ERR_remove_state(0); +#else + ERR_remove_thread_state(NULL); +#endif + ERR_free_strings(); +#endif + + if ( lo->ldo_tls_randfile ) { + LDAP_FREE( lo->ldo_tls_randfile ); + lo->ldo_tls_randfile = NULL; + } +} + +static tls_ctx * +tlso_ctx_new( struct ldapoptions *lo ) +{ + return (tls_ctx *) SSL_CTX_new( SSLv23_method() ); +} + +static void +tlso_ctx_ref( tls_ctx *ctx ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; +#if OPENSSL_VERSION_NUMBER < 0x10100000 +#define SSL_CTX_up_ref(ctx) CRYPTO_add( &(ctx->references), 1, CRYPTO_LOCK_SSL_CTX ) +#endif + SSL_CTX_up_ref( c ); +} + +static void +tlso_ctx_free ( tls_ctx *ctx ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; + SSL_CTX_free( c ); +} + +/* + * initialize a new TLS context + */ +static int +tlso_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) +{ + tlso_ctx *ctx = (tlso_ctx *)lo->ldo_tls_ctx; + int i; + + if ( is_server ) { + SSL_CTX_set_session_id_context( ctx, + (const unsigned char *) "OpenLDAP", sizeof("OpenLDAP")-1 ); + } + +#ifdef SSL_OP_NO_TLSv1 +#ifdef SSL_OP_NO_TLSv1_1 +#ifdef SSL_OP_NO_TLSv1_2 + if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_TLS1_2) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | + SSL_OP_NO_TLSv1_2 ); + else +#endif + if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_TLS1_1) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 ); + else +#endif + if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_TLS1_0) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1); + else +#endif + if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL3 ) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 ); + else if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL2 ) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 ); + + if ( lo->ldo_tls_ciphersuite && + !SSL_CTX_set_cipher_list( ctx, lt->lt_ciphersuite ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list %s.\n", + lo->ldo_tls_ciphersuite, 0, 0 ); + tlso_report_error(); + return -1; + } + + if ( lo->ldo_tls_cacertfile == NULL && lo->ldo_tls_cacertdir == NULL ) { + if ( !SSL_CTX_set_default_verify_paths( ctx ) ) { + Debug( LDAP_DEBUG_ANY, "TLS: " + "could not use default certificate paths", 0, 0, 0 ); + tlso_report_error(); + return -1; + } + } else { + if ( !SSL_CTX_load_verify_locations( ctx, + lt->lt_cacertfile, lt->lt_cacertdir ) ) + { + Debug( LDAP_DEBUG_ANY, "TLS: " + "could not load verify locations (file:`%s',dir:`%s').\n", + lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", + lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", + 0 ); + tlso_report_error(); + return -1; + } + + if ( is_server ) { + STACK_OF(X509_NAME) *calist; + /* List of CA names to send to a client */ + calist = tlso_ca_list( lt->lt_cacertfile, lt->lt_cacertdir ); + if ( !calist ) { + Debug( LDAP_DEBUG_ANY, "TLS: " + "could not load client CA list (file:`%s',dir:`%s').\n", + lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", + lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", + 0 ); + tlso_report_error(); + return -1; + } + + SSL_CTX_set_client_CA_list( ctx, calist ); + } + } + + if ( lo->ldo_tls_certfile && + !SSL_CTX_use_certificate_file( ctx, + lt->lt_certfile, SSL_FILETYPE_PEM ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use certificate `%s'.\n", + lo->ldo_tls_certfile,0,0); + tlso_report_error(); + return -1; + } + + /* Key validity is checked automatically if cert has already been set */ + if ( lo->ldo_tls_keyfile && + !SSL_CTX_use_PrivateKey_file( ctx, + lt->lt_keyfile, SSL_FILETYPE_PEM ) ) + { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use key file `%s'.\n", + lo->ldo_tls_keyfile,0,0); + tlso_report_error(); + return -1; + } + + if ( lo->ldo_tls_dhfile ) { + DH *dh = NULL; + BIO *bio; + SSL_CTX_set_options( ctx, SSL_OP_SINGLE_DH_USE ); + + if (( bio=BIO_new_file( lt->lt_dhfile,"r" )) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not use DH parameters file `%s'.\n", + lo->ldo_tls_dhfile,0,0); + tlso_report_error(); + return -1; + } + if (!( dh=PEM_read_bio_DHparams( bio, NULL, NULL, NULL ))) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not read DH parameters file `%s'.\n", + lo->ldo_tls_dhfile,0,0); + tlso_report_error(); + BIO_free( bio ); + return -1; + } + BIO_free( bio ); + SSL_CTX_set_tmp_dh( ctx, dh ); + } + + if ( tlso_opt_trace ) { + SSL_CTX_set_info_callback( ctx, tlso_info_cb ); + } + + i = SSL_VERIFY_NONE; + if ( lo->ldo_tls_require_cert ) { + i = SSL_VERIFY_PEER; + if ( lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_DEMAND || + lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_HARD ) { + i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } + } + + SSL_CTX_set_verify( ctx, i, + lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_ALLOW ? + tlso_verify_ok : tlso_verify_cb ); +#if OPENSSL_VERSION_NUMBER < 0x10100000 + SSL_CTX_set_tmp_rsa_callback( ctx, tlso_tmp_rsa_cb ); +#endif +#ifdef HAVE_OPENSSL_CRL + if ( lo->ldo_tls_crlcheck ) { + X509_STORE *x509_s = SSL_CTX_get_cert_store( ctx ); + if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_PEER ) { + X509_STORE_set_flags( x509_s, X509_V_FLAG_CRL_CHECK ); + } else if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_ALL ) { + X509_STORE_set_flags( x509_s, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL ); + } + } +#endif + return 0; +} + +static tls_session * +tlso_session_new( tls_ctx *ctx, int is_server ) +{ + tlso_ctx *c = (tlso_ctx *)ctx; + return (tls_session *)SSL_new( c ); +} + +static int +tlso_session_connect( LDAP *ld, tls_session *sess ) +{ + tlso_session *s = (tlso_session *)sess; + + /* Caller expects 0 = success, OpenSSL returns 1 = success */ + return SSL_connect( s ) - 1; +} + +static int +tlso_session_accept( tls_session *sess ) +{ + tlso_session *s = (tlso_session *)sess; + + /* Caller expects 0 = success, OpenSSL returns 1 = success */ + return SSL_accept( s ) - 1; +} + +static int +tlso_session_upflags( Sockbuf *sb, tls_session *sess, int rc ) +{ + tlso_session *s = (tlso_session *)sess; + + /* 1 was subtracted above, offset it back now */ + rc = SSL_get_error(s, rc+1); + if (rc == SSL_ERROR_WANT_READ) { + sb->sb_trans_needs_read = 1; + return 1; + + } else if (rc == SSL_ERROR_WANT_WRITE) { + sb->sb_trans_needs_write = 1; + return 1; + + } else if (rc == SSL_ERROR_WANT_CONNECT) { + return 1; + } + return 0; +} + +static char * +tlso_session_errmsg( tls_session *sess, int rc, char *buf, size_t len ) +{ + char err[256] = ""; + const char *certerr=NULL; + tlso_session *s = (tlso_session *)sess; + + rc = ERR_peek_error(); + if ( rc ) { + ERR_error_string_n( rc, err, sizeof(err) ); + if ( ( ERR_GET_LIB(rc) == ERR_LIB_SSL ) && + ( ERR_GET_REASON(rc) == SSL_R_CERTIFICATE_VERIFY_FAILED ) ) { + int certrc = SSL_get_verify_result(s); + certerr = (char *)X509_verify_cert_error_string(certrc); + } + snprintf(buf, len, "%s%s%s%s", err, certerr ? " (" :"", + certerr ? certerr : "", certerr ? ")" : "" ); + return buf; + } + return NULL; +} + +static int +tlso_session_my_dn( tls_session *sess, struct berval *der_dn ) +{ + tlso_session *s = (tlso_session *)sess; + X509 *x; + X509_NAME *xn; + + x = SSL_get_certificate( s ); + + if (!x) return LDAP_INVALID_CREDENTIALS; + + xn = X509_get_subject_name(x); +#if OPENSSL_VERSION_NUMBER < 0x10100000 + der_dn->bv_len = i2d_X509_NAME( xn, NULL ); + der_dn->bv_val = xn->bytes->data; +#else + { + size_t len = 0; + der_dn->bv_val = NULL; + X509_NAME_get0_der( xn, (const unsigned char **)&der_dn->bv_val, &len ); + der_dn->bv_len = len; + } +#endif + /* Don't X509_free, the session is still using it */ + return 0; +} + +static X509 * +tlso_get_cert( SSL *s ) +{ + /* If peer cert was bad, treat as if no cert was given */ + if (SSL_get_verify_result(s)) { + return NULL; + } + return SSL_get_peer_certificate(s); +} + +static int +tlso_session_peer_dn( tls_session *sess, struct berval *der_dn ) +{ + tlso_session *s = (tlso_session *)sess; + X509 *x = tlso_get_cert( s ); + X509_NAME *xn; + + if ( !x ) + return LDAP_INVALID_CREDENTIALS; + + xn = X509_get_subject_name(x); +#if OPENSSL_VERSION_NUMBER < 0x10100000 + der_dn->bv_len = i2d_X509_NAME( xn, NULL ); + der_dn->bv_val = xn->bytes->data; +#else + { + size_t len = 0; + der_dn->bv_val = NULL; + X509_NAME_get0_der( xn, (const unsigned char **)&der_dn->bv_val, &len ); + der_dn->bv_len = len; + } +#endif + X509_free(x); + return 0; +} + +/* what kind of hostname were we given? */ +#define IS_DNS 0 +#define IS_IP4 1 +#define IS_IP6 2 + +static int +tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in ) +{ + tlso_session *s = (tlso_session *)sess; + int i, ret = LDAP_LOCAL_ERROR; + X509 *x; + const char *name; + char *ptr; + int ntype = IS_DNS, nlen; +#ifdef LDAP_PF_INET6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + + if( ldap_int_hostname && + ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) + { + name = ldap_int_hostname; + } else { + name = name_in; + } + nlen = strlen(name); + + x = tlso_get_cert(s); + if (!x) { + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get peer certificate.\n", + 0, 0, 0 ); + /* If this was a fatal condition, things would have + * aborted long before now. + */ + return LDAP_SUCCESS; + } + +#ifdef LDAP_PF_INET6 + if (inet_pton(AF_INET6, name, &addr)) { + ntype = IS_IP6; + } else +#endif + if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) { + if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4; + } + + i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); + if (i >= 0) { + X509_EXTENSION *ex; + STACK_OF(GENERAL_NAME) *alt; + + ex = X509_get_ext(x, i); + alt = X509V3_EXT_d2i(ex); + if (alt) { + int n, len2 = 0; + char *domain = NULL; + GENERAL_NAME *gn; + + if (ntype == IS_DNS) { + domain = strchr(name, '.'); + if (domain) { + len2 = nlen - (domain-name); + } + } + n = sk_GENERAL_NAME_num(alt); + for (i=0; i<n; i++) { + char *sn; + int sl; + gn = sk_GENERAL_NAME_value(alt, i); + if (gn->type == GEN_DNS) { + if (ntype != IS_DNS) continue; + + sn = (char *) ASN1_STRING_data(gn->d.ia5); + sl = ASN1_STRING_length(gn->d.ia5); + + /* ignore empty */ + if (sl == 0) continue; + + /* Is this an exact match? */ + if ((nlen == sl) && !strncasecmp(name, sn, nlen)) { + break; + } + + /* Is this a wildcard match? */ + if (domain && (sn[0] == '*') && (sn[1] == '.') && + (len2 == sl-1) && !strncasecmp(domain, &sn[1], len2)) + { + break; + } + + } else if (gn->type == GEN_IPADD) { + if (ntype == IS_DNS) continue; + + sn = (char *) ASN1_STRING_data(gn->d.ia5); + sl = ASN1_STRING_length(gn->d.ia5); + +#ifdef LDAP_PF_INET6 + if (ntype == IS_IP6 && sl != sizeof(struct in6_addr)) { + continue; + } else +#endif + if (ntype == IS_IP4 && sl != sizeof(struct in_addr)) { + continue; + } + if (!memcmp(sn, &addr, sl)) { + break; + } + } + } + + GENERAL_NAMES_free(alt); + if (i < n) { /* Found a match */ + ret = LDAP_SUCCESS; + } + } + } + + if (ret != LDAP_SUCCESS) { + X509_NAME *xn; + X509_NAME_ENTRY *ne; + ASN1_OBJECT *obj; + ASN1_STRING *cn = NULL; + int navas; + + /* find the last CN */ + obj = OBJ_nid2obj( NID_commonName ); + if ( !obj ) goto no_cn; /* should never happen */ + + xn = X509_get_subject_name(x); + navas = X509_NAME_entry_count( xn ); + for ( i=navas-1; i>=0; i-- ) { + ne = X509_NAME_get_entry( xn, i ); + if ( !OBJ_cmp( X509_NAME_ENTRY_get_object(ne), obj )) { + cn = X509_NAME_ENTRY_get_data( ne ); + break; + } + } + + if( !cn ) + { +no_cn: + Debug( LDAP_DEBUG_ANY, + "TLS: unable to get common name from peer certificate.\n", + 0, 0, 0 ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: unable to get CN from peer certificate")); + + } else if ( cn->length == nlen && + strncasecmp( name, (char *) cn->data, nlen ) == 0 ) { + ret = LDAP_SUCCESS; + + } else if (( cn->data[0] == '*' ) && ( cn->data[1] == '.' )) { + char *domain = strchr(name, '.'); + if( domain ) { + int dlen; + + dlen = nlen - (domain-name); + + /* Is this a wildcard match? */ + if ((dlen == cn->length-1) && + !strncasecmp(domain, (char *) &cn->data[1], dlen)) { + ret = LDAP_SUCCESS; + } + } + } + + if( ret == LDAP_LOCAL_ERROR ) { + Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " + "common name in certificate (%.*s).\n", + name, cn->length, cn->data ); + ret = LDAP_CONNECT_ERROR; + if ( ld->ld_error ) { + LDAP_FREE( ld->ld_error ); + } + ld->ld_error = LDAP_STRDUP( + _("TLS: hostname does not match CN in peer certificate")); + } + } + X509_free(x); + return ret; +} + +static int +tlso_session_strength( tls_session *sess ) +{ + tlso_session *s = (tlso_session *)sess; + + return SSL_CIPHER_get_bits(SSL_get_current_cipher(s), NULL); +} + +/* + * TLS support for LBER Sockbufs + */ + +struct tls_data { + tlso_session *session; + Sockbuf_IO_Desc *sbiod; +}; + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +#define BIO_set_init(b, x) b->init = x +#define BIO_set_data(b, x) b->ptr = x +#define BIO_clear_flags(b, x) b->flags &= ~(x) +#define BIO_get_data(b) b->ptr +#endif +static int +tlso_bio_create( BIO *b ) { + BIO_set_init( b, 1 ); + BIO_set_data( b, NULL ); + BIO_clear_flags( b, ~0 ); + return 1; +} + +static int +tlso_bio_destroy( BIO *b ) +{ + if ( b == NULL ) return 0; + + BIO_set_data( b, NULL ); /* sb_tls_remove() will free it */ + BIO_set_init( b, 0 ); + BIO_clear_flags( b, ~0 ); + return 1; +} + +static int +tlso_bio_read( BIO *b, char *buf, int len ) +{ + struct tls_data *p; + int ret; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)BIO_get_data(b); + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + ret = LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); + + BIO_clear_retry_flags( b ); + if ( ret < 0 ) { + int err = sock_errno(); + if ( err == EAGAIN || err == EWOULDBLOCK ) { + BIO_set_retry_read( b ); + } + } + + return ret; +} + +static int +tlso_bio_write( BIO *b, const char *buf, int len ) +{ + struct tls_data *p; + int ret; + + if ( buf == NULL || len <= 0 ) return 0; + + p = (struct tls_data *)BIO_get_data(b); + + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } + + ret = LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); + + BIO_clear_retry_flags( b ); + if ( ret < 0 ) { + int err = sock_errno(); + if ( err == EAGAIN || err == EWOULDBLOCK ) { + BIO_set_retry_write( b ); + } + } + + return ret; +} + +static long +tlso_bio_ctrl( BIO *b, int cmd, long num, void *ptr ) +{ + if ( cmd == BIO_CTRL_FLUSH ) { + /* The OpenSSL library needs this */ + return 1; + } + return 0; +} + +static int +tlso_bio_gets( BIO *b, char *buf, int len ) +{ + return -1; +} + +static int +tlso_bio_puts( BIO *b, const char *str ) +{ + return tlso_bio_write( b, str, strlen( str ) ); +} + +static BIO_METHOD * +tlso_bio_setup( void ) +{ + /* it's a source/sink BIO */ + BIO_METHOD * method = BIO_meth_new( 100 | 0x400, "sockbuf glue" ); + BIO_meth_set_write( method, tlso_bio_write ); + BIO_meth_set_read( method, tlso_bio_read ); + BIO_meth_set_puts( method, tlso_bio_puts ); + BIO_meth_set_gets( method, tlso_bio_gets ); + BIO_meth_set_ctrl( method, tlso_bio_ctrl ); + BIO_meth_set_create( method, tlso_bio_create ); + BIO_meth_set_destroy( method, tlso_bio_destroy ); + + return method; +} + +static int +tlso_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) +{ + struct tls_data *p; + BIO *bio; + + assert( sbiod != NULL ); + + p = LBER_MALLOC( sizeof( *p ) ); + if ( p == NULL ) { + return -1; + } + + p->session = arg; + p->sbiod = sbiod; + bio = BIO_new( tlso_bio_method ); + BIO_set_data( bio, p ); + SSL_set_bio( p->session, bio, bio ); + sbiod->sbiod_pvt = p; + return 0; +} + +static int +tlso_sb_remove( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + SSL_free( p->session ); + LBER_FREE( sbiod->sbiod_pvt ); + sbiod->sbiod_pvt = NULL; + return 0; +} + +static int +tlso_sb_close( Sockbuf_IO_Desc *sbiod ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + SSL_shutdown( p->session ); + return 0; +} + +static int +tlso_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) +{ + struct tls_data *p; + + assert( sbiod != NULL ); + assert( sbiod->sbiod_pvt != NULL ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + if ( opt == LBER_SB_OPT_GET_SSL ) { + *((tlso_session **)arg) = p->session; + return 1; + + } else if ( opt == LBER_SB_OPT_DATA_READY ) { + if( SSL_pending( p->session ) > 0 ) { + return 1; + } + } + + return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); +} + +static ber_slen_t +tlso_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = SSL_read( p->session, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->session, ret ); + if (err == SSL_ERROR_WANT_READ ) { + sbiod->sbiod_sb->sb_trans_needs_read = 1; + sock_errset(EWOULDBLOCK); + } + else + sbiod->sbiod_sb->sb_trans_needs_read = 0; + return ret; +} + +static ber_slen_t +tlso_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct tls_data *p; + ber_slen_t ret; + int err; + + assert( sbiod != NULL ); + assert( SOCKBUF_VALID( sbiod->sbiod_sb ) ); + + p = (struct tls_data *)sbiod->sbiod_pvt; + + ret = SSL_write( p->session, (char *)buf, len ); +#ifdef HAVE_WINSOCK + errno = WSAGetLastError(); +#endif + err = SSL_get_error( p->session, ret ); + if (err == SSL_ERROR_WANT_WRITE ) { + sbiod->sbiod_sb->sb_trans_needs_write = 1; + sock_errset(EWOULDBLOCK); + + } else { + sbiod->sbiod_sb->sb_trans_needs_write = 0; + } + return ret; +} + +static Sockbuf_IO tlso_sbio = +{ + tlso_sb_setup, /* sbi_setup */ + tlso_sb_remove, /* sbi_remove */ + tlso_sb_ctrl, /* sbi_ctrl */ + tlso_sb_read, /* sbi_read */ + tlso_sb_write, /* sbi_write */ + tlso_sb_close /* sbi_close */ +}; + +/* Derived from openssl/apps/s_cb.c */ +static void +tlso_info_cb( const SSL *ssl, int where, int ret ) +{ + int w; + char *op; + char *state = (char *) SSL_state_string_long( (SSL *)ssl ); + + w = where & ~SSL_ST_MASK; + if ( w & SSL_ST_CONNECT ) { + op = "SSL_connect"; + } else if ( w & SSL_ST_ACCEPT ) { + op = "SSL_accept"; + } else { + op = "undefined"; + } + +#ifdef HAVE_EBCDIC + if ( state ) { + state = LDAP_STRDUP( state ); + __etoa( state ); + } +#endif + if ( where & SSL_CB_LOOP ) { + Debug( LDAP_DEBUG_TRACE, + "TLS trace: %s:%s\n", + op, state, 0 ); + + } else if ( where & SSL_CB_ALERT ) { + char *atype = (char *) SSL_alert_type_string_long( ret ); + char *adesc = (char *) SSL_alert_desc_string_long( ret ); + op = ( where & SSL_CB_READ ) ? "read" : "write"; +#ifdef HAVE_EBCDIC + if ( atype ) { + atype = LDAP_STRDUP( atype ); + __etoa( atype ); + } + if ( adesc ) { + adesc = LDAP_STRDUP( adesc ); + __etoa( adesc ); + } +#endif + Debug( LDAP_DEBUG_TRACE, + "TLS trace: SSL3 alert %s:%s:%s\n", + op, atype, adesc ); +#ifdef HAVE_EBCDIC + if ( atype ) LDAP_FREE( atype ); + if ( adesc ) LDAP_FREE( adesc ); +#endif + } else if ( where & SSL_CB_EXIT ) { + if ( ret == 0 ) { + Debug( LDAP_DEBUG_TRACE, + "TLS trace: %s:failed in %s\n", + op, state, 0 ); + } else if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "TLS trace: %s:error in %s\n", + op, state, 0 ); + } + } +#ifdef HAVE_EBCDIC + if ( state ) LDAP_FREE( state ); +#endif +} + +static int +tlso_verify_cb( int ok, X509_STORE_CTX *ctx ) +{ + X509 *cert; + int errnum; + int errdepth; + X509_NAME *subject; + X509_NAME *issuer; + char *sname; + char *iname; + char *certerr = NULL; + + cert = X509_STORE_CTX_get_current_cert( ctx ); + errnum = X509_STORE_CTX_get_error( ctx ); + errdepth = X509_STORE_CTX_get_error_depth( ctx ); + + /* + * X509_get_*_name return pointers to the internal copies of + * those things requested. So do not free them. + */ + subject = X509_get_subject_name( cert ); + issuer = X509_get_issuer_name( cert ); + /* X509_NAME_oneline, if passed a NULL buf, allocate memomry */ + sname = X509_NAME_oneline( subject, NULL, 0 ); + iname = X509_NAME_oneline( issuer, NULL, 0 ); + if ( !ok ) certerr = (char *)X509_verify_cert_error_string( errnum ); +#ifdef HAVE_EBCDIC + if ( sname ) __etoa( sname ); + if ( iname ) __etoa( iname ); + if ( certerr ) { + certerr = LDAP_STRDUP( certerr ); + __etoa( certerr ); + } +#endif + Debug( LDAP_DEBUG_TRACE, + "TLS certificate verification: depth: %d, err: %d, subject: %s,", + errdepth, errnum, + sname ? sname : "-unknown-" ); + Debug( LDAP_DEBUG_TRACE, " issuer: %s\n", iname ? iname : "-unknown-", 0, 0 ); + if ( !ok ) { + Debug( LDAP_DEBUG_ANY, + "TLS certificate verification: Error, %s\n", + certerr, 0, 0 ); + } + if ( sname ) + OPENSSL_free ( sname ); + if ( iname ) + OPENSSL_free ( iname ); +#ifdef HAVE_EBCDIC + if ( certerr ) LDAP_FREE( certerr ); +#endif + return ok; +} + +static int +tlso_verify_ok( int ok, X509_STORE_CTX *ctx ) +{ + (void) tlso_verify_cb( ok, ctx ); + return 1; +} + +/* Inspired by ERR_print_errors in OpenSSL */ +static void +tlso_report_error( void ) +{ + unsigned long l; + char buf[200]; + const char *file; + int line; + + while ( ( l = ERR_get_error_line( &file, &line ) ) != 0 ) { + ERR_error_string_n( l, buf, sizeof( buf ) ); +#ifdef HAVE_EBCDIC + if ( file ) { + file = LDAP_STRDUP( file ); + __etoa( (char *)file ); + } + __etoa( buf ); +#endif + Debug( LDAP_DEBUG_ANY, "TLS: %s %s:%d\n", + buf, file, line ); +#ifdef HAVE_EBCDIC + if ( file ) LDAP_FREE( (void *)file ); +#endif + } +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +static RSA * +tlso_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ) +{ + RSA *tmp_rsa; + /* FIXME: Pregenerate the key on startup */ + /* FIXME: Who frees the key? */ +#if OPENSSL_VERSION_NUMBER >= 0x00908000 + BIGNUM *bn = BN_new(); + tmp_rsa = NULL; + if ( bn ) { + if ( BN_set_word( bn, RSA_F4 )) { + tmp_rsa = RSA_new(); + if ( tmp_rsa && !RSA_generate_key_ex( tmp_rsa, key_length, bn, NULL )) { + RSA_free( tmp_rsa ); + tmp_rsa = NULL; + } + } + BN_free( bn ); + } +#else + tmp_rsa = RSA_generate_key( key_length, RSA_F4, NULL, NULL ); +#endif + + if ( !tmp_rsa ) { + Debug( LDAP_DEBUG_ANY, + "TLS: Failed to generate temporary %d-bit %s RSA key\n", + key_length, is_export ? "export" : "domestic", 0 ); + } + return tmp_rsa; +} +#endif /* OPENSSL_VERSION_NUMBER < 1.1 */ + +static int +tlso_seed_PRNG( const char *randfile ) +{ +#ifndef URANDOM_DEVICE + /* no /dev/urandom (or equiv) */ + long total=0; + char buffer[MAXPATHLEN]; + + if (randfile == NULL) { + /* The seed file is $RANDFILE if defined, otherwise $HOME/.rnd. + * If $HOME is not set or buffer too small to hold the pathname, + * an error occurs. - From RAND_file_name() man page. + * The fact is that when $HOME is NULL, .rnd is used. + */ + randfile = RAND_file_name( buffer, sizeof( buffer ) ); + } +#ifndef OPENSSL_NO_EGD + else if (RAND_egd(randfile) > 0) { + /* EGD socket */ + return 0; + } +#endif + + if (randfile == NULL) { + Debug( LDAP_DEBUG_ANY, + "TLS: Use configuration file or $RANDFILE to define seed PRNG\n", + 0, 0, 0); + return -1; + } + + total = RAND_load_file(randfile, -1); + + if (RAND_status() == 0) { + Debug( LDAP_DEBUG_ANY, + "TLS: PRNG not been seeded with enough data\n", + 0, 0, 0); + return -1; + } + + /* assume if there was enough bits to seed that it's okay + * to write derived bits to the file + */ + RAND_write_file(randfile); + +#endif + + return 0; +} + + +tls_impl ldap_int_tls_impl = { + "OpenSSL", + + tlso_init, + tlso_destroy, + + tlso_ctx_new, + tlso_ctx_ref, + tlso_ctx_free, + tlso_ctx_init, + + tlso_session_new, + tlso_session_connect, + tlso_session_accept, + tlso_session_upflags, + tlso_session_errmsg, + tlso_session_my_dn, + tlso_session_peer_dn, + tlso_session_chkhost, + tlso_session_strength, + + &tlso_sbio, + +#ifdef LDAP_R_COMPILE + tlso_thr_init, +#else + NULL, +#endif + + 0 +}; + +#endif /* HAVE_OPENSSL */ diff --git a/libraries/libldap/turn.c b/libraries/libldap/turn.c new file mode 100644 index 0000000..20ef812 --- /dev/null +++ b/libraries/libldap/turn.c @@ -0,0 +1,96 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This program was orignally developed by Kurt D. Zeilenga for inclusion in + * OpenLDAP Software. + */ + +/* + * LDAPv3 Turn Operation Request + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +int +ldap_turn( + LDAP *ld, + int mutual, + LDAP_CONST char* identifier, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ +#ifdef LDAP_EXOP_X_TURN + BerElement *turnvalber = NULL; + struct berval *turnvalp = NULL; + int rc; + + turnvalber = ber_alloc_t( LBER_USE_DER ); + if( mutual ) { + ber_printf( turnvalber, "{bs}", mutual, identifier ); + } else { + ber_printf( turnvalber, "{s}", identifier ); + } + ber_flatten( turnvalber, &turnvalp ); + + rc = ldap_extended_operation( ld, LDAP_EXOP_X_TURN, + turnvalp, sctrls, cctrls, msgidp ); + ber_free( turnvalber, 1 ); + return rc; +#else + return LDAP_CONTROL_NOT_FOUND; +#endif +} + +int +ldap_turn_s( + LDAP *ld, + int mutual, + LDAP_CONST char* identifier, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ +#ifdef LDAP_EXOP_X_TURN + BerElement *turnvalber = NULL; + struct berval *turnvalp = NULL; + int rc; + + turnvalber = ber_alloc_t( LBER_USE_DER ); + if( mutual ) { + ber_printf( turnvalber, "{bs}", 0xFF, identifier ); + } else { + ber_printf( turnvalber, "{s}", identifier ); + } + ber_flatten( turnvalber, &turnvalp ); + + rc = ldap_extended_operation_s( ld, LDAP_EXOP_X_TURN, + turnvalp, sctrls, cctrls, NULL, NULL ); + ber_free( turnvalber, 1 ); + return rc; +#else + return LDAP_CONTROL_NOT_FOUND; +#endif +} + diff --git a/libraries/libldap/txn.c b/libraries/libldap/txn.c new file mode 100644 index 0000000..7f40c72 --- /dev/null +++ b/libraries/libldap/txn.c @@ -0,0 +1,155 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2006-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This program was orignally developed by Kurt D. Zeilenga for inclusion + * in OpenLDAP Software. + */ + +/* + * LDAPv3 Transactions (draft-zeilenga-ldap-txn) + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" +#include "ldap_log.h" + +#ifdef LDAP_X_TXN +int +ldap_txn_start( + LDAP *ld, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + return ldap_extended_operation( ld, LDAP_EXOP_X_TXN_START, + NULL, sctrls, cctrls, msgidp ); +} + +int +ldap_txn_start_s( + LDAP *ld, + LDAPControl **sctrls, + LDAPControl **cctrls, + struct berval **txnid ) +{ + assert( txnid != NULL ); + + return ldap_extended_operation_s( ld, LDAP_EXOP_X_TXN_START, + NULL, sctrls, cctrls, NULL, txnid ); +} + +int +ldap_txn_end( + LDAP *ld, + int commit, + struct berval *txnid, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + int rc; + BerElement *txnber = NULL; + struct berval *txnval = NULL; + + assert( txnid != NULL ); + + txnber = ber_alloc_t( LBER_USE_DER ); + + if( commit ) { + ber_printf( txnber, "{ON}", txnid ); + } else { + ber_printf( txnber, "{bON}", commit, txnid ); + } + + ber_flatten( txnber, &txnval ); + + rc = ldap_extended_operation( ld, LDAP_EXOP_X_TXN_END, + txnval, sctrls, cctrls, msgidp ); + + ber_free( txnber, 1 ); + return rc; +} + +int +ldap_txn_end_s( + LDAP *ld, + int commit, + struct berval *txnid, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *retidp ) +{ + int rc; + BerElement *txnber = NULL; + struct berval *txnval = NULL; + struct berval *retdata = NULL; + + if ( retidp != NULL ) *retidp = -1; + + txnber = ber_alloc_t( LBER_USE_DER ); + + if( commit ) { + ber_printf( txnber, "{ON}", txnid ); + } else { + ber_printf( txnber, "{bON}", commit, txnid ); + } + + ber_flatten( txnber, &txnval ); + + rc = ldap_extended_operation_s( ld, LDAP_EXOP_X_TXN_END, + txnval, sctrls, cctrls, NULL, &retdata ); + + ber_free( txnber, 1 ); + + /* parse retdata */ + if( retdata != NULL ) { + BerElement *ber; + ber_tag_t tag; + ber_int_t retid; + + if( retidp == NULL ) goto done; + + ber = ber_init( retdata ); + + if( ber == NULL ) { + rc = ld->ld_errno = LDAP_NO_MEMORY; + goto done; + } + + tag = ber_scanf( ber, "i", &retid ); + ber_free( ber, 1 ); + + if ( tag != LBER_INTEGER ) { + rc = ld->ld_errno = LDAP_DECODING_ERROR; + goto done; + } + + *retidp = (int) retid; + +done: + ber_bvfree( retdata ); + } + + return rc; +} +#endif diff --git a/libraries/libldap/unbind.c b/libraries/libldap/unbind.c new file mode 100644 index 0000000..4043e1f --- /dev/null +++ b/libraries/libldap/unbind.c @@ -0,0 +1,306 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* An Unbind Request looks like this: + * + * UnbindRequest ::= [APPLICATION 2] NULL + * + * and has no response. (Source: RFC 4511) + */ + +int +ldap_unbind_ext( + LDAP *ld, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int rc; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + /* check client controls */ + rc = ldap_int_client_controls( ld, cctrls ); + if( rc != LDAP_SUCCESS ) return rc; + + return ldap_ld_free( ld, 1, sctrls, cctrls ); +} + +int +ldap_unbind_ext_s( + LDAP *ld, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + return ldap_unbind_ext( ld, sctrls, cctrls ); +} + +int +ldap_unbind( LDAP *ld ) +{ + Debug( LDAP_DEBUG_TRACE, "ldap_unbind\n", 0, 0, 0 ); + + return( ldap_unbind_ext( ld, NULL, NULL ) ); +} + + +int +ldap_ld_free( + LDAP *ld, + int close, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + LDAPMessage *lm, *next; + int err = LDAP_SUCCESS; + + LDAP_MUTEX_LOCK( &ld->ld_ldcmutex ); + /* Someone else is still using this ld. */ + if (ld->ld_ldcrefcnt > 1) { /* but not last thread */ + /* clean up self only */ + ld->ld_ldcrefcnt--; + if ( ld->ld_error != NULL ) { + LDAP_FREE( ld->ld_error ); + ld->ld_error = NULL; + } + + if ( ld->ld_matched != NULL ) { + LDAP_FREE( ld->ld_matched ); + ld->ld_matched = NULL; + } + if ( ld->ld_referrals != NULL) { + LDAP_VFREE(ld->ld_referrals); + ld->ld_referrals = NULL; + } + LDAP_MUTEX_UNLOCK( &ld->ld_ldcmutex ); + LDAP_FREE( (char *) ld ); + return( err ); + } + + /* This ld is the last thread. */ + LDAP_MUTEX_UNLOCK( &ld->ld_ldcmutex ); + + /* free LDAP structure and outstanding requests/responses */ + LDAP_MUTEX_LOCK( &ld->ld_req_mutex ); + while ( ld->ld_requests != NULL ) { + ldap_free_request( ld, ld->ld_requests ); + } + LDAP_MUTEX_UNLOCK( &ld->ld_req_mutex ); + LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); + + /* free and unbind from all open connections */ + while ( ld->ld_conns != NULL ) { + ldap_free_connection( ld, ld->ld_conns, 1, close ); + } + LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); + LDAP_MUTEX_LOCK( &ld->ld_res_mutex ); + for ( lm = ld->ld_responses; lm != NULL; lm = next ) { + next = lm->lm_next; + ldap_msgfree( lm ); + } + + if ( ld->ld_abandoned != NULL ) { + LDAP_FREE( ld->ld_abandoned ); + ld->ld_abandoned = NULL; + } + LDAP_MUTEX_UNLOCK( &ld->ld_res_mutex ); + + ber_sockbuf_free( ld->ld_sb ); + + LDAP_MUTEX_LOCK( &ld->ld_ldopts_mutex ); + + /* final close callbacks */ + { + ldaplist *ll, *next; + + for ( ll = ld->ld_options.ldo_conn_cbs; ll; ll = next ) { + ldap_conncb *cb = ll->ll_data; + next = ll->ll_next; + cb->lc_del( ld, NULL, cb ); + LDAP_FREE( ll ); + } + } + + if ( ld->ld_error != NULL ) { + LDAP_FREE( ld->ld_error ); + ld->ld_error = NULL; + } + + if ( ld->ld_matched != NULL ) { + LDAP_FREE( ld->ld_matched ); + ld->ld_matched = NULL; + } + + if ( ld->ld_referrals != NULL) { + LDAP_VFREE(ld->ld_referrals); + ld->ld_referrals = NULL; + } + + if ( ld->ld_selectinfo != NULL ) { + ldap_free_select_info( ld->ld_selectinfo ); + ld->ld_selectinfo = NULL; + } + + if ( ld->ld_options.ldo_defludp != NULL ) { + ldap_free_urllist( ld->ld_options.ldo_defludp ); + ld->ld_options.ldo_defludp = NULL; + } + +#ifdef LDAP_CONNECTIONLESS + if ( ld->ld_options.ldo_peer != NULL ) { + LDAP_FREE( ld->ld_options.ldo_peer ); + ld->ld_options.ldo_peer = NULL; + } + + if ( ld->ld_options.ldo_cldapdn != NULL ) { + LDAP_FREE( ld->ld_options.ldo_cldapdn ); + ld->ld_options.ldo_cldapdn = NULL; + } +#endif + +#ifdef HAVE_CYRUS_SASL + if ( ld->ld_options.ldo_def_sasl_mech != NULL ) { + LDAP_FREE( ld->ld_options.ldo_def_sasl_mech ); + ld->ld_options.ldo_def_sasl_mech = NULL; + } + + if ( ld->ld_options.ldo_def_sasl_realm != NULL ) { + LDAP_FREE( ld->ld_options.ldo_def_sasl_realm ); + ld->ld_options.ldo_def_sasl_realm = NULL; + } + + if ( ld->ld_options.ldo_def_sasl_authcid != NULL ) { + LDAP_FREE( ld->ld_options.ldo_def_sasl_authcid ); + ld->ld_options.ldo_def_sasl_authcid = NULL; + } + + if ( ld->ld_options.ldo_def_sasl_authzid != NULL ) { + LDAP_FREE( ld->ld_options.ldo_def_sasl_authzid ); + ld->ld_options.ldo_def_sasl_authzid = NULL; + } +#endif + +#ifdef HAVE_TLS + ldap_int_tls_destroy( &ld->ld_options ); +#endif + + if ( ld->ld_options.ldo_sctrls != NULL ) { + ldap_controls_free( ld->ld_options.ldo_sctrls ); + ld->ld_options.ldo_sctrls = NULL; + } + + if ( ld->ld_options.ldo_cctrls != NULL ) { + ldap_controls_free( ld->ld_options.ldo_cctrls ); + ld->ld_options.ldo_cctrls = NULL; + } + LDAP_MUTEX_UNLOCK( &ld->ld_ldopts_mutex ); + +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_destroy( &ld->ld_msgid_mutex ); + ldap_pvt_thread_mutex_destroy( &ld->ld_conn_mutex ); + ldap_pvt_thread_mutex_destroy( &ld->ld_req_mutex ); + ldap_pvt_thread_mutex_destroy( &ld->ld_res_mutex ); + ldap_pvt_thread_mutex_destroy( &ld->ld_abandon_mutex ); + ldap_pvt_thread_mutex_destroy( &ld->ld_ldopts_mutex ); + ldap_pvt_thread_mutex_destroy( &ld->ld_ldcmutex ); +#endif +#ifndef NDEBUG + LDAP_TRASH(ld); +#endif + LDAP_FREE( (char *) ld->ldc ); + LDAP_FREE( (char *) ld ); + + return( err ); +} + +int +ldap_destroy( LDAP *ld ) +{ + return ( ldap_ld_free( ld, 1, NULL, NULL ) ); +} + +int +ldap_unbind_s( LDAP *ld ) +{ + return( ldap_unbind_ext( ld, NULL, NULL ) ); +} + +/* FIXME: this function is called only by ldap_free_connection(), + * which, most of the times, is called with ld_req_mutex locked */ +int +ldap_send_unbind( + LDAP *ld, + Sockbuf *sb, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + BerElement *ber; + ber_int_t id; + + Debug( LDAP_DEBUG_TRACE, "ldap_send_unbind\n", 0, 0, 0 ); + +#ifdef LDAP_CONNECTIONLESS + if (LDAP_IS_UDP(ld)) + return LDAP_SUCCESS; +#endif + /* create a message to send */ + if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) { + return( ld->ld_errno ); + } + + LDAP_NEXT_MSGID(ld, id); + + /* fill it in */ + if ( ber_printf( ber, "{itn" /*}*/, id, + LDAP_REQ_UNBIND ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( ld->ld_errno ); + } + + /* Put Server Controls */ + if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) { + ber_free( ber, 1 ); + return ld->ld_errno; + } + + if ( ber_printf( ber, /*{*/ "N}", LDAP_REQ_UNBIND ) == -1 ) { + ld->ld_errno = LDAP_ENCODING_ERROR; + ber_free( ber, 1 ); + return( ld->ld_errno ); + } + + ld->ld_errno = LDAP_SUCCESS; + /* send the message */ + if ( ber_flush2( sb, ber, LBER_FLUSH_FREE_ALWAYS ) == -1 ) { + ld->ld_errno = LDAP_SERVER_DOWN; + } + + return( ld->ld_errno ); +} diff --git a/libraries/libldap/url.c b/libraries/libldap/url.c new file mode 100644 index 0000000..f26daa8 --- /dev/null +++ b/libraries/libldap/url.c @@ -0,0 +1,1622 @@ +/* LIBLDAP url.c -- LDAP URL (RFC 4516) related routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (c) 1996 Regents of the University of Michigan. + * All rights reserved. + */ + + +/* + * LDAP URLs look like this: + * ldap[is]://host[:port][/[dn[?[attributes][?[scope][?[filter][?exts]]]]]] + * + * where: + * attributes is a comma separated list + * scope is one of these three strings: base one sub (default=base) + * filter is an string-represented filter as in RFC 4515 + * + * e.g., ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension + * + * We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl> + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/ctype.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* local functions */ +static const char* skip_url_prefix LDAP_P(( + const char *url, + int *enclosedp, + const char **scheme )); + +int ldap_pvt_url_scheme2proto( const char *scheme ) +{ + assert( scheme != NULL ); + + if( scheme == NULL ) { + return -1; + } + + if( strcmp("ldap", scheme) == 0 ) { + return LDAP_PROTO_TCP; + } + + if( strcmp("ldapi", scheme) == 0 ) { + return LDAP_PROTO_IPC; + } + + if( strcmp("ldaps", scheme) == 0 ) { + return LDAP_PROTO_TCP; + } +#ifdef LDAP_CONNECTIONLESS + if( strcmp("cldap", scheme) == 0 ) { + return LDAP_PROTO_UDP; + } +#endif + + return -1; +} + +int ldap_pvt_url_scheme_port( const char *scheme, int port ) +{ + assert( scheme != NULL ); + + if( port ) return port; + if( scheme == NULL ) return port; + + if( strcmp("ldap", scheme) == 0 ) { + return LDAP_PORT; + } + + if( strcmp("ldapi", scheme) == 0 ) { + return -1; + } + + if( strcmp("ldaps", scheme) == 0 ) { + return LDAPS_PORT; + } + +#ifdef LDAP_CONNECTIONLESS + if( strcmp("cldap", scheme) == 0 ) { + return LDAP_PORT; + } +#endif + + return -1; +} + +int +ldap_pvt_url_scheme2tls( const char *scheme ) +{ + assert( scheme != NULL ); + + if( scheme == NULL ) { + return -1; + } + + return strcmp("ldaps", scheme) == 0; +} + +int +ldap_is_ldap_url( LDAP_CONST char *url ) +{ + int enclosed; + const char * scheme; + + if( url == NULL ) { + return 0; + } + + if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { + return 0; + } + + return 1; +} + +int +ldap_is_ldaps_url( LDAP_CONST char *url ) +{ + int enclosed; + const char * scheme; + + if( url == NULL ) { + return 0; + } + + if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { + return 0; + } + + return strcmp(scheme, "ldaps") == 0; +} + +int +ldap_is_ldapi_url( LDAP_CONST char *url ) +{ + int enclosed; + const char * scheme; + + if( url == NULL ) { + return 0; + } + + if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { + return 0; + } + + return strcmp(scheme, "ldapi") == 0; +} + +#ifdef LDAP_CONNECTIONLESS +int +ldap_is_ldapc_url( LDAP_CONST char *url ) +{ + int enclosed; + const char * scheme; + + if( url == NULL ) { + return 0; + } + + if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { + return 0; + } + + return strcmp(scheme, "cldap") == 0; +} +#endif + +static const char* +skip_url_prefix( + const char *url, + int *enclosedp, + const char **scheme ) +{ + /* + * return non-zero if this looks like a LDAP URL; zero if not + * if non-zero returned, *urlp will be moved past "ldap://" part of URL + */ + const char *p; + + if ( url == NULL ) { + return( NULL ); + } + + p = url; + + /* skip leading '<' (if any) */ + if ( *p == '<' ) { + *enclosedp = 1; + ++p; + } else { + *enclosedp = 0; + } + + /* skip leading "URL:" (if any) */ + if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) { + p += LDAP_URL_URLCOLON_LEN; + } + + /* check for "ldap://" prefix */ + if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldap://" prefix and return success */ + p += LDAP_URL_PREFIX_LEN; + *scheme = "ldap"; + return( p ); + } + + /* check for "ldaps://" prefix */ + if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldaps://" prefix and return success */ + p += LDAPS_URL_PREFIX_LEN; + *scheme = "ldaps"; + return( p ); + } + + /* check for "ldapi://" prefix */ + if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldapi://" prefix and return success */ + p += LDAPI_URL_PREFIX_LEN; + *scheme = "ldapi"; + return( p ); + } + +#ifdef LDAP_CONNECTIONLESS + /* check for "cldap://" prefix */ + if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) { + /* skip over "cldap://" prefix and return success */ + p += LDAPC_URL_PREFIX_LEN; + *scheme = "cldap"; + return( p ); + } +#endif + + return( NULL ); +} + +int +ldap_pvt_scope2bv( int scope, struct berval *bv ) +{ + switch ( scope ) { + case LDAP_SCOPE_BASE: + BER_BVSTR( bv, "base" ); + break; + + case LDAP_SCOPE_ONELEVEL: + BER_BVSTR( bv, "one" ); + break; + + case LDAP_SCOPE_SUBTREE: + BER_BVSTR( bv, "sub" ); + break; + + case LDAP_SCOPE_SUBORDINATE: + BER_BVSTR( bv, "subordinate" ); + break; + + default: + return LDAP_OTHER; + } + + return LDAP_SUCCESS; +} + +const char * +ldap_pvt_scope2str( int scope ) +{ + struct berval bv; + + if ( ldap_pvt_scope2bv( scope, &bv ) == LDAP_SUCCESS ) { + return bv.bv_val; + } + + return NULL; +} + +int +ldap_pvt_bv2scope( struct berval *bv ) +{ + static struct { + struct berval bv; + int scope; + } v[] = { + { BER_BVC( "one" ), LDAP_SCOPE_ONELEVEL }, + { BER_BVC( "onelevel" ), LDAP_SCOPE_ONELEVEL }, + { BER_BVC( "base" ), LDAP_SCOPE_BASE }, + { BER_BVC( "sub" ), LDAP_SCOPE_SUBTREE }, + { BER_BVC( "subtree" ), LDAP_SCOPE_SUBTREE }, + { BER_BVC( "subord" ), LDAP_SCOPE_SUBORDINATE }, + { BER_BVC( "subordinate" ), LDAP_SCOPE_SUBORDINATE }, + { BER_BVC( "children" ), LDAP_SCOPE_SUBORDINATE }, + { BER_BVNULL, -1 } + }; + int i; + + for ( i = 0; v[ i ].scope != -1; i++ ) { + if ( ber_bvstrcasecmp( bv, &v[ i ].bv ) == 0 ) { + return v[ i ].scope; + } + } + + return( -1 ); +} + +int +ldap_pvt_str2scope( const char *p ) +{ + struct berval bv; + + ber_str2bv( p, 0, 0, &bv ); + + return ldap_pvt_bv2scope( &bv ); +} + +static const char hex[] = "0123456789ABCDEF"; + +#define URLESC_NONE 0x0000U +#define URLESC_COMMA 0x0001U +#define URLESC_SLASH 0x0002U + +static int +hex_escape_len( const char *s, unsigned list ) +{ + int len; + + if ( s == NULL ) { + return 0; + } + + for ( len = 0; s[0]; s++ ) { + switch ( s[0] ) { + /* RFC 2396: reserved */ + case '?': + len += 3; + break; + + case ',': + if ( list & URLESC_COMMA ) { + len += 3; + } else { + len++; + } + break; + + case '/': + if ( list & URLESC_SLASH ) { + len += 3; + } else { + len++; + } + break; + + case ';': + case ':': + case '@': + case '&': + case '=': + case '+': + case '$': + + /* RFC 2396: unreserved mark */ + case '-': + case '_': + case '.': + case '!': + case '~': + case '*': + case '\'': + case '(': + case ')': + len++; + break; + + /* RFC 2396: unreserved alphanum */ + default: + if ( !isalnum( (unsigned char) s[0] ) ) { + len += 3; + } else { + len++; + } + break; + } + } + + return len; +} + +static int +hex_escape( char *buf, int len, const char *s, unsigned list ) +{ + int i; + int pos; + + if ( s == NULL ) { + return 0; + } + + for ( pos = 0, i = 0; s[i] && pos < len; i++ ) { + int escape = 0; + + switch ( s[i] ) { + /* RFC 2396: reserved */ + case '?': + escape = 1; + break; + + case ',': + if ( list & URLESC_COMMA ) { + escape = 1; + } + break; + + case '/': + if ( list & URLESC_SLASH ) { + escape = 1; + } + break; + + case ';': + case ':': + case '@': + case '&': + case '=': + case '+': + case '$': + + /* RFC 2396: unreserved mark */ + case '-': + case '_': + case '.': + case '!': + case '~': + case '*': + case '\'': + case '(': + case ')': + break; + + /* RFC 2396: unreserved alphanum */ + default: + if ( !isalnum( (unsigned char) s[i] ) ) { + escape = 1; + } + break; + } + + if ( escape ) { + buf[pos++] = '%'; + buf[pos++] = hex[ (s[i] >> 4) & 0x0f ]; + buf[pos++] = hex[ s[i] & 0x0f ]; + + } else { + buf[pos++] = s[i]; + } + } + + buf[pos] = '\0'; + + return pos; +} + +static int +hex_escape_len_list( char **s, unsigned flags ) +{ + int len; + int i; + + if ( s == NULL ) { + return 0; + } + + len = 0; + for ( i = 0; s[i] != NULL; i++ ) { + if ( len ) { + len++; + } + len += hex_escape_len( s[i], flags ); + } + + return len; +} + +static int +hex_escape_list( char *buf, int len, char **s, unsigned flags ) +{ + int pos; + int i; + + if ( s == NULL ) { + return 0; + } + + pos = 0; + for ( i = 0; s[i] != NULL; i++ ) { + int curlen; + + if ( pos ) { + buf[pos++] = ','; + len--; + } + curlen = hex_escape( &buf[pos], len, s[i], flags ); + len -= curlen; + pos += curlen; + } + + return pos; +} + +static int +desc2str_len( LDAPURLDesc *u ) +{ + int sep = 0; + int len = 0; + int is_ipc = 0; + struct berval scope; + + if ( u == NULL || u->lud_scheme == NULL ) { + return -1; + } + + if ( !strcmp( "ldapi", u->lud_scheme )) { + is_ipc = 1; + } + + if ( u->lud_exts ) { + len += hex_escape_len_list( u->lud_exts, URLESC_COMMA ); + if ( !sep ) { + sep = 5; + } + } + + if ( u->lud_filter ) { + len += hex_escape_len( u->lud_filter, URLESC_NONE ); + if ( !sep ) { + sep = 4; + } + } + + if ( ldap_pvt_scope2bv( u->lud_scope, &scope ) == LDAP_SUCCESS ) { + len += scope.bv_len; + if ( !sep ) { + sep = 3; + } + } + + if ( u->lud_attrs ) { + len += hex_escape_len_list( u->lud_attrs, URLESC_NONE ); + if ( !sep ) { + sep = 2; + } + } + + if ( u->lud_dn && u->lud_dn[0] ) { + len += hex_escape_len( u->lud_dn, URLESC_NONE ); + if ( !sep ) { + sep = 1; + } + }; + + len += sep; + + if ( u->lud_port ) { + unsigned p = u->lud_port; + if ( p > 65535 ) + return -1; + + len += (p > 999 ? 5 + (p > 9999) : p > 99 ? 4 : 2 + (p > 9)); + } + + if ( u->lud_host && u->lud_host[0] ) { + char *ptr; + len += hex_escape_len( u->lud_host, URLESC_SLASH ); + if ( !is_ipc && ( ptr = strchr( u->lud_host, ':' ))) { + if ( strchr( ptr+1, ':' )) + len += 2; /* IPv6, [] */ + } + } + + len += strlen( u->lud_scheme ) + STRLENOF( "://" ); + + return len; +} + +static int +desc2str( LDAPURLDesc *u, char *s, int len ) +{ + int i; + int sep = 0; + int sofar = 0; + int is_v6 = 0; + int is_ipc = 0; + struct berval scope = BER_BVNULL; + char *ptr; + + if ( u == NULL ) { + return -1; + } + + if ( s == NULL ) { + return -1; + } + + if ( u->lud_scheme && !strcmp( "ldapi", u->lud_scheme )) { + is_ipc = 1; + } + + ldap_pvt_scope2bv( u->lud_scope, &scope ); + + if ( u->lud_exts ) { + sep = 5; + } else if ( u->lud_filter ) { + sep = 4; + } else if ( !BER_BVISEMPTY( &scope ) ) { + sep = 3; + } else if ( u->lud_attrs ) { + sep = 2; + } else if ( u->lud_dn && u->lud_dn[0] ) { + sep = 1; + } + + if ( !is_ipc && u->lud_host && ( ptr = strchr( u->lud_host, ':' ))) { + if ( strchr( ptr+1, ':' )) + is_v6 = 1; + } + + if ( u->lud_port ) { + sofar = sprintf( s, "%s://%s%s%s:%d", u->lud_scheme, + is_v6 ? "[" : "", + u->lud_host ? u->lud_host : "", + is_v6 ? "]" : "", + u->lud_port ); + len -= sofar; + + } else { + sofar = sprintf( s, "%s://", u->lud_scheme ); + len -= sofar; + if ( u->lud_host && u->lud_host[0] ) { + if ( is_v6 ) { + s[sofar++] = '['; + len--; + } + i = hex_escape( &s[sofar], len, u->lud_host, URLESC_SLASH ); + sofar += i; + len -= i; + if ( is_v6 ) { + s[sofar++] = ']'; + len--; + } + } + } + + assert( len >= 0 ); + + if ( sep < 1 ) { + goto done; + } + + s[sofar++] = '/'; + len--; + + assert( len >= 0 ); + + if ( u->lud_dn && u->lud_dn[0] ) { + i = hex_escape( &s[sofar], len, u->lud_dn, URLESC_NONE ); + sofar += i; + len -= i; + + assert( len >= 0 ); + } + + if ( sep < 2 ) { + goto done; + } + s[sofar++] = '?'; + len--; + + assert( len >= 0 ); + + i = hex_escape_list( &s[sofar], len, u->lud_attrs, URLESC_NONE ); + sofar += i; + len -= i; + + assert( len >= 0 ); + + if ( sep < 3 ) { + goto done; + } + s[sofar++] = '?'; + len--; + + assert( len >= 0 ); + + if ( !BER_BVISNULL( &scope ) ) { + strcpy( &s[sofar], scope.bv_val ); + sofar += scope.bv_len; + len -= scope.bv_len; + } + + assert( len >= 0 ); + + if ( sep < 4 ) { + goto done; + } + s[sofar++] = '?'; + len--; + + assert( len >= 0 ); + + i = hex_escape( &s[sofar], len, u->lud_filter, URLESC_NONE ); + sofar += i; + len -= i; + + assert( len >= 0 ); + + if ( sep < 5 ) { + goto done; + } + s[sofar++] = '?'; + len--; + + assert( len >= 0 ); + + i = hex_escape_list( &s[sofar], len, u->lud_exts, URLESC_COMMA ); + sofar += i; + len -= i; + + assert( len >= 0 ); + +done: + if ( len < 0 ) { + return -1; + } + + return sofar; +} + +char * +ldap_url_desc2str( LDAPURLDesc *u ) +{ + int len; + char *s; + + if ( u == NULL ) { + return NULL; + } + + len = desc2str_len( u ); + if ( len < 0 ) { + return NULL; + } + + /* allocate enough to hex escape everything -- overkill */ + s = LDAP_MALLOC( len + 1 ); + + if ( s == NULL ) { + return NULL; + } + + if ( desc2str( u, s, len ) != len ) { + LDAP_FREE( s ); + return NULL; + } + + s[len] = '\0'; + + return s; +} + +int +ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp, unsigned flags ) +{ +/* + * Pick apart the pieces of an LDAP URL. + */ + + LDAPURLDesc *ludp; + char *p, *q, *r; + int i, enclosed, proto, is_v6 = 0; + const char *scheme = NULL; + const char *url_tmp; + char *url; + + int check_dn = 1; + + if( url_in == NULL || ludpp == NULL ) { + return LDAP_URL_ERR_PARAM; + } + +#ifndef LDAP_INT_IN_KERNEL + /* Global options may not be created yet + * We can't test if the global options are initialized + * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate + * the options and cause infinite recursion + */ + Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 ); +#endif + + *ludpp = NULL; /* pessimistic */ + + url_tmp = skip_url_prefix( url_in, &enclosed, &scheme ); + + if ( url_tmp == NULL ) { + return LDAP_URL_ERR_BADSCHEME; + } + + assert( scheme != NULL ); + + proto = ldap_pvt_url_scheme2proto( scheme ); + if ( proto == -1 ) { + return LDAP_URL_ERR_BADSCHEME; + } + + /* make working copy of the remainder of the URL */ + url = LDAP_STRDUP( url_tmp ); + if ( url == NULL ) { + return LDAP_URL_ERR_MEM; + } + + if ( enclosed ) { + p = &url[strlen(url)-1]; + + if( *p != '>' ) { + LDAP_FREE( url ); + return LDAP_URL_ERR_BADENCLOSURE; + } + + *p = '\0'; + } + + /* allocate return struct */ + ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc )); + + if ( ludp == NULL ) { + LDAP_FREE( url ); + return LDAP_URL_ERR_MEM; + } + + ludp->lud_next = NULL; + ludp->lud_host = NULL; + ludp->lud_port = 0; + ludp->lud_dn = NULL; + ludp->lud_attrs = NULL; + ludp->lud_scope = ( flags & LDAP_PVT_URL_PARSE_NODEF_SCOPE ) ? LDAP_SCOPE_BASE : LDAP_SCOPE_DEFAULT; + ludp->lud_filter = NULL; + ludp->lud_exts = NULL; + + ludp->lud_scheme = LDAP_STRDUP( scheme ); + + if ( ludp->lud_scheme == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + /* scan forward for '/' that marks end of hostport and begin. of dn */ + p = strchr( url, '/' ); + q = NULL; + + if( p != NULL ) { + /* terminate hostport; point to start of dn */ + *p++ = '\0'; + } else { + /* check for Novell kludge, see below */ + p = strchr( url, '?' ); + if ( p ) { + *p++ = '\0'; + q = p; + p = NULL; + } + } + + if ( proto != LDAP_PROTO_IPC ) { + /* IPv6 syntax with [ip address]:port */ + if ( *url == '[' ) { + r = strchr( url, ']' ); + if ( r == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + *r++ = '\0'; + q = strchr( r, ':' ); + if ( q && q != r ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + is_v6 = 1; + } else { + q = strchr( url, ':' ); + } + + if ( q != NULL ) { + char *next; + + *q++ = '\0'; + ldap_pvt_hex_unescape( q ); + + if( *q == '\0' ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + + ludp->lud_port = strtol( q, &next, 10 ); + if ( next == q || next[0] != '\0' ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + /* check for Novell kludge */ + if ( !p ) { + if ( *next != '\0' ) { + q = &next[1]; + } else { + q = NULL; + } + } + } + + if ( ( flags & LDAP_PVT_URL_PARSE_DEF_PORT ) && ludp->lud_port == 0 ) { + if ( strcmp( ludp->lud_scheme, "ldaps" ) == 0 ) { + ludp->lud_port = LDAPS_PORT; + } else { + ludp->lud_port = LDAP_PORT; + } + } + } + + ldap_pvt_hex_unescape( url ); + + /* If [ip address]:port syntax, url is [ip and we skip the [ */ + ludp->lud_host = LDAP_STRDUP( url + is_v6 ); + + if( ludp->lud_host == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + if ( ( flags & LDAP_PVT_URL_PARSE_NOEMPTY_HOST ) + && ludp->lud_host != NULL + && *ludp->lud_host == '\0' ) + { + LDAP_FREE( ludp->lud_host ); + ludp->lud_host = NULL; + } + + /* + * Kludge. ldap://111.222.333.444:389??cn=abc,o=company + * + * On early Novell releases, search references/referrals were returned + * in this format, i.e., the dn was kind of in the scope position, + * but the required slash is missing. The whole thing is illegal syntax, + * but we need to account for it. Fortunately it can't be confused with + * anything real. + */ + if( (p == NULL) && (q != NULL) && (*q == '?') ) { + /* ? immediately followed by question */ + q++; + if( *q != '\0' ) { + /* parse dn part */ + ldap_pvt_hex_unescape( q ); + ludp->lud_dn = LDAP_STRDUP( q ); + + } else if ( !( flags & LDAP_PVT_URL_PARSE_NOEMPTY_DN ) ) { + ludp->lud_dn = LDAP_STRDUP( "" ); + + } else { + check_dn = 0; + } + + if ( check_dn && ludp->lud_dn == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + } + + if( p == NULL ) { + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of dn */ + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate dn part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse dn part */ + ldap_pvt_hex_unescape( p ); + ludp->lud_dn = LDAP_STRDUP( p ); + + } else if ( !( flags & LDAP_PVT_URL_PARSE_NOEMPTY_DN ) ) { + ludp->lud_dn = LDAP_STRDUP( "" ); + + } else { + check_dn = 0; + } + + if( check_dn && ludp->lud_dn == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + if( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of attributes */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate attributes part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse attributes */ + ldap_pvt_hex_unescape( p ); + ludp->lud_attrs = ldap_str2charray( p, "," ); + + if( ludp->lud_attrs == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADATTRS; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of scope */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate the scope part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse the scope */ + ldap_pvt_hex_unescape( p ); + ludp->lud_scope = ldap_pvt_str2scope( p ); + + if( ludp->lud_scope == -1 ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADSCOPE; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of filter */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate the filter part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse the filter */ + ldap_pvt_hex_unescape( p ); + + if( ! *p ) { + /* missing filter */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADFILTER; + } + + ludp->lud_filter = LDAP_STRDUP( p ); + + if( ludp->lud_filter == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of extensions */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* extra '?' */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + + /* parse the extensions */ + ludp->lud_exts = ldap_str2charray( p, "," ); + + if( ludp->lud_exts == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADEXTS; + } + + for( i=0; ludp->lud_exts[i] != NULL; i++ ) { + ldap_pvt_hex_unescape( ludp->lud_exts[i] ); + + if( *ludp->lud_exts[i] == '!' ) { + /* count the number of critical extensions */ + ludp->lud_crit_exts++; + } + } + + if( i == 0 ) { + /* must have 1 or more */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADEXTS; + } + + /* no more */ + *ludpp = ludp; + LDAP_FREE( url ); + return LDAP_URL_SUCCESS; +} + +int +ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) +{ + return ldap_url_parse_ext( url_in, ludpp, LDAP_PVT_URL_PARSE_HISTORIC ); +} + +LDAPURLDesc * +ldap_url_dup ( LDAPURLDesc *ludp ) +{ + LDAPURLDesc *dest; + + if ( ludp == NULL ) { + return NULL; + } + + dest = LDAP_MALLOC( sizeof(LDAPURLDesc) ); + if (dest == NULL) + return NULL; + + *dest = *ludp; + dest->lud_scheme = NULL; + dest->lud_host = NULL; + dest->lud_dn = NULL; + dest->lud_filter = NULL; + dest->lud_attrs = NULL; + dest->lud_exts = NULL; + dest->lud_next = NULL; + + if ( ludp->lud_scheme != NULL ) { + dest->lud_scheme = LDAP_STRDUP( ludp->lud_scheme ); + if (dest->lud_scheme == NULL) { + ldap_free_urldesc(dest); + return NULL; + } + } + + if ( ludp->lud_host != NULL ) { + dest->lud_host = LDAP_STRDUP( ludp->lud_host ); + if (dest->lud_host == NULL) { + ldap_free_urldesc(dest); + return NULL; + } + } + + if ( ludp->lud_dn != NULL ) { + dest->lud_dn = LDAP_STRDUP( ludp->lud_dn ); + if (dest->lud_dn == NULL) { + ldap_free_urldesc(dest); + return NULL; + } + } + + if ( ludp->lud_filter != NULL ) { + dest->lud_filter = LDAP_STRDUP( ludp->lud_filter ); + if (dest->lud_filter == NULL) { + ldap_free_urldesc(dest); + return NULL; + } + } + + if ( ludp->lud_attrs != NULL ) { + dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs ); + if (dest->lud_attrs == NULL) { + ldap_free_urldesc(dest); + return NULL; + } + } + + if ( ludp->lud_exts != NULL ) { + dest->lud_exts = ldap_charray_dup( ludp->lud_exts ); + if (dest->lud_exts == NULL) { + ldap_free_urldesc(dest); + return NULL; + } + } + + return dest; +} + +LDAPURLDesc * +ldap_url_duplist (LDAPURLDesc *ludlist) +{ + LDAPURLDesc *dest, *tail, *ludp, *newludp; + + dest = NULL; + tail = NULL; + for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) { + newludp = ldap_url_dup(ludp); + if (newludp == NULL) { + ldap_free_urllist(dest); + return NULL; + } + if (tail == NULL) + dest = newludp; + else + tail->lud_next = newludp; + tail = newludp; + } + return dest; +} + +static int +ldap_url_parselist_int (LDAPURLDesc **ludlist, const char *url, const char *sep, unsigned flags ) + +{ + int i, rc; + LDAPURLDesc *ludp; + char **urls; + + assert( ludlist != NULL ); + assert( url != NULL ); + + *ludlist = NULL; + + if ( sep == NULL ) { + sep = ", "; + } + + urls = ldap_str2charray( url, sep ); + if (urls == NULL) + return LDAP_URL_ERR_MEM; + + /* count the URLs... */ + for (i = 0; urls[i] != NULL; i++) ; + /* ...and put them in the "stack" backward */ + while (--i >= 0) { + rc = ldap_url_parse_ext( urls[i], &ludp, flags ); + if ( rc != 0 ) { + ldap_charray_free( urls ); + ldap_free_urllist( *ludlist ); + *ludlist = NULL; + return rc; + } + ludp->lud_next = *ludlist; + *ludlist = ludp; + } + ldap_charray_free( urls ); + return LDAP_URL_SUCCESS; +} + +int +ldap_url_parselist (LDAPURLDesc **ludlist, const char *url ) +{ + return ldap_url_parselist_int( ludlist, url, ", ", LDAP_PVT_URL_PARSE_HISTORIC ); +} + +int +ldap_url_parselist_ext (LDAPURLDesc **ludlist, const char *url, const char *sep, unsigned flags ) +{ + return ldap_url_parselist_int( ludlist, url, sep, flags ); +} + +int +ldap_url_parsehosts( + LDAPURLDesc **ludlist, + const char *hosts, + int port ) +{ + int i; + LDAPURLDesc *ludp; + char **specs, *p; + + assert( ludlist != NULL ); + assert( hosts != NULL ); + + *ludlist = NULL; + + specs = ldap_str2charray(hosts, ", "); + if (specs == NULL) + return LDAP_NO_MEMORY; + + /* count the URLs... */ + for (i = 0; specs[i] != NULL; i++) /* EMPTY */; + + /* ...and put them in the "stack" backward */ + while (--i >= 0) { + ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) ); + if (ludp == NULL) { + ldap_charray_free(specs); + ldap_free_urllist(*ludlist); + *ludlist = NULL; + return LDAP_NO_MEMORY; + } + ludp->lud_port = port; + ludp->lud_host = specs[i]; + specs[i] = NULL; + p = strchr(ludp->lud_host, ':'); + if (p != NULL) { + /* more than one :, IPv6 address */ + if ( strchr(p+1, ':') != NULL ) { + /* allow [address] and [address]:port */ + if ( *ludp->lud_host == '[' ) { + p = LDAP_STRDUP(ludp->lud_host+1); + /* copied, make sure we free source later */ + specs[i] = ludp->lud_host; + ludp->lud_host = p; + p = strchr( ludp->lud_host, ']' ); + if ( p == NULL ) { + LDAP_FREE(ludp); + ldap_charray_free(specs); + return LDAP_PARAM_ERROR; + } + *p++ = '\0'; + if ( *p != ':' ) { + if ( *p != '\0' ) { + LDAP_FREE(ludp); + ldap_charray_free(specs); + return LDAP_PARAM_ERROR; + } + p = NULL; + } + } else { + p = NULL; + } + } + if (p != NULL) { + char *next; + + *p++ = 0; + ldap_pvt_hex_unescape(p); + ludp->lud_port = strtol( p, &next, 10 ); + if ( next == p || next[0] != '\0' ) { + LDAP_FREE(ludp); + ldap_charray_free(specs); + return LDAP_PARAM_ERROR; + } + } + } + ldap_pvt_hex_unescape(ludp->lud_host); + ludp->lud_scheme = LDAP_STRDUP("ldap"); + ludp->lud_next = *ludlist; + *ludlist = ludp; + } + + /* this should be an array of NULLs now */ + /* except entries starting with [ */ + ldap_charray_free(specs); + return LDAP_SUCCESS; +} + +char * +ldap_url_list2hosts (LDAPURLDesc *ludlist) +{ + LDAPURLDesc *ludp; + int size; + char *s, *p, buf[32]; /* big enough to hold a long decimal # (overkill) */ + + if (ludlist == NULL) + return NULL; + + /* figure out how big the string is */ + size = 1; /* nul-term */ + for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) { + if ( ludp->lud_host == NULL ) continue; + size += strlen(ludp->lud_host) + 1; /* host and space */ + if (strchr(ludp->lud_host, ':')) /* will add [ ] below */ + size += 2; + if (ludp->lud_port != 0) + size += sprintf(buf, ":%d", ludp->lud_port); + } + s = LDAP_MALLOC(size); + if (s == NULL) + return NULL; + + p = s; + for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) { + if ( ludp->lud_host == NULL ) continue; + if (strchr(ludp->lud_host, ':')) { + p += sprintf(p, "[%s]", ludp->lud_host); + } else { + strcpy(p, ludp->lud_host); + p += strlen(ludp->lud_host); + } + if (ludp->lud_port != 0) + p += sprintf(p, ":%d", ludp->lud_port); + *p++ = ' '; + } + if (p != s) + p--; /* nuke that extra space */ + *p = '\0'; + return s; +} + +char * +ldap_url_list2urls( + LDAPURLDesc *ludlist ) +{ + LDAPURLDesc *ludp; + int size, sofar; + char *s; + + if ( ludlist == NULL ) { + return NULL; + } + + /* figure out how big the string is */ + for ( size = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) { + int len = desc2str_len( ludp ); + if ( len < 0 ) { + return NULL; + } + size += len + 1; + } + + s = LDAP_MALLOC( size ); + + if ( s == NULL ) { + return NULL; + } + + for ( sofar = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) { + int len; + + len = desc2str( ludp, &s[sofar], size ); + + if ( len < 0 ) { + LDAP_FREE( s ); + return NULL; + } + + sofar += len; + size -= len; + + s[sofar++] = ' '; + size--; + + assert( size >= 0 ); + } + + s[sofar - 1] = '\0'; + + return s; +} + +void +ldap_free_urllist( LDAPURLDesc *ludlist ) +{ + LDAPURLDesc *ludp, *next; + + for (ludp = ludlist; ludp != NULL; ludp = next) { + next = ludp->lud_next; + ldap_free_urldesc(ludp); + } +} + +void +ldap_free_urldesc( LDAPURLDesc *ludp ) +{ + if ( ludp == NULL ) { + return; + } + + if ( ludp->lud_scheme != NULL ) { + LDAP_FREE( ludp->lud_scheme ); + } + + if ( ludp->lud_host != NULL ) { + LDAP_FREE( ludp->lud_host ); + } + + if ( ludp->lud_dn != NULL ) { + LDAP_FREE( ludp->lud_dn ); + } + + if ( ludp->lud_filter != NULL ) { + LDAP_FREE( ludp->lud_filter); + } + + if ( ludp->lud_attrs != NULL ) { + LDAP_VFREE( ludp->lud_attrs ); + } + + if ( ludp->lud_exts != NULL ) { + LDAP_VFREE( ludp->lud_exts ); + } + + LDAP_FREE( ludp ); +} + +static int +ldap_int_is_hexpair( char *s ) +{ + int i; + + for ( i = 0; i < 2; i++ ) { + if ( s[i] >= '0' && s[i] <= '9' ) { + continue; + } + + if ( s[i] >= 'A' && s[i] <= 'F' ) { + continue; + } + + if ( s[i] >= 'a' && s[i] <= 'f' ) { + continue; + } + + return 0; + } + + return 1; +} + +static int +ldap_int_unhex( int c ) +{ + return( c >= '0' && c <= '9' ? c - '0' + : c >= 'A' && c <= 'F' ? c - 'A' + 10 + : c - 'a' + 10 ); +} + +void +ldap_pvt_hex_unescape( char *s ) +{ + /* + * Remove URL hex escapes from s... done in place. The basic concept for + * this routine is borrowed from the WWW library HTUnEscape() routine. + */ + char *p, + *save_s = s; + + for ( p = s; *s != '\0'; ++s ) { + if ( *s == '%' ) { + /* + * FIXME: what if '%' is followed + * by non-hexpair chars? + */ + if ( !ldap_int_is_hexpair( s + 1 ) ) { + p = save_s; + break; + } + + if ( *++s == '\0' ) { + break; + } + *p = ldap_int_unhex( *s ) << 4; + if ( *++s == '\0' ) { + break; + } + *p++ += ldap_int_unhex( *s ); + } else { + *p++ = *s; + } + } + + *p = '\0'; +} + diff --git a/libraries/libldap/urltest.c b/libraries/libldap/urltest.c new file mode 100644 index 0000000..918472b --- /dev/null +++ b/libraries/libldap/urltest.c @@ -0,0 +1,128 @@ +/* urltest.c -- OpenLDAP URL API Test Program */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENT: + * This program was initially developed by Pierangelo Masarati + * <ando@OpenLDAP.org> for inclusion in OpenLDAP Software. + */ + +/* + * This program is designed to test the ldap_url_* functions + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include <ldap.h> + +#include "ldap-int.h" + +#include "ldap_defaults.h" + +int +main(int argc, char *argv[]) +{ + const char *url, + *scope = NULL; + LDAPURLDesc *lud; + enum { + IS_LDAP = 0, + IS_LDAPS, + IS_LDAPI + } type = IS_LDAP; + int rc; + + if ( argc != 2 ) { + fprintf( stderr, "usage: urltest <url>\n" ); + exit( EXIT_FAILURE ); + } + + url = argv[ 1 ]; + + if ( ldap_is_ldaps_url( url ) ) { + fprintf( stdout, "LDAPS url\n" ); + type = IS_LDAPS; + + } else if ( ldap_is_ldapi_url( url ) ) { + fprintf( stdout, "LDAPI url\n" ); + type = IS_LDAPI; + + } else if ( ldap_is_ldap_url( url ) ) { + fprintf( stdout, "generic LDAP url\n" ); + + } else { + fprintf( stderr, "Need a valid LDAP url\n" ); + exit( EXIT_FAILURE ); + } + + rc = ldap_url_parse( url, &lud ); + if ( rc != LDAP_URL_SUCCESS ) { + fprintf( stderr, "ldap_url_parse(%s) failed (%d)\n", url, rc ); + exit( EXIT_FAILURE ); + } + + fprintf( stdout, "PROTO: %s\n", lud->lud_scheme ); + switch ( type ) { + case IS_LDAPI: + fprintf( stdout, "PATH: %s\n", lud->lud_host ); + break; + + default: + fprintf( stdout, "HOST: %s\n", lud->lud_host ); + if ( lud->lud_port != 0 ) { + fprintf( stdout, "PORT: %d\n", lud->lud_port ); + } + } + + if ( lud->lud_dn && lud->lud_dn[ 0 ] ) { + fprintf( stdout, "DN: %s\n", lud->lud_dn ); + } + + if ( lud->lud_attrs ) { + int i; + + fprintf( stdout, "ATTRS:\n" ); + for ( i = 0; lud->lud_attrs[ i ]; i++ ) { + fprintf( stdout, "\t%s\n", lud->lud_attrs[ i ] ); + } + } + + scope = ldap_pvt_scope2str( lud->lud_scope ); + if ( scope ) { + fprintf( stdout, "SCOPE: %s\n", scope ); + } + + if ( lud->lud_filter ) { + fprintf( stdout, "FILTER: %s\n", lud->lud_filter ); + } + + if ( lud->lud_exts ) { + int i; + + fprintf( stdout, "EXTS:\n" ); + for ( i = 0; lud->lud_exts[ i ]; i++ ) { + fprintf( stdout, "\t%s\n", lud->lud_exts[ i ] ); + } + } + + fprintf( stdout, "URL: %s\n", ldap_url_desc2str( lud )); + + return EXIT_SUCCESS; +} diff --git a/libraries/libldap/utf-8-conv.c b/libraries/libldap/utf-8-conv.c new file mode 100644 index 0000000..7162813 --- /dev/null +++ b/libraries/libldap/utf-8-conv.c @@ -0,0 +1,485 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (C) 1999, 2000 Novell, Inc. All Rights Reserved. + * + * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND + * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT + * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS + * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" + * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION + * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP + * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT + * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. + *--- + * Note: A verbatim copy of version 2.0.1 of the OpenLDAP Public License + * can be found in the file "build/LICENSE-2.0.1" in this distribution + * of OpenLDAP Software. + */ + +/* + * UTF-8 Conversion Routines + * + * These routines convert between Wide Character and UTF-8, + * or between MultiByte and UTF-8 encodings. + * + * Both single character and string versions of the functions are provided. + * All functions return -1 if the character or string cannot be converted. + */ + +#include "portable.h" + +#if SIZEOF_WCHAR_T >= 4 +/* These routines assume ( sizeof(wchar_t) >= 4 ) */ + +#include <stdio.h> +#include <ac/stdlib.h> /* For wctomb, wcstombs, mbtowc, mbstowcs */ +#include <ac/string.h> +#include <ac/time.h> /* for time_t */ + +#include "ldap-int.h" + +#include <ldap_utf8.h> + +static unsigned char mask[] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; + + +/*----------------------------------------------------------------------------- + UTF-8 Format Summary + +ASCII chars 7 bits + 0xxxxxxx + +2-character UTF-8 sequence: 11 bits + 110xxxxx 10xxxxxx + +3-character UTF-8 16 bits + 1110xxxx 10xxxxxx 10xxxxxx + +4-char UTF-8 21 bits + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + +5-char UTF-8 26 bits + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + +6-char UTF-8 31 bits + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + +Unicode address space (0 - 0x10FFFF) 21 bits +ISO-10646 address space (0 - 0x7FFFFFFF) 31 bits + +Note: This code does not prevent UTF-8 sequences which are longer than + necessary from being decoded. +*/ + +/*----------------------------------------------------------------------------- + Convert a UTF-8 character to a wide char. + Return the length of the UTF-8 input character in bytes. +*/ +int +ldap_x_utf8_to_wc ( wchar_t *wchar, const char *utf8char ) +{ + int utflen, i; + wchar_t ch; + + if (utf8char == NULL) return -1; + + /* Get UTF-8 sequence length from 1st byte */ + utflen = LDAP_UTF8_CHARLEN2(utf8char, utflen); + + if( utflen==0 || utflen > (int)LDAP_MAX_UTF8_LEN ) return -1; + + /* First byte minus length tag */ + ch = (wchar_t)(utf8char[0] & mask[utflen]); + + for(i=1; i < utflen; i++) { + /* Subsequent bytes must start with 10 */ + if ((utf8char[i] & 0xc0) != 0x80) return -1; + + ch <<= 6; /* 6 bits of data in each subsequent byte */ + ch |= (wchar_t)(utf8char[i] & 0x3f); + } + + if (wchar) *wchar = ch; + + return utflen; +} + +/*----------------------------------------------------------------------------- + Convert a UTF-8 string to a wide char string. + No more than 'count' wide chars will be written to the output buffer. + Return the size of the converted string in wide chars, excl null terminator. +*/ +int +ldap_x_utf8s_to_wcs ( wchar_t *wcstr, const char *utf8str, size_t count ) +{ + size_t wclen = 0; + int utflen, i; + wchar_t ch; + + + /* If input ptr is NULL or empty... */ + if (utf8str == NULL || !*utf8str) { + if ( wcstr ) + *wcstr = 0; + return 0; + } + + /* Examine next UTF-8 character. If output buffer is NULL, ignore count */ + while ( *utf8str && (wcstr==NULL || wclen<count) ) { + /* Get UTF-8 sequence length from 1st byte */ + utflen = LDAP_UTF8_CHARLEN2(utf8str, utflen); + + if( utflen==0 || utflen > (int)LDAP_MAX_UTF8_LEN ) return -1; + + /* First byte minus length tag */ + ch = (wchar_t)(utf8str[0] & mask[utflen]); + + for(i=1; i < utflen; i++) { + /* Subsequent bytes must start with 10 */ + if ((utf8str[i] & 0xc0) != 0x80) return -1; + + ch <<= 6; /* 6 bits of data in each subsequent byte */ + ch |= (wchar_t)(utf8str[i] & 0x3f); + } + + if (wcstr) wcstr[wclen] = ch; + + utf8str += utflen; /* Move to next UTF-8 character */ + wclen++; /* Count number of wide chars stored/required */ + } + + /* Add null terminator if there's room in the buffer. */ + if (wcstr && wclen < count) wcstr[wclen] = 0; + + return wclen; +} + + +/*----------------------------------------------------------------------------- + Convert one wide char to a UTF-8 character. + Return the length of the converted UTF-8 character in bytes. + No more than 'count' bytes will be written to the output buffer. +*/ +int +ldap_x_wc_to_utf8 ( char *utf8char, wchar_t wchar, size_t count ) +{ + int len=0; + + if (utf8char == NULL) /* Just determine the required UTF-8 char length. */ + { /* Ignore count */ + if( wchar < 0 ) + return -1; + if( wchar < 0x80 ) + return 1; + if( wchar < 0x800 ) + return 2; + if( wchar < 0x10000 ) + return 3; + if( wchar < 0x200000 ) + return 4; + if( wchar < 0x4000000 ) + return 5; +#if SIZEOF_WCHAR_T > 4 + /* UL is not strictly needed by ANSI C */ + if( wchar < (wchar_t)0x80000000UL ) +#endif /* SIZEOF_WCHAR_T > 4 */ + return 6; + return -1; + } + + + if ( wchar < 0 ) { /* Invalid wide character */ + len = -1; + + } else if( wchar < 0x80 ) { + if (count >= 1) { + utf8char[len++] = (char)wchar; + } + + } else if( wchar < 0x800 ) { + if (count >=2) { + utf8char[len++] = 0xc0 | ( wchar >> 6 ); + utf8char[len++] = 0x80 | ( wchar & 0x3f ); + } + + } else if( wchar < 0x10000 ) { + if (count >= 3) { + utf8char[len++] = 0xe0 | ( wchar >> 12 ); + utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f ); + utf8char[len++] = 0x80 | ( wchar & 0x3f ); + } + + } else if( wchar < 0x200000 ) { + if (count >= 4) { + utf8char[len++] = 0xf0 | ( wchar >> 18 ); + utf8char[len++] = 0x80 | ( (wchar >> 12) & 0x3f ); + utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f ); + utf8char[len++] = 0x80 | ( wchar & 0x3f ); + } + + } else if( wchar < 0x4000000 ) { + if (count >= 5) { + utf8char[len++] = 0xf8 | ( wchar >> 24 ); + utf8char[len++] = 0x80 | ( (wchar >> 18) & 0x3f ); + utf8char[len++] = 0x80 | ( (wchar >> 12) & 0x3f ); + utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f ); + utf8char[len++] = 0x80 | ( wchar & 0x3f ); + } + + } else +#if SIZEOF_WCHAR_T > 4 + /* UL is not strictly needed by ANSI C */ + if( wchar < (wchar_t)0x80000000UL ) +#endif /* SIZEOF_WCHAR_T > 4 */ + { + if (count >= 6) { + utf8char[len++] = 0xfc | ( wchar >> 30 ); + utf8char[len++] = 0x80 | ( (wchar >> 24) & 0x3f ); + utf8char[len++] = 0x80 | ( (wchar >> 18) & 0x3f ); + utf8char[len++] = 0x80 | ( (wchar >> 12) & 0x3f ); + utf8char[len++] = 0x80 | ( (wchar >> 6) & 0x3f ); + utf8char[len++] = 0x80 | ( wchar & 0x3f ); + } + +#if SIZEOF_WCHAR_T > 4 + } else { + len = -1; +#endif /* SIZEOF_WCHAR_T > 4 */ + } + + return len; + +} + + +/*----------------------------------------------------------------------------- + Convert a wide char string to a UTF-8 string. + No more than 'count' bytes will be written to the output buffer. + Return the # of bytes written to the output buffer, excl null terminator. +*/ +int +ldap_x_wcs_to_utf8s ( char *utf8str, const wchar_t *wcstr, size_t count ) +{ + int len = 0; + int n; + char *p = utf8str; + wchar_t empty = 0; /* To avoid use of L"" construct */ + + if (wcstr == NULL) /* Treat input ptr NULL as an empty string */ + wcstr = ∅ + + if (utf8str == NULL) /* Just compute size of output, excl null */ + { + while (*wcstr) + { + /* Get UTF-8 size of next wide char */ + n = ldap_x_wc_to_utf8( NULL, *wcstr++, LDAP_MAX_UTF8_LEN); + if (n == -1) + return -1; + len += n; + } + + return len; + } + + + /* Do the actual conversion. */ + + n = 1; /* In case of empty wcstr */ + while (*wcstr) + { + n = ldap_x_wc_to_utf8( p, *wcstr++, count); + + if (n <= 0) /* If encoding error (-1) or won't fit (0), quit */ + break; + + p += n; + count -= n; /* Space left in output buffer */ + } + + /* If not enough room for last character, pad remainder with null + so that return value = original count, indicating buffer full. */ + if (n == 0) + { + while (count--) + *p++ = 0; + } + + /* Add a null terminator if there's room. */ + else if (count) + *p = 0; + + if (n == -1) /* Conversion encountered invalid wide char. */ + return -1; + + /* Return the number of bytes written to output buffer, excl null. */ + return (p - utf8str); +} + +#ifdef ANDROID +int wctomb(char *s, wchar_t wc) { return wcrtomb(s,wc,NULL); } +int mbtowc(wchar_t *pwc, const char *s, size_t n) { return mbrtowc(pwc, s, n, NULL); } +#endif + +/*----------------------------------------------------------------------------- + Convert a UTF-8 character to a MultiByte character. + Return the size of the converted character in bytes. +*/ +int +ldap_x_utf8_to_mb ( char *mbchar, const char *utf8char, + int (*f_wctomb)(char *mbchar, wchar_t wchar) ) +{ + wchar_t wchar; + int n; + char tmp[6]; /* Large enough for biggest multibyte char */ + + if (f_wctomb == NULL) /* If no conversion function was given... */ + f_wctomb = wctomb; /* use the local ANSI C function */ + + /* First convert UTF-8 char to a wide char */ + n = ldap_x_utf8_to_wc( &wchar, utf8char); + + if (n == -1) + return -1; /* Invalid UTF-8 character */ + + if (mbchar == NULL) + n = f_wctomb( tmp, wchar ); + else + n = f_wctomb( mbchar, wchar); + + return n; +} + +/*----------------------------------------------------------------------------- + Convert a UTF-8 string to a MultiByte string. + No more than 'count' bytes will be written to the output buffer. + Return the size of the converted string in bytes, excl null terminator. +*/ +int +ldap_x_utf8s_to_mbs ( char *mbstr, const char *utf8str, size_t count, + size_t (*f_wcstombs)(char *mbstr, const wchar_t *wcstr, size_t count) ) +{ + wchar_t *wcs; + size_t wcsize; + int n; + + if (f_wcstombs == NULL) /* If no conversion function was given... */ + f_wcstombs = wcstombs; /* use the local ANSI C function */ + + if (utf8str == NULL || *utf8str == 0) /* NULL or empty input string */ + { + if (mbstr) + *mbstr = 0; + return 0; + } + +/* Allocate memory for the maximum size wchar string that we could get. */ + wcsize = strlen(utf8str) + 1; + wcs = (wchar_t *)LDAP_MALLOC(wcsize * sizeof(wchar_t)); + if (wcs == NULL) + return -1; /* Memory allocation failure. */ + + /* First convert the UTF-8 string to a wide char string */ + n = ldap_x_utf8s_to_wcs( wcs, utf8str, wcsize); + + /* Then convert wide char string to multi-byte string */ + if (n != -1) + { + n = f_wcstombs(mbstr, wcs, count); + } + + LDAP_FREE(wcs); + + return n; +} + +/*----------------------------------------------------------------------------- + Convert a MultiByte character to a UTF-8 character. + 'mbsize' indicates the number of bytes of 'mbchar' to check. + Returns the number of bytes written to the output character. +*/ +int +ldap_x_mb_to_utf8 ( char *utf8char, const char *mbchar, size_t mbsize, + int (*f_mbtowc)(wchar_t *wchar, const char *mbchar, size_t count) ) +{ + wchar_t wchar; + int n; + + if (f_mbtowc == NULL) /* If no conversion function was given... */ + f_mbtowc = mbtowc; /* use the local ANSI C function */ + + if (mbsize == 0) /* 0 is not valid. */ + return -1; + + if (mbchar == NULL || *mbchar == 0) + { + if (utf8char) + *utf8char = 0; + return 1; + } + + /* First convert the MB char to a Wide Char */ + n = f_mbtowc( &wchar, mbchar, mbsize); + + if (n == -1) + return -1; + + /* Convert the Wide Char to a UTF-8 character. */ + n = ldap_x_wc_to_utf8( utf8char, wchar, LDAP_MAX_UTF8_LEN); + + return n; +} + + +/*----------------------------------------------------------------------------- + Convert a MultiByte string to a UTF-8 string. + No more than 'count' bytes will be written to the output buffer. + Return the size of the converted string in bytes, excl null terminator. +*/ +int +ldap_x_mbs_to_utf8s ( char *utf8str, const char *mbstr, size_t count, + size_t (*f_mbstowcs)(wchar_t *wcstr, const char *mbstr, size_t count) ) +{ + wchar_t *wcs; + int n; + size_t wcsize; + + if (mbstr == NULL) /* Treat NULL input string as an empty string */ + mbstr = ""; + + if (f_mbstowcs == NULL) /* If no conversion function was given... */ + f_mbstowcs = mbstowcs; /* use the local ANSI C function */ + + /* Allocate memory for the maximum size wchar string that we could get. */ + wcsize = strlen(mbstr) + 1; + wcs = (wchar_t *)LDAP_MALLOC( wcsize * sizeof(wchar_t) ); + if (wcs == NULL) + return -1; + + /* First convert multi-byte string to a wide char string */ + n = f_mbstowcs(wcs, mbstr, wcsize); + + /* Convert wide char string to UTF-8 string */ + if (n != -1) + { + n = ldap_x_wcs_to_utf8s( utf8str, wcs, count); + } + + LDAP_FREE(wcs); + + return n; +} + +#endif /* SIZEOF_WCHAR_T >= 4 */ diff --git a/libraries/libldap/utf-8.c b/libraries/libldap/utf-8.c new file mode 100644 index 0000000..65c8b01 --- /dev/null +++ b/libraries/libldap/utf-8.c @@ -0,0 +1,562 @@ +/* utf-8.c -- Basic UTF-8 routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Basic UTF-8 routines + * + * These routines are "dumb". Though they understand UTF-8, + * they don't grok Unicode. That is, they can push bits, + * but don't have a clue what the bits represent. That's + * good enough for use with the LDAP Client SDK. + * + * These routines are not optimized. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/stdlib.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap_utf8.h" + +#include "ldap-int.h" +#include "ldap_defaults.h" + +/* + * return the number of bytes required to hold the + * NULL-terminated UTF-8 string NOT INCLUDING the + * termination. + */ +ber_len_t ldap_utf8_bytes( const char * p ) +{ + ber_len_t bytes; + + for( bytes=0; p[bytes]; bytes++ ) { + /* EMPTY */ ; + } + + return bytes; +} + +ber_len_t ldap_utf8_chars( const char * p ) +{ + /* could be optimized and could check for invalid sequences */ + ber_len_t chars=0; + + for( ; *p ; LDAP_UTF8_INCR(p) ) { + chars++; + } + + return chars; +} + +/* return offset to next character */ +int ldap_utf8_offset( const char * p ) +{ + return LDAP_UTF8_NEXT(p) - p; +} + +/* + * Returns length indicated by first byte. + */ +const char ldap_utf8_lentab[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0 }; + +int ldap_utf8_charlen( const char * p ) +{ + if (!(*p & 0x80)) + return 1; + + return ldap_utf8_lentab[*(const unsigned char *)p ^ 0x80]; +} + +/* + * Make sure the UTF-8 char used the shortest possible encoding + * returns charlen if valid, 0 if not. + * + * Here are the valid UTF-8 encodings, taken from RFC 2279 page 4. + * The table is slightly modified from that of the RFC. + * + * UCS-4 range (hex) UTF-8 sequence (binary) + * 0000 0000-0000 007F 0....... + * 0000 0080-0000 07FF 110++++. 10...... + * 0000 0800-0000 FFFF 1110++++ 10+..... 10...... + * 0001 0000-001F FFFF 11110+++ 10++.... 10...... 10...... + * 0020 0000-03FF FFFF 111110++ 10+++... 10...... 10...... 10...... + * 0400 0000-7FFF FFFF 1111110+ 10++++.. 10...... 10...... 10...... 10...... + * + * The '.' bits are "don't cares". When validating a UTF-8 sequence, + * at least one of the '+' bits must be set, otherwise the character + * should have been encoded in fewer octets. Note that in the two-octet + * case, only the first octet needs to be validated, and this is done + * in the ldap_utf8_lentab[] above. + */ + +/* mask of required bits in second octet */ +#undef c +#define c const char +c ldap_utf8_mintab[] = { + (c)0x20, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, + (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, + (c)0x30, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, + (c)0x38, (c)0x80, (c)0x80, (c)0x80, (c)0x3c, (c)0x80, (c)0x00, (c)0x00 }; +#undef c + +int ldap_utf8_charlen2( const char * p ) +{ + int i = LDAP_UTF8_CHARLEN( p ); + + if ( i > 2 ) { + if ( !( ldap_utf8_mintab[*p & 0x1f] & p[1] ) ) + i = 0; + } + return i; +} + +/* conv UTF-8 to UCS-4, useful for comparisons */ +ldap_ucs4_t ldap_x_utf8_to_ucs4( const char * p ) +{ + const unsigned char *c = (const unsigned char *) p; + ldap_ucs4_t ch; + int len, i; + static unsigned char mask[] = { + 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; + + len = LDAP_UTF8_CHARLEN2(p, len); + + if( len == 0 ) return LDAP_UCS4_INVALID; + + ch = c[0] & mask[len]; + + for(i=1; i < len; i++) { + if ((c[i] & 0xc0) != 0x80) { + return LDAP_UCS4_INVALID; + } + + ch <<= 6; + ch |= c[i] & 0x3f; + } + + return ch; +} + +/* conv UCS-4 to UTF-8, not used */ +int ldap_x_ucs4_to_utf8( ldap_ucs4_t c, char *buf ) +{ + int len=0; + unsigned char* p = (unsigned char *) buf; + + /* not a valid Unicode character */ + if ( c < 0 ) return 0; + + /* Just return length, don't convert */ + if(buf == NULL) { + if( c < 0x80 ) return 1; + else if( c < 0x800 ) return 2; + else if( c < 0x10000 ) return 3; + else if( c < 0x200000 ) return 4; + else if( c < 0x4000000 ) return 5; + else return 6; + } + + if( c < 0x80 ) { + p[len++] = c; + + } else if( c < 0x800 ) { + p[len++] = 0xc0 | ( c >> 6 ); + p[len++] = 0x80 | ( c & 0x3f ); + + } else if( c < 0x10000 ) { + p[len++] = 0xe0 | ( c >> 12 ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + + } else if( c < 0x200000 ) { + p[len++] = 0xf0 | ( c >> 18 ); + p[len++] = 0x80 | ( (c >> 12) & 0x3f ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + + } else if( c < 0x4000000 ) { + p[len++] = 0xf8 | ( c >> 24 ); + p[len++] = 0x80 | ( (c >> 18) & 0x3f ); + p[len++] = 0x80 | ( (c >> 12) & 0x3f ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + + } else /* if( c < 0x80000000 ) */ { + p[len++] = 0xfc | ( c >> 30 ); + p[len++] = 0x80 | ( (c >> 24) & 0x3f ); + p[len++] = 0x80 | ( (c >> 18) & 0x3f ); + p[len++] = 0x80 | ( (c >> 12) & 0x3f ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + } + + return len; +} + +#define LDAP_UCS_UTF8LEN(c) \ + c < 0 ? 0 : (c < 0x80 ? 1 : (c < 0x800 ? 2 : (c < 0x10000 ? 3 : \ + (c < 0x200000 ? 4 : (c < 0x4000000 ? 5 : 6))))) + +/* Convert a string to UTF-8 format. The input string is expected to + * have characters of 1, 2, or 4 octets (in network byte order) + * corresponding to the ASN.1 T61STRING, BMPSTRING, and UNIVERSALSTRING + * types respectively. (Here T61STRING just means that there is one + * octet per character and characters may use the high bit of the octet. + * The characters are assumed to use ISO mappings, no provision is made + * for converting from T.61 coding rules to Unicode.) + */ + +int +ldap_ucs_to_utf8s( struct berval *ucs, int csize, struct berval *utf8s ) +{ + unsigned char *in, *end; + char *ptr; + ldap_ucs4_t u; + int i, l = 0; + + utf8s->bv_val = NULL; + utf8s->bv_len = 0; + + in = (unsigned char *)ucs->bv_val; + + /* Make sure we stop at an even multiple of csize */ + end = in + ( ucs->bv_len & ~(csize-1) ); + + for (; in < end; ) { + u = *in++; + if (csize > 1) { + u <<= 8; + u |= *in++; + } + if (csize > 2) { + u <<= 8; + u |= *in++; + u <<= 8; + u |= *in++; + } + i = LDAP_UCS_UTF8LEN(u); + if (i == 0) + return LDAP_INVALID_SYNTAX; + l += i; + } + + utf8s->bv_val = LDAP_MALLOC( l+1 ); + if (utf8s->bv_val == NULL) + return LDAP_NO_MEMORY; + utf8s->bv_len = l; + + ptr = utf8s->bv_val; + for (in = (unsigned char *)ucs->bv_val; in < end; ) { + u = *in++; + if (csize > 1) { + u <<= 8; + u |= *in++; + } + if (csize > 2) { + u <<= 8; + u |= *in++; + u <<= 8; + u |= *in++; + } + ptr += ldap_x_ucs4_to_utf8(u, ptr); + } + *ptr = '\0'; + return LDAP_SUCCESS; +} + +/* + * Advance to the next UTF-8 character + * + * Ignores length of multibyte character, instead rely on + * continuation markers to find start of next character. + * This allows for "resyncing" of when invalid characters + * are provided provided the start of the next character + * is appears within the 6 bytes examined. + */ +char* ldap_utf8_next( const char * p ) +{ + int i; + const unsigned char *u = (const unsigned char *) p; + + if( LDAP_UTF8_ISASCII(u) ) { + return (char *) &p[1]; + } + + for( i=1; i<6; i++ ) { + if ( ( u[i] & 0xc0 ) != 0x80 ) { + return (char *) &p[i]; + } + } + + return (char *) &p[i]; +} + +/* + * Advance to the previous UTF-8 character + * + * Ignores length of multibyte character, instead rely on + * continuation markers to find start of next character. + * This allows for "resyncing" of when invalid characters + * are provided provided the start of the next character + * is appears within the 6 bytes examined. + */ +char* ldap_utf8_prev( const char * p ) +{ + int i; + const unsigned char *u = (const unsigned char *) p; + + for( i=-1; i>-6 ; i-- ) { + if ( ( u[i] & 0xc0 ) != 0x80 ) { + return (char *) &p[i]; + } + } + + return (char *) &p[i]; +} + +/* + * Copy one UTF-8 character from src to dst returning + * number of bytes copied. + * + * Ignores length of multibyte character, instead rely on + * continuation markers to find start of next character. + * This allows for "resyncing" of when invalid characters + * are provided provided the start of the next character + * is appears within the 6 bytes examined. + */ +int ldap_utf8_copy( char* dst, const char *src ) +{ + int i; + const unsigned char *u = (const unsigned char *) src; + + dst[0] = src[0]; + + if( LDAP_UTF8_ISASCII(u) ) { + return 1; + } + + for( i=1; i<6; i++ ) { + if ( ( u[i] & 0xc0 ) != 0x80 ) { + return i; + } + dst[i] = src[i]; + } + + return i; +} + +#ifndef UTF8_ALPHA_CTYPE +/* + * UTF-8 ctype routines + * Only deals with characters < 0x80 (ie: US-ASCII) + */ + +int ldap_utf8_isascii( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + return LDAP_ASCII(c); +} + +int ldap_utf8_isdigit( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + + if(!LDAP_ASCII(c)) return 0; + + return LDAP_DIGIT( c ); +} + +int ldap_utf8_isxdigit( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + + if(!LDAP_ASCII(c)) return 0; + + return LDAP_HEX(c); +} + +int ldap_utf8_isspace( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + + if(!LDAP_ASCII(c)) return 0; + + switch(c) { + case ' ': + case '\t': + case '\n': + case '\r': + case '\v': + case '\f': + return 1; + } + + return 0; +} + +/* + * These are not needed by the C SDK and are + * not "good enough" for general use. + */ +int ldap_utf8_isalpha( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + + if(!LDAP_ASCII(c)) return 0; + + return LDAP_ALPHA(c); +} + +int ldap_utf8_isalnum( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + + if(!LDAP_ASCII(c)) return 0; + + return LDAP_ALNUM(c); +} + +int ldap_utf8_islower( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + + if(!LDAP_ASCII(c)) return 0; + + return LDAP_LOWER(c); +} + +int ldap_utf8_isupper( const char * p ) +{ + unsigned c = * (const unsigned char *) p; + + if(!LDAP_ASCII(c)) return 0; + + return LDAP_UPPER(c); +} +#endif + + +/* + * UTF-8 string routines + */ + +/* like strchr() */ +char * (ldap_utf8_strchr)( const char *str, const char *chr ) +{ + for( ; *str != '\0'; LDAP_UTF8_INCR(str) ) { + if( ldap_x_utf8_to_ucs4( str ) == ldap_x_utf8_to_ucs4( chr ) ) { + return (char *) str; + } + } + + return NULL; +} + +/* like strcspn() but returns number of bytes, not characters */ +ber_len_t (ldap_utf8_strcspn)( const char *str, const char *set ) +{ + const char *cstr; + const char *cset; + + for( cstr = str; *cstr != '\0'; LDAP_UTF8_INCR(cstr) ) { + for( cset = set; *cset != '\0'; LDAP_UTF8_INCR(cset) ) { + if( ldap_x_utf8_to_ucs4( cstr ) == ldap_x_utf8_to_ucs4( cset ) ) { + return cstr - str; + } + } + } + + return cstr - str; +} + +/* like strspn() but returns number of bytes, not characters */ +ber_len_t (ldap_utf8_strspn)( const char *str, const char *set ) +{ + const char *cstr; + const char *cset; + + for( cstr = str; *cstr != '\0'; LDAP_UTF8_INCR(cstr) ) { + for( cset = set; ; LDAP_UTF8_INCR(cset) ) { + if( *cset == '\0' ) { + return cstr - str; + } + + if( ldap_x_utf8_to_ucs4( cstr ) == ldap_x_utf8_to_ucs4( cset ) ) { + break; + } + } + } + + return cstr - str; +} + +/* like strpbrk(), replaces strchr() as well */ +char *(ldap_utf8_strpbrk)( const char *str, const char *set ) +{ + for( ; *str != '\0'; LDAP_UTF8_INCR(str) ) { + const char *cset; + + for( cset = set; *cset != '\0'; LDAP_UTF8_INCR(cset) ) { + if( ldap_x_utf8_to_ucs4( str ) == ldap_x_utf8_to_ucs4( cset ) ) { + return (char *) str; + } + } + } + + return NULL; +} + +/* like strtok_r(), not strtok() */ +char *(ldap_utf8_strtok)(char *str, const char *sep, char **last) +{ + char *begin; + char *end; + + if( last == NULL ) return NULL; + + begin = str ? str : *last; + + begin += ldap_utf8_strspn( begin, sep ); + + if( *begin == '\0' ) { + *last = NULL; + return NULL; + } + + end = &begin[ ldap_utf8_strcspn( begin, sep ) ]; + + if( *end != '\0' ) { + char *next = LDAP_UTF8_NEXT( end ); + *end = '\0'; + end = next; + } + + *last = end; + return begin; +} diff --git a/libraries/libldap/util-int.c b/libraries/libldap/util-int.c new file mode 100644 index 0000000..e65db0c --- /dev/null +++ b/libraries/libldap/util-int.c @@ -0,0 +1,901 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * Portions Copyright 1998 A. Hartgers. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Bart Hartgers for inclusion in + * OpenLDAP Software. + */ + +/* + * util-int.c Various functions to replace missing threadsafe ones. + * Without the real *_r funcs, things will + * work, but might not be threadsafe. + */ + +#include "portable.h" + +#include <ac/stdlib.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <ac/unistd.h> + +#include "ldap-int.h" + +#ifndef h_errno +/* newer systems declare this in <netdb.h> for you, older ones don't. + * harmless to declare it again (unless defined by a macro). + */ +extern int h_errno; +#endif + +#ifdef HAVE_HSTRERROR +# define HSTRERROR(e) hstrerror(e) +#else +# define HSTRERROR(e) hp_strerror(e) +#endif + +#ifndef LDAP_R_COMPILE +# undef HAVE_REENTRANT_FUNCTIONS +# undef HAVE_CTIME_R +# undef HAVE_GETHOSTBYNAME_R +# undef HAVE_GETHOSTBYADDR_R + +#else +# include <ldap_pvt_thread.h> + ldap_pvt_thread_mutex_t ldap_int_resolv_mutex; + ldap_pvt_thread_mutex_t ldap_int_hostname_mutex; + static ldap_pvt_thread_mutex_t ldap_int_gettime_mutex; + +# if (defined( HAVE_CTIME_R ) || defined( HAVE_REENTRANT_FUNCTIONS)) \ + && defined( CTIME_R_NARGS ) +# define USE_CTIME_R +# else + static ldap_pvt_thread_mutex_t ldap_int_ctime_mutex; +# endif + +/* USE_GMTIME_R and USE_LOCALTIME_R defined in ldap_pvt.h */ + +#ifdef LDAP_DEVEL + /* to be released with 2.5 */ +#if !defined( USE_GMTIME_R ) || !defined( USE_LOCALTIME_R ) + /* we use the same mutex for gmtime(3) and localtime(3) + * because implementations may use the same buffer + * for both functions */ + static ldap_pvt_thread_mutex_t ldap_int_gmtime_mutex; +#endif +#else /* ! LDAP_DEVEL */ + ldap_pvt_thread_mutex_t ldap_int_gmtime_mutex; +#endif /* ! LDAP_DEVEL */ + +# if defined(HAVE_GETHOSTBYNAME_R) && \ + (GETHOSTBYNAME_R_NARGS < 5) || (6 < GETHOSTBYNAME_R_NARGS) + /* Don't know how to handle this version, pretend it's not there */ +# undef HAVE_GETHOSTBYNAME_R +# endif +# if defined(HAVE_GETHOSTBYADDR_R) && \ + (GETHOSTBYADDR_R_NARGS < 7) || (8 < GETHOSTBYADDR_R_NARGS) + /* Don't know how to handle this version, pretend it's not there */ +# undef HAVE_GETHOSTBYADDR_R +# endif +#endif /* LDAP_R_COMPILE */ + +char *ldap_pvt_ctime( const time_t *tp, char *buf ) +{ +#ifdef USE_CTIME_R +# if (CTIME_R_NARGS > 3) || (CTIME_R_NARGS < 2) +# error "CTIME_R_NARGS should be 2 or 3" +# elif CTIME_R_NARGS > 2 && defined(CTIME_R_RETURNS_INT) + return( ctime_r(tp,buf,26) < 0 ? 0 : buf ); +# elif CTIME_R_NARGS > 2 + return ctime_r(tp,buf,26); +# else + return ctime_r(tp,buf); +# endif + +#else + + LDAP_MUTEX_LOCK( &ldap_int_ctime_mutex ); + AC_MEMCPY( buf, ctime(tp), 26 ); + LDAP_MUTEX_UNLOCK( &ldap_int_ctime_mutex ); + + return buf; +#endif +} + +#if !defined( USE_GMTIME_R ) || !defined( USE_LOCALTIME_R ) +int +ldap_pvt_gmtime_lock( void ) +{ +# ifndef LDAP_R_COMPILE + return 0; +# else /* LDAP_R_COMPILE */ + return ldap_pvt_thread_mutex_lock( &ldap_int_gmtime_mutex ); +# endif /* LDAP_R_COMPILE */ +} + +int +ldap_pvt_gmtime_unlock( void ) +{ +# ifndef LDAP_R_COMPILE + return 0; +# else /* LDAP_R_COMPILE */ + return ldap_pvt_thread_mutex_unlock( &ldap_int_gmtime_mutex ); +# endif /* LDAP_R_COMPILE */ +} +#endif /* !USE_GMTIME_R || !USE_LOCALTIME_R */ + +#ifndef USE_GMTIME_R +struct tm * +ldap_pvt_gmtime( const time_t *timep, struct tm *result ) +{ + struct tm *tm_ptr; + + LDAP_MUTEX_LOCK( &ldap_int_gmtime_mutex ); + tm_ptr = gmtime( timep ); + if ( tm_ptr == NULL ) { + result = NULL; + + } else { + *result = *tm_ptr; + } + LDAP_MUTEX_UNLOCK( &ldap_int_gmtime_mutex ); + + return result; +} +#endif /* !USE_GMTIME_R */ + +#ifndef USE_LOCALTIME_R +struct tm * +ldap_pvt_localtime( const time_t *timep, struct tm *result ) +{ + struct tm *tm_ptr; + + LDAP_MUTEX_LOCK( &ldap_int_gmtime_mutex ); + tm_ptr = localtime( timep ); + if ( tm_ptr == NULL ) { + result = NULL; + + } else { + *result = *tm_ptr; + } + LDAP_MUTEX_UNLOCK( &ldap_int_gmtime_mutex ); + + return result; +} +#endif /* !USE_LOCALTIME_R */ + +static int _ldap_pvt_gt_subs; + +#ifdef _WIN32 +/* Windows SYSTEMTIME only has 10 millisecond resolution, so we + * also need to use a high resolution timer to get microseconds. + * This is pretty clunky. + */ +static LARGE_INTEGER _ldap_pvt_gt_freq; +static LARGE_INTEGER _ldap_pvt_gt_prev; +static int _ldap_pvt_gt_offset; + +#define SEC_TO_UNIX_EPOCH 11644473600LL +#define TICKS_PER_SECOND 10000000 + +static int +ldap_pvt_gettimeusec(int *sec) +{ + LARGE_INTEGER count; + + QueryPerformanceCounter( &count ); + + /* It shouldn't ever go backwards, but multiple CPUs might + * be able to hit in the same tick. + */ + LDAP_MUTEX_LOCK( &ldap_int_gettime_mutex ); + /* We assume Windows has at least a vague idea of + * when a second begins. So we align our microsecond count + * with the Windows millisecond count using this offset. + * We retain the submillisecond portion of our own count. + * + * Note - this also assumes that the relationship between + * the PerformanceCounter and SystemTime stays constant; + * that assumption breaks if the SystemTime is adjusted by + * an external action. + */ + if ( !_ldap_pvt_gt_freq.QuadPart ) { + LARGE_INTEGER c2; + ULARGE_INTEGER ut; + FILETIME ft0, ft1; + long long t; + int usec; + + /* Initialize our offset */ + QueryPerformanceFrequency( &_ldap_pvt_gt_freq ); + + /* Wait for a tick of the system time: 10-15ms */ + GetSystemTimeAsFileTime( &ft0 ); + do { + GetSystemTimeAsFileTime( &ft1 ); + } while ( ft1.dwLowDateTime == ft0.dwLowDateTime ); + + ut.LowPart = ft1.dwLowDateTime; + ut.HighPart = ft1.dwHighDateTime; + QueryPerformanceCounter( &c2 ); + + /* get second and fraction portion of counter */ + t = c2.QuadPart % (_ldap_pvt_gt_freq.QuadPart*10); + + /* convert to microseconds */ + t *= 1000000; + usec = t / _ldap_pvt_gt_freq.QuadPart; + + ut.QuadPart /= 10; + ut.QuadPart %= 10000000; + _ldap_pvt_gt_offset = usec - ut.QuadPart; + count = c2; + } + if ( count.QuadPart <= _ldap_pvt_gt_prev.QuadPart ) { + _ldap_pvt_gt_subs++; + } else { + _ldap_pvt_gt_subs = 0; + _ldap_pvt_gt_prev = count; + } + LDAP_MUTEX_UNLOCK( &ldap_int_gettime_mutex ); + + /* convert to microseconds */ + count.QuadPart %= _ldap_pvt_gt_freq.QuadPart*10; + count.QuadPart *= 1000000; + count.QuadPart /= _ldap_pvt_gt_freq.QuadPart; + count.QuadPart -= _ldap_pvt_gt_offset; + + /* We've extracted the 1s and microseconds. + * The 1sec digit is used to detect wraparound in microsecnds. + */ + if (count.QuadPart < 0) + count.QuadPart += 10000000; + else if (count.QuadPart >= 10000000) + count.QuadPart -= 10000000; + + *sec = count.QuadPart / 1000000; + return count.QuadPart % 1000000; +} + + +/* emulate POSIX gettimeofday */ +int +ldap_pvt_gettimeofday( struct timeval *tv, void *unused ) +{ + FILETIME ft; + ULARGE_INTEGER ut; + int sec, sec0; + + GetSystemTimeAsFileTime( &ft ); + ut.LowPart = ft.dwLowDateTime; + ut.HighPart = ft.dwHighDateTime; + + /* convert to usec */ + ut.QuadPart /= (TICKS_PER_SECOND / 1000000); + + tv->tv_usec = ldap_pvt_gettimeusec(&sec); + tv->tv_sec = ut.QuadPart / 1000000 - SEC_TO_UNIX_EPOCH; + + /* check for carry from microseconds */ + sec0 = tv->tv_sec % 10; + if (sec0 < sec || (sec0 == 9 && !sec)) + tv->tv_sec++; + + return 0; +} + +/* return a broken out time, with microseconds + */ +void +ldap_pvt_gettime( struct lutil_tm *tm ) +{ + SYSTEMTIME st; + int sec, sec0; + static const char daysPerMonth[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + GetSystemTime( &st ); + tm->tm_usec = ldap_pvt_gettimeusec(&sec); + tm->tm_usub = _ldap_pvt_gt_subs; + + /* any difference larger than microseconds is + * already reflected in st + */ + tm->tm_sec = st.wSecond; + tm->tm_min = st.wMinute; + tm->tm_hour = st.wHour; + tm->tm_mday = st.wDay; + tm->tm_mon = st.wMonth - 1; + tm->tm_year = st.wYear - 1900; + + /* check for carry from microseconds */ + sec0 = tm->tm_sec % 10; + if (sec0 < sec || (sec0 == 9 && !sec)) { + tm->tm_sec++; + /* FIXME: we don't handle leap seconds */ + if (tm->tm_sec > 59) { + tm->tm_sec = 0; + tm->tm_min++; + if (tm->tm_min > 59) { + tm->tm_min = 0; + tm->tm_hour++; + if (tm->tm_hour > 23) { + int days = daysPerMonth[tm->tm_mon]; + tm->tm_hour = 0; + tm->tm_mday++; + + /* if it's February of a leap year, + * add 1 day to this month + */ + if (tm->tm_mon == 1 && + ((!(st.wYear % 4) && (st.wYear % 100)) || + !(st.wYear % 400))) + days++; + + if (tm->tm_mday > days) { + tm->tm_mday = 1; + tm->tm_mon++; + if (tm->tm_mon > 11) { + tm->tm_mon = 0; + tm->tm_year++; + } + } + } + } + } + } +} +#else + +static struct timeval _ldap_pvt_gt_prevTv; + +void +ldap_pvt_gettime( struct lutil_tm *ltm ) +{ + struct timeval tv; + + struct tm tm; + time_t t; + + gettimeofday( &tv, NULL ); + t = tv.tv_sec; + + LDAP_MUTEX_LOCK( &ldap_int_gettime_mutex ); + if ( tv.tv_sec < _ldap_pvt_gt_prevTv.tv_sec + || ( tv.tv_sec == _ldap_pvt_gt_prevTv.tv_sec + && tv.tv_usec <= _ldap_pvt_gt_prevTv.tv_usec )) { + _ldap_pvt_gt_subs++; + } else { + _ldap_pvt_gt_subs = 0; + _ldap_pvt_gt_prevTv = tv; + } + LDAP_MUTEX_UNLOCK( &ldap_int_gettime_mutex ); + + ltm->tm_usub = _ldap_pvt_gt_subs; + + ldap_pvt_gmtime( &t, &tm ); + + ltm->tm_sec = tm.tm_sec; + ltm->tm_min = tm.tm_min; + ltm->tm_hour = tm.tm_hour; + ltm->tm_mday = tm.tm_mday; + ltm->tm_mon = tm.tm_mon; + ltm->tm_year = tm.tm_year; + ltm->tm_usec = tv.tv_usec; +} +#endif + +size_t +ldap_pvt_csnstr(char *buf, size_t len, unsigned int replica, unsigned int mod) +{ + struct lutil_tm tm; + int n; + + ldap_pvt_gettime( &tm ); + + n = snprintf( buf, len, + "%4d%02d%02d%02d%02d%02d.%06dZ#%06x#%03x#%06x", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec, tm.tm_usec, tm.tm_usub, replica, mod ); + + if( n < 0 ) return 0; + return ( (size_t) n < len ) ? n : 0; +} + +#define BUFSTART (1024-32) +#define BUFMAX (32*1024-32) + +#if defined(LDAP_R_COMPILE) +static char *safe_realloc( char **buf, int len ); + +#if !(defined(HAVE_GETHOSTBYNAME_R) && defined(HAVE_GETHOSTBYADDR_R)) +static int copy_hostent( struct hostent *res, + char **buf, struct hostent * src ); +#endif +#endif + +int ldap_pvt_gethostbyname_a( + const char *name, + struct hostent *resbuf, + char **buf, + struct hostent **result, + int *herrno_ptr ) +{ +#if defined( HAVE_GETHOSTBYNAME_R ) + +# define NEED_SAFE_REALLOC 1 + int r=-1; + int buflen=BUFSTART; + *buf = NULL; + for(;buflen<BUFMAX;) { + if (safe_realloc( buf, buflen )==NULL) + return r; + +#if (GETHOSTBYNAME_R_NARGS < 6) + *result=gethostbyname_r( name, resbuf, *buf, buflen, herrno_ptr ); + r = (*result == NULL) ? -1 : 0; +#else + r = gethostbyname_r( name, resbuf, *buf, + buflen, result, herrno_ptr ); +#endif + + Debug( LDAP_DEBUG_TRACE, "ldap_pvt_gethostbyname_a: host=%s, r=%d\n", + name, r, 0 ); + +#ifdef NETDB_INTERNAL + if ((r<0) && + (*herrno_ptr==NETDB_INTERNAL) && + (errno==ERANGE)) + { + buflen*=2; + continue; + } +#endif + return r; + } + return -1; +#elif defined( LDAP_R_COMPILE ) +# define NEED_COPY_HOSTENT + struct hostent *he; + int retval; + *buf = NULL; + + LDAP_MUTEX_LOCK( &ldap_int_resolv_mutex ); + + he = gethostbyname( name ); + + if (he==NULL) { + *herrno_ptr = h_errno; + retval = -1; + } else if (copy_hostent( resbuf, buf, he )<0) { + *herrno_ptr = -1; + retval = -1; + } else { + *result = resbuf; + retval = 0; + } + + LDAP_MUTEX_UNLOCK( &ldap_int_resolv_mutex ); + + return retval; +#else + *buf = NULL; + *result = gethostbyname( name ); + + if (*result!=NULL) { + return 0; + } + + *herrno_ptr = h_errno; + + return -1; +#endif +} + +#if !defined( HAVE_GETNAMEINFO ) && !defined( HAVE_HSTRERROR ) +static const char * +hp_strerror( int err ) +{ + switch (err) { + case HOST_NOT_FOUND: return _("Host not found (authoritative)"); + case TRY_AGAIN: return _("Host not found (server fail?)"); + case NO_RECOVERY: return _("Non-recoverable failure"); + case NO_DATA: return _("No data of requested type"); +#ifdef NETDB_INTERNAL + case NETDB_INTERNAL: return STRERROR( errno ); +#endif + } + return _("Unknown resolver error"); +} +#endif + +int ldap_pvt_get_hname( + const struct sockaddr *sa, + int len, + char *name, + int namelen, + char **err ) +{ + int rc; +#if defined( HAVE_GETNAMEINFO ) + + LDAP_MUTEX_LOCK( &ldap_int_resolv_mutex ); + rc = getnameinfo( sa, len, name, namelen, NULL, 0, 0 ); + LDAP_MUTEX_UNLOCK( &ldap_int_resolv_mutex ); + if ( rc ) *err = (char *)AC_GAI_STRERROR( rc ); + return rc; + +#else /* !HAVE_GETNAMEINFO */ + char *addr; + int alen; + struct hostent *hp = NULL; +#ifdef HAVE_GETHOSTBYADDR_R + struct hostent hb; + int buflen=BUFSTART, h_errno; + char *buf=NULL; +#endif + +#ifdef LDAP_PF_INET6 + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa; + addr = (char *)&sin->sin6_addr; + alen = sizeof(sin->sin6_addr); + } else +#endif + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + addr = (char *)&sin->sin_addr; + alen = sizeof(sin->sin_addr); + } else { + rc = NO_RECOVERY; + *err = (char *)HSTRERROR( rc ); + return rc; + } +#if defined( HAVE_GETHOSTBYADDR_R ) + for(;buflen<BUFMAX;) { + if (safe_realloc( &buf, buflen )==NULL) { + *err = (char *)STRERROR( ENOMEM ); + return ENOMEM; + } +#if (GETHOSTBYADDR_R_NARGS < 8) + hp=gethostbyaddr_r( addr, alen, sa->sa_family, + &hb, buf, buflen, &h_errno ); + rc = (hp == NULL) ? -1 : 0; +#else + rc = gethostbyaddr_r( addr, alen, sa->sa_family, + &hb, buf, buflen, + &hp, &h_errno ); +#endif +#ifdef NETDB_INTERNAL + if ((rc<0) && + (h_errno==NETDB_INTERNAL) && + (errno==ERANGE)) + { + buflen*=2; + continue; + } +#endif + break; + } + if (hp) { + strncpy( name, hp->h_name, namelen ); + } else { + *err = (char *)HSTRERROR( h_errno ); + } + LDAP_FREE(buf); +#else /* HAVE_GETHOSTBYADDR_R */ + + LDAP_MUTEX_LOCK( &ldap_int_resolv_mutex ); + hp = gethostbyaddr( addr, alen, sa->sa_family ); + if (hp) { + strncpy( name, hp->h_name, namelen ); + rc = 0; + } else { + rc = h_errno; + *err = (char *)HSTRERROR( h_errno ); + } + LDAP_MUTEX_UNLOCK( &ldap_int_resolv_mutex ); + +#endif /* !HAVE_GETHOSTBYADDR_R */ + return rc; +#endif /* !HAVE_GETNAMEINFO */ +} + +int ldap_pvt_gethostbyaddr_a( + const char *addr, + int len, + int type, + struct hostent *resbuf, + char **buf, + struct hostent **result, + int *herrno_ptr ) +{ +#if defined( HAVE_GETHOSTBYADDR_R ) + +# undef NEED_SAFE_REALLOC +# define NEED_SAFE_REALLOC + int r=-1; + int buflen=BUFSTART; + *buf = NULL; + for(;buflen<BUFMAX;) { + if (safe_realloc( buf, buflen )==NULL) + return r; +#if (GETHOSTBYADDR_R_NARGS < 8) + *result=gethostbyaddr_r( addr, len, type, + resbuf, *buf, buflen, herrno_ptr ); + r = (*result == NULL) ? -1 : 0; +#else + r = gethostbyaddr_r( addr, len, type, + resbuf, *buf, buflen, + result, herrno_ptr ); +#endif + +#ifdef NETDB_INTERNAL + if ((r<0) && + (*herrno_ptr==NETDB_INTERNAL) && + (errno==ERANGE)) + { + buflen*=2; + continue; + } +#endif + return r; + } + return -1; +#elif defined( LDAP_R_COMPILE ) +# undef NEED_COPY_HOSTENT +# define NEED_COPY_HOSTENT + struct hostent *he; + int retval; + *buf = NULL; + + LDAP_MUTEX_LOCK( &ldap_int_resolv_mutex ); + he = gethostbyaddr( addr, len, type ); + + if (he==NULL) { + *herrno_ptr = h_errno; + retval = -1; + } else if (copy_hostent( resbuf, buf, he )<0) { + *herrno_ptr = -1; + retval = -1; + } else { + *result = resbuf; + retval = 0; + } + LDAP_MUTEX_UNLOCK( &ldap_int_resolv_mutex ); + + return retval; + +#else /* gethostbyaddr() */ + *buf = NULL; + *result = gethostbyaddr( addr, len, type ); + + if (*result!=NULL) { + return 0; + } + return -1; +#endif +} +/* + * ldap_int_utils_init() should be called before any other function. + */ + +void ldap_int_utils_init( void ) +{ + static int done=0; + if (done) + return; + done=1; + +#ifdef LDAP_R_COMPILE +#if !defined( USE_CTIME_R ) && !defined( HAVE_REENTRANT_FUNCTIONS ) + ldap_pvt_thread_mutex_init( &ldap_int_ctime_mutex ); +#endif +#if !defined( USE_GMTIME_R ) && !defined( USE_LOCALTIME_R ) + ldap_pvt_thread_mutex_init( &ldap_int_gmtime_mutex ); +#endif + ldap_pvt_thread_mutex_init( &ldap_int_resolv_mutex ); + + ldap_pvt_thread_mutex_init( &ldap_int_hostname_mutex ); + + ldap_pvt_thread_mutex_init( &ldap_int_gettime_mutex ); + +#ifdef HAVE_GSSAPI + ldap_pvt_thread_mutex_init( &ldap_int_gssapi_mutex ); +#endif +#endif + + /* call other module init functions here... */ +} + +#if defined( NEED_COPY_HOSTENT ) +# undef NEED_SAFE_REALLOC +#define NEED_SAFE_REALLOC + +static char *cpy_aliases( + char ***tgtio, + char *buf, + char **src ) +{ + int len; + char **tgt=*tgtio; + for( ; (*src) ; src++ ) { + len = strlen( *src ) + 1; + AC_MEMCPY( buf, *src, len ); + *tgt++=buf; + buf+=len; + } + *tgtio=tgt; + return buf; +} + +static char *cpy_addresses( + char ***tgtio, + char *buf, + char **src, + int len ) +{ + char **tgt=*tgtio; + for( ; (*src) ; src++ ) { + AC_MEMCPY( buf, *src, len ); + *tgt++=buf; + buf+=len; + } + *tgtio=tgt; + return buf; +} + +static int copy_hostent( + struct hostent *res, + char **buf, + struct hostent * src ) +{ + char **p; + char **tp; + char *tbuf; + int name_len; + int n_alias=0; + int total_alias_len=0; + int n_addr=0; + int total_addr_len=0; + int total_len; + + /* calculate the size needed for the buffer */ + name_len = strlen( src->h_name ) + 1; + + if( src->h_aliases != NULL ) { + for( p = src->h_aliases; (*p) != NULL; p++ ) { + total_alias_len += strlen( *p ) + 1; + n_alias++; + } + } + + if( src->h_addr_list != NULL ) { + for( p = src->h_addr_list; (*p) != NULL; p++ ) { + n_addr++; + } + total_addr_len = n_addr * src->h_length; + } + + total_len = (n_alias + n_addr + 2) * sizeof( char * ) + + total_addr_len + total_alias_len + name_len; + + if (safe_realloc( buf, total_len )) { + tp = (char **) *buf; + tbuf = *buf + (n_alias + n_addr + 2) * sizeof( char * ); + AC_MEMCPY( res, src, sizeof( struct hostent ) ); + /* first the name... */ + AC_MEMCPY( tbuf, src->h_name, name_len ); + res->h_name = tbuf; tbuf+=name_len; + /* now the aliases */ + res->h_aliases = tp; + if ( src->h_aliases != NULL ) { + tbuf = cpy_aliases( &tp, tbuf, src->h_aliases ); + } + *tp++=NULL; + /* finally the addresses */ + res->h_addr_list = tp; + if ( src->h_addr_list != NULL ) { + tbuf = cpy_addresses( &tp, tbuf, src->h_addr_list, src->h_length ); + } + *tp++=NULL; + return 0; + } + return -1; +} +#endif + +#if defined( NEED_SAFE_REALLOC ) +static char *safe_realloc( char **buf, int len ) +{ + char *tmpbuf; + tmpbuf = LDAP_REALLOC( *buf, len ); + if (tmpbuf) { + *buf=tmpbuf; + } + return tmpbuf; +} +#endif + +char * ldap_pvt_get_fqdn( char *name ) +{ + char *fqdn, *ha_buf; + char hostbuf[MAXHOSTNAMELEN+1]; + struct hostent *hp, he_buf; + int rc, local_h_errno; + + if( name == NULL ) { + if( gethostname( hostbuf, MAXHOSTNAMELEN ) == 0 ) { + hostbuf[MAXHOSTNAMELEN] = '\0'; + name = hostbuf; + } else { + name = "localhost"; + } + } + + rc = ldap_pvt_gethostbyname_a( name, + &he_buf, &ha_buf, &hp, &local_h_errno ); + + if( rc < 0 || hp == NULL || hp->h_name == NULL ) { + fqdn = LDAP_STRDUP( name ); + } else { + fqdn = LDAP_STRDUP( hp->h_name ); + } + + LDAP_FREE( ha_buf ); + return fqdn; +} + +#if ( defined( HAVE_GETADDRINFO ) || defined( HAVE_GETNAMEINFO ) ) \ + && !defined( HAVE_GAI_STRERROR ) +char *ldap_pvt_gai_strerror (int code) { + static struct { + int code; + const char *msg; + } values[] = { +#ifdef EAI_ADDRFAMILY + { EAI_ADDRFAMILY, N_("Address family for hostname not supported") }, +#endif + { EAI_AGAIN, N_("Temporary failure in name resolution") }, + { EAI_BADFLAGS, N_("Bad value for ai_flags") }, + { EAI_FAIL, N_("Non-recoverable failure in name resolution") }, + { EAI_FAMILY, N_("ai_family not supported") }, + { EAI_MEMORY, N_("Memory allocation failure") }, +#ifdef EAI_NODATA + { EAI_NODATA, N_("No address associated with hostname") }, +#endif + { EAI_NONAME, N_("Name or service not known") }, + { EAI_SERVICE, N_("Servname not supported for ai_socktype") }, + { EAI_SOCKTYPE, N_("ai_socktype not supported") }, +#ifdef EAI_SYSTEM + { EAI_SYSTEM, N_("System error") }, +#endif + { 0, NULL } + }; + + int i; + + for ( i = 0; values[i].msg != NULL; i++ ) { + if ( values[i].code == code ) { + return (char *) _(values[i].msg); + } + } + + return _("Unknown error"); +} +#endif diff --git a/libraries/libldap/vlvctrl.c b/libraries/libldap/vlvctrl.c new file mode 100644 index 0000000..abad783 --- /dev/null +++ b/libraries/libldap/vlvctrl.c @@ -0,0 +1,361 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Portions Copyright (C) 1999, 2000 Novell, Inc. All Rights Reserved. + * + * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND + * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT + * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS + * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE" + * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION + * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP + * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT + * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY. + *--- + * Note: A verbatim copy of version 2.0.1 of the OpenLDAP Public License + * can be found in the file "build/LICENSE-2.0.1" in this distribution + * of OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +#define LDAP_VLVBYINDEX_IDENTIFIER 0xa0L +#define LDAP_VLVBYVALUE_IDENTIFIER 0x81L +#define LDAP_VLVCONTEXT_IDENTIFIER 0x04L + + +/*--- + ldap_create_vlv_control + + Create and encode the Virtual List View control. + + ld (IN) An LDAP session handle. + + vlvinfop (IN) The address of an LDAPVLVInfo structure whose contents + are used to construct the value of the control + that is created. + + value (OUT) A struct berval that contains the value to be assigned to the ldctl_value member + of an LDAPControl structure that contains the + VirtualListViewRequest control. + The bv_val member of the berval structure + SHOULD be freed when it is no longer in use by + calling ldap_memfree(). + + + Ber encoding + + VirtualListViewRequest ::= SEQUENCE { + beforeCount INTEGER (0 .. maxInt), + afterCount INTEGER (0 .. maxInt), + CHOICE { + byoffset [0] SEQUENCE, { + offset INTEGER (0 .. maxInt), + contentCount INTEGER (0 .. maxInt) } + [1] greaterThanOrEqual assertionValue } + contextID OCTET STRING OPTIONAL } + + + Note: The first time the VLV control is created, the ldvlv_context + field of the LDAPVLVInfo structure should be set to NULL. + The context obtained from calling ldap_parse_vlv_control() + should be used as the context in the next ldap_create_vlv_control + call. + + ---*/ + +int +ldap_create_vlv_control_value( + LDAP *ld, + LDAPVLVInfo *vlvinfop, + struct berval *value ) +{ + ber_tag_t tag; + BerElement *ber; + + if ( ld == NULL || vlvinfop == NULL || value == NULL ) { + if ( ld ) + ld->ld_errno = LDAP_PARAM_ERROR; + return LDAP_PARAM_ERROR; + } + + assert( LDAP_VALID( ld ) ); + + value->bv_val = NULL; + value->bv_len = 0; + ld->ld_errno = LDAP_SUCCESS; + + ber = ldap_alloc_ber_with_options( ld ); + if ( ber == NULL ) { + ld->ld_errno = LDAP_NO_MEMORY; + return ld->ld_errno; + } + + tag = ber_printf( ber, "{ii" /*}*/, + vlvinfop->ldvlv_before_count, + vlvinfop->ldvlv_after_count ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + + if ( vlvinfop->ldvlv_attrvalue == NULL ) { + tag = ber_printf( ber, "t{iiN}", + LDAP_VLVBYINDEX_IDENTIFIER, + vlvinfop->ldvlv_offset, + vlvinfop->ldvlv_count ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + + } else { + tag = ber_printf( ber, "tO", + LDAP_VLVBYVALUE_IDENTIFIER, + vlvinfop->ldvlv_attrvalue ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + } + + if ( vlvinfop->ldvlv_context ) { + tag = ber_printf( ber, "tO", + LDAP_VLVCONTEXT_IDENTIFIER, + vlvinfop->ldvlv_context ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + } + + tag = ber_printf( ber, /*{*/ "N}" ); + if ( tag == LBER_ERROR ) { + goto error_return; + } + + if ( ber_flatten2( ber, value, 1 ) == -1 ) { + ld->ld_errno = LDAP_NO_MEMORY; + } + + if ( 0 ) { +error_return:; + ld->ld_errno = LDAP_ENCODING_ERROR; + } + + if ( ber != NULL ) { + ber_free( ber, 1 ); + } + + return ld->ld_errno; +} + +/*--- + ldap_create_vlv_control + + Create and encode the Virtual List View control. + + ld (IN) An LDAP session handle. + + vlvinfop (IN) The address of an LDAPVLVInfo structure whose contents + are used to construct the value of the control + that is created. + + ctrlp (OUT) A result parameter that will be assigned the address + of an LDAPControl structure that contains the + VirtualListViewRequest control created by this function. + The memory occupied by the LDAPControl structure + SHOULD be freed when it is no longer in use by + calling ldap_control_free(). + + + Ber encoding + + VirtualListViewRequest ::= SEQUENCE { + beforeCount INTEGER (0 .. maxInt), + afterCount INTEGER (0 .. maxInt), + CHOICE { + byoffset [0] SEQUENCE, { + offset INTEGER (0 .. maxInt), + contentCount INTEGER (0 .. maxInt) } + [1] greaterThanOrEqual assertionValue } + contextID OCTET STRING OPTIONAL } + + + Note: The first time the VLV control is created, the ldvlv_context + field of the LDAPVLVInfo structure should be set to NULL. + The context obtained from calling ldap_parse_vlv_control() + should be used as the context in the next ldap_create_vlv_control + call. + + ---*/ + +int +ldap_create_vlv_control( + LDAP *ld, + LDAPVLVInfo *vlvinfop, + LDAPControl **ctrlp ) +{ + struct berval value; + + if ( ctrlp == NULL ) { + ld->ld_errno = LDAP_PARAM_ERROR; + return ld->ld_errno; + } + + ld->ld_errno = ldap_create_vlv_control_value( ld, vlvinfop, &value ); + if ( ld->ld_errno == LDAP_SUCCESS ) { + + ld->ld_errno = ldap_control_create( LDAP_CONTROL_VLVREQUEST, + 1, &value, 0, ctrlp ); + if ( ld->ld_errno != LDAP_SUCCESS ) { + LDAP_FREE( value.bv_val ); + } + } + + return ld->ld_errno; +} + + +/*--- + ldap_parse_vlvresponse_control + + Decode the Virtual List View control return information. + + ld (IN) An LDAP session handle. + + ctrl (IN) The address of the LDAPControl structure. + + target_posp (OUT) This result parameter is filled in with the list + index of the target entry. If this parameter is + NULL, the target position is not returned. + + list_countp (OUT) This result parameter is filled in with the server's + estimate of the size of the list. If this parameter + is NULL, the size is not returned. + + contextp (OUT) This result parameter is filled in with the address + of a struct berval that contains the server- + generated context identifier if one was returned by + the server. If the server did not return a context + identifier, this parameter will be set to NULL, even + if an error occured. + The returned context SHOULD be used in the next call + to create a VLV sort control. The struct berval + returned SHOULD be disposed of by calling ber_bvfree() + when it is no longer needed. If NULL is passed for + contextp, the context identifier is not returned. + + errcodep (OUT) This result parameter is filled in with the VLV + result code. If this parameter is NULL, the result + code is not returned. + + + Ber encoding + + VirtualListViewResponse ::= SEQUENCE { + targetPosition INTEGER (0 .. maxInt), + contentCount INTEGER (0 .. maxInt), + virtualListViewResult ENUMERATED { + success (0), + operatonsError (1), + unwillingToPerform (53), + insufficientAccessRights (50), + busy (51), + timeLimitExceeded (3), + adminLimitExceeded (11), + sortControlMissing (60), + offsetRangeError (61), + other (80) }, + contextID OCTET STRING OPTIONAL } + +---*/ + +int +ldap_parse_vlvresponse_control( + LDAP *ld, + LDAPControl *ctrl, + ber_int_t *target_posp, + ber_int_t *list_countp, + struct berval **contextp, + ber_int_t *errcodep ) +{ + BerElement *ber; + ber_int_t pos, count, err; + ber_tag_t tag, berTag; + ber_len_t berLen; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + + if (contextp) { + *contextp = NULL; /* Make sure we return a NULL if error occurs. */ + } + + if (ctrl == NULL) { + ld->ld_errno = LDAP_PARAM_ERROR; + return(ld->ld_errno); + } + + if (strcmp(LDAP_CONTROL_VLVRESPONSE, ctrl->ldctl_oid) != 0) { + /* Not VLV Response control */ + ld->ld_errno = LDAP_CONTROL_NOT_FOUND; + return(ld->ld_errno); + } + + /* Create a BerElement from the berval returned in the control. */ + ber = ber_init(&ctrl->ldctl_value); + + if (ber == NULL) { + ld->ld_errno = LDAP_NO_MEMORY; + return(ld->ld_errno); + } + + /* Extract the data returned in the control. */ + tag = ber_scanf(ber, "{iie" /*}*/, &pos, &count, &err); + + if( tag == LBER_ERROR) { + ber_free(ber, 1); + ld->ld_errno = LDAP_DECODING_ERROR; + return(ld->ld_errno); + } + + + /* Since the context is the last item encoded, if caller doesn't want + it returned, don't decode it. */ + if (contextp) { + if (LDAP_VLVCONTEXT_IDENTIFIER == ber_peek_tag(ber, &berLen)) { + tag = ber_scanf(ber, "tO", &berTag, contextp); + + if( tag == LBER_ERROR) { + ber_free(ber, 1); + ld->ld_errno = LDAP_DECODING_ERROR; + return(ld->ld_errno); + } + } + } + + ber_free(ber, 1); + + /* Return data to the caller for items that were requested. */ + if (target_posp) *target_posp = pos; + if (list_countp) *list_countp = count; + if (errcodep) *errcodep = err; + + ld->ld_errno = LDAP_SUCCESS; + return(ld->ld_errno); +} diff --git a/libraries/libldap/whoami.c b/libraries/libldap/whoami.c new file mode 100644 index 0000000..533fe89 --- /dev/null +++ b/libraries/libldap/whoami.c @@ -0,0 +1,102 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * 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 the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This program was orignally developed by Kurt D. Zeilenga for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/stdlib.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "ldap-int.h" + +/* + * LDAP Who Am I? (Extended) Operation <draft-zeilenga-ldap-authzid-xx.txt> + */ + +int ldap_parse_whoami( + LDAP *ld, + LDAPMessage *res, + struct berval **authzid ) +{ + int rc; + char *retoid = NULL; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( res != NULL ); + assert( authzid != NULL ); + + *authzid = NULL; + + rc = ldap_parse_extended_result( ld, res, &retoid, authzid, 0 ); + + if( rc != LDAP_SUCCESS ) { + ldap_perror( ld, "ldap_parse_whoami" ); + return rc; + } + + ber_memfree( retoid ); + return rc; +} + +int +ldap_whoami( LDAP *ld, + LDAPControl **sctrls, + LDAPControl **cctrls, + int *msgidp ) +{ + int rc; + + assert( ld != NULL ); + assert( LDAP_VALID( ld ) ); + assert( msgidp != NULL ); + + rc = ldap_extended_operation( ld, LDAP_EXOP_WHO_AM_I, + NULL, sctrls, cctrls, msgidp ); + + return rc; +} + +int +ldap_whoami_s( + LDAP *ld, + struct berval **authzid, + LDAPControl **sctrls, + LDAPControl **cctrls ) +{ + int rc; + int msgid; + LDAPMessage *res; + + rc = ldap_whoami( ld, sctrls, cctrls, &msgid ); + if ( rc != LDAP_SUCCESS ) return rc; + + if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *) NULL, &res ) == -1 || !res ) { + return ld->ld_errno; + } + + rc = ldap_parse_whoami( ld, res, authzid ); + if( rc != LDAP_SUCCESS ) { + ldap_msgfree( res ); + return rc; + } + + return( ldap_result2error( ld, res, 1 ) ); +} |