From bc65d18e2bff3d3f504832994128f87046d1a89c Mon Sep 17 00:00:00 2001 From: Kurt Zeilenga Date: Sun, 24 Feb 2002 00:24:43 +0000 Subject: [PATCH] C portability from HEAD --- libraries/libldap/getdn.c | 3346 +++++++++++++++++++++++++++++++-- libraries/liblunicode/ucstr.c | 7 +- servers/slapd/acl.c | 2052 +++++++++++++++++--- servers/slapd/backglue.c | 907 +++++++++ servers/slapd/entry.c | 622 +++++- servers/slapd/sets.c | 401 ++++ servers/slapd/tools/slapadd.c | 185 ++ 7 files changed, 6955 insertions(+), 565 deletions(-) create mode 100644 servers/slapd/backglue.c create mode 100644 servers/slapd/sets.c create mode 100644 servers/slapd/tools/slapadd.c diff --git a/libraries/libldap/getdn.c b/libraries/libldap/getdn.c index 2ea0d037c4..e0d36154a6 100644 --- a/libraries/libldap/getdn.c +++ b/libraries/libldap/getdn.c @@ -1,33 +1,83 @@ +/* $OpenLDAP$ */ /* + * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ +/* Portions * Copyright (c) 1994 Regents of the University of Michigan. * All rights reserved. * * getdn.c */ -#ifndef lint -static char copyright[] = "@(#) Copyright (c) 1990 Regents of the University of Michigan.\nAll rights reserved.\n"; -#endif +#include "portable.h" #include -#include -#include -#ifdef MACOS -#include -#include "macos.h" -#else /* MACOS */ -#if defined( DOS ) || defined( _WIN32 ) -#include -#include "msdos.h" -#else /* DOS */ -#include -#include -#endif /* DOS */ -#endif /* MACOS */ - -#include "lber.h" -#include "ldap.h" +#include +#include +#include +#include + +#include "ldap-int.h" +#include "ldap_schema.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 +#define PRETTY_ESCAPE + +/* parsing/printing routines */ +static int str2strval( const char *str, ber_len_t stoplen, struct berval *val, + const char **next, unsigned flags, unsigned *retFlags ); +static int DCE2strval( const char *str, struct berval *val, + const char **next, unsigned flags ); +static int IA52strval( const char *str, struct berval *val, + const char **next, unsigned flags ); +static int quotedIA52strval( const char *str, struct berval *val, + const char **next, unsigned flags ); +static int hexstr2binval( const char *str, struct berval *val, + const char **next, unsigned flags ); +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 ); + +/* 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 ) { @@ -42,7 +92,7 @@ ldap_get_dn( LDAP *ld, LDAPMessage *entry ) } tmp = *entry->lm_ber; /* struct copy */ - if ( ber_scanf( &tmp, "{a", &dn ) == LBER_ERROR ) { + if ( ber_scanf( &tmp, "{a" /*}*/, &dn ) == LBER_ERROR ) { ld->ld_errno = LDAP_DECODING_ERROR; return( NULL ); } @@ -50,220 +100,3142 @@ ldap_get_dn( LDAP *ld, LDAPMessage *entry ) return( dn ); } +/* + * RFC 1823 ldap_dn2ufn + */ char * -ldap_dn2ufn( char *dn ) +ldap_dn2ufn( LDAP_CONST char *dn ) { - char *p, *ufn, *r; - int state; + char *out = NULL; Debug( LDAP_DEBUG_TRACE, "ldap_dn2ufn\n", 0, 0, 0 ); - if ( ldap_is_dns_dn( dn ) || ( p = strchr( dn, '=' )) == NULL ) - return( strdup( dn )); + ( 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; + } - ufn = strdup( ++p ); + if( tmpDN == NULL ) { + values = LDAP_MALLOC( sizeof( char * ) ); + if( values == NULL ) return NULL; -#define INQUOTE 1 -#define OUTQUOTE 2 - state = OUTQUOTE; - for ( p = ufn, r = ufn; *p; p++ ) { - switch ( *p ) { - case '\\': - if ( *++p == '\0' ) - p--; - else { - *r++ = '\\'; - *r++ = *p; - } - break; - case '"': - if ( state == INQUOTE ) - state = OUTQUOTE; - else - state = INQUOTE; - *r++ = *p; - break; - case ';': - case ',': - if ( state == OUTQUOTE ) - *r++ = ','; - else - *r++ = *p; - break; - case '=': - if ( state == INQUOTE ) - *r++ = *p; - else { - char *rsave = r; - - *r-- = '\0'; - while ( !isspace( *r ) && *r != ';' - && *r != ',' && r > ufn ) - r--; - r++; - - if ( strcasecmp( r, "c" ) - && strcasecmp( r, "o" ) - && strcasecmp( r, "ou" ) - && strcasecmp( r, "st" ) - && strcasecmp( r, "l" ) - && strcasecmp( r, "cn" ) ) { - r = rsave; - *r++ = '='; - } - } - break; - default: - *r++ = *p; - break; - } + values[0] = NULL; + return values; + } + + for ( iRDN = 0; tmpDN[ 0 ][ iRDN ]; iRDN++ ); + + values = LDAP_MALLOC( sizeof( char * ) * ( 1 + iRDN ) ); + if ( values == NULL ) { + ldap_dnfree( tmpDN ); + return NULL; + } + + for ( iRDN = 0; tmpDN[ 0 ][ iRDN ]; iRDN++ ) { + ldap_rdn2str( tmpDN[ 0 ][ iRDN ], &values[ iRDN ], flag ); } - *r = '\0'; + ldap_dnfree( tmpDN ); + values[ iRDN ] = NULL; - return( ufn ); + return values; } char ** -ldap_explode_dns( char *dn ) +ldap_explode_rdn( LDAP_CONST char *rdn, int notypes ) { - int ncomps, maxcomps; - char *s; - char **rdns; + LDAPRDN *tmpRDN; + char **values = NULL; + const char *p; + int iAVA; + + Debug( LDAP_DEBUG_TRACE, "ldap_explode_rdn\n", 0, 0, 0 ); - if ( (rdns = (char **) malloc( 8 * sizeof(char *) )) == NULL ) { + /* + * 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 ); } - maxcomps = 8; - ncomps = 0; - for ( s = strtok( dn, "@." ); s != NULL; s = strtok( NULL, "@." ) ) { - if ( ncomps == maxcomps ) { - maxcomps *= 2; - if ( (rdns = (char **) realloc( rdns, maxcomps * - sizeof(char *) )) == NULL ) { - return( NULL ); + for ( iAVA = 0; tmpRDN[ 0 ][ iAVA ]; iAVA++ ) ; + values = LDAP_MALLOC( sizeof( char * ) * ( 1 + iAVA ) ); + if ( values == NULL ) { + ldap_rdnfree( tmpRDN ); + return( NULL ); + } + + for ( iAVA = 0; tmpRDN[ 0 ][ iAVA ]; iAVA++ ) { + ber_len_t l = 0, vl, al = 0; + char *str; + LDAPAVA *ava = tmpRDN[ 0 ][ 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; } } - rdns[ncomps++] = strdup( s ); + + 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; } - rdns[ncomps] = NULL; + values[ iAVA ] = NULL; - return( rdns ); + ldap_rdnfree( tmpRDN ); + + return( values ); + +error_return:; + LBER_VFREE( values ); + ldap_rdnfree( tmpRDN ); + return( NULL ); } -char ** -ldap_explode_dn( char *dn, int notypes ) +char * +ldap_dn2dcedn( LDAP_CONST char *dn ) { - char *p, *q, *rdnstart, **rdns = NULL; - int state, count = 0, endquote, len; + char *out = NULL; - Debug( LDAP_DEBUG_TRACE, "ldap_explode_dn\n", 0, 0, 0 ); + 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 2253 and ldapbis liberal, + * plus some rfc 1779) + * LDAP_DN_FORMAT_LDAPV3 (rfc 2253 and ldapbis) + * 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 ); + + *dnout = NULL; + + if ( dnin == NULL ) { + return( LDAP_SUCCESS ); + } - if ( ldap_is_dns_dn( dn ) ) { - return( ldap_explode_dns( dn ) ); + rc = ldap_str2dn( dnin , &tmpDN, fin ); + if ( rc != LDAP_SUCCESS ) { + return( rc ); } - rdnstart = dn; - p = dn-1; - state = OUTQUOTE; + rc = ldap_dn2str( tmpDN, dnout, fout ); - do { + ldap_dnfree( tmpDN ); - ++p; - switch ( *p ) { - case '\\': - if ( *++p == '\0' ) - p--; - break; - case '"': - if ( state == INQUOTE ) - state = OUTQUOTE; - else - state = INQUOTE; - break; - case ';': - case ',': - case '\0': - if ( state == OUTQUOTE ) { - ++count; - if ( rdns == NULL ) { - if (( rdns = (char **)malloc( 8 - * sizeof( char *))) == NULL ) - return( NULL ); - } else if ( count >= 8 ) { - if (( rdns = (char **)realloc( rdns, - (count+1) * sizeof( char *))) - == NULL ) - return( NULL ); - } - rdns[ count ] = NULL; - endquote = 0; - if ( notypes ) { - for ( q = rdnstart; - q < p && *q != '='; ++q ) { - ; - } - if ( q < p ) { - rdnstart = ++q; - } - if ( *rdnstart == '"' ) { - ++rdnstart; - } - - if ( *(p-1) == '"' ) { - endquote = 1; - --p; - } + 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) ) +#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_ASCII_SPACE(c) || LDAP_DN_OCTOTHORPE(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][0] && !(r)[0][1] \ + && ((r)[0][0]->la_flags == LDAP_AVA_STRING) \ + && ((r)[0][0]->la_attr.bv_len == 2) \ + && (((r)[0][0]->la_attr.bv_val[0] == LDAP_DC_ATTR[0]) \ + || ((r)[0][0]->la_attr.bv_val[0] == LDAP_DC_ATTRU[0])) \ + && (((r)[0][0]->la_attr.bv_val[1] == LDAP_DC_ATTR[1]) \ + || ((r)[0][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). + */ +LDAPAVA * +ldapava_new( const struct berval *attr, const struct berval *val, + unsigned flags ) +{ + LDAPAVA *ava; + + assert( attr ); + assert( val ); + + ava = LDAP_MALLOC( sizeof( LDAPAVA ) + attr->bv_len + 1 ); + + /* should we test it? */ + if ( ava == NULL ) { + return( NULL ); + } + + 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; + + ava->la_private = NULL; + + return( ava ); +} + +void +ldap_avafree( LDAPAVA *ava ) +{ + assert( ava ); + +#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 0 + /* la_attr is now contiguous with ava, not freed separately */ + LDAP_FREE( ava->la_attr.bv_val ); +#endif + LDAP_FREE( ava->la_value.bv_val ); + + LDAP_FREE( ava ); +} + +void +ldap_rdnfree( LDAPRDN *rdn ) +{ + int iAVA; + + if ( rdn == NULL ) { + return; + } + + for ( iAVA = 0; rdn[ 0 ][ iAVA ]; iAVA++ ) { + ldap_avafree( rdn[ 0 ][ iAVA ] ); + } + + LDAP_FREE( rdn ); +} + +void +ldap_dnfree( LDAPDN *dn ) +{ + int iRDN; + + if ( dn == NULL ) { + return; + } + + for ( iRDN = 0; dn[ 0 ][ iRDN ]; iRDN++ ) { + ldap_rdnfree( dn[ 0 ][ iRDN ] ); + } + + LDAP_FREE( dn ); +} + +/* + * 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 ); + + bv.bv_len = strlen( str ); + bv.bv_val = (char *) str; + + return ldap_bv2dn( &bv, dn, flags ); +} + +int +ldap_bv2dn( struct berval *bv, LDAPDN **dn, unsigned flags ) +{ + 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 = bv->bv_val; + char *end = str + bv->bv_len; + + assert( bv ); + assert( bv->bv_val ); + assert( dn ); + + Debug( LDAP_DEBUG_TRACE, "=> ldap_bv2dn(%s,%u)\n%s", str, flags, "" ); + + *dn = 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; + 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( &tmpbv, &newRDN, (char **) &p, flags ); + 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; + } + } + - len = p - rdnstart; - if (( rdns[ count-1 ] = (char *)calloc( 1, - len + 1 )) != NULL ) { - SAFEMEMCPY( rdns[ count-1 ], rdnstart, - len ); - rdns[ count-1 ][ len ] = '\0'; + tmpDN[nrdns++] = newRDN; + newRDN = NULL; + + /* + * make the static RDN array dynamically rescalable + */ + if ( nrdns == num_slots ) { + LDAPRDN **tmp; + + if ( tmpDN == tmpDN_ ) { + tmp = LDAP_MALLOC( num_slots * 2 * sizeof( LDAPRDN * ) ); + if ( tmp == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; } + AC_MEMCPY( tmp, tmpDN, num_slots * sizeof( LDAPRDN * ) ); - /* - * Don't forget to increment 'p' back to where - * it should be. If we don't, then we will - * never get past an "end quote." - */ - if ( endquote == 1 ) - p++; + } else { + tmp = LDAP_REALLOC( tmpDN, num_slots * 2 * sizeof( LDAPRDN * ) ); + 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_MALLOC( sizeof(LDAPDN) + + sizeof(LDAPRDN *) * (nrdns+1)); + if ( newDN == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } else { + int i; + + newDN[0] = (LDAPRDN **)(newDN+1); - rdnstart = *p ? p + 1 : p; - while ( isspace( *rdnstart )) - ++rdnstart; + if ( LDAP_DN_DCE( flags ) ) { + /* add in reversed order */ + for ( i=0; i= 0; nrdns-- ) { + ldap_rdnfree( tmpDN[nrdns] ); + } +return_result:; + + if ( tmpDN != tmpDN_ ) { + LDAP_FREE( tmpDN ); + } + + Debug( LDAP_DEBUG_TRACE, "<= ldap_bv2dn(%s,%u)=%d\n", str, flags, 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_is_dns_dn( char *dn ) +ldap_str2rdn( LDAP_CONST char *str, LDAPRDN **rdn, + char **n_in, unsigned flags ) { - return( dn[ 0 ] != '\0' && strchr( dn, '=' ) == NULL && - strchr( dn, ',' ) == NULL ); -} + struct berval bv; + assert( str ); + assert( str[ 0 ] != '\0' ); /* FIXME: is this required? */ -#if defined( ultrix ) || defined( NeXT ) + bv.bv_len = strlen( str ); + bv.bv_val = (char *) str; -char *strdup( char *s ) + return ldap_bv2rdn( &bv, rdn, n_in, flags ); +} + +int +ldap_bv2rdn( struct berval *bv, LDAPRDN **rdn, + char **n_in, unsigned flags ) { - char *p; + 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; - if ( (p = (char *) malloc( strlen( s ) + 1 )) == NULL ) - return( NULL ); + struct berval attrType = { 0, NULL }; + struct berval attrValue = { 0, NULL }; + + LDAPRDN *newRDN = NULL; + LDAPAVA *tmpRDN_[TMP_AVA_SLOTS], **tmpRDN = tmpRDN_; + int num_slots = TMP_AVA_SLOTS; + + char *str; + ber_len_t stoplen; + + assert( bv ); + assert( bv->bv_len ); + assert( bv->bv_val ); + assert( rdn || flags & LDAP_DN_SKIP ); + assert( n ); + + 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 2.3 of draft-ietf-ldapbis-dn-XX.txt, and + * MAY recognize other names. + * - its numeric OID (a dotted decimal string); in detail + * RFC 2253 asserts that ``Implementations MUST allow + * an oid in the attribute type to be prefixed by one + * of the character strings "oid." or "OID."''. As soon + * as draft-ietf-ldapbis-dn-XX.txt obsoletes RFC 2253 + * I'm not sure whether this is required or not any + * longer; to be liberal, we still implement it. + */ + 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 2253 does not explicitly + * allow lang extensions to attribute + * types in DNs ... + */ + 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 2253 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 ) ) { + 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 ) ) { + goto parsing_error; + } + break; + + case LDAP_DN_FORMAT_DCE: + if ( DCE2strval( p, &attrValue, &p, flags ) ) { + goto parsing_error; + } + break; + + default: + assert( 0 ); + } + + state = GOTAVA; + break; + + case B4IA5VALUE: + if ( IA52strval( p, &attrValue, &p, flags ) ) { + goto parsing_error; + } + + state = GOTAVA; + break; + + case B4IA5VALUEQUOTED: + + /* lead quote already stripped */ + if ( quotedIA52strval( p, &attrValue, + &p, flags ) ) { + 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 ); + + 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_MALLOC( num_slots * 2 * sizeof( LDAPAVA * ) ); + if ( tmp == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + AC_MEMCPY( tmp, tmpRDN, num_slots * sizeof( LDAPAVA * ) ); + + } else { + tmp = LDAP_REALLOC( tmpRDN, num_slots * 2 * sizeof( LDAPAVA * ) ); + 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_MALLOC( sizeof(LDAPRDN) + + sizeof(LDAPAVA *) * (navas+1) ); + if ( newRDN == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } else { + int i; + + newRDN[0] = (LDAPAVA**)(newRDN+1); + + for (i=0; i= 0; navas-- ) { + ldap_avafree( tmpRDN[navas] ); + } + +return_result:; + + if ( tmpRDN != tmpRDN_ ) { + LDAP_FREE( tmpRDN ); + } + + 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, unsigned *retFlags ) +{ + const char *p, *end, *startPos, *endPos = NULL; + ber_len_t len, escapes; + + assert( str ); + assert( val ); + assert( next ); + + *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_MALLOC( len + 1 ); + AC_MEMCPY( val->bv_val, startPos, len ); + val->bv_val[ len ] = '\0'; + } else { + val->bv_val = LDAP_STRNDUP( startPos, len ); + } + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOC( len + 1 ); + 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 ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len, escapes; + + assert( str ); + assert( val ); + assert( next ); + + *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_STRNDUP( startPos, len ); + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOC( len + 1 ); + 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 ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len, escapes; + + assert( str ); + assert( val ); + assert( next ); + + *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_STRNDUP( startPos, len ); + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOC( len + 1 ); + 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 ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len; + unsigned escapes = 0; + + assert( str ); + assert( val ); + assert( next ); + + *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( len >= 0 ); + val->bv_len = len; + if ( escapes == 0 ) { + val->bv_val = LDAP_STRNDUP( startPos, len ); + + } else { + ber_len_t s, d; + + val->bv_val = LDAP_MALLOC( len + 1 ); + 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 ); + assert( c ); + + 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 ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len; + ber_len_t s, d; + + assert( str ); + assert( val ); + assert( next ); + + *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_MALLOC( len + 1 ); + 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 ); + assert( pair ); + + /* + * 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 ); + assert( str ); + + 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; + 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 ); + assert( len ); + + *len = 0; + if ( val->bv_len == 0 ) { + return( 0 ); + } + + for ( l = 0, p = val->bv_val; p < val->bv_val + val->bv_len; 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 ] ) + || ( p == val->bv_val && LDAP_DN_NEEDESCAPE_LEAD( p[ 0 ] ) ) + || ( !p[ 1 ] && 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 ); + assert( str ); + assert( len ); + + 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(); + * LDAP_UTF8_CHARLEN() should suffice + */ + cl = LDAP_UTF8_CHARLEN2( &val->bv_val[ s ], cl ); + assert( cl > 0 ); + + /* + * 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 ] ) + || ( 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 ] ) + || ( 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 ); + assert( len ); + + *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 ] ) + || ( 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 ); + assert( str ); + assert( len ); + + 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 ] ) + || ( 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 ); + assert( len ); + + *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 ); + assert( str ); + assert( len ); + + 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 escaped hex of UTF-8 chars + */ +static int +strval2ADstrlen( struct berval *val, unsigned flags, ber_len_t *len ) +{ + ber_len_t l; + char *p; + + assert( val ); + assert( len ); + + *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_AD( p[ 0 ] ) ) { + l += 2; + + } else { + l++; + } + } + } + + *len = l; + + return( 0 ); +} + +/* + * convert to (supposedly) AD string representation, + * escaping with hex the UTF-8 stuff; + * 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; + + assert( val ); + assert( str ); + assert( len ); + + 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_AD( val->bv_val[ s ] ) ) { + str[ d++ ] = '\\'; + } + 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 ); + assert( bv ); + assert( iRDN ); + assert( *iRDN >= 0 ); + + str = bv->bv_val + pos; + + for ( i = *iRDN; i >= 0; i-- ) { + LDAPRDN *rdn; + LDAPAVA *ava; + + assert( dn[ 0 ][ i ] ); + rdn = dn[ 0 ][ i ]; + + assert( rdn[ 0 ][ 0 ] ); + ava = rdn[ 0 ][ 0 ]; + + if ( !LDAP_DN_IS_RDN_DC( rdn ) ) { + 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[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ 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[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ 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[ 0 ][ 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[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ iAVA ]; + + /* len(type) + '=' + ',' | '/' */ + l += ava->la_attr.bv_len + 2; + + switch ( ava->la_flags ) { + case LDAP_AVA_BINARY: + /* octothorpe + twice the length */ + l += 1 + 2 * ava->la_value.bv_len; + break; + + case LDAP_AVA_STRING: { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2DCEstrlen( &ava->la_value, f, &vl ) ) { + return( -1 ); + } + l += vl; + break; + } + + default: + return( -1 ); + } + } + + *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[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ 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++ ] = '='; + + switch ( ava->la_flags ) { + case LDAP_AVA_BINARY: + str[ l++ ] = '#'; + if ( binval2hexstr( &ava->la_value, &str[ l ] ) ) { + return( -1 ); + } + l += 2 * ava->la_value.bv_len; + break; + + case LDAP_AVA_STRING: { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2DCEstr( &ava->la_value, &str[ l ], f, &vl ) ) { + return( -1 ); + } + l += vl; + break; + } + + default: + return( -1 ); + } + } + + *len = l; + + return( 0 ); +} + +static int +rdn2UFNstrlen( LDAPRDN *rdn, unsigned flags, ber_len_t *len ) +{ + int iAVA; + ber_len_t l = 0; + + assert( rdn ); + assert( len ); + + *len = 0; + + for ( iAVA = 0; rdn[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ iAVA ]; + + /* ' + ' | ', ' */ + l += ( rdn[ 0 ][ 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[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ 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[ 0 ][ 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 ); + assert( len ); + + *len = 0; + + for ( iAVA = 0; rdn[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ iAVA ]; + + /* ',' | '/' */ + l++; + + /* FIXME: are binary values allowed in UFN? */ + switch ( ava->la_flags ) { + case LDAP_AVA_BINARY: + /* octothorpe + twice the value */ + l += 1 + 2 * ava->la_value.bv_len; + break; + + case LDAP_AVA_STRING: { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2ADstrlen( &ava->la_value, f, &vl ) ) { + return( -1 ); + } + l += vl; + break; + } + + default: + return( -1 ); + } + } + + *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[ 0 ][ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ 0 ][ iAVA ]; + + if ( first ) { + first = 0; + } else { + str[ l++ ] = ( iAVA ? ',' : '/' ); + } + + switch ( ava->la_flags ) { + case LDAP_AVA_BINARY: + str[ l++ ] = '#'; + if ( binval2hexstr( &ava->la_value, &str[ l ] ) ) { + return( -1 ); + } + l += 2 * ava->la_value.bv_len; + break; + + case LDAP_AVA_STRING: { + ber_len_t vl; + unsigned f = flags | ava->la_flags; + + if ( strval2ADstr( &ava->la_value, &str[ l ], f, &vl ) ) { + return( -1 ); + } + l += vl; + break; + } + + default: + return( -1 ); + } + } + + *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 ); + + if((flags & LDAP_DN_FORMAT_MASK) == LDAP_DN_FORMAT_LBER) { + return LDAP_PARAM_ERROR; + } + + rc = ldap_rdn2bv( rdn, &bv, flags ); + *str = bv.bv_val; + return rc; +} + +int +ldap_rdn2bv( LDAPRDN *rdn, struct berval *bv, unsigned flags ) +{ + int rc, back; + ber_len_t l; + + assert( bv ); + + bv->bv_len = 0; + bv->bv_val = NULL; + + if ( rdn == NULL ) { + bv->bv_val = LDAP_STRDUP( "" ); + 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_MALLOC( l + 1 ); + + 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_memfree( bv->bv_val ); + 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 ); + + if((flags & LDAP_DN_FORMAT_MASK) == LDAP_DN_FORMAT_LBER) { + return LDAP_PARAM_ERROR; + } + + rc = ldap_dn2bv( dn, &bv, flags ); + *str = bv.bv_val; + return rc; +} + +int ldap_dn2bv( LDAPDN *dn, struct berval *bv, unsigned flags ) +{ + 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 ); + bv->bv_len = 0; + bv->bv_val = NULL; + + Debug( LDAP_DEBUG_TRACE, "=> ldap_dn2bv(%u)\n%s%s", flags, "", "" ); + + /* + * a null dn means an empty dn string + * FIXME: better raise an error? + */ + if ( dn == NULL ) { + bv->bv_val = LDAP_STRDUP( "" ); + 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[ 0 ][ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2strlen( rdn, flags, &rdnl, sv2l ) ) { + goto return_results; + } + + len += rdnl; + } + + if ( ( bv->bv_val = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + + for ( l = 0, iRDN = 0; dn[ 0 ][ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2str( rdn, &bv->bv_val[ l ], flags, + &rdnl, sv2s ) ) { + LDAP_FREE( bv->bv_val ); + 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[ 0 ][ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2UFNstrlen( rdn, flags, &rdnl ) ) { + goto return_results; + } + len += rdnl; + +#ifdef DC_IN_UFN + if ( LDAP_DN_IS_RDN_DC( rdn ) ) { + if ( leftmost_dc == -1 ) { + leftmost_dc = iRDN; + } + } else { + leftmost_dc = -1; + } +#endif /* DC_IN_UFN */ + } + + if ( ( bv->bv_val = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + +#ifdef DC_IN_UFN + if ( leftmost_dc == -1 ) { +#endif /* DC_IN_UFN */ + for ( l = 0, iRDN = 0; dn[ 0 ][ iRDN ]; iRDN++ ) { + ber_len_t vl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2UFNstr( rdn, &bv->bv_val[ l ], + flags, &vl ) ) { + LDAP_FREE( bv->bv_val ); + 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; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2UFNstr( rdn, &bv->bv_val[ l ], + flags, &vl ) ) { + LDAP_FREE( bv->bv_val ); + bv->bv_val = NULL; + goto return_results; + } + l += vl; + } + + if ( !dn2domain( dn, bv, l, &last_iRDN ) ) { + LDAP_FREE( bv->bv_val ); + 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[ 0 ][ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2DCEstrlen( rdn, flags, &rdnl ) ) { + goto return_results; + } + + len += rdnl; + } + + if ( ( bv->bv_val = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + + for ( l = 0; iRDN--; ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2DCEstr( rdn, &bv->bv_val[ l ], flags, + &rdnl, 0 ) ) { + LDAP_FREE( bv->bv_val ); + 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: { + /* + * 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[ 0 ][ iRDN ]; iRDN++ ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2ADstrlen( rdn, flags, &rdnl ) ) { + goto return_results; + } + + len += rdnl; + } + + if ( ( bv->bv_val = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + + iRDN--; + if ( iRDN && dn2domain( dn, bv, 0, &iRDN ) ) { + for ( l = bv->bv_len; iRDN >= 0 ; iRDN-- ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2ADstr( rdn, &bv->bv_val[ l ], + flags, &rdnl, 0 ) ) { + LDAP_FREE( bv->bv_val ); + 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_FREE( bv->bv_val ); + bv->bv_val = NULL; + rc = LDAP_ENCODING_ERROR; + break; + } + + for ( l = 0; iRDN >= 0 ; iRDN-- ) { + ber_len_t rdnl; + LDAPRDN *rdn = dn[ 0 ][ iRDN ]; + + if ( rdn2ADstr( rdn, &bv->bv_val[ l ], + flags, &rdnl, first ) ) { + LDAP_FREE( bv->bv_val ); + bv->bv_val = NULL; + goto return_results; + } + if ( first ) { + first = 0; + } + l += rdnl; + } + } + + bv->bv_len = len; + bv->bv_val[ bv->bv_len ] = '\0'; + + rc = LDAP_SUCCESS; + } break; + + default: + return LDAP_PARAM_ERROR; + } - strcpy( p, s ); + Debug( LDAP_DEBUG_TRACE, "<= ldap_dn2bv(%s,%u)=%d\n", + bv->bv_val, flags, rc ); - return( p ); +return_results:; + return( rc ); } -#endif /* ultrix */ diff --git a/libraries/liblunicode/ucstr.c b/libraries/liblunicode/ucstr.c index 395bd6b950..31110542d1 100644 --- a/libraries/liblunicode/ucstr.c +++ b/libraries/liblunicode/ucstr.c @@ -12,13 +12,12 @@ #include -#define malloc(x) ber_memalloc(x) -#define realloc(x,y) ber_memrealloc(x,y) -#define free(x) ber_memfree(x) - #include #include +#define malloc(x) ber_memalloc(x) +#define realloc(x,y) ber_memrealloc(x,y) +#define free(x) ber_memfree(x) int ucstrncmp( const ldap_unicode_t *u1, diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c index 6c3b22ee85..5182380843 100644 --- a/servers/slapd/acl.c +++ b/servers/slapd/acl.c @@ -1,42 +1,103 @@ /* acl.c - routines to parse and check acl's */ +/* $OpenLDAP$ */ +/* + * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ + +#include "portable.h" #include -#include -#include -#include -#include -#include -#ifdef sunos5 -#include "regexpr.h" -#else -#include "regex.h" -#endif + +#include +#include +#include + #include "slap.h" +#include "sets.h" +#include "lber_pvt.h" + -extern Attribute *attr_find(); -extern char *re_comp(); -extern struct acl *global_acl; -extern int global_default_access; -extern char *access2str(); -extern char *dn_normalize_case(); +/* + * speed up compares + */ +static struct berval + aci_bv_entry = { sizeof("entry") - 1, "entry" }, + aci_bv_br_entry = { sizeof("[entry]") - 1, "[entry]" }, + aci_bv_br_all = { sizeof("[all]") - 1, "[all]" }, + aci_bv_access_id = { sizeof("access-id") - 1, "access-id" }, + aci_bv_anonymous = { sizeof("anonymous") - 1, "anonymous" }, + aci_bv_users = { sizeof("users") - 1, "users" }, + aci_bv_self = { sizeof("self") - 1, "self" }, + aci_bv_dnattr = { sizeof("dnattr") - 1, "dnattr" }, + aci_bv_group = { sizeof("group") - 1, "group" }, + aci_bv_role = { sizeof("role") - 1, "role" }, + aci_bv_set = { sizeof("set") - 1, "set" }, + aci_bv_set_ref = { sizeof("set-ref") - 1, "set-ref"}, + aci_bv_grant = { sizeof("grant") - 1, "grant" }, + aci_bv_deny = { sizeof("deny") - 1, "deny" }; + +static AccessControl * acl_get( + AccessControl *ac, int *count, + Backend *be, Operation *op, + Entry *e, + AttributeDescription *desc, + int nmatches, regmatch_t *matches ); + +static slap_control_t acl_mask( + AccessControl *ac, slap_mask_t *mask, + Backend *be, Connection *conn, Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + regmatch_t *matches, + int count, + AccessControlState *state ); -int acl_access_allowed(); -int access_allowed(); -struct acl *acl_get_applicable(); +#ifdef SLAPD_ACI_ENABLED +static int aci_mask( + Backend *be, + Connection *conn, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *aci, + regmatch_t *matches, + slap_access_t *grant, + slap_access_t *deny ); +#endif + +static int regex_matches( + struct berval *pat, char *str, char *buf, regmatch_t *matches); +static void string_expand( + struct berval *newbuf, struct berval *pattern, + char *match, regmatch_t *matches); -static int regex_matches(); +typedef struct AciSetCookie { + Backend *be; + Entry *e; + Connection *conn; + Operation *op; +} AciSetCookie; -extern pthread_mutex_t regex_mutex; +SLAP_SET_GATHER aci_set_gather; +static int aci_match_set ( struct berval *subj, Backend *be, + Entry *e, Connection *conn, Operation *op, int setref ); /* - * access_allowed - check whether dn is allowed the requested access + * access_allowed - check whether op->o_ndn is allowed the requested access * to entry e, attribute attr, value val. if val is null, access to - * the whole attribute is assumed (all values). this routine finds - * the applicable acl and calls acl_access_allowed() to make the - * decision. + * the whole attribute is assumed (all values). * - * returns 0 access NOT allowed - * 1 access allowed + * This routine loops through all access controls and calls + * acl_mask() on each applicable access control. + * The loop exits when a definitive answer is reached or + * or no more controls remain. + * + * returns: + * 0 access denied + * 1 access granted */ int @@ -45,107 +106,413 @@ access_allowed( Connection *conn, Operation *op, Entry *e, - char *attr, + AttributeDescription *desc, struct berval *val, - char *dn, - int access -) + slap_access_t access, + AccessControlState *state ) { - int rc; - struct acl *a; + int ret = 1; + int count; + AccessControl *a; +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; +#endif + slap_mask_t mask; + slap_control_t control; + const char *attr; + regmatch_t matches[MAXREMATCHES]; - if ( be == NULL ) { - return( 0 ); + assert( e != NULL ); + assert( desc != NULL ); + assert( access > ACL_NONE ); + + attr = desc->ad_cname.bv_val; + + assert( attr != NULL ); + + if( state && state->as_recorded ) { + if( state->as_recorded & ACL_STATE_RECORDED_NV && + val == NULL ) + { + return state->as_result; + + } else if ( state->as_recorded & ACL_STATE_RECORDED_VD && + val != NULL && state->as_vd_acl == NULL ) + { + return state->as_result; + } + } + +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY, + "access_allowed: conn %d %s access to \"%s\" \"%s\" requested\n", + conn ? conn->c_connid : -1, access2str( access ), e->e_dn, attr )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: %s access to \"%s\" \"%s\" requested\n", + access2str( access ), e->e_dn, attr ); +#endif + + if ( op == NULL ) { + /* no-op call */ + goto done; + } + + if ( be == NULL ) be = &backends[0]; + assert( be != NULL ); + + /* grant database root access */ + if ( be != NULL && be_isroot( be, &op->o_ndn ) ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_INFO, + "access_allowed: conn %d root access granted\n", + conn->c_connid)); +#else + Debug( LDAP_DEBUG_ACL, + "<= root access granted\n", + 0, 0, 0 ); +#endif + goto done; + } + + /* + * no-user-modification operational attributes are ignored + * by ACL_WRITE checking as any found here are not provided + * by the user + */ + if ( access >= ACL_WRITE && is_at_no_user_mod( desc->ad_type ) + && desc != slap_schema.si_ad_entry + && desc != slap_schema.si_ad_children ) + { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "access_allowed: conn %d NoUserMod Operational attribute: %s access granted\n", + conn->c_connid, attr )); +#else + Debug( LDAP_DEBUG_ACL, "NoUserMod Operational attribute:" + " %s access granted\n", + attr, 0, 0 ); +#endif + goto done; + } + + /* use backend default access if no backend acls */ + if( be != NULL && be->be_acl == NULL ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "access_allowed: conn %d backend default %s access %s to \"%s\"\n", + conn->c_connid, access2str( access ), + be->be_dfltaccess >= access ? "granted" : "denied", op->o_dn.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: backend default %s access %s to \"%s\"\n", + access2str( access ), + be->be_dfltaccess >= access ? "granted" : "denied", op->o_dn.bv_val ); +#endif + ret = be->be_dfltaccess >= access; + goto done; + +#ifdef notdef + /* be is always non-NULL */ + /* use global default access if no global acls */ + } else if ( be == NULL && global_acl == NULL ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "access_allowed: conn %d global default %s access %s to \"%s\"\n", + conn->c_connid, access2str( access ), + global_default_access >= access ? "granted" : "denied", op->o_dn.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: global default %s access %s to \"%s\"\n", + access2str( access ), + global_default_access >= access ? "granted" : "denied", op->o_dn.bv_val ); +#endif + ret = global_default_access >= access; + goto done; +#endif + } + + ret = 0; + control = ACL_BREAK; + + if( state && ( state->as_recorded & ACL_STATE_RECORDED_VD )) { + assert( state->as_vd_acl != NULL ); + + a = state->as_vd_acl; + mask = state->as_vd_acl_mask; + count = state->as_vd_acl_count; + AC_MEMCPY( matches, state->as_vd_acl_matches, + sizeof(matches) ); + goto vd_access; + + } else { + a = NULL; + ACL_INIT(mask); + count = 0; + memset(matches, '\0', sizeof(matches)); + } + + while((a = acl_get( a, &count, be, op, e, desc, + MAXREMATCHES, matches )) != NULL) + { + int i; + + for (i = 0; i < MAXREMATCHES && matches[i].rm_so > 0; i++) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "access_allowed: conn %d match[%d]: %d %d ", + conn->c_connid, i, + (int)matches[i].rm_so, (int)matches[i].rm_eo )); +#else + Debug( LDAP_DEBUG_ACL, "=> match[%d]: %d %d ", i, + (int)matches[i].rm_so, (int)matches[i].rm_eo ); +#endif + if( matches[i].rm_so <= matches[0].rm_eo ) { + int n; + for ( n = matches[i].rm_so; n < matches[i].rm_eo; n++) { + Debug( LDAP_DEBUG_ACL, "%c", e->e_ndn[n], 0, 0 ); + } + } +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_ARGS, "\n" )); +#else + Debug( LDAP_DEBUG_ARGS, "\n", 0, 0, 0 ); +#endif + } + +vd_access: + control = acl_mask( a, &mask, be, conn, op, + e, desc, val, matches, count, state ); + + if ( control != ACL_BREAK ) { + break; + } + + memset(matches, '\0', sizeof(matches)); + } + + if ( ACL_IS_INVALID( mask ) ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "access_allowed: conn %d \"%s\" (%s) invalid!\n", + conn->c_connid, e->e_dn, attr )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: \"%s\" (%s) invalid!\n", + e->e_dn, attr, 0 ); +#endif + ACL_INIT(mask); + + } else if ( control == ACL_BREAK ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "access_allowed: conn %d no more rules\n", conn->c_connid )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: no more rules\n", 0, 0, 0); +#endif + + goto done; } - a = acl_get_applicable( be, op, e, attr ); - rc = acl_access_allowed( a, be, conn, e, val, op, access ); +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY, + "access_allowed: conn %d %s access %s by %s\n", + conn->c_connid, + access2str( access ), + ACL_GRANT( mask, access ) ? "granted" : "denied", + accessmask2str( mask, accessmaskbuf ) )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: %s access %s by %s\n", + access2str( access ), + ACL_GRANT(mask, access) ? "granted" : "denied", + accessmask2str( mask, accessmaskbuf ) ); +#endif - return( rc ); + ret = ACL_GRANT(mask, access); + +done: + if( state != NULL ) { + state->as_recorded |= ACL_STATE_RECORDED; + state->as_result = ret; + } + return ret; } /* - * acl_get_applicable - return the acl applicable to entry e, attribute + * acl_get - return the acl applicable to entry e, attribute * attr. the acl returned is suitable for use in subsequent calls to * acl_access_allowed(). */ -struct acl * -acl_get_applicable( +static AccessControl * +acl_get( + AccessControl *a, + int *count, Backend *be, - Operation *op, + Operation *op, Entry *e, - char *attr -) + AttributeDescription *desc, + int nmatch, + regmatch_t *matches ) { - int i; - struct acl *a; - char *edn; + const char *attr; + int dnlen, patlen; - Debug( LDAP_DEBUG_ACL, "=> acl_get: entry (%s) attr (%s)\n", e->e_dn, - attr, 0 ); + assert( e != NULL ); + assert( count != NULL ); + assert( desc != NULL ); - if ( be_isroot( be, op->o_dn ) ) { - Debug( LDAP_DEBUG_ACL, - "<= acl_get: no acl applicable to database root\n", 0, 0, - 0 ); - return( NULL ); - } + attr = desc->ad_cname.bv_val; - /* check for a backend-specific acl that matches the entry */ - for ( i = 1, a = be->be_acl; a != NULL; a = a->acl_next, i++ ) { - if ( a->acl_dnpat != NULL ) { - edn = dn_normalize_case( strdup( e->e_dn ) ); - if ( ! regex_matches( a->acl_dnpat, edn ) ) { - free( edn ); - continue; - } - free( edn ); - } - if ( a->acl_filter != NULL ) { - if ( test_filter( NULL, NULL, NULL, e, a->acl_filter ) - != 0 ) { - continue; - } - } - if ( attr == NULL || a->acl_attrs == NULL || - charray_inlist( a->acl_attrs, attr ) ) { - Debug( LDAP_DEBUG_ACL, "<= acl_get: backend acl #%d\n", - i, e->e_dn, attr ); - return( a ); + assert( attr != NULL ); + + if( a == NULL ) { + if( be == NULL ) { + a = global_acl; + } else { + a = be->be_acl; } + + assert( a != NULL ); + + } else { + a = a->acl_next; } - /* check for a global acl that matches the entry */ - for ( i = 1, a = global_acl; a != NULL; a = a->acl_next, i++ ) { - if ( a->acl_dnpat != NULL ) { - edn = dn_normalize_case( strdup( e->e_dn ) ); - if ( ! regex_matches( a->acl_dnpat, edn ) ) { - free( edn ); - continue; + dnlen = e->e_nname.bv_len; + + for ( ; a != NULL; a = a->acl_next ) { + (*count) ++; + + if (a->acl_dn_pat.bv_len != 0) { + if ( a->acl_dn_style == ACL_STYLE_REGEX ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_get: dnpat [%d] %s nsub: %d\n", + *count, a->acl_dn_pat.bv_val, (int) a->acl_dn_re.re_nsub )); +#else + Debug( LDAP_DEBUG_ACL, "=> dnpat: [%d] %s nsub: %d\n", + *count, a->acl_dn_pat.bv_val, (int) a->acl_dn_re.re_nsub ); +#endif + if (regexec(&a->acl_dn_re, e->e_ndn, nmatch, matches, 0)) + continue; + + } else { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_get: dn [%d] %s\n", + *count, a->acl_dn_pat.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, "=> dn: [%d] %s\n", + *count, a->acl_dn_pat.bv_val, 0 ); +#endif + patlen = a->acl_dn_pat.bv_len; + if ( dnlen < patlen ) + continue; + + if ( a->acl_dn_style == ACL_STYLE_BASE ) { + /* base dn -- entire object DN must match */ + if ( dnlen != patlen ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_ONE ) { + int rdnlen = -1; + + if ( dnlen <= patlen ) + continue; + + if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) ) + continue; + + rdnlen = dn_rdnlen( NULL, &e->e_nname ); + if ( rdnlen != dnlen - patlen - 1 ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_SUBTREE ) { + if ( dnlen > patlen && !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) ) + continue; + + } else if ( a->acl_dn_style == ACL_STYLE_CHILDREN ) { + if ( dnlen <= patlen ) + continue; + if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) ) + continue; + } + + if ( strcmp( a->acl_dn_pat.bv_val, e->e_ndn + dnlen - patlen ) != 0 ) + continue; } - free( edn ); + +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_get: [%d] matched\n", + *count )); +#else + Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] matched\n", + *count, 0, 0 ); +#endif } + if ( a->acl_filter != NULL ) { - if ( test_filter( NULL, NULL, NULL, e, a->acl_filter ) - != 0 ) { + ber_int_t rc = test_filter( NULL, NULL, NULL, e, a->acl_filter ); + if ( rc != LDAP_COMPARE_TRUE ) { continue; } } - if ( attr == NULL || a->acl_attrs == NULL || charray_inlist( - a->acl_attrs, attr ) ) { - Debug( LDAP_DEBUG_ACL, "<= acl_get: global acl #%d\n", - i, e->e_dn, attr ); - return( a ); + +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_get: [%d] check attr %s\n", + *count, attr )); +#else + Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] check attr %s\n", + *count, attr, 0); +#endif + if ( attr == NULL || a->acl_attrs == NULL || + ad_inlist( desc, a->acl_attrs ) ) + { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_get: [%d] acl %s attr: %s\n", + *count, e->e_dn, attr )); +#else + Debug( LDAP_DEBUG_ACL, + "<= acl_get: [%d] acl %s attr: %s\n", + *count, e->e_dn, attr ); +#endif + return a; } + matches[0].rm_so = matches[0].rm_eo = -1; } - Debug( LDAP_DEBUG_ACL, "<= acl_get: no match\n", 0, 0, 0 ); +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY, + "acl_get: done.\n" )); +#else + Debug( LDAP_DEBUG_ACL, "<= acl_get: done.\n", 0, 0, 0 ); +#endif return( NULL ); } /* - * acl_access_allowed - check whether the given acl allows dn the + * Record value-dependent access control state + */ +#define ACL_RECORD_VALUE_STATE do { \ + if( state && !( state->as_recorded & ACL_STATE_RECORDED_VD )) { \ + state->as_recorded |= ACL_STATE_RECORDED_VD; \ + state->as_vd_acl = a; \ + AC_MEMCPY( state->as_vd_acl_matches, matches, \ + sizeof( state->as_vd_acl_matches )) ; \ + state->as_vd_acl_count = count; \ + state->as_vd_access = b; \ + state->as_vd_access_count = i; \ + } \ + } while( 0 ) + +/* + * acl_mask - modifies mask based upon the given acl and the * requested access to entry e, attribute attr, value val. if val * is null, access to the whole attribute is assumed (all values). * @@ -153,252 +520,1353 @@ acl_get_applicable( * 1 access allowed */ -int -acl_access_allowed( - struct acl *a, +static slap_control_t +acl_mask( + AccessControl *a, + slap_mask_t *mask, Backend *be, - Connection *conn, + Connection *conn, + Operation *op, Entry *e, + AttributeDescription *desc, struct berval *val, - Operation *op, - int access -) + regmatch_t *matches, + int count, + AccessControlState *state ) { - int i; - char *edn, *odn; - struct access *b; - Attribute *at; - struct berval bv; - int default_access; - - Debug( LDAP_DEBUG_ACL, "=> acl: %s access to value \"%s\" by \"%s\"\n", - access2str( access ), val ? val->bv_val : "any", op->o_dn ? - op->o_dn : "" ); - - if ( be_isroot( be, op->o_dn ) ) { - Debug( LDAP_DEBUG_ACL, "<= acl: granted to database root\n", - 0, 0, 0 ); - return( 1 ); - } + int i, odnlen, patlen; + int vd_recorded = 0; + Access *b; +#ifdef LDAP_DEBUG + char accessmaskbuf[ACCESSMASK_MAXLEN]; +#endif + const char *attr; - default_access = be->be_dfltaccess ? be->be_dfltaccess : - global_default_access; - if ( a == NULL ) { - Debug( LDAP_DEBUG_ACL, - "<= acl: %s by default (no matching to)\n", - default_access >= access ? "granted" : "denied", 0, 0 ); - return( default_access >= access ); - } + assert( a != NULL ); + assert( mask != NULL ); + assert( desc != NULL ); + + attr = desc->ad_cname.bv_val; + + assert( attr != NULL ); + +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY, + "acl_mask: conn %d access to entry \"%s\", attr \"%s\" requested\n", + conn->c_connid, e->e_dn, attr )); + + LDAP_LOG(( "acl", LDAP_LEVEL_ARGS, + " to %s by \"%s\", (%s) \n", + val ? "value" : "all values", + op->o_ndn.bv_val ? op->o_ndn.bv_val : "", + accessmask2str( *mask, accessmaskbuf ) )); +#else + Debug( LDAP_DEBUG_ACL, + "=> acl_mask: access to entry \"%s\", attr \"%s\" requested\n", + e->e_dn, attr, 0 ); + + Debug( LDAP_DEBUG_ACL, + "=> acl_mask: to %s by \"%s\", (%s) \n", + val ? "value" : "all values", + op->o_ndn.bv_val ? op->o_ndn.bv_val : "", + accessmask2str( *mask, accessmaskbuf ) ); +#endif + + if( state && ( state->as_recorded & ACL_STATE_RECORDED_VD ) + && state->as_vd_acl == a ) + { + b = state->as_vd_access; + i = state->as_vd_access_count; - odn = NULL; - if ( op->o_dn != NULL ) { - odn = dn_normalize_case( strdup( op->o_dn ) ); - bv.bv_val = odn; - bv.bv_len = strlen( odn ); + } else { + b = a->acl_access; + i = 1; } - for ( i = 1, b = a->acl_access; b != NULL; b = b->a_next, i++ ) { - if ( b->a_dnpat != NULL ) { + + for ( ; b != NULL; b = b->a_next, i++ ) { + slap_mask_t oldmask, modmask; + + ACL_INVALIDATE( modmask ); + + /* AND clauses */ + if ( b->a_dn_pat.bv_len != 0 ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_dn_pat: %s\n", + conn->c_connid, b->a_dn_pat.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, "<= check a_dn_pat: %s\n", + b->a_dn_pat.bv_val, 0, 0); +#endif /* * if access applies to the entry itself, and the * user is bound as somebody in the same namespace as * the entry, OR the given dn matches the dn pattern */ - if ( strcasecmp( b->a_dnpat, "self" ) == 0 && op->o_dn - != NULL && *(op->o_dn) && e->e_dn != NULL ) { - edn = dn_normalize_case( strdup( e->e_dn ) ); - if ( strcasecmp( edn, op->o_dn ) == 0 ) { - free( edn ); - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= - access ? "granted" : "denied", 0 ); - - return( (b->a_access & ~ACL_SELF) - >= access ); - } - free( edn ); - } else { - if ( regex_matches( b->a_dnpat, odn ) ) { - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); - - return( (b->a_access & ~ACL_SELF) - >= access ); + if ( ber_bvcmp( &b->a_dn_pat, &aci_bv_anonymous ) == 0 ) { + if ( op->o_ndn.bv_len != 0 ) { + continue; } - } - } - if ( b->a_addrpat != NULL ) { - if ( regex_matches( b->a_addrpat, conn->c_addr ) ) { - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); - return( (b->a_access & ~ACL_SELF) >= access ); - } - } - if ( b->a_domainpat != NULL ) { - if ( regex_matches( b->a_domainpat, conn->c_domain ) ) { - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); + } else if ( ber_bvcmp( &b->a_dn_pat, &aci_bv_users ) == 0 ) { + if ( op->o_ndn.bv_len == 0 ) { + continue; + } - return( (b->a_access & ~ACL_SELF) >= access ); - } - } - if ( b->a_dnattr != NULL && op->o_dn != NULL ) { - /* see if asker is listed in dnattr */ - if ( (at = attr_find( e->e_attrs, b->a_dnattr )) - != NULL && value_find( at->a_vals, &bv, - at->a_syntax, 3 ) == 0 ) - { - if ( (b->a_access & ACL_SELF) && (val == NULL - || value_cmp( &bv, val, at->a_syntax, - 2 )) ) { + } else if ( ber_bvcmp( &b->a_dn_pat, &aci_bv_self ) == 0 ) { + if ( op->o_ndn.bv_len == 0 ) { + continue; + } + + if ( e->e_dn == NULL || !dn_match( &e->e_nname, &op->o_ndn ) ) { continue; } - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl: matched by clause #%d access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? - "granted" : "denied", 0 ); + } else if ( b->a_dn_style == ACL_STYLE_REGEX ) { + if ( ber_bvccmp( &b->a_dn_pat, '*' ) == 0 ) { + int ret = regex_matches( &b->a_dn_pat, + op->o_ndn.bv_val, e->e_ndn, matches ); - return( (b->a_access & ~ACL_SELF) >= access ); - } + if( ret == 0 ) { + continue; + } + } - /* asker not listed in dnattr - check for self access */ - if ( ! (b->a_access & ACL_SELF) || val == NULL || - value_cmp( &bv, val, at->a_syntax, 2 ) != 0 ) { - continue; - } + } else { + if ( e->e_dn == NULL ) + continue; - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, - "<= acl: matched by clause #%d (self) access %s\n", - i, (b->a_access & ~ACL_SELF) >= access ? "granted" - : "denied", 0 ); + patlen = b->a_dn_pat.bv_len; + odnlen = op->o_ndn.bv_len; + if ( odnlen < patlen ) + continue; - return( (b->a_access & ~ACL_SELF) >= access ); - } - } + if ( b->a_dn_style == ACL_STYLE_BASE ) { + /* base dn -- entire object DN must match */ + if ( odnlen != patlen ) + continue; - if ( odn ) free( odn ); - Debug( LDAP_DEBUG_ACL, "<= acl: %s by default (no matching by)\n", - default_access >= access ? "granted" : "denied", 0, 0 ); + } else if ( b->a_dn_style == ACL_STYLE_ONE ) { + int rdnlen = -1; - return( default_access >= access ); -} + if ( odnlen <= patlen ) + continue; -/* - * acl_check_mods - check access control on the given entry to see if - * it allows the given modifications by the user associated with op. - * returns LDAP_SUCCESS mods allowed ok - * anything else mods not allowed - return is an error - * code indicating the problem - */ + if ( !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) ) + continue; -int -acl_check_mods( - Backend *be, - Connection *conn, - Operation *op, - Entry *e, - LDAPMod *mods -) -{ - int i; - struct acl *a; + rdnlen = dn_rdnlen( NULL, &op->o_ndn ); + if ( rdnlen != odnlen - patlen - 1 ) + continue; - for ( ; mods != NULL; mods = mods->mod_next ) { - if ( strcasecmp( mods->mod_type, "modifiersname" ) == 0 || - strcasecmp( mods->mod_type, "modifytimestamp" ) == 0 ) { - continue; + } else if ( b->a_dn_style == ACL_STYLE_SUBTREE ) { + if ( odnlen > patlen && !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) ) + continue; + + } else if ( b->a_dn_style == ACL_STYLE_CHILDREN ) { + if ( odnlen <= patlen ) + continue; + if ( !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) ) + continue; + } + + if ( strcmp( b->a_dn_pat.bv_val, op->o_ndn.bv_val + odnlen - patlen ) != 0 ) + continue; + } } - a = acl_get_applicable( be, op, e, mods->mod_type ); + if ( b->a_sockurl_pat.bv_len ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_sockurl_pat: %s\n", + conn->c_connid, b->a_sockurl_pat.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, "<= check a_sockurl_pat: %s\n", + b->a_sockurl_pat.bv_val, 0, 0 ); +#endif - switch ( mods->mod_op & ~LDAP_MOD_BVALUES ) { - case LDAP_MOD_REPLACE: - case LDAP_MOD_ADD: - if ( mods->mod_bvalues == NULL ) { - break; + if ( ber_bvccmp( &b->a_sockurl_pat, '*' ) != 0) { + if ( b->a_sockurl_style == ACL_STYLE_REGEX) { + if (!regex_matches( &b->a_sockurl_pat, conn->c_listener_url.bv_val, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( ber_bvstrcasecmp( &b->a_sockurl_pat, &conn->c_listener_url ) != 0 ) + continue; + } } - for ( i = 0; mods->mod_bvalues[i] != NULL; i++ ) { - if ( ! acl_access_allowed( a, be, conn, e, - mods->mod_bvalues[i], op, ACL_WRITE ) ) { - return( LDAP_INSUFFICIENT_ACCESS ); + } + + if ( b->a_domain_pat.bv_len ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_domain_pat: %s\n", + conn->c_connid, b->a_domain_pat.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, "<= check a_domain_pat: %s\n", + b->a_domain_pat.bv_val, 0, 0 ); +#endif + if ( ber_bvccmp( &b->a_domain_pat, '*' ) != 0) { + if ( b->a_domain_style == ACL_STYLE_REGEX) { + if (!regex_matches( &b->a_domain_pat, conn->c_peer_domain.bv_val, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( ber_bvstrcasecmp( &b->a_domain_pat, &conn->c_peer_domain ) != 0 ) + continue; } } - break; + } - case LDAP_MOD_DELETE: - if ( mods->mod_bvalues == NULL ) { - if ( ! acl_access_allowed( a, be, conn, e, - NULL, op, ACL_WRITE ) ) { - return( LDAP_INSUFFICIENT_ACCESS ); + if ( b->a_peername_pat.bv_len ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_perrname_path: %s\n", + conn->c_connid, b->a_peername_pat.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, "<= check a_peername_path: %s\n", + b->a_peername_pat.bv_val, 0, 0 ); +#endif + if ( ber_bvccmp( &b->a_peername_pat, '*' ) != 0) { + if ( b->a_peername_style == ACL_STYLE_REGEX) { + if (!regex_matches( &b->a_peername_pat, conn->c_peer_name.bv_val, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( ber_bvstrcasecmp( &b->a_peername_pat, &conn->c_peer_name ) != 0 ) + continue; } - break; } - for ( i = 0; mods->mod_bvalues[i] != NULL; i++ ) { - if ( ! acl_access_allowed( a, be, conn, e, - mods->mod_bvalues[i], op, ACL_WRITE ) ) { - return( LDAP_INSUFFICIENT_ACCESS ); + } + + if ( b->a_sockname_pat.bv_len ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_sockname_path: %s\n", + conn->c_connid, b->a_sockname_pat.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, "<= check a_sockname_path: %s\n", + b->a_sockname_pat.bv_val, 0, 0 ); +#endif + if ( ber_bvccmp( &b->a_sockname_pat, '*' ) != 0) { + if ( b->a_sockname_style == ACL_STYLE_REGEX) { + if (!regex_matches( &b->a_sockname_pat, conn->c_sock_name.bv_val, + e->e_ndn, matches ) ) + { + continue; + } + } else { + if ( ber_bvstrcasecmp( &b->a_sockname_pat, &conn->c_sock_name ) != 0 ) + continue; } } - break; } - } - return( LDAP_SUCCESS ); -} + if ( b->a_dn_at != NULL && op->o_ndn.bv_len != 0 ) { + Attribute *at; + struct berval bv; + int rc, match = 0; + const char *text; + const char *attr = b->a_dn_at->ad_cname.bv_val; -#ifdef sunos5 + assert( attr != NULL ); -static int -regex_matches( char *pat, char *str ) -{ - char *e; - int rc; +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_dn_pat: %s\n", + conn->c_connid, attr )); +#else + Debug( LDAP_DEBUG_ACL, "<= check a_dn_at: %s\n", + attr, 0, 0); +#endif + bv = op->o_ndn; - if ( (e = compile( pat, NULL, NULL )) == NULL ) { - Debug( LDAP_DEBUG_ANY, - "compile( \"%s\", \"%s\") failed\n", pat, str, 0 ); - return( 0 ); - } - rc = step( str ? str : "", e ); - free( e ); + /* see if asker is listed in dnattr */ + for( at = attrs_find( e->e_attrs, b->a_dn_at ); + at != NULL; + at = attrs_find( at->a_next, b->a_dn_at ) ) + { + if( value_find( b->a_dn_at, at->a_vals, &bv ) == 0 ) { + /* found it */ + match = 1; + break; + } + } - return( rc ); -} + if( match ) { + /* have a dnattr match. if this is a self clause then + * the target must also match the op dn. + */ + if ( b->a_dn_self ) { + /* check if the target is an attribute. */ + if ( val == NULL ) + continue; + /* target is attribute, check if the attribute value + * is the op dn. + */ + rc = value_match( &match, b->a_dn_at, + b->a_dn_at->ad_type->sat_equality, 0, + val, &bv, &text ); + /* on match error or no match, fail the ACL clause */ + if (rc != LDAP_SUCCESS || match != 0 ) + continue; + } + } else { + /* no dnattr match, check if this is a self clause */ + if ( ! b->a_dn_self ) + continue; -#else /* sunos5 */ + ACL_RECORD_VALUE_STATE; + + /* this is a self clause, check if the target is an + * attribute. + */ + if ( val == NULL ) + continue; -static int -regex_matches( char *pat, char *str ) -{ - char *e; - int rc; + /* target is attribute, check if the attribute value + * is the op dn. + */ + rc = value_match( &match, b->a_dn_at, + b->a_dn_at->ad_type->sat_equality, 0, + val, &bv, &text ); - pthread_mutex_lock( ®ex_mutex ); - if ( (e = re_comp( pat )) != NULL ) { - Debug( LDAP_DEBUG_ANY, - "re_comp( \"%s\", \"%s\") failed because (%s)\n", pat, str, - e ); - pthread_mutex_unlock( ®ex_mutex ); - return( 0 ); - } - rc = re_exec( str ? str : "" ); - pthread_mutex_unlock( ®ex_mutex ); + /* on match error or no match, fail the ACL clause */ + if (rc != LDAP_SUCCESS || match != 0 ) + continue; + } + } + + if ( b->a_group_pat.bv_len && op->o_ndn.bv_len ) { + char buf[1024]; + struct berval bv; + struct berval ndn = { 0, NULL }; + int rc; + + bv.bv_len = sizeof(buf) - 1; + bv.bv_val = buf; - return( rc == 1 ); + /* b->a_group is an unexpanded entry name, expanded it should be an + * entry with objectclass group* and we test to see if odn is one of + * the values in the attribute group + */ + /* see if asker is listed in dnattr */ + if ( b->a_group_style == ACL_STYLE_REGEX ) { + string_expand(&bv, &b->a_group_pat, e->e_ndn, matches); + if ( dnNormalize2(NULL, &bv, &ndn) != LDAP_SUCCESS ) { + /* did not expand to a valid dn */ + continue; + } + bv = ndn; + } else { + bv = b->a_group_pat; + } + + rc = backend_group(be, conn, op, e, &bv, &op->o_ndn, + b->a_group_oc, b->a_group_at); + if ( ndn.bv_val ) + free( ndn.bv_val ); + if ( rc != 0 ) { + continue; + } + } + + if ( b->a_set_pat.bv_len != 0 ) { + if (aci_match_set( &b->a_set_pat, be, e, conn, op, 0 ) == 0) { + continue; + } + } + + if ( b->a_authz.sai_ssf ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_authz.sai_ssf: ACL %u > OP %u\n", + conn->c_connid, b->a_authz.sai_ssf, op->o_ssf )); +#else + Debug( LDAP_DEBUG_ACL, "<= check a_authz.sai_ssf: ACL %u > OP %u\n", + b->a_authz.sai_ssf, op->o_ssf, 0 ); +#endif + if ( b->a_authz.sai_ssf > op->o_ssf ) { + continue; + } + } + + if ( b->a_authz.sai_transport_ssf ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_authz.sai_transport_ssf: ACL %u > OP %u\n", + conn->c_connid, b->a_authz.sai_transport_ssf, op->o_transport_ssf )); +#else + Debug( LDAP_DEBUG_ACL, + "<= check a_authz.sai_transport_ssf: ACL %u > OP %u\n", + b->a_authz.sai_transport_ssf, op->o_transport_ssf, 0 ); +#endif + if ( b->a_authz.sai_transport_ssf > op->o_transport_ssf ) { + continue; + } + } + + if ( b->a_authz.sai_tls_ssf ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_authz.sai_tls_ssf: ACL %u > OP %u\n", + conn->c_connid, b->a_authz.sai_tls_ssf, op->o_tls_ssf )); +#else + Debug( LDAP_DEBUG_ACL, + "<= check a_authz.sai_tls_ssf: ACL %u > OP %u\n", + b->a_authz.sai_tls_ssf, op->o_tls_ssf, 0 ); +#endif + if ( b->a_authz.sai_tls_ssf > op->o_tls_ssf ) { + continue; + } + } + + if ( b->a_authz.sai_sasl_ssf ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d check a_authz.sai_sasl_ssf: ACL %u > OP %u\n", + conn->c_connid, b->a_authz.sai_sasl_ssf, op->o_sasl_ssf )); +#else + Debug( LDAP_DEBUG_ACL, + "<= check a_authz.sai_sasl_ssf: ACL %u > OP %u\n", + b->a_authz.sai_sasl_ssf, op->o_sasl_ssf, 0 ); +#endif + if ( b->a_authz.sai_sasl_ssf > op->o_sasl_ssf ) { + continue; + } + } + +#ifdef SLAPD_ACI_ENABLED + if ( b->a_aci_at != NULL ) { + Attribute *at; + slap_access_t grant, deny, tgrant, tdeny; + + /* this case works different from the others above. + * since aci's themselves give permissions, we need + * to first check b->a_access_mask, the ACL's access level. + */ + + if ( e->e_nname.bv_len == 0 ) { + /* no ACIs in the root DSE */ + continue; + } + + /* first check if the right being requested + * is allowed by the ACL clause. + */ + if ( ! ACL_GRANT( b->a_access_mask, *mask ) ) { + continue; + } + + /* get the aci attribute */ + at = attr_find( e->e_attrs, b->a_aci_at ); + if ( at == NULL ) { + continue; + } + + ACL_RECORD_VALUE_STATE; + + /* start out with nothing granted, nothing denied */ + ACL_INIT(tgrant); + ACL_INIT(tdeny); + + /* the aci is an multi-valued attribute. The + * rights are determined by OR'ing the individual + * rights given by the acis. + */ + for ( i = 0; at->a_vals[i].bv_val != NULL; i++ ) { + if (aci_mask( be, conn, op, + e, desc, val, &at->a_vals[i], + matches, &grant, &deny ) != 0) + { + tgrant |= grant; + tdeny |= deny; + } + } + + /* remove anything that the ACL clause does not allow */ + tgrant &= b->a_access_mask & ACL_PRIV_MASK; + tdeny &= ACL_PRIV_MASK; + + /* see if we have anything to contribute */ + if( ACL_IS_INVALID(tgrant) && ACL_IS_INVALID(tdeny) ) { + continue; + } + + /* this could be improved by changing acl_mask so that it can deal with + * by clauses that return grant/deny pairs. Right now, it does either + * additive or subtractive rights, but not both at the same time. So, + * we need to combine the grant/deny pair into a single rights mask in + * a smart way: if either grant or deny is "empty", then we use the + * opposite as is, otherwise we remove any denied rights from the grant + * rights mask and construct an additive mask. + */ + if (ACL_IS_INVALID(tdeny)) { + modmask = tgrant | ACL_PRIV_ADDITIVE; + + } else if (ACL_IS_INVALID(tgrant)) { + modmask = tdeny | ACL_PRIV_SUBSTRACTIVE; + + } else { + modmask = (tgrant & ~tdeny) | ACL_PRIV_ADDITIVE; + } + + } else +#endif + { + modmask = b->a_access_mask; + } + +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_RESULTS, + "acl_mask: conn %d [%d] applying %s (%s)\n", + conn->c_connid, i, accessmask2str( modmask, accessmaskbuf), + b->a_type == ACL_CONTINUE ? "continue" : b->a_type == ACL_BREAK + ? "break" : "stop" )); +#else + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: [%d] applying %s (%s)\n", + i, accessmask2str( modmask, accessmaskbuf ), + b->a_type == ACL_CONTINUE + ? "continue" + : b->a_type == ACL_BREAK + ? "break" + : "stop" ); +#endif + /* save old mask */ + oldmask = *mask; + + if( ACL_IS_ADDITIVE(modmask) ) { + /* add privs */ + ACL_PRIV_SET( *mask, modmask ); + + /* cleanup */ + ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK ); + + } else if( ACL_IS_SUBTRACTIVE(modmask) ) { + /* substract privs */ + ACL_PRIV_CLR( *mask, modmask ); + + /* cleanup */ + ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK ); + + } else { + /* assign privs */ + *mask = modmask; + } + +#ifdef NEW_LOGGING + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1, + "acl_mask: conn %d [%d] mask: %s\n", + conn->c_connid, i, accessmask2str( *mask, accessmaskbuf) )); +#else + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: [%d] mask: %s\n", + i, accessmask2str(*mask, accessmaskbuf), 0 ); +#endif + + if( b->a_type == ACL_CONTINUE ) { + continue; + + } else if ( b->a_type == ACL_BREAK ) { + return ACL_BREAK; + + } else { + return ACL_STOP; + } + } + + /* implicit "by * none" clause */ + ACL_INIT(*mask); + +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_RESULTS, + "acl_mask: conn %d no more clauses, returning %d (stop)\n", + conn->c_connid, accessmask2str( *mask, accessmaskbuf) )); +#else + Debug( LDAP_DEBUG_ACL, + "<= acl_mask: no more clauses, returning %s (stop)\n", + accessmask2str(*mask, accessmaskbuf), 0, 0 ); +#endif + return ACL_STOP; +} + +/* + * acl_check_modlist - check access control on the given entry to see if + * it allows the given modifications by the user associated with op. + * returns 1 if mods allowed ok + * 0 mods not allowed + */ + +int +acl_check_modlist( + Backend *be, + Connection *conn, + Operation *op, + Entry *e, + Modifications *mlist +) +{ + struct berval *bv; + + assert( be != NULL ); + + /* short circuit root database access */ + if ( be_isroot( be, &op->o_ndn ) ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1, + "acl_check_modlist: conn %d access granted to root user\n", + conn->c_connid )); +#else + Debug( LDAP_DEBUG_ACL, + "<= acl_access_allowed: granted to database root\n", + 0, 0, 0 ); +#endif + return 1; + } + + /* use backend default access if no backend acls */ + if( be != NULL && be->be_acl == NULL ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1, + "acl_check_modlist: conn %d backend default %s access %s to \"%s\"\n", + conn->c_connid, access2str( ACL_WRITE ), + be->be_dfltaccess >= ACL_WRITE ? "granted" : "denied", op->o_dn.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: backend default %s access %s to \"%s\"\n", + access2str( ACL_WRITE ), + be->be_dfltaccess >= ACL_WRITE ? "granted" : "denied", op->o_dn.bv_val ); +#endif + return be->be_dfltaccess >= ACL_WRITE; + +#ifdef notdef + /* be is always non-NULL */ + /* use global default access if no global acls */ + } else if ( be == NULL && global_acl == NULL ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1, + "acl_check_modlist: conn %d global default %s access %s to \"%s\"\n", + conn->c_connid, access2str( ACL_WRITE ), + global_default_access >= ACL_WRITE ? "granted" : "denied", op->o_dn )); +#else + Debug( LDAP_DEBUG_ACL, + "=> access_allowed: global default %s access %s to \"%s\"\n", + access2str( ACL_WRITE ), + global_default_access >= ACL_WRITE ? "granted" : "denied", op->o_dn ); +#endif + return global_default_access >= ACL_WRITE; +#endif + } + + for ( ; mlist != NULL; mlist = mlist->sml_next ) { + static AccessControlState state_init = ACL_STATE_INIT; + AccessControlState state; + + /* + * no-user-modification operational attributes are ignored + * by ACL_WRITE checking as any found here are not provided + * by the user + */ + if ( is_at_no_user_mod( mlist->sml_desc->ad_type ) ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1, + "acl_check_modlist: conn %d no-user-mod %s: modify access granted\n", + conn->c_connid, mlist->sml_desc->ad_cname.bv_val )); +#else + Debug( LDAP_DEBUG_ACL, "acl: no-user-mod %s:" + " modify access granted\n", + mlist->sml_desc->ad_cname.bv_val, 0, 0 ); +#endif + continue; + } + + state = state_init; + + switch ( mlist->sml_op ) { + case LDAP_MOD_REPLACE: + /* + * We must check both permission to delete the whole + * attribute and permission to add the specific attributes. + * This prevents abuse from selfwriters. + */ + if ( ! access_allowed( be, conn, op, e, + mlist->sml_desc, NULL, ACL_WRITE, &state ) ) + { + return( 0 ); + } + + if ( mlist->sml_bvalues == NULL ) break; + + /* fall thru to check value to add */ + + case LDAP_MOD_ADD: + assert( mlist->sml_bvalues != NULL ); + + for ( bv = mlist->sml_bvalues; bv->bv_val != NULL; bv++ ) { + if ( ! access_allowed( be, conn, op, e, + mlist->sml_desc, bv, ACL_WRITE, &state ) ) + { + return( 0 ); + } + } + break; + + case LDAP_MOD_DELETE: + if ( mlist->sml_bvalues == NULL ) { + if ( ! access_allowed( be, conn, op, e, + mlist->sml_desc, NULL, ACL_WRITE, NULL ) ) + { + return( 0 ); + } + break; + } + for ( bv = mlist->sml_bvalues; bv->bv_val != NULL; bv++ ) { + if ( ! access_allowed( be, conn, op, e, + mlist->sml_desc, bv, ACL_WRITE, &state ) ) + { + return( 0 ); + } + } + break; + + case SLAP_MOD_SOFTADD: + /* allow adding attribute via modrdn thru */ + break; + + default: + assert( 0 ); + return( 0 ); + } + } + + return( 1 ); +} + +static char * +aci_bvstrdup( struct berval *bv ) +{ + char *s; + + s = (char *)ch_malloc(bv->bv_len + 1); + if (s != NULL) { + AC_MEMCPY(s, bv->bv_val, bv->bv_len); + s[bv->bv_len] = 0; + } + return(s); +} + +static int +aci_get_part( + struct berval *list, + int ix, + char sep, + struct berval *bv ) +{ + int len; + char *p; + + if (bv) { + bv->bv_len = 0; + bv->bv_val = NULL; + } + len = list->bv_len; + p = list->bv_val; + while (len >= 0 && --ix >= 0) { + while (--len >= 0 && *p++ != sep) ; + } + while (len >= 0 && *p == ' ') { + len--; + p++; + } + if (len < 0) + return(-1); + + if (!bv) + return(0); + + bv->bv_val = p; + while (--len >= 0 && *p != sep) { + bv->bv_len++; + p++; + } + while (bv->bv_len > 0 && *--p == ' ') + bv->bv_len--; + return(bv->bv_len); +} + +BerVarray +aci_set_gather (void *cookie, struct berval *name, struct berval *attr) +{ + AciSetCookie *cp = cookie; + BerVarray bvals = NULL; + struct berval ndn; + + /* this routine needs to return the bervals instead of + * plain strings, since syntax is not known. It should + * also return the syntax or some "comparison cookie". + */ + + if (dnNormalize2(NULL, name, &ndn) == LDAP_SUCCESS) { + const char *text; + AttributeDescription *desc = NULL; + if (slap_bv2ad(attr, &desc, &text) == LDAP_SUCCESS) { + backend_attribute(cp->be, NULL, NULL, + cp->e, &ndn, desc, &bvals); + } + free(ndn.bv_val); + } + return(bvals); +} + +static int +aci_match_set ( + struct berval *subj, + Backend *be, + Entry *e, + Connection *conn, + Operation *op, + int setref +) +{ + struct berval set = { 0, NULL }; + int rc = 0; + AciSetCookie cookie; + + if (setref == 0) { + ber_dupbv( &set, subj ); + } else { + struct berval subjdn, ndn = { 0, NULL }; + struct berval setat; + BerVarray bvals; + const char *text; + AttributeDescription *desc = NULL; + + /* format of string is "entry/setAttrName" */ + if (aci_get_part(subj, 0, '/', &subjdn) < 0) { + return(0); + } + + if ( aci_get_part(subj, 1, '/', &setat) < 0 ) { + setat.bv_val = SLAPD_ACI_SET_ATTR; + setat.bv_len = sizeof(SLAPD_ACI_SET_ATTR)-1; + } + + if ( setat.bv_val != NULL ) { + /* + * NOTE: dnNormalize2 honors the ber_len field + * as the length of the dn to be normalized + */ + if ( dnNormalize2(NULL, &subjdn, &ndn) == LDAP_SUCCESS + && slap_bv2ad(&setat, &desc, &text) == LDAP_SUCCESS ) + { + backend_attribute(be, NULL, NULL, e, + &ndn, desc, &bvals); + if ( bvals != NULL ) { + if ( bvals[0].bv_val != NULL ) { + int i; + set = bvals[0]; + bvals[0].bv_val = NULL; + for (i=1;bvals[i].bv_val;i++); + bvals[0].bv_val = bvals[i-1].bv_val; + bvals[i-1].bv_val = NULL; + } + ber_bvarray_free(bvals); + } + } + if (ndn.bv_val) + free(ndn.bv_val); + } + } + + if (set.bv_val != NULL) { + cookie.be = be; + cookie.e = e; + cookie.conn = conn; + cookie.op = op; + rc = (slap_set_filter(aci_set_gather, &cookie, &set, + &op->o_ndn, &e->e_nname, NULL) > 0); + ch_free(set.bv_val); + } + return(rc); +} + +#ifdef SLAPD_ACI_ENABLED +static int +aci_list_map_rights( + struct berval *list ) +{ + struct berval bv; + slap_access_t mask; + int i; + + ACL_INIT(mask); + for (i = 0; aci_get_part(list, i, ',', &bv) >= 0; i++) { + if (bv.bv_len <= 0) + continue; + switch (*bv.bv_val) { + case 'c': + ACL_PRIV_SET(mask, ACL_PRIV_COMPARE); + break; + case 's': + /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines + * the right 's' to mean "set", but in the examples states + * that the right 's' means "search". The latter definition + * is used here. + */ + ACL_PRIV_SET(mask, ACL_PRIV_SEARCH); + break; + case 'r': + ACL_PRIV_SET(mask, ACL_PRIV_READ); + break; + case 'w': + ACL_PRIV_SET(mask, ACL_PRIV_WRITE); + break; + case 'x': + /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not + * define any equivalent to the AUTH right, so I've just used + * 'x' for now. + */ + ACL_PRIV_SET(mask, ACL_PRIV_AUTH); + break; + default: + break; + } + + } + return(mask); +} + +static int +aci_list_has_attr( + struct berval *list, + const struct berval *attr, + struct berval *val ) +{ + struct berval bv, left, right; + int i; + + for (i = 0; aci_get_part(list, i, ',', &bv) >= 0; i++) { + if (aci_get_part(&bv, 0, '=', &left) < 0 + || aci_get_part(&bv, 1, '=', &right) < 0) + { + if (ber_bvstrcasecmp(attr, &bv) == 0) + return(1); + } else if (val == NULL) { + if (ber_bvstrcasecmp(attr, &left) == 0) + return(1); + } else { + if (ber_bvstrcasecmp(attr, &left) == 0) { + /* this is experimental code that implements a + * simple (prefix) match of the attribute value. + * the ACI draft does not provide for aci's that + * apply to specific values, but it would be + * nice to have. If the part of an aci's + * rights list is of the form =, + * that means the aci applies only to attrs with + * the given value. Furthermore, if the attr is + * of the form =*, then is + * treated as a prefix, and the aci applies to + * any value with that prefix. + * + * Ideally, this would allow r.e. matches. + */ + if (aci_get_part(&right, 0, '*', &left) < 0 + || right.bv_len <= left.bv_len) + { + if (ber_bvstrcasecmp(val, &right) == 0) + return(1); + } else if (val->bv_len >= left.bv_len) { + if (strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0) + return(1); + } + } + } + } + return(0); +} + +static slap_access_t +aci_list_get_attr_rights( + struct berval *list, + const struct berval *attr, + struct berval *val ) +{ + struct berval bv; + slap_access_t mask; + int i; + + /* loop through each rights/attr pair, skip first part (action) */ + ACL_INIT(mask); + for (i = 1; aci_get_part(list, i + 1, ';', &bv) >= 0; i += 2) { + if (aci_list_has_attr(&bv, attr, val) == 0) + continue; + if (aci_get_part(list, i, ';', &bv) < 0) + continue; + mask |= aci_list_map_rights(&bv); + } + return(mask); +} + +static int +aci_list_get_rights( + struct berval *list, + const struct berval *attr, + struct berval *val, + slap_access_t *grant, + slap_access_t *deny ) +{ + struct berval perm, actn; + slap_access_t *mask; + int i, found; + + if (attr == NULL || attr->bv_len == 0 + || ber_bvstrcasecmp( attr, &aci_bv_entry ) == 0) { + attr = &aci_bv_br_entry; + } + + found = 0; + ACL_INIT(*grant); + ACL_INIT(*deny); + /* loop through each permissions clause */ + for (i = 0; aci_get_part(list, i, '$', &perm) >= 0; i++) { + if (aci_get_part(&perm, 0, ';', &actn) < 0) + continue; + if (ber_bvstrcasecmp( &aci_bv_grant, &actn ) == 0) { + mask = grant; + } else if (ber_bvstrcasecmp( &aci_bv_deny, &actn ) == 0) { + mask = deny; + } else { + continue; + } + + found = 1; + *mask |= aci_list_get_attr_rights(&perm, attr, val); + *mask |= aci_list_get_attr_rights(&perm, &aci_bv_br_all, NULL); + } + return(found); +} + +static int +aci_group_member ( + struct berval *subj, + struct berval *defgrpoc, + struct berval *defgrpat, + Backend *be, + Entry *e, + Connection *conn, + Operation *op, + regmatch_t *matches +) +{ + struct berval bv; + struct berval subjdn; + struct berval grpoc; + struct berval grpat; + ObjectClass *grp_oc = NULL; + AttributeDescription *grp_ad = NULL; + const char *text; + int rc; + + /* format of string is "group/objectClassValue/groupAttrName" */ + if (aci_get_part(subj, 0, '/', &subjdn) < 0) { + return(0); + } + + if (aci_get_part(subj, 1, '/', &grpoc) < 0) { + grpoc = *defgrpoc; + } + + if (aci_get_part(subj, 2, '/', &grpat) < 0) { + grpat = *defgrpat; + } + + rc = slap_bv2ad( &grpat, &grp_ad, &text ); + if( rc != LDAP_SUCCESS ) { + rc = 0; + goto done; + } + rc = 0; + + grp_oc = oc_bvfind( &grpoc ); + + if (grp_oc != NULL && grp_ad != NULL ) { + struct berval ndn; + bv.bv_val = (char *)ch_malloc(1024); + bv.bv_len = 1024; + string_expand(&bv, &subjdn, e->e_ndn, matches); + if ( dnNormalize2(NULL, &bv, &ndn) == LDAP_SUCCESS ) { + rc = (backend_group(be, conn, op, e, &ndn, &op->o_ndn, grp_oc, grp_ad) == 0); + free( ndn.bv_val ); + } + ch_free(bv.bv_val); + } + +done: + return(rc); +} + +static struct berval GroupClass = { + sizeof(SLAPD_GROUP_CLASS)-1, SLAPD_GROUP_CLASS }; +static struct berval GroupAttr = { + sizeof(SLAPD_GROUP_ATTR)-1, SLAPD_GROUP_ATTR }; +static struct berval RoleClass = { + sizeof(SLAPD_ROLE_CLASS)-1, SLAPD_ROLE_CLASS }; +static struct berval RoleAttr = { + sizeof(SLAPD_ROLE_ATTR)-1, SLAPD_ROLE_ATTR }; + +static int +aci_mask( + Backend *be, + Connection *conn, + Operation *op, + Entry *e, + AttributeDescription *desc, + struct berval *val, + struct berval *aci, + regmatch_t *matches, + slap_access_t *grant, + slap_access_t *deny +) +{ + struct berval bv, perms, sdn; + int rc; + + + assert( desc->ad_cname.bv_val != NULL ); + + /* parse an aci of the form: + oid#scope#action;rights;attr;rights;attr$action;rights;attr;rights;attr#dnType#subjectDN + + See draft-ietf-ldapext-aci-model-04.txt section 9.1 for + a full description of the format for this attribute. + + For now, this routine only supports scope=entry. + */ + + /* check that the aci has all 5 components */ + if (aci_get_part(aci, 4, '#', NULL) < 0) + return(0); + + /* check that the aci family is supported */ + if (aci_get_part(aci, 0, '#', &bv) < 0) + return(0); + + /* check that the scope is "entry" */ + if (aci_get_part(aci, 1, '#', &bv) < 0 + || ber_bvstrcasecmp( &aci_bv_entry, &bv ) != 0) + { + return(0); + } + + /* get the list of permissions clauses, bail if empty */ + if (aci_get_part(aci, 2, '#', &perms) <= 0) + return(0); + + /* check if any permissions allow desired access */ + if (aci_list_get_rights(&perms, &desc->ad_cname, val, grant, deny) == 0) + return(0); + + /* see if we have a DN match */ + if (aci_get_part(aci, 3, '#', &bv) < 0) + return(0); + + if (aci_get_part(aci, 4, '#', &sdn) < 0) + return(0); + + if (ber_bvstrcasecmp( &aci_bv_access_id, &bv ) == 0) { + struct berval ndn; + rc = 1; + if ( dnNormalize2(NULL, &sdn, &ndn) == LDAP_SUCCESS ) { + if (!dn_match( &op->o_ndn, &ndn)) + rc = 0; + free(ndn.bv_val); + } + return(rc); + } + + if (ber_bvstrcasecmp( &aci_bv_self, &bv ) == 0) { + if (dn_match(&op->o_ndn, &e->e_nname)) + return(1); + + } else if (ber_bvstrcasecmp( &aci_bv_dnattr, &bv ) == 0) { + Attribute *at; + AttributeDescription *ad = NULL; + const char *text; + + rc = slap_bv2ad( &sdn, &ad, &text ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + rc = 0; + + bv = op->o_ndn; + + for(at = attrs_find( e->e_attrs, ad ); + at != NULL; + at = attrs_find( at->a_next, ad ) ) + { + if (value_find( ad, at->a_vals, &bv) == 0 ) { + rc = 1; + break; + } + } + + return rc; + + + } else if (ber_bvstrcasecmp( &aci_bv_group, &bv ) == 0) { + if (aci_group_member(&sdn, &GroupClass, &GroupAttr, be, e, conn, op, matches)) + return(1); + + } else if (ber_bvstrcasecmp( &aci_bv_role, &bv ) == 0) { + if (aci_group_member(&sdn, &RoleClass, &RoleAttr, be, e, conn, op, matches)) + return(1); + + } else if (ber_bvstrcasecmp( &aci_bv_set, &bv ) == 0) { + if (aci_match_set(&sdn, be, e, conn, op, 0)) + return(1); + + } else if (ber_bvstrcasecmp( &aci_bv_set_ref, &bv ) == 0) { + if (aci_match_set(&sdn, be, e, conn, op, 1)) + return(1); + + } + + return(0); +} + +#endif /* SLAPD_ACI_ENABLED */ + +static void +string_expand( + struct berval *bv, + struct berval *pat, + char *match, + regmatch_t *matches) +{ + ber_len_t size; + char *sp; + char *dp; + int flag; + + size = 0; + bv->bv_val[0] = '\0'; + bv->bv_len--; /* leave space for lone $ */ + + flag = 0; + for ( dp = bv->bv_val, sp = pat->bv_val; size < bv->bv_len && + sp < pat->bv_val + pat->bv_len ; sp++) { + /* did we previously see a $ */ + if (flag) { + if (*sp == '$') { + *dp++ = '$'; + size++; + } else if (*sp >= '0' && *sp <= '9' ) { + int n; + int i; + int l; + + n = *sp - '0'; + *dp = '\0'; + i = matches[n].rm_so; + l = matches[n].rm_eo; + for ( ; size < bv->bv_len && i < l; size++, i++ ) { + *dp++ = match[i]; + size++; + } + *dp = '\0'; + } + flag = 0; + } else { + if (*sp == '$') { + flag = 1; + } else { + *dp++ = *sp; + size++; + } + } + } + + if (flag) { + /* must have ended with a single $ */ + *dp++ = '$'; + size++; + } + + *dp = '\0'; + bv->bv_len = size; + +#ifdef NEW_LOGGING + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1, + "string_expand: pattern = %.*s\n", pat->bv_len, pat->bv_val )); + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1, + "string_expand: expanded = %s\n", bv->bv_val )); +#else + Debug( LDAP_DEBUG_TRACE, "=> string_expand: pattern: %.*s\n", pat->bv_len, pat->bv_val, 0 ); + Debug( LDAP_DEBUG_TRACE, "=> string_expand: expanded: %s\n", bv->bv_val, 0, 0 ); +#endif +} + +static int +regex_matches( + struct berval *pat, /* pattern to expand and match against */ + char *str, /* string to match against pattern */ + char *buf, /* buffer with $N expansion variables */ + regmatch_t *matches /* offsets in buffer for $N expansion variables */ +) +{ + regex_t re; + char newbuf[512]; + struct berval bv; + int rc; + + bv.bv_len = sizeof(newbuf); + bv.bv_val = newbuf; + + if(str == NULL) str = ""; + + string_expand(&bv, pat, buf, matches); + if (( rc = regcomp(&re, newbuf, REG_EXTENDED|REG_ICASE))) { + char error[512]; + regerror(rc, &re, error, sizeof(error)); + +#ifdef NEW_LOGGING + LDAP_LOG(( "aci", LDAP_LEVEL_ERR, + "regex_matches: compile( \"%s\", \"%s\") failed %s\n", + pat->bv_val, str, error )); +#else + Debug( LDAP_DEBUG_TRACE, + "compile( \"%s\", \"%s\") failed %s\n", + pat->bv_val, str, error ); +#endif + return( 0 ); + } + + rc = regexec(&re, str, 0, NULL, 0); + regfree( &re ); + +#ifdef NEW_LOGGING + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL2, + "regex_matches: string: %s\n", str )); + LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL2, + "regex_matches: rc: %d %s\n", + rc, rc ? "matches" : "no matches" )); +#else + Debug( LDAP_DEBUG_TRACE, + "=> regex_matches: string: %s\n", str, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, + "=> regex_matches: rc: %d %s\n", + rc, !rc ? "matches" : "no matches", 0 ); +#endif + return( !rc ); } -#endif /* sunos5 */ diff --git a/servers/slapd/backglue.c b/servers/slapd/backglue.c new file mode 100644 index 0000000000..4b0b49000f --- /dev/null +++ b/servers/slapd/backglue.c @@ -0,0 +1,907 @@ +/* backglue.c - backend glue routines */ +/* $OpenLDAP$ */ +/* + * Copyright 2001-2002 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ + +/* + * Functions to glue a bunch of other backends into a single tree. + * All of the glued backends must share a common suffix. E.g., you + * can glue o=foo and ou=bar,o=foo but you can't glue o=foo and o=bar. + * + * This uses the backend structures and routines extensively, but is + * not an actual backend of its own. To use it you must add a "subordinate" + * keyword to the configuration of other backends. Subordinates will + * automatically be connected to their parent backend. + * + * The purpose of these functions is to allow you to split a single database + * into pieces (for load balancing purposes, whatever) but still be able + * to treat it as a single database after it's been split. As such, each + * of the glued backends should have identical rootdn and rootpw. + * + * If you need more elaborate configuration, you probably should be using + * back-meta instead. + * -- Howard Chu + */ + +#include "portable.h" + +#include + +#include + +#define SLAPD_TOOLS +#include "slap.h" + +typedef struct gluenode { + BackendDB *be; + struct berval pdn; +} gluenode; + +typedef struct glueinfo { + BackendDB *be; + int nodes; + gluenode n[1]; +} glueinfo; + +static int glueMode; +static BackendDB *glueBack; + +/* Just like select_backend, but only for our backends */ +static BackendDB * +glue_back_select ( + BackendDB *be, + const char *dn +) +{ + glueinfo *gi = (glueinfo *) be->be_private; + struct berval bv; + int i; + + bv.bv_len = strlen(dn); + bv.bv_val = (char *) dn; + + for (i = 0; inodes; i++) { + if (dnIsSuffix(&bv, gi->n[i].be->be_nsuffix[0])) { + return gi->n[i].be; + } + } + return NULL; +} + +/* This function will only be called in tool mode */ +static int +glue_back_open ( + BackendInfo *bi +) +{ + int rc = 0; + static int glueOpened = 0; + + if (glueOpened) return 0; + + glueOpened = 1; + + /* If we were invoked in tool mode, open all the underlying backends */ + if (slapMode & SLAP_TOOL_MODE) { + rc = backend_startup (NULL); + } /* other case is impossible */ + return rc; +} + +/* This function will only be called in tool mode */ +static int +glue_back_close ( + BackendInfo *bi +) +{ + static int glueClosed = 0; + int rc; + + if (glueClosed) return 0; + + glueClosed = 1; + + if (slapMode & SLAP_TOOL_MODE) { + rc = backend_shutdown (NULL); + } + return rc; +} + +static int +glue_back_db_open ( + BackendDB *be +) +{ + glueinfo *gi = (glueinfo *)be->be_private; + static int glueOpened = 0; + int rc = 0; + + if (glueOpened) return 0; + + glueOpened = 1; + + gi->be->be_acl = be->be_acl; + + if (gi->be->bd_info->bi_db_open) + rc = gi->be->bd_info->bi_db_open(gi->be); + + return rc; +} + +static int +glue_back_db_close ( + BackendDB *be +) +{ + glueinfo *gi = (glueinfo *)be->be_private; + static int glueClosed = 0; + + if (glueClosed) return 0; + + glueClosed = 1; + + /* Close the master */ + if (gi->be->bd_info->bi_db_close) + gi->be->bd_info->bi_db_close( gi->be ); + + return 0; +} + +static int +glue_back_db_destroy ( + BackendDB *be +) +{ + glueinfo *gi = (glueinfo *)be->be_private; + + if (gi->be->bd_info->bi_db_destroy) + gi->be->bd_info->bi_db_destroy( gi->be ); + free (gi->be); + free (gi); + return 0; +} + +typedef struct glue_state { + int err; + int nentries; + int matchlen; + char *matched; + int nrefs; + BerVarray refs; + slap_callback *prevcb; +} glue_state; + +static void +glue_back_response ( + Connection *conn, + Operation *op, + ber_tag_t tag, + ber_int_t msgid, + ber_int_t err, + const char *matched, + const char *text, + BerVarray ref, + const char *resoid, + struct berval *resdata, + struct berval *sasldata, + LDAPControl **ctrls +) +{ + glue_state *gs = op->o_callback->sc_private; + + if (err == LDAP_SUCCESS || gs->err != LDAP_SUCCESS) + gs->err = err; + if (gs->err == LDAP_SUCCESS && gs->matched) { + free (gs->matched); + gs->matchlen = 0; + } + if (gs->err != LDAP_SUCCESS && matched) { + int len; + len = strlen (matched); + if (len > gs->matchlen) { + if (gs->matched) + free (gs->matched); + gs->matched = ch_strdup (matched); + gs->matchlen = len; + } + } + if (ref) { + int i, j, k; + BerVarray new; + + for (i=0; ref[i].bv_val; i++); + + j = gs->nrefs; + if (!j) { + new = ch_malloc ((i+1)*sizeof(struct berval)); + } else { + new = ch_realloc(gs->refs, + (j+i+1)*sizeof(struct berval)); + } + for (k=0; knrefs = j; + gs->refs = new; + } +} + +static void +glue_back_sresult ( + Connection *c, + Operation *op, + ber_int_t err, + const char *matched, + const char *text, + BerVarray refs, + LDAPControl **ctrls, + int nentries +) +{ + glue_state *gs = op->o_callback->sc_private; + + gs->nentries += nentries; + glue_back_response (c, op, 0, 0, err, matched, text, refs, + NULL, NULL, NULL, ctrls); +} + +static int +glue_back_sendentry ( + BackendDB *be, + Connection *c, + Operation *op, + Entry *e, + AttributeName *an, + int ao, + LDAPControl **ctrls +) +{ + slap_callback *tmp = op->o_callback; + glue_state *gs = tmp->sc_private; + int rc; + + op->o_callback = gs->prevcb; + if (op->o_callback && op->o_callback->sc_sendentry) { + rc = op->o_callback->sc_sendentry(be, c, op, e, an, ao, ctrls); + } else { + rc = send_search_entry(be, c, op, e, an, ao, ctrls); + } + op->o_callback = tmp; + return rc; +} + +static int +glue_back_search ( + BackendDB *b0, + Connection *conn, + Operation *op, + struct berval *dn, + struct berval *ndn, + int scope, + int deref, + int slimit, + int tlimit, + Filter *filter, + struct berval *filterstr, + AttributeName *attrs, + int attrsonly +) +{ + glueinfo *gi = (glueinfo *)b0->be_private; + BackendDB *be; + int i, rc, t2limit = 0, s2limit = 0; + long stoptime = 0; + struct berval bv; + glue_state gs = {0}; + slap_callback cb; + + cb.sc_response = glue_back_response; + cb.sc_sresult = glue_back_sresult; + cb.sc_sendentry = glue_back_sendentry; + cb.sc_private = &gs; + + gs.prevcb = op->o_callback; + + if (tlimit) { + stoptime = slap_get_time () + tlimit; + } + + switch (scope) { + case LDAP_SCOPE_BASE: + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_search) { + rc = be->be_search (be, conn, op, dn, ndn, scope, + deref, slimit, tlimit, filter, filterstr, + attrs, attrsonly); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result (conn, op, rc, NULL, + "No search target found", NULL, NULL); + } + return rc; + + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBTREE: + op->o_callback = &cb; + + /* + * Execute in reverse order, most general first + */ + for (i = gi->nodes-1; i >= 0; i--) { + if (!gi->n[i].be || !gi->n[i].be->be_search) + continue; + if (tlimit) { + t2limit = stoptime - slap_get_time (); + if (t2limit <= 0) + break; + } + if (slimit) { + s2limit = slimit - gs.nentries; + if (s2limit <= 0) + break; + } + /* + * check for abandon + */ + ldap_pvt_thread_mutex_lock (&op->o_abandonmutex); + rc = op->o_abandon; + ldap_pvt_thread_mutex_unlock (&op->o_abandonmutex); + if (rc) { + rc = 0; + goto done; + } + be = gi->n[i].be; + if (scope == LDAP_SCOPE_ONELEVEL && + dn_match(&gi->n[i].pdn, ndn)) { + rc = be->be_search (be, conn, op, + be->be_suffix[0], be->be_nsuffix[0], + LDAP_SCOPE_BASE, deref, + s2limit, t2limit, filter, filterstr, + attrs, attrsonly); + + } else if (scope == LDAP_SCOPE_SUBTREE && + dnIsSuffix(be->be_nsuffix[0], ndn)) { + rc = be->be_search (be, conn, op, + be->be_suffix[0], be->be_nsuffix[0], + scope, deref, + s2limit, t2limit, filter, filterstr, + attrs, attrsonly); + + } else if (dnIsSuffix(&bv, be->be_nsuffix[0])) { + rc = be->be_search (be, conn, op, dn, ndn, + scope, deref, + s2limit, t2limit, filter, filterstr, + attrs, attrsonly); + } + } + break; + } + op->o_callback = gs.prevcb; + + send_search_result (conn, op, gs.err, gs.matched, NULL, + gs.refs, NULL, gs.nentries); + +done: + if (gs.matched) + free (gs.matched); + if (gs.refs) + ber_bvarray_free(gs.refs); + return rc; +} + +static int +glue_back_bind ( + BackendDB *b0, + Connection *conn, + Operation *op, + struct berval *dn, + struct berval *ndn, + int method, + struct berval *cred, + struct berval *edn +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_bind) { + conn->c_authz_backend = be; + rc = be->be_bind (be, conn, op, dn, ndn, method, cred, edn); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result (conn, op, rc, NULL, "No bind target found", + NULL, NULL); + } + return rc; +} + +static int +glue_back_compare ( + BackendDB *b0, + Connection *conn, + Operation *op, + struct berval *dn, + struct berval *ndn, + AttributeAssertion *ava +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_compare) { + rc = be->be_compare (be, conn, op, dn, ndn, ava); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result (conn, op, rc, NULL, "No compare target found", + NULL, NULL); + } + return rc; +} + +static int +glue_back_modify ( + BackendDB *b0, + Connection *conn, + Operation *op, + struct berval *dn, + struct berval *ndn, + Modifications *mod +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_modify) { + rc = be->be_modify (be, conn, op, dn, ndn, mod); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result (conn, op, rc, NULL, + "No modify target found", NULL, NULL); + } + return rc; +} + +static int +glue_back_modrdn ( + BackendDB *b0, + Connection *conn, + Operation *op, + struct berval *dn, + struct berval *ndn, + struct berval *newrdn, + struct berval *nnewrdn, + int del, + struct berval *newsup, + struct berval *nnewsup +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_modrdn) { + rc = be->be_modrdn (be, conn, op, dn, ndn, + newrdn, nnewrdn, del, newsup, nnewsup ); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result (conn, op, rc, NULL, + "No modrdn target found", NULL, NULL); + } + return rc; +} + +static int +glue_back_add ( + BackendDB *b0, + Connection *conn, + Operation *op, + Entry *e +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, e->e_ndn); + + if (be && be->be_add) { + rc = be->be_add (be, conn, op, e); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result (conn, op, rc, NULL, "No add target found", + NULL, NULL); + } + return rc; +} + +static int +glue_back_delete ( + BackendDB *b0, + Connection *conn, + Operation *op, + struct berval *dn, + struct berval *ndn +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_delete) { + rc = be->be_delete (be, conn, op, dn, ndn); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result (conn, op, rc, NULL, "No delete target found", + NULL, NULL); + } + return rc; +} + +static int +glue_back_release_rw ( + BackendDB *b0, + Connection *conn, + Operation *op, + Entry *e, + int rw +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, e->e_ndn); + + if (be && be->be_release) { + rc = be->be_release (be, conn, op, e, rw); + } else { + entry_free (e); + rc = 0; + } + return rc; +} + +static int +glue_back_group ( + BackendDB *b0, + Connection *conn, + Operation *op, + Entry *target, + struct berval *ndn, + struct berval *ondn, + ObjectClass *oc, + AttributeDescription * ad +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_group) { + rc = be->be_group (be, conn, op, target, ndn, ondn, oc, ad); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + } + return rc; +} + +static int +glue_back_attribute ( + BackendDB *b0, + Connection *conn, + Operation *op, + Entry *target, + struct berval *ndn, + AttributeDescription *ad, + BerVarray *vals +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_attribute) { + rc = be->be_attribute (be, conn, op, target, ndn, ad, vals); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + } + return rc; +} + +static int +glue_back_referrals ( + BackendDB *b0, + Connection *conn, + Operation *op, + struct berval *dn, + struct berval *ndn, + const char **text +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, ndn->bv_val); + + if (be && be->be_chk_referrals) { + rc = be->be_chk_referrals (be, conn, op, dn, ndn, text); + } else { + rc = LDAP_SUCCESS;; + } + return rc; +} + +static int +glue_tool_entry_open ( + BackendDB *b0, + int mode +) +{ + /* We don't know which backend to talk to yet, so just + * remember the mode and move on... + */ + + glueMode = mode; + glueBack = NULL; + + return 0; +} + +static int +glue_tool_entry_close ( + BackendDB *b0 +) +{ + int rc = 0; + + if (glueBack) { + if (!glueBack->be_entry_close) + return 0; + rc = glueBack->be_entry_close (glueBack); + } + return rc; +} + +static ID +glue_tool_entry_first ( + BackendDB *b0 +) +{ + glueinfo *gi = (glueinfo *) b0->be_private; + int i; + + /* If we're starting from scratch, start at the most general */ + if (!glueBack) { + for (i = gi->nodes-1; i >= 0; i--) { + if (gi->n[i].be->be_entry_open && + gi->n[i].be->be_entry_first) { + glueBack = gi->n[i].be; + break; + } + } + + } + if (!glueBack || glueBack->be_entry_open (glueBack, glueMode) != 0) + return NOID; + + return glueBack->be_entry_first (glueBack); +} + +static ID +glue_tool_entry_next ( + BackendDB *b0 +) +{ + glueinfo *gi = (glueinfo *) b0->be_private; + int i; + ID rc; + + if (!glueBack || !glueBack->be_entry_next) + return NOID; + + rc = glueBack->be_entry_next (glueBack); + + /* If we ran out of entries in one database, move on to the next */ + if (rc == NOID) { + glueBack->be_entry_close (glueBack); + for (i=0; inodes; i++) { + if (gi->n[i].be == glueBack) + break; + } + if (i == 0) { + glueBack = NULL; + rc = NOID; + } else { + glueBack = gi->n[i-1].be; + rc = glue_tool_entry_first (b0); + } + } + return rc; +} + +static Entry * +glue_tool_entry_get ( + BackendDB *b0, + ID id +) +{ + if (!glueBack || !glueBack->be_entry_get) + return NULL; + + return glueBack->be_entry_get (glueBack, id); +} + +static ID +glue_tool_entry_put ( + BackendDB *b0, + Entry *e, + struct berval *text +) +{ + BackendDB *be; + int rc; + + be = glue_back_select (b0, e->e_ndn); + if (!be->be_entry_put) + return NOID; + + if (!glueBack) { + rc = be->be_entry_open (be, glueMode); + if (rc != 0) + return NOID; + } else if (be != glueBack) { + /* If this entry belongs in a different branch than the + * previous one, close the current database and open the + * new one. + */ + glueBack->be_entry_close (glueBack); + rc = be->be_entry_open (be, glueMode); + if (rc != 0) + return NOID; + } + glueBack = be; + return be->be_entry_put (be, e, text); +} + +static int +glue_tool_entry_reindex ( + BackendDB *b0, + ID id +) +{ + if (!glueBack || !glueBack->be_entry_reindex) + return -1; + + return glueBack->be_entry_reindex (glueBack, id); +} + +static int +glue_tool_sync ( + BackendDB *b0 +) +{ + glueinfo *gi = (glueinfo *) b0->be_private; + int i; + + /* just sync everyone */ + for (i = 0; inodes; i++) + if (gi->n[i].be->be_sync) + gi->n[i].be->be_sync (gi->n[i].be); + return 0; +} + +int +glue_sub_init( ) +{ + int i, j; + int cont = num_subordinates; + BackendDB *b1, *be; + BackendInfo *bi; + glueinfo *gi; + + /* While there are subordinate backends, search backwards through the + * backends and connect them to their superior. + */ + for (i = nBackendDB - 1, b1=&backendDB[i]; cont && i>=0; b1--,i--) { + if (b1->be_flags & SLAP_BFLAG_GLUE_SUBORDINATE) { + /* The last database cannot be a subordinate of noone */ + if (i == nBackendDB - 1) { + b1->be_flags ^= SLAP_BFLAG_GLUE_SUBORDINATE; + } + continue; + } + gi = NULL; + for (j = i-1, be=&backendDB[j]; j>=0; be--,j--) { + if (!(be->be_flags & SLAP_BFLAG_GLUE_SUBORDINATE)) { + continue; + } + /* We will only link it once */ + if (be->be_flags & SLAP_BFLAG_GLUE_LINKED) { + continue; + } + if (!dnIsSuffix(be->be_nsuffix[0], b1->be_nsuffix[0])) { + continue; + } + cont--; + be->be_flags |= SLAP_BFLAG_GLUE_LINKED; + if (gi == NULL) { + /* We create a copy of the superior's be + * structure, pointing to all of its original + * information. Then we replace elements of + * the superior's info with our own. The copy + * is used whenever we have operations to pass + * down to the real database. + */ + b1->be_flags |= SLAP_BFLAG_GLUE_INSTANCE; + gi = (glueinfo *)ch_malloc(sizeof(glueinfo)); + gi->be = (BackendDB *)ch_malloc( + sizeof(BackendDB) + sizeof(BackendInfo)); + bi = (BackendInfo *)(gi->be+1); + *gi->be = *b1; + gi->nodes = 0; + *bi = *b1->bd_info; + bi->bi_open = glue_back_open; + bi->bi_close = glue_back_close; + bi->bi_db_open = glue_back_db_open; + bi->bi_db_close = glue_back_db_close; + bi->bi_db_destroy = glue_back_db_destroy; + + bi->bi_op_bind = glue_back_bind; + bi->bi_op_search = glue_back_search; + bi->bi_op_compare = glue_back_compare; + bi->bi_op_modify = glue_back_modify; + bi->bi_op_modrdn = glue_back_modrdn; + bi->bi_op_add = glue_back_add; + bi->bi_op_delete = glue_back_delete; + + bi->bi_entry_release_rw = glue_back_release_rw; + bi->bi_acl_group = glue_back_group; + bi->bi_acl_attribute = glue_back_attribute; + bi->bi_chk_referrals = glue_back_referrals; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = glue_tool_entry_open; + bi->bi_tool_entry_close = glue_tool_entry_close; + bi->bi_tool_entry_first = glue_tool_entry_first; + bi->bi_tool_entry_next = glue_tool_entry_next; + bi->bi_tool_entry_get = glue_tool_entry_get; + bi->bi_tool_entry_put = glue_tool_entry_put; + bi->bi_tool_entry_reindex = glue_tool_entry_reindex; + bi->bi_tool_sync = glue_tool_sync; + } else { + gi = (glueinfo *)ch_realloc(gi, + sizeof(glueinfo) + + gi->nodes * sizeof(gluenode)); + } + gi->n[gi->nodes].be = be; + dnParent( be->be_nsuffix[0], &gi->n[gi->nodes].pdn ); + gi->nodes++; + } + if (gi) { + /* One more node for the master */ + gi = (glueinfo *)ch_realloc(gi, + sizeof(glueinfo) + gi->nodes * sizeof(gluenode)); + gi->n[gi->nodes].be = gi->be; + dnParent( b1->be_nsuffix[0], &gi->n[gi->nodes].pdn ); + gi->nodes++; + b1->be_private = gi; + b1->bd_info = bi; + } + } + /* If there are any unresolved subordinates left, something is wrong */ + return cont; +} diff --git a/servers/slapd/entry.c b/servers/slapd/entry.c index d5ee966b57..8094a09e8b 100644 --- a/servers/slapd/entry.c +++ b/servers/slapd/entry.c @@ -1,37 +1,56 @@ /* entry.c - routines for dealing with entries */ +/* $OpenLDAP$ */ +/* + * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ + +#include "portable.h" #include -#include -#include -#include -#include -#include "slap.h" -void entry_free(); -char *entry2str(); +#include +#include +#include +#include + +#include "slap.h" +#include "ldif.h" -static unsigned char *ebuf; /* buf returned by entry2str */ +static unsigned char *ebuf; /* buf returned by entry2str */ static unsigned char *ecur; /* pointer to end of currently used ebuf */ -static int emaxsize;/* max size of ebuf */ +static int emaxsize;/* max size of ebuf */ + +/* + * Empty root entry + */ +const Entry slap_entry_root = { NOID, { 0, "" }, { 0, "" }, NULL, 0, { 0, "" }, NULL }; + +int entry_destroy(void) +{ + if ( ebuf ) free( ebuf ); + ebuf = NULL; + ecur = NULL; + emaxsize = 0; + return 0; +} + Entry * -str2entry( char *s ) +str2entry( char *s ) { - int i; + int rc; Entry *e; - Attribute **a; char *type; - char *value; - char *next; - int vlen, nvals, maxvals; - struct berval bval; - struct berval *vals[2]; - char ptype[64]; + struct berval vals[2]; + AttributeDescription *ad; + const char *text; + char *next; /* - * In string format, an entry looks like this: + * LDIF is used as the string format. + * An entry looks like this: * - * \n * dn: \n * [:[:] \n] * [\n]* @@ -43,134 +62,277 @@ str2entry( char *s ) * or newline. */ - Debug( LDAP_DEBUG_TRACE, "=> str2entry\n", s, 0, 0 ); +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, + "str2entry: \"%s\"\n", s ? s : "NULL" )); +#else + Debug( LDAP_DEBUG_TRACE, "=> str2entry\n", + s ? s : "NULL", 0, 0 ); +#endif + /* initialize reader/writer lock */ e = (Entry *) ch_calloc( 1, sizeof(Entry) ); - /* check to see if there's an id included */ - next = s; - if ( isdigit( *s ) ) { - e->e_id = atoi( s ); - if ( (s = str_getline( &next )) == NULL ) { - Debug( LDAP_DEBUG_TRACE, - "<= str2entry NULL (missing newline after id)\n", - 0, 0, 0 ); - return( NULL ); - } + if( e == NULL ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_ERR, + "str2entry: entry allocation failed.\n" )); +#else + Debug( LDAP_DEBUG_ANY, + "<= str2entry NULL (entry allocation failed)\n", + 0, 0, 0 ); +#endif + return( NULL ); } + /* initialize entry */ + e->e_id = NOID; + /* dn + attributes */ - e->e_attrs = NULL; - vals[0] = &bval; - vals[1] = NULL; - ptype[0] = '\0'; - while ( (s = str_getline( &next )) != NULL ) { + vals[1].bv_val = NULL; + + next = s; + while ( (s = ldif_getline( &next )) != NULL ) { if ( *s == '\n' || *s == '\0' ) { break; } - if ( str_parse_line( s, &type, &value, &vlen ) != 0 ) { + if ( ldif_parse_line( s, &type, &vals[0].bv_val, &vals[0].bv_len ) != 0 ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, + "str2entry: NULL (parse_line)\n" )); +#else Debug( LDAP_DEBUG_TRACE, "<= str2entry NULL (parse_line)\n", 0, 0, 0 ); +#endif continue; } - if ( strcasecmp( type, ptype ) != 0 ) { - strncpy( ptype, type, sizeof(ptype) - 1 ); - nvals = 0; - maxvals = 0; - a = NULL; - } if ( strcasecmp( type, "dn" ) == 0 ) { + struct berval *pdn = NULL; + + free( type ); + if ( e->e_dn != NULL ) { - Debug( LDAP_DEBUG_ANY, - "str2entry: entry %d has multiple dns \"%s\" and \"%s\" (second ignored)\n", - e->e_id, e->e_dn, value ); - continue; +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, "str2entry: " + "entry %ld has multiple DNs \"%s\" and \"%s\"\n", + (long) e->e_id, e->e_dn, + vals[0].bv_val != NULL ? vals[0].bv_val : "" )); +#else + Debug( LDAP_DEBUG_ANY, "str2entry: " + "entry %ld has multiple DNs \"%s\" and \"%s\"\n", + (long) e->e_id, e->e_dn, + vals[0].bv_val != NULL ? vals[0].bv_val : "" ); +#endif + if( vals[0].bv_val != NULL ) free( vals[0].bv_val ); + entry_free( e ); + return NULL; + } + + rc = dnPrettyNormal( NULL, &vals[0], &e->e_name, &e->e_nname ); + free( vals[0].bv_val ); + if( rc != LDAP_SUCCESS ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, "str2entry: " + "entry %ld has invalid DN \"%s\"\n", + (long) e->e_id, + pdn->bv_val ? pdn->bv_val : "" )); +#else + Debug( LDAP_DEBUG_ANY, "str2entry: " + "entry %ld has invalid DN \"%s\"\n", + (long) e->e_id, + pdn->bv_val ? pdn->bv_val : "", 0 ); +#endif + entry_free( e ); + return NULL; } - e->e_dn = strdup( value ); continue; } - bval.bv_val = value; - bval.bv_len = vlen; - if ( attr_merge_fast( e, type, vals, nvals, 1, &maxvals, &a ) - != 0 ) { - Debug( LDAP_DEBUG_TRACE, + ad = NULL; + rc = slap_str2ad( type, &ad, &text ); + + if( rc != LDAP_SUCCESS ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, + "str2entry: str2ad(%s): %s\n", type, text )); +#else + Debug( slapMode & SLAP_TOOL_MODE + ? LDAP_DEBUG_ANY : LDAP_DEBUG_TRACE, + "<= str2entry: str2ad(%s): %s\n", type, text, 0 ); +#endif + if( slapMode & SLAP_TOOL_MODE ) { + entry_free( e ); + free( vals[0].bv_val ); + free( type ); + return NULL; + } + + rc = slap_str2undef_ad( type, &ad, &text ); + if( rc != LDAP_SUCCESS ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, + "str2entry: str2undef_ad(%s): %s\n", type, text )); +#else + Debug( LDAP_DEBUG_ANY, + "<= str2entry: str2undef_ad(%s): %s\n", + type, text, 0 ); +#endif + entry_free( e ); + free( vals[0].bv_val ); + free( type ); + return NULL; + } + } + + if( slapMode & SLAP_TOOL_MODE ) { + struct berval pval; + slap_syntax_validate_func *validate = + ad->ad_type->sat_syntax->ssyn_validate; + slap_syntax_transform_func *pretty = + ad->ad_type->sat_syntax->ssyn_pretty; + + if( pretty ) { + rc = pretty( ad->ad_type->sat_syntax, + &vals[0], &pval ); + + } else if( validate ) { + /* + * validate value per syntax + */ + rc = validate( ad->ad_type->sat_syntax, &vals[0] ); + + } else { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_INFO, + "str2entry: no validator for syntax %s\n", + ad->ad_type->sat_syntax->ssyn_oid )); +#else + Debug( LDAP_DEBUG_ANY, + "str2entry: no validator for syntax %s\n", + ad->ad_type->sat_syntax->ssyn_oid, 0, 0 ); +#endif + entry_free( e ); + free( vals[0].bv_val ); + free( type ); + return NULL; + } + + if( rc != 0 ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_ERR, + "str2entry: invalid value for syntax %s\n", + ad->ad_type->sat_syntax->ssyn_oid )); +#else + Debug( LDAP_DEBUG_ANY, + "str2entry: invalid value for syntax %s\n", + ad->ad_type->sat_syntax->ssyn_oid, 0, 0 ); +#endif + entry_free( e ); + free( vals[0].bv_val ); + free( type ); + return NULL; + } + + if( pretty ) { + free( vals[0].bv_val ); + vals[0] = pval; + } + } + + rc = attr_merge( e, ad, vals ); + if( rc != 0 ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, + "str2entry: NULL (attr_merge)\n" )); +#else + Debug( LDAP_DEBUG_ANY, "<= str2entry NULL (attr_merge)\n", 0, 0, 0 ); +#endif + entry_free( e ); + free( vals[0].bv_val ); + free( type ); return( NULL ); } - nvals++; + + free( type ); + free( vals[0].bv_val ); } /* check to make sure there was a dn: line */ if ( e->e_dn == NULL ) { - Debug( LDAP_DEBUG_ANY, "str2entry: entry %d has no dn\n", - e->e_id, 0, 0 ); +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_INFO, + "str2entry: entry %ld has no dn.\n", + (long) e->e_id )); +#else + Debug( LDAP_DEBUG_ANY, "str2entry: entry %ld has no dn\n", + (long) e->e_id, 0, 0 ); +#endif entry_free( e ); return( NULL ); } - Debug( LDAP_DEBUG_TRACE, "<= str2entry 0x%x\n", e, 0, 0 ); +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL2, + "str2entry(%s) -> 0x%lx\n", e->e_dn, (unsigned long)e )); +#else + Debug(LDAP_DEBUG_TRACE, "<= str2entry(%s) -> 0x%lx\n", + e->e_dn, (unsigned long) e, 0 ); +#endif return( e ); } + #define GRABSIZE BUFSIZ #define MAKE_SPACE( n ) { \ while ( ecur + (n) > ebuf + emaxsize ) { \ - int offset; \ + ptrdiff_t offset; \ offset = (int) (ecur - ebuf); \ ebuf = (unsigned char *) ch_realloc( (char *) ebuf, \ emaxsize + GRABSIZE ); \ emaxsize += GRABSIZE; \ ecur = ebuf + offset; \ } \ -} + } char * entry2str( Entry *e, - int *len, - int printid -) + int *len ) { Attribute *a; struct berval *bv; - int i, tmplen; + int i; + ber_len_t tmplen; /* * In string format, an entry looks like this: - * \n * dn: \n * [: \n]* */ ecur = ebuf; - if ( printid ) { - /* id + newline */ - MAKE_SPACE( 10 ); - sprintf( (char *) ecur, "%ld\n", e->e_id ); - ecur = (unsigned char *) strchr( (char *) ecur, '\0' ); - } - /* put the dn */ if ( e->e_dn != NULL ) { /* put "dn: " */ - tmplen = strlen( e->e_dn ); + tmplen = e->e_name.bv_len; MAKE_SPACE( LDIF_SIZE_NEEDED( 2, tmplen )); - put_type_and_value( (char **) &ecur, "dn", e->e_dn, tmplen ); + ldif_sput( (char **) &ecur, LDIF_PUT_VALUE, "dn", e->e_dn, tmplen ); } /* put the attributes */ for ( a = e->e_attrs; a != NULL; a = a->a_next ) { /* put ":[:] " line for each value */ - for ( i = 0; a->a_vals[i] != NULL; i++ ) { - bv = a->a_vals[i]; - tmplen = strlen( a->a_type ); + for ( i = 0; a->a_vals[i].bv_val != NULL; i++ ) { + bv = &a->a_vals[i]; + tmplen = a->a_desc->ad_cname.bv_len; MAKE_SPACE( LDIF_SIZE_NEEDED( tmplen, bv->bv_len )); - put_type_and_value( (char **) &ecur, a->a_type, + ldif_sput( (char **) &ecur, LDIF_PUT_VALUE, + a->a_desc->ad_cname.bv_val, bv->bv_val, bv->bv_len ); } } @@ -184,15 +346,311 @@ entry2str( void entry_free( Entry *e ) { - int i; - Attribute *a, *next; + /* free an entry structure */ + assert( e != NULL ); + + /* e_private must be freed by the caller */ + assert( e->e_private == NULL ); + e->e_private = NULL; + /* free DNs */ if ( e->e_dn != NULL ) { free( e->e_dn ); + e->e_dn = NULL; } - for ( a = e->e_attrs; a != NULL; a = next ) { - next = a->a_next; - attr_free( a ); + if ( e->e_ndn != NULL ) { + free( e->e_ndn ); + e->e_ndn = NULL; } + + if ( e->e_bv.bv_val != NULL ) { + free( e->e_bv.bv_val ); + e->e_bv.bv_val = NULL; + } + + /* free attributes */ + attrs_free( e->e_attrs ); + e->e_attrs = NULL; + free( e ); } + +/* + * These routines are used only by Backend. + * + * the Entry has three entry points (ways to find things): + * + * by entry e.g., if you already have an entry from the cache + * and want to delete it. (really by entry ptr) + * by dn e.g., when looking for the base object of a search + * by id e.g., for search candidates + * + * these correspond to three different avl trees that are maintained. + */ + +int +entry_cmp( Entry *e1, Entry *e2 ) +{ + return( e1 < e2 ? -1 : (e1 > e2 ? 1 : 0) ); +} + +int +entry_dn_cmp( Entry *e1, Entry *e2 ) +{ + /* compare their normalized UPPERCASED dn's */ + int rc = e1->e_nname.bv_len - e2->e_nname.bv_len; + if (rc) return rc; + return( strcmp( e1->e_ndn, e2->e_ndn ) ); +} + +int +entry_id_cmp( Entry *e1, Entry *e2 ) +{ + return( e1->e_id < e2->e_id ? -1 : (e1->e_id > e2->e_id ? 1 : 0) ); +} + +#ifdef SLAPD_BDB + +/* This is like a ber_len */ +static ber_len_t +entry_lenlen(ber_len_t len) +{ + if (len <= 0x7f) + return 1; + if (len <= 0xff) + return 2; + if (len <= 0xffff) + return 3; + if (len <= 0xffffff) + return 4; + return 5; +} + +static void +entry_putlen(unsigned char **buf, ber_len_t len) +{ + ber_len_t lenlen = entry_lenlen(len); + + if (lenlen == 1) { + **buf = (unsigned char) len; + } else { + int i; + **buf = 0x80 | ((unsigned char) lenlen - 1); + for (i=lenlen-1; i>0; i--) { + (*buf)[i] = (unsigned char) len; + len >>= 8; + } + } + *buf += lenlen; +} + +static ber_len_t +entry_getlen(unsigned char **buf) +{ + ber_len_t len; + int i; + + len = *(*buf)++; + if (len <= 0x7f) + return len; + i = len & 0x7f; + len = 0; + for (;i > 0; i--) { + len <<= 8; + len |= *(*buf)++; + } + return len; +} + +/* Flatten an Entry into a buffer. The buffer is filled with just the + * strings/bervals of all the entry components. Each field is preceded + * by its length, encoded the way ber_put_len works. Every field is NUL + * terminated. The entire buffer size is precomputed so that a single + * malloc can be performed. The entry size is also recorded, + * to aid in entry_decode. + */ +int entry_encode(Entry *e, struct berval *bv) +{ + ber_len_t siz = sizeof(Entry); + ber_len_t len, dnlen, ndnlen; + int i; + Attribute *a; + unsigned char *ptr; + +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, + "entry_encode: id: 0x%08lx \"%s\"\n", + (long) e->e_id, e->e_dn )); +#else + Debug( LDAP_DEBUG_TRACE, "=> entry_encode(0x%08lx): %s\n", + (long) e->e_id, e->e_dn, 0 ); +#endif + dnlen = e->e_name.bv_len; + ndnlen = e->e_nname.bv_len; + len = dnlen + ndnlen + 2; /* two trailing NUL bytes */ + len += entry_lenlen(dnlen); + len += entry_lenlen(ndnlen); + for (a=e->e_attrs; a; a=a->a_next) { + /* For AttributeDesc, we only store the attr name */ + siz += sizeof(Attribute); + len += a->a_desc->ad_cname.bv_len+1; + len += entry_lenlen(a->a_desc->ad_cname.bv_len); + for (i=0; a->a_vals[i].bv_val; i++) { + siz += sizeof(struct berval); + len += a->a_vals[i].bv_len + 1; + len += entry_lenlen(a->a_vals[i].bv_len); + } + len += entry_lenlen(i); + siz += sizeof(struct berval); /* empty berval at end */ + } + len += 1; /* NUL byte at end */ + len += entry_lenlen(siz); + bv->bv_len = len; + bv->bv_val = ch_malloc(len); + ptr = (unsigned char *)bv->bv_val; + entry_putlen(&ptr, siz); + entry_putlen(&ptr, dnlen); + AC_MEMCPY(ptr, e->e_dn, dnlen); + ptr += dnlen; + *ptr++ = '\0'; + entry_putlen(&ptr, ndnlen); + AC_MEMCPY(ptr, e->e_ndn, ndnlen); + ptr += ndnlen; + *ptr++ = '\0'; + + for (a=e->e_attrs; a; a=a->a_next) { + entry_putlen(&ptr, a->a_desc->ad_cname.bv_len); + AC_MEMCPY(ptr, a->a_desc->ad_cname.bv_val, + a->a_desc->ad_cname.bv_len); + ptr += a->a_desc->ad_cname.bv_len; + *ptr++ = '\0'; + if (a->a_vals) { + for (i=0; a->a_vals[i].bv_val; i++); + entry_putlen(&ptr, i); + for (i=0; a->a_vals[i].bv_val; i++) { + entry_putlen(&ptr, a->a_vals[i].bv_len); + memcpy(ptr, a->a_vals[i].bv_val, + a->a_vals[i].bv_len); + ptr += a->a_vals[i].bv_len; + *ptr++ = '\0'; + } + } + } + *ptr = '\0'; + return 0; +} + +/* Retrieve an Entry that was stored using entry_encode above. + * We malloc a single block with the size stored above for the Entry + * and all if its Attributes. We also must lookup the stored + * attribute names to get AttributeDescriptions. To detect if the + * attributes of an Entry are later modified, we note that e->e_attr + * is always a constant offset from (e). + * + * Note: everything is stored in a single contiguous block, so + * you can not free individual attributes or names from this + * structure. Attempting to do so will likely corrupt memory. + */ +int entry_decode(struct berval *bv, Entry **e) +{ + int i, j; + int rc; + Attribute *a; + Entry *x; + const char *text; + AttributeDescription *ad; + unsigned char *ptr = (unsigned char *)bv->bv_val; + BerVarray bptr; + + i = entry_getlen(&ptr); + x = ch_calloc(1, i); + i = entry_getlen(&ptr); + x->e_name.bv_val = ptr; + x->e_name.bv_len = i; + ptr += i+1; + i = entry_getlen(&ptr); + x->e_nname.bv_val = ptr; + x->e_nname.bv_len = i; + ptr += i+1; +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL2, + "entry_decode: \"%s\"\n", x->e_dn )); +#else + Debug( LDAP_DEBUG_TRACE, + "entry_decode: \"%s\"\n", + x->e_dn, 0, 0 ); +#endif + x->e_bv = *bv; + + /* A valid entry must have at least one attr, so this + * pointer can never be NULL + */ + x->e_attrs = (Attribute *)(x+1); + bptr = (BerVarray)x->e_attrs; + a = NULL; + + while (i = entry_getlen(&ptr)) { + struct berval bv; + bv.bv_len = i; + bv.bv_val = ptr; + if (a) { + a->a_next = (Attribute *)bptr; + } + a = (Attribute *)bptr; + ad = NULL; + rc = slap_bv2ad( &bv, &ad, &text ); + + if( rc != LDAP_SUCCESS ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_INFO, + "entry_decode: str2ad(%s): %s\n", ptr, text )); +#else + Debug( LDAP_DEBUG_TRACE, + "<= entry_decode: str2ad(%s): %s\n", ptr, text, 0 ); +#endif + rc = slap_bv2undef_ad( &bv, &ad, &text ); + + if( rc != LDAP_SUCCESS ) { +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_INFO, + "entry_decode: str2undef_ad(%s): %s\n", ptr, text)); +#else + Debug( LDAP_DEBUG_ANY, + "<= entry_decode: str2undef_ad(%s): %s\n", + ptr, text, 0 ); +#endif + return rc; + } + } + ptr += i + 1; + a->a_desc = ad; + bptr = (BerVarray)(a+1); + a->a_vals = bptr; + a->a_flags = 0; + j = entry_getlen(&ptr); + + while (j) { + i = entry_getlen(&ptr); + bptr->bv_len = i; + bptr->bv_val = (char *)ptr; + ptr += i+1; + bptr++; + j--; + } + bptr->bv_val = NULL; + bptr->bv_len = 0; + bptr++; + } + if (a) + a->a_next = NULL; +#ifdef NEW_LOGGING + LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1, + "entry_decode: %s\n", x->e_dn )); +#else + Debug(LDAP_DEBUG_TRACE, "<= entry_decode(%s)\n", + x->e_dn, 0, 0 ); +#endif + *e = x; + return 0; +} +#endif diff --git a/servers/slapd/sets.c b/servers/slapd/sets.c new file mode 100644 index 0000000000..9d01b9c671 --- /dev/null +++ b/servers/slapd/sets.c @@ -0,0 +1,401 @@ +/* $OpenLDAP$ */ +/* + * Copyright 2000-2002 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ + +#include "portable.h" + +#include +#include + +#include "slap.h" +#include "sets.h" + +static BerVarray set_join (BerVarray lset, int op, BerVarray rset); +static BerVarray set_chase (SLAP_SET_GATHER gatherer, + void *cookie, BerVarray set, struct berval *attr, int closure); +static int set_samedn (char *dn1, char *dn2); + +long +slap_set_size (BerVarray set) +{ + int i; + + i = 0; + if (set != NULL) { + while (set[i].bv_val) + i++; + } + return(i); +} + +void +slap_set_dispose (BerVarray set) +{ + ber_bvarray_free(set); +} + +static BerVarray +set_join (BerVarray lset, int op, BerVarray rset) +{ + BerVarray set; + long i, j, last; + + set = NULL; + if (op == '|') { + if (lset == NULL || lset->bv_val == NULL) { + if (rset == NULL) { + if (lset == NULL) + return(ch_calloc(1, sizeof(struct berval))); + return(lset); + } + slap_set_dispose(lset); + return(rset); + } + if (rset == NULL || rset->bv_val == NULL) { + slap_set_dispose(rset); + return(lset); + } + + i = slap_set_size(lset) + slap_set_size(rset) + 1; + set = ch_calloc(i, sizeof(struct berval)); + if (set != NULL) { + /* set_chase() depends on this routine to + * keep the first elements of the result + * set the same (and in the same order) + * as the left-set. + */ + for (i = 0; lset[i].bv_val; i++) + set[i] = lset[i]; + ch_free(lset); + for (i = 0; rset[i].bv_val; i++) { + for (j = 0; set[j].bv_val; j++) { + if (set_samedn(rset[i].bv_val, set[j].bv_val)) { + ch_free(rset[i].bv_val); + rset[i].bv_val = NULL; + break; + } + } + if (rset[i].bv_val) + set[j] = rset[i]; + } + ch_free(rset); + } + return(set); + } + + if (op == '&') { + if (lset == NULL || lset->bv_val == NULL || rset == NULL || rset->bv_val == NULL) { + set = ch_calloc(1, sizeof(struct berval)); + } else { + set = lset; + lset = NULL; + last = slap_set_size(set) - 1; + for (i = 0; set[i].bv_val; i++) { + for (j = 0; rset[j].bv_val; j++) { + if (set_samedn(set[i].bv_val, rset[j].bv_val)) + break; + } + if (rset[j].bv_val == NULL) { + ch_free(set[i].bv_val); + set[i] = set[last]; + set[last].bv_val = NULL; + last--; + i--; + } + } + } + } + + slap_set_dispose(lset); + slap_set_dispose(rset); + return(set); +} + +static BerVarray +set_chase (SLAP_SET_GATHER gatherer, + void *cookie, BerVarray set, struct berval *attr, int closure) +{ + BerVarray vals, nset; + char attrstr[32]; + struct berval bv; + int i; + + bv.bv_len = attr->bv_len; + bv.bv_val = attrstr; + + if (set == NULL) + return(ch_calloc(1, sizeof(struct berval))); + + if (set->bv_val == NULL) + return(set); + + if (attr->bv_len > (sizeof(attrstr) - 1)) { + slap_set_dispose(set); + return(NULL); + } + AC_MEMCPY(attrstr, attr->bv_val, attr->bv_len); + attrstr[attr->bv_len] = 0; + + nset = ch_calloc(1, sizeof(struct berval)); + if (nset == NULL) { + slap_set_dispose(set); + return(NULL); + } + for (i = 0; set[i].bv_val; i++) { + vals = (gatherer)(cookie, &set[i], &bv); + if (vals != NULL) + nset = set_join(nset, '|', vals); + } + slap_set_dispose(set); + + if (closure) { + for (i = 0; nset[i].bv_val; i++) { + vals = (gatherer)(cookie, &nset[i], &bv); + if (vals != NULL) { + nset = set_join(nset, '|', vals); + if (nset == NULL) + break; + } + } + } + return(nset); +} + +static int +set_samedn (char *dn1, char *dn2) +{ + char c1, c2; + + while (*dn1 == ' ') dn1++; + while (*dn2 == ' ') dn2++; + while (*dn1 || *dn2) { + if (*dn1 != '=' && *dn1 != ',' + && *dn2 != '=' && *dn2 != ',') + { + c1 = *dn1++; + c2 = *dn2++; + if (c1 >= 'a' && c1 <= 'z') + c1 -= 'a' - 'A'; + if (c2 >= 'a' && c2 <= 'z') + c2 -= 'a' - 'A'; + if (c1 != c2) + return(0); + } else { + while (*dn1 == ' ') dn1++; + while (*dn2 == ' ') dn2++; + if (*dn1++ != *dn2++) + return(0); + while (*dn1 == ' ') dn1++; + while (*dn2 == ' ') dn2++; + } + } + return(1); +} + +int +slap_set_filter (SLAP_SET_GATHER gatherer, + void *cookie, struct berval *fbv, + struct berval *user, struct berval *this, BerVarray *results) +{ +#define IS_SET(x) ( (long)(x) >= 256 ) +#define IS_OP(x) ( (long)(x) < 256 ) +#define SF_ERROR(x) do { rc = -1; goto _error; } while (0) +#define SF_TOP() ( (BerVarray)( (stp < 0) ? 0 : stack[stp] ) ) +#define SF_POP() ( (BerVarray)( (stp < 0) ? 0 : stack[stp--] ) ) +#define SF_PUSH(x) do { \ + if (stp >= 63) SF_ERROR(overflow); \ + stack[++stp] = (BerVarray)(long)(x); \ + } while (0) + + BerVarray set, lset; + BerVarray stack[64]; + int len, op, rc, stp; + char c, *filter = fbv->bv_val; + + if (results) + *results = NULL; + + stp = -1; + while ((c = *filter++)) { + set = NULL; + switch (c) { + case ' ': + case '\t': + case '\x0A': + case '\x0D': + break; + + case '(': + if (IS_SET(SF_TOP())) + SF_ERROR(syntax); + SF_PUSH(c); + break; + + case ')': + set = SF_POP(); + if (IS_OP(set)) + SF_ERROR(syntax); + if (SF_TOP() == (void *)'(') { + SF_POP(); + SF_PUSH(set); + set = NULL; + } else if (IS_OP(SF_TOP())) { + op = (long)SF_POP(); + lset = SF_POP(); + SF_POP(); + set = set_join(lset, op, set); + if (set == NULL) + SF_ERROR(memory); + SF_PUSH(set); + set = NULL; + } else { + SF_ERROR(syntax); + } + break; + + case '&': + case '|': + set = SF_POP(); + if (IS_OP(set)) + SF_ERROR(syntax); + if (SF_TOP() == 0 || SF_TOP() == (void *)'(') { + SF_PUSH(set); + set = NULL; + } else if (IS_OP(SF_TOP())) { + op = (long)SF_POP(); + lset = SF_POP(); + set = set_join(lset, op, set); + if (set == NULL) + SF_ERROR(memory); + SF_PUSH(set); + set = NULL; + } else { + SF_ERROR(syntax); + } + SF_PUSH(c); + break; + + case '[': + if ((SF_TOP() == (void *)'/') || IS_SET(SF_TOP())) + SF_ERROR(syntax); + for ( len = 0; + (c = *filter++) && (c != ']'); + len++) + { } + if (c == 0) + SF_ERROR(syntax); + + set = ch_calloc(2, sizeof(struct berval)); + if (set == NULL) + SF_ERROR(memory); + set->bv_val = ch_calloc(len + 1, sizeof(char)); + if (set->bv_val == NULL) + SF_ERROR(memory); + AC_MEMCPY(set->bv_val, &filter[-len - 1], len); + set->bv_len = len; + SF_PUSH(set); + set = NULL; + break; + + case '-': + c = *filter++; + if (c != '>') + SF_ERROR(syntax); + /* fall through to next case */ + + case '/': + if (IS_OP(SF_TOP())) + SF_ERROR(syntax); + SF_PUSH('/'); + break; + + default: + if ((c != '_') + && (c < 'A' || c > 'Z') + && (c < 'a' || c > 'z')) + { + SF_ERROR(syntax); + } + filter--; + for ( len = 1; + (c = filter[len]) + && ((c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z')); + len++) + { } + if (len == 4 + && memcmp("this", filter, len) == 0) + { + if ((SF_TOP() == (void *)'/') || IS_SET(SF_TOP())) + SF_ERROR(syntax); + set = ch_calloc(2, sizeof(struct berval)); + if (set == NULL) + SF_ERROR(memory); + ber_dupbv( set, this ); + if (set->bv_val == NULL) + SF_ERROR(memory); + } else if (len == 4 + && memcmp("user", filter, len) == 0) + { + if ((SF_TOP() == (void *)'/') || IS_SET(SF_TOP())) + SF_ERROR(syntax); + set = ch_calloc(2, sizeof(struct berval)); + if (set == NULL) + SF_ERROR(memory); + ber_dupbv( set, user ); + if (set->bv_val == NULL) + SF_ERROR(memory); + } else if (SF_TOP() != (void *)'/') { + SF_ERROR(syntax); + } else { + struct berval fb2; + SF_POP(); + fb2.bv_val = filter; + fb2.bv_len = len; + set = set_chase(gatherer, + cookie, SF_POP(), &fb2, c == '*'); + if (set == NULL) + SF_ERROR(memory); + if (c == '*') + len++; + } + filter += len; + SF_PUSH(set); + set = NULL; + break; + } + } + + set = SF_POP(); + if (IS_OP(set)) + SF_ERROR(syntax); + if (SF_TOP() == 0) { + + } else if (IS_OP(SF_TOP())) { + op = (long)SF_POP(); + lset = SF_POP(); + set = set_join(lset, op, set); + if (set == NULL) + SF_ERROR(memory); + } else { + SF_ERROR(syntax); + } + + rc = slap_set_size(set); + if (results) { + *results = set; + set = NULL; + } + +_error: + if (IS_SET(set)) + slap_set_dispose(set); + while ((set = SF_POP())) { + if (IS_SET(set)) + slap_set_dispose(set); + } + return(rc); +} diff --git a/servers/slapd/tools/slapadd.c b/servers/slapd/tools/slapadd.c new file mode 100644 index 0000000000..d3ce99b9d2 --- /dev/null +++ b/servers/slapd/tools/slapadd.c @@ -0,0 +1,185 @@ +/* $OpenLDAP$ */ +/* + * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ +#include "portable.h" + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include "slapcommon.h" + +int +main( int argc, char **argv ) +{ + char *buf = NULL; + int lineno; + int lmax; + int rc = EXIT_SUCCESS; + + const char *text; + char textbuf[SLAP_TEXT_BUFLEN] = { '\0' }; + size_t textlen = sizeof textbuf; + + slap_tool_init( "slapadd", SLAPADD, argc, argv ); + + if( !be->be_entry_open || + !be->be_entry_close || + !be->be_entry_put ) + { + fprintf( stderr, "%s: database doesn't support necessary operations.\n", + progname ); + exit( EXIT_FAILURE ); + } + + lmax = 0; + lineno = 0; + + if( be->be_entry_open( be, 1 ) != 0 ) { + fprintf( stderr, "%s: could not open database.\n", + progname ); + exit( EXIT_FAILURE ); + } + + while( ldif_read_record( ldiffp, &lineno, &buf, &lmax ) ) { + Entry *e = str2entry( buf ); + struct berval bvtext; + + bvtext.bv_len = textlen; + bvtext.bv_val = textbuf; + + if( e == NULL ) { + fprintf( stderr, "%s: could not parse entry (line=%d)\n", + progname, lineno ); + rc = EXIT_FAILURE; + if( continuemode ) continue; + break; + } + + /* make sure the DN is not empty */ + if( !e->e_nname.bv_len ) { + fprintf( stderr, "%s: empty dn=\"%s\" (line=%d)\n", + progname, e->e_dn, lineno ); + rc = EXIT_FAILURE; + entry_free( e ); + if( continuemode ) continue; + break; + } + + /* check backend */ + if( select_backend( &e->e_nname, is_entry_referral(e), nosubordinates ) + != be ) + { + fprintf( stderr, "%s: line %d: " + "database (%s) not configured to hold \"%s\"\n", + progname, lineno, + be ? be->be_suffix[0]->bv_val : "", + e->e_dn ); + fprintf( stderr, "%s: line %d: " + "database (%s) not configured to hold \"%s\"\n", + progname, lineno, + be ? be->be_nsuffix[0]->bv_val : "", + e->e_ndn ); + rc = EXIT_FAILURE; + entry_free( e ); + if( continuemode ) continue; + break; + } + + { + Attribute *sc = attr_find( e->e_attrs, + slap_schema.si_ad_structuralObjectClass ); + Attribute *oc = attr_find( e->e_attrs, + slap_schema.si_ad_objectClass ); + + if( oc == NULL ) { + fprintf( stderr, "%s: dn=\"%s\" (line=%d): %s\n", + progname, e->e_dn, lineno, + "no objectClass attribute"); + rc = EXIT_FAILURE; + entry_free( e ); + if( continuemode ) continue; + break; + } + + if( sc == NULL ) { + struct berval vals[2]; + + int ret = structural_class( oc->a_vals, vals, + NULL, &text, textbuf, textlen ); + + if( vals[0].bv_len == 0 ) { + fprintf( stderr, "%s: dn=\"%s\" (line=%d): %s\n", + progname, e->e_dn, lineno, text ); + rc = EXIT_FAILURE; + entry_free( e ); + if( continuemode ) continue; + break; + } + + vals[1].bv_val = NULL; + attr_merge( e, slap_schema.si_ad_structuralObjectClass, + vals ); + } + } + + if( global_schemacheck ) { + /* check schema */ + + rc = entry_schema_check( be, e, NULL, &text, textbuf, textlen ); + + if( rc != LDAP_SUCCESS ) { + fprintf( stderr, "%s: dn=\"%s\" (line=%d): %s\n", + progname, e->e_dn, lineno, text ); + rc = EXIT_FAILURE; + entry_free( e ); + if( continuemode ) continue; + break; + } + } + + if (!dryrun) { + ID id = be->be_entry_put( be, e, &bvtext ); + if( id == NOID ) { + fprintf( stderr, "%s: could not add entry dn=\"%s\" (line=%d): %s\n", + progname, e->e_dn, lineno, bvtext.bv_val ); + rc = EXIT_FAILURE; + entry_free( e ); + if( continuemode ) continue; + break; + } + + if ( verbose ) { + fprintf( stderr, "added: \"%s\" (%08lx)\n", + e->e_dn, (long) id ); + } + } else { + if ( verbose ) { + fprintf( stderr, "(dry) added: \"%s\"\n", e->e_dn ); + } + } + + entry_free( e ); + } + + ch_free( buf ); + + be->be_entry_close( be ); + + if( be->be_sync ) { + be->be_sync( be ); + } + + slap_tool_destroy(); + return rc; +} -- 2.39.5