From fefc29786cc4169d2c0f1a3ea538cf753fd1683d Mon Sep 17 00:00:00 2001 From: Pierangelo Masarati Date: Thu, 18 Oct 2001 19:00:07 +0000 Subject: [PATCH] skeleton of ldap_str2nd/dn2str; works with most of the simple cases, but there's much to do with unicode, I think --- include/ldap.h | 11 +- libraries/liblber/lber-int.h | 4 + libraries/liblber/memory.c | 53 + libraries/libldap/Makefile.in | 4 +- libraries/libldap/dntest.c | 98 ++ libraries/libldap/getdn.c | 2061 +++++++++++++++++++++++++++++++++ libraries/libldap/ldap-int.h | 1 + libraries/libldap/schema.c | 2 +- 8 files changed, 2228 insertions(+), 6 deletions(-) create mode 100644 libraries/libldap/dntest.c diff --git a/include/ldap.h b/include/ldap.h index da9e59f78a..c2b13f9cef 100644 --- a/include/ldap.h +++ b/include/ldap.h @@ -1177,8 +1177,9 @@ typedef struct ldap_ava { char *la_attr; struct berval *la_value; unsigned la_flags; -#define LDAP_AVA_STRING 0x0000U -#define LDAP_AVA_BINARY 0x0001U +#define LDAP_AVA_STRING 0x0000U +#define LDAP_AVA_BINARY 0x0001U +#define LDAP_AVA_UTF8STRING 0x0002U } LDAPAVA; typedef LDAPAVA** LDAPRDN; @@ -1189,10 +1190,12 @@ typedef LDAPRDN** LDAPDN; #define LDAP_DN_FORMAT_LDAPV2 0x0001U #define LDAP_DN_FORMAT_DCE 0x0002U #define LDAP_DN_FORMAT_UFN 0x0003U /* dn2str only */ +#define LDAP_DN_FORMAT_AD_CANONICAL 0x0004U /* dn2str only */ #define LDAP_DN_FORMAT_MASK 0x000FU -/* str2dn flags */ -#define LDAP_DN_PEDANTIC 0x1000U +/* str2dn flags */ +#define LDAP_DN_P_LEADTRAILSPACES 0x1000U +#define LDAP_DN_PEDANTIC 0xF000U LDAP_F( int ) ldap_str2dn LDAP_P(( diff --git a/libraries/liblber/lber-int.h b/libraries/liblber/lber-int.h index bf03644868..b477d26491 100644 --- a/libraries/liblber/lber-int.h +++ b/libraries/liblber/lber-int.h @@ -147,6 +147,8 @@ ber_log_sos_dump LDAP_P(( /* memory.c */ /* simple macros to realloc for now */ LBER_F (BerMemoryFunctions *) ber_int_memory_fns; +LBER_F (char *) ber_strndup( LDAP_CONST char *, ber_len_t ); +LBER_F (char *) ber_strndup__( LDAP_CONST char *, size_t ); #ifdef CSRIMALLOC #define LBER_INT_MALLOC malloc @@ -162,6 +164,7 @@ LBER_F (BerMemoryFunctions *) ber_int_memory_fns; #define LBER_FREE free #define LBER_VFREE ber_memvfree #define LBER_STRDUP strdup +#define LBER_STRNDUP ber_strndup__ #else #define LBER_INT_MALLOC(s) ber_memalloc((s)) @@ -177,6 +180,7 @@ LBER_F (BerMemoryFunctions *) ber_int_memory_fns; #define LBER_FREE(p) ber_memfree((p)) #define LBER_VFREE(v) ber_memvfree((void**)(v)) #define LBER_STRDUP(s) ber_strdup((s)) +#define LBER_STRNDUP(s,l) ber_strndup((s),(l)) #endif /* sockbuf.c */ diff --git a/libraries/liblber/memory.c b/libraries/liblber/memory.c index 309fbeab77..723a03e58d 100644 --- a/libraries/liblber/memory.c +++ b/libraries/liblber/memory.c @@ -563,3 +563,56 @@ ber_strdup( LDAP_CONST char *s ) AC_MEMCPY( p, s, len ); return p; } + +char * +ber_strndup( LDAP_CONST char *s, ber_len_t l ) +{ + char *p; + size_t len; + + ber_int_options.lbo_valid = LBER_INITIALIZED; + +#ifdef LDAP_MEMORY_DEBUG + assert(s != NULL); /* bv damn better point to something */ +#endif + + if( s == NULL ) { + ber_errno = LBER_ERROR_PARAM; + return NULL; + } + + len = strlen( s ); + + if ( len > l ) { + len = l; + } + + if ( (p = LBER_MALLOC( len + 1 )) == NULL ) { + ber_errno = LBER_ERROR_MEMORY; + return NULL; + } + + AC_MEMCPY( p, s, len ); + p[ len ] = '\0'; + return p; +} + +char * +ber_strndup__( LDAP_CONST char *s, size_t l ) +{ + char *p; + size_t len; + + if ( s == NULL ) { + return NULL; + } + + len = strlen( s ); + if (( p = LBER_MALLOC( len + 1 ) ) == NULL ) { + return NULL; + } + + AC_MEMCPY( p, s, len ); + p[ len ] = '\0'; + return p; +} diff --git a/libraries/libldap/Makefile.in b/libraries/libldap/Makefile.in index 731d3adb15..4c108b8297 100644 --- a/libraries/libldap/Makefile.in +++ b/libraries/libldap/Makefile.in @@ -7,7 +7,7 @@ LIBRARY = libldap.la XLIBRARY = ../libldap.a -PROGRAMS = apitest ltest ttest +PROGRAMS = apitest ltest ttest dntest SRCS = bind.c open.c result.c error.c compare.c search.c \ controls.c messages.c references.c extended.c cyrus.c \ @@ -60,6 +60,8 @@ ltest: $(LIBRARY) test.o $(LDAP_LIBLBER_DEPEND) $(LTLINK) -o $@ test.o $(LIBS) ttest: $(LIBRARY) tmpltest.o $(LDAP_LIBLBER_DEPEND) $(LTLINK) -o $@ tmpltest.o $(LIBS) +dntest: $(LIBRARY) dntest.o $(LDAP_LIBLBER_DEPEND) + $(LTLINK) -o $@ dntest.o $(LIBS) CFFILES=ldap.conf ldapfilter.conf ldaptemplates.conf ldapsearchprefs.conf diff --git a/libraries/libldap/dntest.c b/libraries/libldap/dntest.c new file mode 100644 index 0000000000..3ed5cb9d7c --- /dev/null +++ b/libraries/libldap/dntest.c @@ -0,0 +1,98 @@ +/* $OpenLDAP$ */ +/* + * Copyright 1998-2000 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ +/* + * OpenLDAP API Test + * Written by: Pierangelo Masarati + * + * This program is designed to test the ldap_str2dn/ldap_dn2str + * functions + */ +#include "portable.h" + +#include +#include + +#include + +#include +#include "ldif.h" +#include "lutil.h" +#include "lutil_ldap.h" +#include "ldap_defaults.h" + +int +main(int argc, char *argv[]) +{ + int rc, i, debug = -1; + unsigned flags[ 2 ] = { 0U, 0U }; + char *str, buf[1024]; + LDAPDN *dn = NULL; + + if (argc < 2) { + fprintf(stderr, "usage: dntest [flags-in[,...]] [flags-out[,...]]\n\n"); + fprintf(stderr, "\tflags-in: V3,V2,DCE,PEDANTIC\n"); + fprintf(stderr, "\tflags-out: V3,V2,UFN,DCE,AD,PEDANTIC\n\n"); + return 0; + } + + if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &debug) != LBER_OPT_SUCCESS) { + fprintf(stderr, "Could not set LBER_OPT_DEBUG_LEVEL %d\n", debug); + } + if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug) != LDAP_OPT_SUCCESS) { + fprintf(stderr, "Could not set LDAP_OPT_DEBUG_LEVEL %d\n", debug); + } + + if ( strcmp(argv[1], "-") == 0) { + size_t len; + + fgets(buf, sizeof(buf), stdin); + len = strlen(buf)-1; + if (len >= 0 && buf[len] == '\n') { + buf[len] = '\0'; + } + str = buf; + } else { + str = argv[1]; + } + + if (argc >= 3) { + for ( i = 0; i < argc-2; i++ ) { + char *s, *e; + for (s = argv[2+i]; s; s = e) { + e = strchr(s, ','); + if (e != NULL) { + e[0] = '\0'; + e++; + } + + if (!strcasecmp(s, "V3")) { + flags[i] |= LDAP_DN_FORMAT_LDAPV3; + } else if (!strcasecmp(s, "V2")) { + flags[i] |= LDAP_DN_FORMAT_LDAPV2; + } else if (!strcasecmp(s, "DCE")) { + flags[i] |= LDAP_DN_FORMAT_DCE; + } else if (!strcasecmp(s, "UFN")) { + flags[i] |= LDAP_DN_FORMAT_UFN; + } else if (!strcasecmp(s, "AD")) { + flags[i] |= LDAP_DN_FORMAT_AD_CANONICAL; + } else if (!strcasecmp(s, "PEDANTIC")) { + flags[i] |= LDAP_DN_PEDANTIC; + } + } + } + } + + rc = ldap_str2dn(str, &dn, flags[0]); + + if ( rc == LDAP_SUCCESS && + ldap_dn2str( dn, &str, flags[argc > 3 ? 1 : 0] ) + == LDAP_SUCCESS ) { + fprintf( stdout, "%s\n", str ); + } + + return 0; +} + diff --git a/libraries/libldap/getdn.c b/libraries/libldap/getdn.c index 90be475553..3d2e32d4a3 100644 --- a/libraries/libldap/getdn.c +++ b/libraries/libldap/getdn.c @@ -321,3 +321,2064 @@ explode_name( const char *name, int notypes, int is_type ) return( parts ); } + +/* 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 slapd.h; maybe it should be rewritten from this) */ +#define LDAP_DN_ASCII_SPACE(c) \ + ( (c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' ) +#define LDAP_DN_ASCII_LOWER(c) ( (c) >= 'a' && (c) <= 'z' ) +#define LDAP_DN_ASCII_UPPER(c) ( (c) >= 'A' && (c) <= 'Z' ) +#define LDAP_DN_ASCII_ALPHA(c) \ + ( LDAP_DN_ASCII_LOWER(c) || LDAP_DN_ASCII_UPPER(c) ) +#define LDAP_DN_ASCII_DIGIT(c) ( (c) >= '0' && (c) <= '9' ) +#define LDAP_DN_ASCII_LCASE_HEXALPHA(c) ( (c) >= 'a' && (c) <= 'f' ) +#define LDAP_DN_ASCII_UCASE_HEXALPHA(c) ( (c) >= 'A' && (c) <= 'F' ) +#define LDAP_DN_ASCII_HEXDIGIT(c) \ + ( LDAP_DN_ASCII_DIGIT(c) \ + || LDAP_DN_ASCII_LCASE_HEXALPHA(c) \ + || LDAP_DN_ASCII_UCASE_HEXALPHA(c) ) +#define LDAP_DN_ASCII_ALNUM(c) \ + ( LDAP_DN_ASCII_ALPHA(c) || LDAP_DN_ASCII_DIGIT(c) ) +#define LDAP_DN_ASCII_PRINTABLE(c) ( (c) >= ' ' && (c) <= '~' ) + +/* attribute type */ +#define LDAP_DN_OID_LEADCHAR(c) ( LDAP_DN_ASCII_DIGIT(c) ) +#define LDAP_DN_DESC_LEADCHAR(c) ( LDAP_DN_ASCII_ALPHA(c) ) +#define LDAP_DN_DESC_CHAR(c) ( LDAP_DN_ASCII_ALNUM(c) || (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_NEEDESCAPE(c) \ + ( LDAP_DN_ESCAPE(c) || LDAP_DN_NE(c) ) +#define LDAP_DN_NEEDESCAPE_LEAD(c) \ + ( LDAP_DN_ASCII_SPACE(c) || LDAP_DN_OCTOTHORPE(c) || LDAP_DN_NE(c) ) +#define LDAP_DN_NEEDESCAPE_TRAIL(c) \ + ( ( LDAP_DN_ASCII_SPACE(c) || LDAP_DN_NEEDESCAPE(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. + * + * 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]) ) +#define LDAP_DC_ATTR "dc" + +/* Composite rules */ +#define LDAP_DN_ALLOW_SPACES(f) \ + ( ( (f) & LDAP_DN_FORMAT_LDAPV2 ) || !( (f) & LDAP_DN_PEDANTIC ) ) +#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 ) + +/* from libraries/libldap/schema.c */ +extern char * parse_numericoid(const char **sp, int *code, const int flags); + +static int str2strval( const char *str, 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, unsigned *c ); +static int byte2hexpair( const char *val, char *pair ); +static int binval2hexstr( struct berval *val, char *str ); +static ber_len_t strval2strlen( struct berval *val, unsigned flags ); +static int strval2str( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static ber_len_t strval2IA5strlen( struct berval *val, unsigned flags ); +static int strval2IA5str( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static ber_len_t strval2DCEstrlen( struct berval *val, unsigned flags ); +static int strval2DCEstr( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static ber_len_t strval2ADstrlen( struct berval *val, unsigned flags ); +static int strval2ADstr( struct berval *val, char *str, unsigned flags, + ber_len_t *len ); +static int dn2domain( LDAPDN *dn, char **str, int *iRDN ); + +/* + * LDAPAVA helpers + */ +static LDAPAVA * +ldapava_new( const char *attr, const struct berval *val, unsigned flags ) +{ + LDAPAVA *ava; + + assert( attr ); + assert( val ); + + ava = LDAP_MALLOC( sizeof( LDAPAVA ) ); + + /* should we test it? */ + if ( ava == NULL ) { + return NULL; + } + + ava->la_attr = ( char * )attr; + ava->la_value = ( struct berval * )val; + ava->la_flags = flags; + + return ava; +} + +static void +ldapava_free( LDAPAVA *ava ) +{ + assert( ava ); + + LDAP_FREE( ava->la_attr ); + ber_bvfree( ava->la_value ); + + LDAP_FREE( ava ); +} + +static LDAPRDN * +ldapava_append_to_rdn( LDAPRDN *rdn, LDAPAVA *ava ) +{ + LDAPRDN *newRDN; + unsigned i = 0U; + + assert( ava ); + + if ( rdn != NULL ) { + for ( i = 0U; rdn[ i ]; i++ ) { + /* no op */ + } + } + newRDN = LDAP_REALLOC( rdn, ( i + 2 ) * sizeof( LDAPAVA ** ) ); + newRDN[ i ] = LDAP_MALLOC( sizeof( LDAPAVA * ) ); + newRDN[ i ][ 0 ] = ava; + newRDN[ i + 1 ] = NULL; + + return newRDN; +} + +static void +ldapava_free_rdn( LDAPRDN *rdn ) +{ + int iAVA; + + if ( rdn == NULL ) { + return; + } + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + assert( rdn[ iAVA ][ 0 ] ); + + ldapava_free( rdn[ iAVA ][ 0 ] ); + } + + LDAP_VFREE( rdn ); +} + +static LDAPDN * +ldapava_append_to_dn( LDAPDN *dn, LDAPRDN *rdn ) +{ + LDAPDN *newDN; + unsigned i = 0U; + + assert( rdn ); + + if ( dn != NULL ) { + for ( i = 0U; dn[ i ]; i++ ) { + /* no op */ + } + } + newDN = LDAP_REALLOC( dn, ( i + 2 ) * sizeof( LDAPRDN ** ) ); + newDN[ i ] = LDAP_MALLOC( sizeof( LDAPRDN * ) ); + newDN[ i ][ 0 ] = rdn; + newDN[ i + 1 ] = NULL; + + return newDN; +} + +static LDAPDN * +ldapava_insert_into_dn( LDAPDN *dn, LDAPRDN *rdn ) +{ + LDAPDN *newDN; + unsigned i = 0U; + + assert( rdn ); + + if ( dn != NULL ) { + for ( i = 0U; dn[ i ]; i++ ) { + /* no op */ + } + } + newDN = LDAP_MALLOC( ( i + 2 ) * sizeof( LDAPDN ) ); + AC_MEMCPY( &newDN[ 1 ], dn, i * sizeof( LDAPDN * ) ); + LDAP_FREE( dn ); + + newDN[ 0 ] = LDAP_MALLOC( sizeof( LDAPRDN * ) ); + newDN[ 0 ][ 0 ] = rdn; + newDN[ i + 1 ] = NULL; + + return newDN; +} + +static void +ldapava_free_dn( LDAPDN *dn ) +{ + int iRDN; + + if ( dn == NULL ) { + return; + } + + for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) { + assert( dn[ iRDN ][ 0 ] ); + + ldapava_free_rdn( dn[ iRDN ][ 0 ] ); + } + + LDAP_VFREE( dn ); +} + +/* + * Converts a string representation of a DN (in LDAPv3, LDAPv2 or DCE) + * into a structured 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. + */ +int +ldap_str2dn( const char *str, LDAPDN **dn, unsigned flags ) +{ + const char *p; + int state = B4AVA; + int rc = LDAP_INVALID_DN_SYNTAX; + int attrTypeEncoding, attrValueEncoding; + + char *attrType = NULL; + struct berval *attrValue = NULL; + + LDAPDN *newDN = NULL; + LDAPRDN *newRDN = NULL; + + assert( str ); + assert( dn ); + + Debug( LDAP_DEBUG_TRACE, "=> ldap_str2dn(%s,%u)\n%s", str, flags, "" ); + + *dn = NULL; + + switch ( LDAP_DN_FORMAT( flags ) ) { + 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_INVALID_DN_SYNTAX ); + + default: + return( LDAP_OTHER ); + } + + if ( str[ 0 ] == '\0' ) { + return( LDAP_SUCCESS ); + } + + 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++; + } + + 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_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; + } + } + + state = B4ATTRTYPE; + break; + + case B4ATTRTYPE: + /* 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 = parse_numericoid( &p, &err, 0 ); + if ( attrType == NULL ) { + goto parsing_error; + } + 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 + */ + for ( startPos = p++; p[ 0 ]; p++ ) { + if ( LDAP_DN_DESC_CHAR( p[ 0 ] ) ) { + continue; + } + + if ( LDAP_DN_LANG_SEP( p[ 0 ] ) ) { + + /* + * FIXME: RFC 2253 does not explicitly + * allow lang extensions to attribute + * types in DNs ... + */ + if ( flags & LDAP_DN_PEDANTIC ) { + goto parsing_error; + } + + /* + * should we rather trim ';' 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; + } + + assert( attrType == NULL ); + attrType = LDAP_STRNDUP( startPos, len ); + 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; + 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 ) ) { + if ( LDAP_DN_QUOTES( p[ 0 ] ) ) { + p++; + state = B4IA5VALUEQUOTED; + break; + } + + state = B4IA5VALUE; + break; + } + + /* FIXME: here STRING means UTF-8 string, right? */ + 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_LDAPV3: + if ( str2strval( p, &attrValue, + &p, flags, + &attrValueEncoding ) ) { + goto parsing_error; + } + break; + + case LDAP_DN_FORMAT_DCE: + /* FIXME: does DCE use UTF-8? */ + 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: { + LDAPAVA *ava; + LDAPRDN *rdn; + int rdnsep = 0; + + /* + * FIXME: should we accept empty values? + */ + + ava = ldapava_new( attrType, attrValue, + attrValueEncoding ); + if ( ava == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + + rdn = ldapava_append_to_rdn( newRDN, ava ); + if ( rdn == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + newRDN = rdn; + + /* add the AVA to this RDN */ +#if 1 +#if 0 + { + wchar_t buf[1024]; + + ldap_x_utf8s_to_wcs( buf, attrType, + sizeof( buf ) ); + fprintf( stderr, "***< %ls", buf ); + ldap_x_utf8s_to_wcs( buf, attrValue->bv_val, + sizeof( buf ) ); + fprintf( stderr, " = %ls >***\n", buf ); + } +#else + fprintf( stderr, "***< %s = %s >***\n", + attrType, attrValue->bv_val ); +#endif +#endif + + /* + * if we got an AVA separator ('+', | ',' * 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_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 ) { + LDAPDN *dn; + + if ( LDAP_DN_DCE( flags ) ) { + /* add in reversed order */ + dn = ldapava_insert_into_dn( newDN, + newRDN ); + } else { + dn = ldapava_append_to_dn( newDN, + newRDN ); + } + + if ( dn == NULL ) { + rc = LDAP_NO_MEMORY; + goto parsing_error; + } + newDN = dn; + + if ( p[ 0 ] == '\0' ) { + + /* + * the DN is over, phew + */ + rc = LDAP_SUCCESS; + goto return_result; + } + + /* expect AVA for a new RDN */ + newRDN = NULL; + } + + /* they should have been used in an AVA */ + attrType = NULL; + attrValue = NULL; + + p++; + state = B4AVA; + break; + } + + default: + assert( 0 ); + goto parsing_error; + } + } + +parsing_error:; + /* They are set to NULL after they're used in an AVA */ + if ( attrType ) { + LDAP_FREE( attrType ); + } + + if ( attrValue ) { + ber_bvfree( attrValue ); + } + + if ( newRDN ) { + ldapava_free_rdn( newRDN ); + } + + if ( newDN ) { + ldapava_free_dn( newDN ); + newDN = NULL; + } + +return_result:; + + Debug( LDAP_DEBUG_TRACE, "<= ldap_str2dn(%s,%u)=%d\n", str, flags, rc ); + *dn = newDN; + + 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, struct berval **val, const char **next, unsigned flags, unsigned *retFlags ) +{ + const char *p, *startPos, *endPos = NULL; + ber_len_t len, escapes; + + assert( str ); + assert( val ); + assert( next ); + + *val = NULL; + *next = NULL; + + 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 ] ) ) { + escapes++; + continue; + } + + if ( LDAP_DN_HEXPAIR( p ) ) { + /* + * FIXME: here I guess I need to decode + * the byte; it would be nice to check + * the resulting encoding is a legal + * UTF-8 char + */ + p++; + escapes += 2; + + /* + * we assume the string is UTF-8 + */ + *retFlags = LDAP_AVA_UTF8STRING; + continue; + } + + if ( LDAP_DN_PEDANTIC & flags ) { + return( 1 ); + } + /* + * FIXME: we allow escaping + * of chars that don't need + * to and do not belong to + * HEXDIGITS (we also allow + * single hexdigit; maybe we + * shouldn't). + */ + + } else if ( LDAP_DN_VALUE_END( p[ 0 ] ) ) { + break; + + } else if ( LDAP_DN_NEEDESCAPE( p[ 0 ] ) ) { + /* + * FIXME: maybe we can add + * escapes? + */ + 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 */ + } + } + + /* + * FIXME: test memory? + */ + len = ( endPos ? endPos : p ) - startPos - escapes; + *val = LDAP_MALLOC( sizeof( struct berval ) ); + ( *val )->bv_len = len; + + if ( escapes == 0 ) { + ( *val )->bv_val = LDAP_STRNDUP( startPos, len ); + } else { + ber_len_t s, d; + char *utfStart = NULL; + + ( *val )->bv_val = LDAP_MALLOC( len + 1 ); + for ( s = 0, d = 0; d < len; ) { + if ( LDAP_DN_ESCAPE( startPos[ s ] ) ) { + s++; + if ( LDAP_DN_NEEDESCAPE( startPos[ s ] ) ) { + ( *val )->bv_val[ d++ ] = + startPos[ s++ ]; + } else if ( LDAP_DN_HEXPAIR( &startPos[ s ] ) ) { + unsigned c; + + hexstr2bin( &startPos[ s ], &c ); + ( *val )->bv_val[ d++ ] = c; + s += 2; + } else { + /* + * we allow escaping of chars + * that do not need to + */ + ( *val )->bv_val[ d++ ] = + startPos[ s++ ]; + } + + } else { + ( *val )->bv_val[ d++ ] = startPos[ s++ ]; + } + } + ( *val )->bv_val[ d ] = '\0'; + assert( strlen( ( *val )->bv_val ) == len ); + } + + + *next = p; + + 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 ); + + *val = NULL; + *next = NULL; + + for ( startPos = p = str, escapes = 0; p[ 0 ]; p++ ) { + /* + * FIXME: is '\' the escape char for DCE? + */ + 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; + } + } + + /* + * (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 */ + } + } + + + len = ( endPos ? endPos : p ) - startPos - escapes; + *val = LDAP_MALLOC( sizeof( struct berval ) ); + ( *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 ); + } + + *next = p; + + 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 ); + + *val = NULL; + *next = NULL; + + /* + * FIXME: need to check how escape stuff works + * with LDAPv2 (RFC 1779, right?) + */ + + 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; + } + } + + /* strip trailing (unescaped) spaces */ + for ( endPos = p; + endPos > startPos + 1 && + LDAP_DN_ASCII_SPACE( endPos[ -1 ] ) && + !LDAP_DN_ESCAPE( endPos[ -2 ] ); + endPos-- ) { + /* no op */ + } + + *val = LDAP_MALLOC( sizeof( struct berval ) ); + 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 ); + } + *next = p; + + 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 ); + + *val = NULL; + *next = NULL; + + /* + * FIXME: of course, as long as we remove the quotes, + * we need to escape chars as required ... + */ + + /* initial quote already eaten */ + for ( startPos = p = str; p[ 0 ]; p++ ) { + /* + * FIXME: 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; + } + } + + if ( endPos == NULL ) { + return( 1 ); + } + + /* FIXME: strip trailing (unescaped) spaces? */ + for ( ; p[ 0 ] && LDAP_DN_ASCII_SPACE( p[ 0 ] ); p++ ) { + /* no op */ + } + + len = endPos - startPos - escapes; + assert( len >= 0 ); + *val = LDAP_MALLOC( sizeof( struct berval ) ); + ( *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 ); + } + + *next = p; + + return( 0 ); +} + +static int +hexstr2bin( const char *str, unsigned *c ) +{ + unsigned c1, c2; + + assert( str ); + assert( c ); + + c1 = str[ 0 ]; + c2 = str[ 1 ]; + + if ( LDAP_DN_ASCII_DIGIT( c1 ) ) { + *c = c1 - '0'; + } else { + c1 = tolower( c1 ); + + if ( LDAP_DN_ASCII_LCASE_HEXALPHA( c1 ) ) { + *c = c1 - 'a' + 10; + } + } + + *c <<= 4; + + if ( LDAP_DN_ASCII_DIGIT( c2 ) ) { + *c += c2 - '0'; + } else { + c2 = tolower( c2 ); + + if ( 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; + ber_len_t len; + ber_len_t s, d; + + assert( str ); + assert( val ); + assert( next ); + + *val = NULL; + *next = NULL; + + for ( startPos = p = str; p[ 0 ]; p += 2 ) { + /* + * FIXME: add test for spaces to allow trailing spaces + */ + if ( LDAP_DN_VALUE_END( p[ 0 ] ) ) { + break; + } + + if ( !LDAP_DN_HEXPAIR( p ) ) { + return( 1 ); + } + } + + /* FIXME: no trailing spaces allowed? */ + len = ( p - startPos ) / 2; + assert( 2 * len == p - startPos ); /* must be even! */ + + *val = LDAP_MALLOC( sizeof( struct berval ) ); + if ( *val == NULL ) { + return( LDAP_NO_MEMORY ); + } + + ( *val )->bv_len = len; + ( *val )->bv_val = LDAP_MALLOC( len + 1 ); + if ( ( *val )->bv_val == NULL ) { + LDAP_FREE( *val ); + return( LDAP_NO_MEMORY ); + } + + for ( s = 0, d = 0; d < len; s += 2, d++ ) { + unsigned c; + + hexstr2bin( &startPos[ s ], &c ); + + ( *val )->bv_val[ d ] = c; + } + + ( *val )->bv_val[ d ] = '\0'; + *next = p; + + 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 ); + + /* FIXME: what should I do with a null value? */ + 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 ber_len_t +strval2strlen( struct berval *val, unsigned flags ) +{ + ber_len_t l, cl; + char *p; + + assert( val ); + + /* FIXME: what should I do with a null value? */ + if ( val->bv_len == 0 ) { + return( 0 ); + } + + for ( l = 0, p = val->bv_val; p[ 0 ]; p += cl ) { + cl = ldap_utf8_charlen( p ); + if ( cl > 1 ) { + /* need to escape it */ + l += 3 * cl; + } else if ( LDAP_DN_NEEDESCAPE( p[ 0 ] ) ) { + l += 2; + } else { + l++; + } + } + + return l; +} + +/* + * 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, cl; + + assert( val ); + assert( str ); + assert( len ); + + /* FIXME: what should I do with a null value? */ + 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; s < val->bv_len; ) { + cl = ldap_utf8_charlen( &val->bv_val[ s ] ); + + if ( cl > 1 ) { + for ( ; cl--; ) { + str[ d++ ] = '\\'; + byte2hexpair( &val->bv_val[ s ], &str[ d ] ); + s++; + d += 2; + } + } else { + if ( LDAP_DN_NEEDESCAPE( val->bv_val[ s ] ) ) { + str[ d++ ] = '\\'; + } + str[ d++ ] = val->bv_val[ s++ ]; + } + } + + *len = d; + + return( 0 ); +} + +/* + * Length of the IA5 string representation (no UTF-8 allowed) + */ +static ber_len_t +strval2IA5strlen( struct berval *val, unsigned flags ) +{ + ber_len_t l; + char *p; + + assert( val ); + + /* FIXME: what should I do with a null value? */ + if ( val->bv_len == 0 ) { + return( 0 ); + } + + if ( flags & LDAP_AVA_UTF8STRING ) { + /* + * FIXME: binary encoded BER + */ + return( 0 ); + + } else { + for ( l = 0, p = val->bv_val; p[ 0 ]; p++ ) { + if ( LDAP_DN_NEEDESCAPE( p[ 0 ] ) ) { + l += 2; + } else { + l++; + } + } + } + + return l; +} + +/* + * 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; + + assert( val ); + assert( str ); + assert( len ); + + /* FIXME: what should I do with a null value? */ + if ( val->bv_len == 0 ) { + *len = 0; + return ( 0 ); + } + + if ( flags & LDAP_AVA_UTF8STRING ) { + /* + * FIXME: binary encoded BER + */ + 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( 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 ber_len_t +strval2DCEstrlen( struct berval *val, unsigned flags ) +{ + ber_len_t l; + char *p; + + assert( val ); + + /* FIXME: what should I do with a null value? */ + if ( val->bv_len == 0 ) { + return ( 0 ); + } + + if ( flags & LDAP_AVA_UTF8STRING ) { + /* + * FIXME: binary encoded BER + */ + return( 0 ); + + } else { + for ( l = 0, p = val->bv_val; p[ 0 ]; p++ ) { + if ( LDAP_DN_NEEDESCAPE_DCE( p[ 0 ] ) ) { + l += 2; + } else { + l++; + } + } + } + + return l; +} + +/* + * 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 ); + + /* FIXME: what should I do with a null value? */ + if ( val->bv_len == 0 ) { + *len = 0; + return ( 0 ); + } + + if ( flags & LDAP_AVA_UTF8STRING ) { + /* + * FIXME: binary encoded BER + */ + 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 ber_len_t +strval2ADstrlen( struct berval *val, unsigned flags ) +{ + ber_len_t l; + char *p; + + assert( val ); + + /* FIXME: what should I do with a null value? */ + if ( val->bv_len == 0 ) { + return ( 0 ); + } + + if ( flags & LDAP_AVA_UTF8STRING ) { + /* + * FIXME: binary encoded BER + */ + return( 0 ); + + } else { + for ( l = 0, p = val->bv_val; p[ 0 ]; p++ ) { + if ( LDAP_DN_NEEDESCAPE_AD( p[ 0 ] ) ) { + l += 2; + } else { + l++; + } + } + } + + return l; +} + +/* + * 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, cl; + + assert( val ); + assert( str ); + assert( len ); + + /* FIXME: what should I do with a null value? */ + if ( val->bv_len == 0 ) { + *len = 0; + return ( 0 ); + } + + if ( flags & LDAP_AVA_UTF8STRING ) { + /* + * FIXME: binary encoded BER + */ + 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 forst 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, char **str, int *iRDN ) +{ + int i; + int domain = 0, first = 1; + ber_len_t l = 1; /* we move the null also */ + + /* we are guaranteed there's enough memory in str */ + + /* sanity */ + assert( dn ); + assert( str ); + assert( *str ); + assert( iRDN ); + assert( *iRDN > 0 ); + + for ( i = *iRDN; i >= 0; i-- ) { + LDAPRDN *rdn; + LDAPAVA *ava; + + assert( dn[ i ][ 0 ] ); + rdn = dn[ i ][ 0 ]; + + assert( rdn[ 0 ][ 0 ] ); + ava = rdn[ 0 ][ 0 ]; + + /* FIXME: no composite rdn or non-"dc" types */ + /* FIXME: we do not allow binary values in domain */ + if ( rdn[ 1 ] || strcasecmp( ava->la_attr, LDAP_DC_ATTR ) + || ava->la_flags & LDAP_AVA_BINARY ) { + 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, *str, 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; + + return( domain ); +} + +/* + * 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 encode + */ +int ldap_dn2str( LDAPDN *dn, char **str, unsigned flags ) +{ + int iRDN, iAVA; + int rc = LDAP_OTHER; + ber_len_t len, l; + + ber_len_t ( *s2l )( struct berval *, unsigned ); + int ( *s2s )( struct berval *, char *, unsigned, ber_len_t * ); + + assert( str ); + + Debug( LDAP_DEBUG_TRACE, "=> ldap_dn2str(%u)\n%s%s", flags, "", "" ); + + *str = NULL; + + if ( dn == NULL ) { + *str = LDAP_STRDUP( "" ); + return( LDAP_SUCCESS ); + } + + switch ( LDAP_DN_FORMAT( flags ) ) { + case LDAP_DN_FORMAT_LDAPV3: + s2l = strval2strlen; + s2s = strval2str; + goto v2_v3; + + case LDAP_DN_FORMAT_LDAPV2: + s2l = strval2IA5strlen; + s2s = strval2IA5str; +v2_v3: + + /* + * FIXME: we're treating LDAPv3 and LDAPv2 the same way; + * is it correct? No. LDAPv2 need to use binary encode + * ( '#' + hex form of BER) in case of non-IA5 chars. + */ + for ( iRDN = 0, len = 0; dn[ iRDN ]; iRDN++ ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + + /* len(type) + '=' + '+' | ',' */ + len += strlen( ava->la_attr ) + 2; + + /* FIXME: are binary values allowed + * in LDAPv2? */ + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the length */ + len += 1 + 2 * ava->la_value->bv_len; + } else { + len += ( *s2l )( ava->la_value, + ava->la_flags ); + } + } + } + + if ( ( *str = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + ( *str )[ 0 ] = '\0'; + + for ( l = 0, iRDN = 0; dn[ iRDN ]; iRDN++ ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + ber_len_t al = strlen( ava->la_attr ); + + AC_MEMCPY( &( *str )[ l ], ava->la_attr, al ); + l += al; + + ( *str )[ l++ ] = '='; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + ( *str )[ l++ ] = '#'; + binval2hexstr( ava->la_value, + &( *str )[ l ] ); + l += 2 * ava->la_value->bv_len; + } else { + ber_len_t vl; + ( *s2s )( ava->la_value, + &( *str )[ l ], + ava->la_flags, &vl ); + l += vl; + } + ( *str )[ l++ ] = + ( rdn[ iAVA + 1 ] ? '+' : ',' ); + } + } + + /* + * trim the last ',' (the allocated memory + * is one byte longer than required) + */ + ( *str )[ len - 1 ] = '\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. + * 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. + */ + + for ( iRDN = 0, len = 0; dn[ iRDN ]; iRDN++ ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + + /* ' + ' | ', ' */ + len += ( rdn[ iAVA + 1 ] ? 3 : 2 ); + + /* FIXME: are binary values allowed in UFN? */ + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the value */ + len += 1 + 2 * ava->la_value->bv_len; + } else { + len += strval2strlen( ava->la_value, + ava->la_flags ); + } + } + } + + if ( ( *str = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + ( *str )[ 0 ] = '\0'; + + for ( l = 0, iRDN = 0; dn[ iRDN ]; iRDN++ ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + + if ( ava->la_flags & LDAP_AVA_BINARY ) { + ( *str )[ l++ ] = '#'; + binval2hexstr( ava->la_value, + &( *str )[ l ] ); + l += 2 * ava->la_value->bv_len; + } else { + ber_len_t vl; + strval2str( ava->la_value, + &( *str )[ l ], + ava->la_flags, &vl ); + l += vl; + } + + if ( rdn[ iAVA + 1 ]) { + AC_MEMCPY( &( *str )[ l ], " + ", 3 ); + l += 3; + } else { + AC_MEMCPY( &( *str )[ l ], ", ", 2 ); + l += 2; + } + } + } + + /* + * trim the last ', ' (the allocated memory + * is two bytes longer than required) + */ + ( *str )[ len - 2 ] = '\0'; + + rc = LDAP_SUCCESS; + break; + + case LDAP_DN_FORMAT_DCE: + for ( iRDN = 0, len = 0; dn[ iRDN ]; iRDN++ ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + + /* len(type) + '=' + ( ',' || '/' ) */ + len += strlen( ava->la_attr ) + 2; + + /* FIXME: are binary values allowed in DCE? */ + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the value */ + len += 1 + 2 * ava->la_value->bv_len; + } else { + len += strval2DCEstrlen( ava->la_value, + ava->la_flags ); + } + } + } + + if ( ( *str = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + ( *str )[ 0 ] = '\0'; + + for ( l = 0; iRDN--; ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + /* + * FIXME: does DCE allow '+'? + */ + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + ber_len_t al = strlen( ava->la_attr ); + + ( *str )[ l++ ] = ( iAVA ? ',' : '/' ); + AC_MEMCPY( &( *str )[ l ], ava->la_attr, al ); + l += al; + ( *str )[ l++ ] = '='; + if ( ava->la_flags & LDAP_AVA_BINARY ) { + ( *str )[ l++ ]= '#'; + binval2hexstr( ava->la_value, + &( *str )[ l ] ); + l += 2 * ava->la_value->bv_len; + } else { + ber_len_t vl; + strval2DCEstr( ava->la_value, + &( *str )[ l ], + ava->la_flags, &vl ); + l += vl; + } + } + } + + ( *str )[ len ] = '\0'; + + rc = LDAP_SUCCESS; + break; + + case LDAP_DN_FORMAT_AD_CANONICAL: { + for ( iRDN = 0, len = -1; dn[ iRDN ]; iRDN++ ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + + /* ',' || '/' || '.' */ + len += 1; + + /* FIXME: are binary values allowed in AD? */ + if ( ava->la_flags & LDAP_AVA_BINARY ) { + /* octothorpe + twice the value */ + len += 1 + 2 * ava->la_value->bv_len; + } else { + len += strval2ADstrlen( ava->la_value, + ava->la_flags ); + } + } + } + + if ( ( *str = LDAP_MALLOC( len + 1 ) ) == NULL ) { + rc = LDAP_NO_MEMORY; + break; + } + ( *str )[ 0 ] = '\0'; + + iRDN--; + if ( iRDN && dn2domain( dn, str, &iRDN ) ) { + for ( l = strlen( *str ); iRDN >= 0 ; iRDN-- ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + + ( *str)[ l++ ] = ( iAVA ? ',' : '/' ); + if ( ava->la_flags & LDAP_AVA_BINARY ) { + ( *str )[ l++ ] = '#'; + binval2hexstr( ava->la_value, + &( *str )[ l ] ); + l += 2 * ava->la_value->bv_len; + } else { + ber_len_t vl; + strval2ADstr( ava->la_value, + &( *str )[ l ], + ava->la_flags, + &vl ); + l += vl; + } + } + } + } else { + int first = 1; + + /* + * FIXME: strictly speaking, AD canonical requires + * a DN to be in the form "..., dc=smtg" + */ + if ( flags & LDAP_DN_PEDANTIC ) { + LDAP_FREE( *str ); + *str = NULL; + rc = LDAP_INVALID_DN_SYNTAX; + break; + } + + for ( l = 0; iRDN >= 0 ; iRDN-- ) { + LDAPRDN *rdn = dn[ iRDN ][ 0 ]; + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ][ 0 ]; + + if ( first ) { + first = 0; + } else { + ( *str )[ l++ ] = + ( iAVA ? ',' : '/' ); + } + if ( ava->la_flags & LDAP_AVA_BINARY ) { + ( *str )[ l++ ] = '#'; + binval2hexstr( ava->la_value, + &( *str )[ l ] ); + l += 2 * ava->la_value->bv_len; + } else { + ber_len_t vl; + strval2ADstr( ava->la_value, + &( *str )[ l ], + ava->la_flags, + &vl ); + l += vl; + } + } + } + } + + ( *str )[ len ] = '\0'; + + rc = LDAP_SUCCESS; + break; + } + + default: + assert( 0 ); + + } + + Debug( LDAP_DEBUG_TRACE, "<= ldap_dn2str(%s,%u)=%d\n", *str, flags, rc ); + + return( rc ); +} + + diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h index 8cfc81e3ac..33ba74d2cb 100644 --- a/libraries/libldap/ldap-int.h +++ b/libraries/libldap/ldap-int.h @@ -340,6 +340,7 @@ LDAP_F ( void ) ldap_int_initialize_global_options LDAP_P(( #define LDAP_FREE(p) (LBER_FREE((p))) #define LDAP_VFREE(v) (LBER_VFREE((void **)(v))) #define LDAP_STRDUP(s) (LBER_STRDUP((s))) +#define LDAP_STRNDUP(s,l) (LBER_STRNDUP((s),(l))) /* * in error.c diff --git a/libraries/libldap/schema.c b/libraries/libldap/schema.c index 18a6648358..cf7a9923c4 100644 --- a/libraries/libldap/schema.c +++ b/libraries/libldap/schema.c @@ -736,7 +736,7 @@ parse_whsp(const char **sp) */ /* Parse a sequence of dot-separated decimal strings */ -static char * +char * parse_numericoid(const char **sp, int *code, const int flags) { char * res; -- 2.39.5