/* This is extracted from the OpenLDAP sources. * * Stuff that isn't used in e-book-backend-ldap.c was dropped, like * the LDAPSchemaExtensionItem stuff. * * This file basically has three parts: * * - some general macros from OpenLDAP that work as such on all * implementations. * * - ldap_str2objectclass() * * - ldap_url_parse() */ /* This work is part of OpenLDAP Software . * * Copyright 1998-2005 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in file COPYING.OPENLDAP in * the top-level directory of the distribution or, alternatively, at * . */ #include #include /* from various header files */ #define LDAP_CONST const #define LDAP_PORT 389 /* ldap:/// default LDAP port */ #define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */ #define LDAP_ROOT_DSE "" #define LDAP_SPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') #define LDAP_DIGIT(c) ((c) >= '0' && (c) <= '9') #define LDAP_EXOP_START_TLS "1.3.6.1.4.1.1466.20037" /* RFC 2830 */ #define LDAP_MALLOC(n) malloc((n)) #define LDAP_CALLOC(n,s) calloc((n),(s)) #define LDAP_REALLOC(p,s) realloc((p),(s)) #define LDAP_FREE(p) free((p)) #define LDAP_VFREE(p) vfree((gpointer *)(p)) #define LDAP_STRDUP(s) strdup((s)) #define LDAP_RANGE(n,x,y) (((x) <= (n)) && ((n) <= (y))) #define LDAP_NAME_ERROR(n) LDAP_RANGE((n),0x20,0x24) /* 32-34,36 */ #define ldap_msgtype(lm) (lm)->lm_msgtype #define ldap_msgid(lm) (lm)->lm_msgid #define LDAP_SCHERR_OUTOFMEM 1 #define LDAP_SCHERR_UNEXPTOKEN 2 #define LDAP_SCHERR_NOLEFTPAREN 3 #define LDAP_SCHERR_NORIGHTPAREN 4 #define LDAP_SCHERR_NODIGIT 5 #define LDAP_SCHERR_BADNAME 6 #define LDAP_SCHERR_BADDESC 7 #define LDAP_SCHERR_BADSUP 8 #define LDAP_SCHERR_DUPOPT 9 #define LDAP_SCHERR_EMPTY 10 #define LDAP_SCHERR_MISSING 11 #define LDAP_SCHERR_OUT_OF_ORDER 12 #define LDAP_SCHEMA_YES 1 #define LDAP_SCHEMA_ABSTRACT 0 #define LDAP_SCHEMA_STRUCTURAL 1 #define LDAP_SCHEMA_AUXILIARY 2 #define LDAP_SCHEMA_ALLOW_NONE 0x00U /* Strict parsing */ #define LDAP_SCHEMA_ALLOW_NO_OID 0x01U /* Allow missing oid */ #define LDAP_SCHEMA_ALLOW_QUOTED 0x02U /* Allow bogus extra quotes */ #define LDAP_SCHEMA_ALLOW_DESCR 0x04U /* Allow descr instead of OID */ #define LDAP_SCHEMA_ALLOW_DESCR_PREFIX 0x08U /* Allow descr as OID prefix */ #define LDAP_SCHEMA_ALLOW_OID_MACRO 0x10U /* Allow OID macros in slapd */ #define LDAP_SCHEMA_ALLOW_OUT_OF_ORDER_FIELDS 0x20U /* Allow fields in most any order */ #define LDAP_SCHEMA_ALLOW_ALL 0x3fU /* Be very liberal in parsing */ #define LDAP_SCHEMA_SKIP 0x80U /* Don't malloc any result */ typedef struct ldap_objectclass { gchar *oc_oid; /* REQUIRED */ gchar **oc_names; /* OPTIONAL */ gchar *oc_desc; /* OPTIONAL */ gint oc_obsolete; /* 0=no, 1=yes */ gchar **oc_sup_oids; /* OPTIONAL */ gint oc_kind; /* 0=ABSTRACT, 1=STRUCTURAL, 2=AUXILIARY */ gchar **oc_at_oids_must; /* OPTIONAL */ gchar **oc_at_oids_may; /* OPTIONAL */ } LDAPObjectClass; static void vfree(gpointer *vec) { gint i; for (i = 0; vec[i] != NULL; i++) free(vec[i]); } /* from schema.c */ /* * Now come the parsers. There is one parser for each entity type: * objectclasses, attributetypes, etc. * * Each of them is written as a recursive-descent parser, except that * none of them is really recursive. But the idea is kept: there * is one routine per non-terminal that eithers gobbles lexical tokens * or calls lower-level routines, etc. * * The scanner is implemented in the routine get_token. Actually, * get_token is more than a scanner and will return tokens that are * in fact non-terminals in the grammar. So you can see the whole * approach as the combination of a low-level bottom-up recognizer * combined with a scanner and a number of top-down parsers. Or just * consider that the real grammars recognized by the parsers are not * those of the standards. As a matter of fact, our parsers are more * liberal than the spec when there is no ambiguity. * * The difference is pretty academic (modulo bugs or incorrect * interpretation of the specs). */ #define TK_NOENDQUOTE -2 #define TK_OUTOFMEM -1 #define TK_EOS 0 #define TK_UNEXPCHAR 1 #define TK_BAREWORD 2 #define TK_QDSTRING 3 #define TK_LEFTPAREN 4 #define TK_RIGHTPAREN 5 #define TK_DOLLAR 6 #define TK_QDESCR TK_QDSTRING struct token { gint type; gchar *sval; }; static gint get_token( const gchar ** sp, gchar ** token_val ) { gint kind; const gchar * p; const gchar * q; gchar * res; *token_val = NULL; switch (**sp) { case '\0': kind = TK_EOS; (*sp)++; break; case '(': kind = TK_LEFTPAREN; (*sp)++; break; case ')': kind = TK_RIGHTPAREN; (*sp)++; break; case '$': kind = TK_DOLLAR; (*sp)++; break; case '\'': kind = TK_QDSTRING; (*sp)++; p = *sp; while ( **sp != '\'' && **sp != '\0' ) (*sp)++; if ( **sp == '\'' ) { q = *sp; res = LDAP_MALLOC(q-p+1); if ( !res ) { kind = TK_OUTOFMEM; } else { strncpy(res,p,q-p); res[q-p] = '\0'; *token_val = res; } (*sp)++; } else { kind = TK_NOENDQUOTE; } break; default: kind = TK_BAREWORD; p = *sp; while ( !LDAP_SPACE(**sp) && **sp != '(' && **sp != ')' && **sp != '$' && **sp != '\'' && **sp != '\0' ) (*sp)++; q = *sp; res = LDAP_MALLOC(q-p+1); if ( !res ) { kind = TK_OUTOFMEM; } else { strncpy(res,p,q-p); res[q-p] = '\0'; *token_val = res; } break; /* kind = TK_UNEXPCHAR; */ /* break; */ } return kind; } /* Gobble optional whitespace */ static void parse_whsp(const gchar **sp) { while (LDAP_SPACE(**sp)) (*sp)++; } /* Parse a sequence of dot-separated decimal strings */ static gchar * ldap_int_parse_numericoid(const gchar **sp, gint *code, const gint flags) { gchar * res = NULL; const gchar * start = *sp; gint len; gint quoted = 0; /* Netscape puts the SYNTAX value in quotes (incorrectly) */ if ( flags & LDAP_SCHEMA_ALLOW_QUOTED && **sp == '\'' ) { quoted = 1; (*sp)++; start++; } /* Each iteration of this loop gets one decimal string */ while (**sp) { if ( !LDAP_DIGIT(**sp) ) { /* * Initial gchar is not a digit or gchar after dot is * not a digit */ *code = LDAP_SCHERR_NODIGIT; return NULL; } (*sp)++; while ( LDAP_DIGIT(**sp) ) (*sp)++; if ( **sp != '.' ) break; /* Otherwise, gobble the dot and loop again */ (*sp)++; } /* Now *sp points at the gchar past the numericoid. Perfect. */ len = *sp - start; if ( flags & LDAP_SCHEMA_ALLOW_QUOTED && quoted ) { if ( **sp == '\'' ) { (*sp)++; } else { *code = LDAP_SCHERR_UNEXPTOKEN; return NULL; } } if (flags & LDAP_SCHEMA_SKIP) { res = (gchar *)start; } else { res = LDAP_MALLOC(len+1); if (!res) { *code = LDAP_SCHERR_OUTOFMEM; return(NULL); } strncpy(res,start,len); res[len] = '\0'; } return(res); } /* Parse a qdescr or a list of them enclosed in () */ static gchar ** parse_qdescrs(const gchar **sp, gint *code) { gchar ** res; gchar ** res1; gint kind; gchar * sval; gint size; gint pos; parse_whsp(sp); kind = get_token(sp,&sval); if ( kind == TK_LEFTPAREN ) { /* Let's presume there will be at least 2 entries */ size = 3; res = LDAP_CALLOC(3,sizeof(gchar *)); if ( !res ) { *code = LDAP_SCHERR_OUTOFMEM; return NULL; } pos = 0; while (1) { parse_whsp(sp); kind = get_token(sp,&sval); if ( kind == TK_RIGHTPAREN ) break; if ( kind == TK_QDESCR ) { if ( pos == size-2 ) { size++; res1 = LDAP_REALLOC(res,size*sizeof(gchar *)); if ( !res1 ) { LDAP_VFREE(res); LDAP_FREE(sval); *code = LDAP_SCHERR_OUTOFMEM; return(NULL); } res = res1; } res[pos++] = sval; res[pos] = NULL; parse_whsp(sp); } else { LDAP_VFREE(res); LDAP_FREE(sval); *code = LDAP_SCHERR_UNEXPTOKEN; return(NULL); } } parse_whsp(sp); return(res); } else if ( kind == TK_QDESCR ) { res = LDAP_CALLOC(2,sizeof(gchar *)); if ( !res ) { *code = LDAP_SCHERR_OUTOFMEM; return NULL; } res[0] = sval; res[1] = NULL; parse_whsp(sp); return res; } else { LDAP_FREE(sval); *code = LDAP_SCHERR_BADNAME; return NULL; } } /* Parse a woid or a $-separated list of them enclosed in () */ static gchar ** parse_oids(const gchar **sp, gint *code, const gint allow_quoted) { gchar ** res; gchar ** res1; gint kind; gchar * sval; gint size; gint pos; /* * Strictly speaking, doing this here accepts whsp before the * ( at the begining of an oidlist, but this is harmless. Also, * we are very liberal in what we accept as an OID. Maybe * refine later. */ parse_whsp(sp); kind = get_token(sp,&sval); if ( kind == TK_LEFTPAREN ) { /* Let's presume there will be at least 2 entries */ size = 3; res = LDAP_CALLOC(3,sizeof(gchar *)); if ( !res ) { *code = LDAP_SCHERR_OUTOFMEM; return NULL; } pos = 0; parse_whsp(sp); kind = get_token(sp,&sval); if ( kind == TK_BAREWORD || ( allow_quoted && kind == TK_QDSTRING ) ) { res[pos++] = sval; res[pos] = NULL; } else { *code = LDAP_SCHERR_UNEXPTOKEN; LDAP_FREE(sval); LDAP_VFREE(res); return NULL; } parse_whsp(sp); while (1) { kind = get_token(sp,&sval); if ( kind == TK_RIGHTPAREN ) break; if ( kind == TK_DOLLAR ) { parse_whsp(sp); kind = get_token(sp,&sval); if ( kind == TK_BAREWORD || ( allow_quoted && kind == TK_QDSTRING ) ) { if ( pos == size-2 ) { size++; res1 = LDAP_REALLOC(res,size*sizeof(gchar *)); if ( !res1 ) { LDAP_FREE(sval); LDAP_VFREE(res); *code = LDAP_SCHERR_OUTOFMEM; return(NULL); } res = res1; } res[pos++] = sval; res[pos] = NULL; } else { *code = LDAP_SCHERR_UNEXPTOKEN; LDAP_FREE(sval); LDAP_VFREE(res); return NULL; } parse_whsp(sp); } else { *code = LDAP_SCHERR_UNEXPTOKEN; LDAP_FREE(sval); LDAP_VFREE(res); return NULL; } } parse_whsp(sp); return(res); } else if ( kind == TK_BAREWORD || ( allow_quoted && kind == TK_QDSTRING ) ) { res = LDAP_CALLOC(2,sizeof(gchar *)); if ( !res ) { LDAP_FREE(sval); *code = LDAP_SCHERR_OUTOFMEM; return NULL; } res[0] = sval; res[1] = NULL; parse_whsp(sp); return res; } else { LDAP_FREE(sval); *code = LDAP_SCHERR_BADNAME; return NULL; } } static void ldap_objectclass_free(LDAPObjectClass * oc) { LDAP_FREE(oc->oc_oid); if (oc->oc_names) LDAP_VFREE(oc->oc_names); if (oc->oc_desc) LDAP_FREE(oc->oc_desc); if (oc->oc_sup_oids) LDAP_VFREE(oc->oc_sup_oids); if (oc->oc_at_oids_must) LDAP_VFREE(oc->oc_at_oids_must); if (oc->oc_at_oids_may) LDAP_VFREE(oc->oc_at_oids_may); LDAP_FREE(oc); } static LDAPObjectClass * ldap_str2objectclass( LDAP_CONST gchar * s, gint * code, LDAP_CONST gchar ** errp, LDAP_CONST unsigned flags ) { gint kind; const gchar * ss = s; gchar * sval; gint seen_name = 0; gint seen_desc = 0; gint seen_obsolete = 0; gint seen_sup = 0; gint seen_kind = 0; gint seen_must = 0; gint seen_may = 0; LDAPObjectClass * oc; gchar ** ext_vals; const gchar * savepos; if ( !s ) { *code = LDAP_SCHERR_EMPTY; *errp = ""; return NULL; } *errp = s; oc = LDAP_CALLOC(1,sizeof(LDAPObjectClass)); if ( !oc ) { *code = LDAP_SCHERR_OUTOFMEM; return NULL; } oc->oc_kind = LDAP_SCHEMA_STRUCTURAL; kind = get_token(&ss,&sval); if ( kind != TK_LEFTPAREN ) { *code = LDAP_SCHERR_NOLEFTPAREN; LDAP_FREE(sval); ldap_objectclass_free(oc); return NULL; } /* * Definitions MUST begin with an OID in the numericoid format. * However, this routine is used by clients to parse the response * from servers and very well known servers will provide an OID * in the wrong format or even no OID at all. We do our best to * extract info from those servers. */ parse_whsp(&ss); savepos = ss; oc->oc_oid = ldap_int_parse_numericoid(&ss,code,0); if ( !oc->oc_oid ) { if ( (flags & LDAP_SCHEMA_ALLOW_ALL) && (ss == savepos) ) { /* Backtracking */ ss = savepos; kind = get_token(&ss,&sval); if ( kind == TK_BAREWORD ) { if ( !strcasecmp(sval, "NAME") || !strcasecmp(sval, "DESC") || !strcasecmp(sval, "OBSOLETE") || !strcasecmp(sval, "SUP") || !strcasecmp(sval, "ABSTRACT") || !strcasecmp(sval, "STRUCTURAL") || !strcasecmp(sval, "AUXILIARY") || !strcasecmp(sval, "MUST") || !strcasecmp(sval, "MAY") || !strncasecmp(sval, "X-", 2) ) { /* Missing OID, backtrack */ ss = savepos; } else if ( flags & LDAP_SCHEMA_ALLOW_OID_MACRO ) { /* Non-numerical OID, ignore */ gint len = ss-savepos; oc->oc_oid = LDAP_MALLOC(len+1); strncpy(oc->oc_oid, savepos, len); oc->oc_oid[len] = 0; } } LDAP_FREE(sval); } else { *errp = ss; ldap_objectclass_free(oc); return NULL; } } parse_whsp(&ss); /* * Beyond this point we will be liberal an accept the items * in any order. */ while (1) { kind = get_token(&ss,&sval); switch (kind) { case TK_EOS: *code = LDAP_SCHERR_NORIGHTPAREN; *errp = ss; ldap_objectclass_free(oc); return NULL; case TK_RIGHTPAREN: return oc; case TK_BAREWORD: if ( !strcasecmp(sval,"NAME") ) { LDAP_FREE(sval); if ( seen_name ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_name = 1; oc->oc_names = parse_qdescrs(&ss,code); if ( !oc->oc_names ) { if ( *code != LDAP_SCHERR_OUTOFMEM ) *code = LDAP_SCHERR_BADNAME; *errp = ss; ldap_objectclass_free(oc); return NULL; } } else if ( !strcasecmp(sval,"DESC") ) { LDAP_FREE(sval); if ( seen_desc ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_desc = 1; parse_whsp(&ss); kind = get_token(&ss,&sval); if ( kind != TK_QDSTRING ) { *code = LDAP_SCHERR_UNEXPTOKEN; *errp = ss; LDAP_FREE(sval); ldap_objectclass_free(oc); return NULL; } oc->oc_desc = sval; parse_whsp(&ss); } else if ( !strcasecmp(sval,"OBSOLETE") ) { LDAP_FREE(sval); if ( seen_obsolete ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_obsolete = 1; oc->oc_obsolete = LDAP_SCHEMA_YES; parse_whsp(&ss); } else if ( !strcasecmp(sval,"SUP") ) { LDAP_FREE(sval); if ( seen_sup ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_sup = 1; oc->oc_sup_oids = parse_oids(&ss, code, flags); if ( !oc->oc_sup_oids ) { *errp = ss; ldap_objectclass_free(oc); return NULL; } } else if ( !strcasecmp(sval,"ABSTRACT") ) { LDAP_FREE(sval); if ( seen_kind ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_kind = 1; oc->oc_kind = LDAP_SCHEMA_ABSTRACT; parse_whsp(&ss); } else if ( !strcasecmp(sval,"STRUCTURAL") ) { LDAP_FREE(sval); if ( seen_kind ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_kind = 1; oc->oc_kind = LDAP_SCHEMA_STRUCTURAL; parse_whsp(&ss); } else if ( !strcasecmp(sval,"AUXILIARY") ) { LDAP_FREE(sval); if ( seen_kind ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_kind = 1; oc->oc_kind = LDAP_SCHEMA_AUXILIARY; parse_whsp(&ss); } else if ( !strcasecmp(sval,"MUST") ) { LDAP_FREE(sval); if ( seen_must ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_must = 1; oc->oc_at_oids_must = parse_oids(&ss,code,0); if ( !oc->oc_at_oids_must ) { *errp = ss; ldap_objectclass_free(oc); return NULL; } parse_whsp(&ss); } else if ( !strcasecmp(sval,"MAY") ) { LDAP_FREE(sval); if ( seen_may ) { *code = LDAP_SCHERR_DUPOPT; *errp = ss; ldap_objectclass_free(oc); return(NULL); } seen_may = 1; oc->oc_at_oids_may = parse_oids(&ss,code,0); if ( !oc->oc_at_oids_may ) { *errp = ss; ldap_objectclass_free(oc); return NULL; } parse_whsp(&ss); } else if ( sval[0] == 'X' && sval[1] == '-' ) { /* Should be parse_qdstrings */ ext_vals = parse_qdescrs(&ss, code); if ( !ext_vals ) { *errp = ss; ldap_objectclass_free(oc); return NULL; } #if 0 if ( add_extension(&oc->oc_extensions, sval, ext_vals) ) { *code = LDAP_SCHERR_OUTOFMEM; *errp = ss; LDAP_FREE(sval); ldap_objectclass_free(oc); return NULL; } #endif } else { *code = LDAP_SCHERR_UNEXPTOKEN; *errp = ss; LDAP_FREE(sval); ldap_objectclass_free(oc); return NULL; } break; default: *code = LDAP_SCHERR_UNEXPTOKEN; *errp = ss; LDAP_FREE(sval); ldap_objectclass_free(oc); return NULL; } } } /* from utf-8.c */ #define LDAP_UTF8_NEXT(p) g_utf8_next_char((p)) #define LDAP_UTF8_INCR(p) ((p)=LDAP_UTF8_NEXT((p))) #define ldap_x_utf8_to_ucs4(str) g_utf8_get_char(str) static gchar *ldap_utf8_strchr( const gchar *str, const gchar *chr ) { for(; *str != '\0'; LDAP_UTF8_INCR(str) ) { if( ldap_x_utf8_to_ucs4( str ) == ldap_x_utf8_to_ucs4( chr ) ) { return (gchar *) str; } } return NULL; } static gsize ldap_utf8_strcspn( const gchar *str, const gchar *set ) { const gchar *cstr; const gchar *cset; for( cstr = str; *cstr != '\0'; LDAP_UTF8_INCR(cstr) ) { for( cset = set; *cset != '\0'; LDAP_UTF8_INCR(cset) ) { if( ldap_x_utf8_to_ucs4( cstr ) == ldap_x_utf8_to_ucs4( cset ) ) { return cstr - str; } } } return cstr - str; } static gsize ldap_utf8_strspn( const gchar *str, const gchar *set ) { const gchar *cstr; const gchar *cset; for( cstr = str; *cstr != '\0'; LDAP_UTF8_INCR(cstr) ) { for( cset = set; ; LDAP_UTF8_INCR(cset) ) { if( *cset == '\0' ) { return cstr - str; } if( ldap_x_utf8_to_ucs4( cstr ) == ldap_x_utf8_to_ucs4( cset ) ) { break; } } } return cstr - str; } static gchar *ldap_utf8_strtok(gchar *str, const gchar *sep, gchar **last) { gchar *begin; gchar *end; if( last == NULL ) return NULL; begin = str ? str : *last; begin += ldap_utf8_strspn( begin, sep ); if( *begin == '\0' ) { *last = NULL; return NULL; } end = &begin[ ldap_utf8_strcspn( begin, sep ) ]; if( *end != '\0' ) { gchar *next = LDAP_UTF8_NEXT( end ); *end = '\0'; end = next; } *last = end; return begin; } /* from ldap.h */ #define LDAP_URL_SUCCESS 0x00 /* Success */ #define LDAP_URL_ERR_MEM 0x01 /* can't allocate memory space */ #define LDAP_URL_ERR_PARAM 0x02 /* parameter is bad */ #define LDAP_URL_ERR_BADSCHEME 0x03 /* URL doesn't begin with "ldap[si]://" */ #define LDAP_URL_ERR_BADENCLOSURE 0x04 /* URL is missing trailing ">" */ #define LDAP_URL_ERR_BADURL 0x05 /* URL is bad */ #define LDAP_URL_ERR_BADHOST 0x06 /* host port is bad */ #define LDAP_URL_ERR_BADATTRS 0x07 /* bad (or missing) attributes */ #define LDAP_URL_ERR_BADSCOPE 0x08 /* scope string is invalid (or missing) */ #define LDAP_URL_ERR_BADFILTER 0x09 /* bad or missing filter */ #define LDAP_URL_ERR_BADEXTS 0x0a /* bad or missing extensions */ #define LDAP_URL_PREFIX "ldap://" #define LDAP_URL_PREFIX_LEN (sizeof(LDAP_URL_PREFIX)-1) #define LDAPS_URL_PREFIX "ldaps://" #define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1) #define LDAPI_URL_PREFIX "ldapi://" #define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1) #define LDAP_URL_URLCOLON "URL:" #define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1) typedef struct ldap_url_desc { struct ldap_url_desc *lud_next; gchar *lud_scheme; gchar *lud_host; gint lud_port; gchar *lud_dn; gchar **lud_attrs; gint lud_scope; gchar *lud_filter; gchar **lud_exts; gint lud_crit_exts; } LDAPURLDesc; /* from url.c */ static const gchar * skip_url_prefix( const gchar *url, gint *enclosedp, const gchar **scheme ) { /* * return non-zero if this looks like a LDAP URL; zero if not * if non-zero returned, *urlp will be moved past "ldap://" part of URL */ const gchar *p; if ( url == NULL ) { return( NULL ); } p = url; /* skip leading '<' (if any) */ if ( *p == '<' ) { *enclosedp = 1; ++p; } else { *enclosedp = 0; } /* skip leading "URL:" (if any) */ if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) { p += LDAP_URL_URLCOLON_LEN; } /* check for "ldap://" prefix */ if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) { /* skip over "ldap://" prefix and return success */ p += LDAP_URL_PREFIX_LEN; *scheme = "ldap"; return( p ); } /* check for "ldaps://" prefix */ if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) { /* skip over "ldaps://" prefix and return success */ p += LDAPS_URL_PREFIX_LEN; *scheme = "ldaps"; return( p ); } /* check for "ldapi://" prefix */ if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) { /* skip over "ldapi://" prefix and return success */ p += LDAPI_URL_PREFIX_LEN; *scheme = "ldapi"; return( p ); } #ifdef LDAP_CONNECTIONLESS /* check for "cldap://" prefix */ if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) { /* skip over "cldap://" prefix and return success */ p += LDAPC_URL_PREFIX_LEN; *scheme = "cldap"; return( p ); } #endif return( NULL ); } static gint str2scope( const gchar *p ) { if ( strcasecmp( p, "one" ) == 0 ) { return LDAP_SCOPE_ONELEVEL; } else if ( strcasecmp( p, "onelevel" ) == 0 ) { return LDAP_SCOPE_ONELEVEL; } else if ( strcasecmp( p, "base" ) == 0 ) { return LDAP_SCOPE_BASE; } else if ( strcasecmp( p, "sub" ) == 0 ) { return LDAP_SCOPE_SUBTREE; } else if ( strcasecmp( p, "subtree" ) == 0 ) { return LDAP_SCOPE_SUBTREE; } return( -1 ); } static void ldap_free_urldesc( LDAPURLDesc *ludp ) { if ( ludp == NULL ) { return; } if ( ludp->lud_scheme != NULL ) { LDAP_FREE( ludp->lud_scheme ); } if ( ludp->lud_host != NULL ) { LDAP_FREE( ludp->lud_host ); } if ( ludp->lud_dn != NULL ) { LDAP_FREE( ludp->lud_dn ); } if ( ludp->lud_filter != NULL ) { LDAP_FREE( ludp->lud_filter); } if ( ludp->lud_attrs != NULL ) { LDAP_VFREE( ludp->lud_attrs ); } if ( ludp->lud_exts != NULL ) { LDAP_VFREE( ludp->lud_exts ); } LDAP_FREE( ludp ); } static gint ldap_int_unhex( gint c ) { return( c >= '0' && c <= '9' ? c - '0' : c >= 'A' && c <= 'F' ? c - 'A' + 10 : c - 'a' + 10 ); } static void ldap_pvt_hex_unescape( gchar *s ) { /* * Remove URL hex escapes from s... done in place. The basic concept for * this routine is borrowed from the WWW library HTUnEscape() routine. */ gchar *p; for ( p = s; *s != '\0'; ++s ) { if ( *s == '%' ) { if ( *++s == '\0' ) { break; } *p = ldap_int_unhex( *s ) << 4; if ( *++s == '\0' ) { break; } *p++ += ldap_int_unhex( *s ); } else { *p++ = *s; } } *p = '\0'; } static gchar ** ldap_str2charray( const gchar *str_in, const gchar *brkstr ) { gchar **res; gchar *str, *s; gchar *lasts; gint i; /* protect the input string from strtok */ str = LDAP_STRDUP( str_in ); if( str == NULL ) { return NULL; } i = 1; for ( s = str; *s; s++ ) { if ( ldap_utf8_strchr( brkstr, s ) != NULL ) { i++; } } res = (gchar **) LDAP_MALLOC( (i + 1) * sizeof(gchar *) ); if( res == NULL ) { LDAP_FREE( str ); return NULL; } i = 0; for ( s = ldap_utf8_strtok( str, brkstr, &lasts ); s != NULL; s = ldap_utf8_strtok( NULL, brkstr, &lasts ) ) { res[i] = LDAP_STRDUP( s ); if(res[i] == NULL) { for( --i; i >= 0; i-- ) { LDAP_FREE( res[i] ); } LDAP_FREE( res ); LDAP_FREE( str ); return NULL; } i++; } res[i] = NULL; LDAP_FREE( str ); return( res ); } static gint ldap_url_parse_ext( LDAP_CONST gchar *url_in, LDAPURLDesc **ludpp ) { /* * Pick apart the pieces of an LDAP URL. */ LDAPURLDesc *ludp; gchar *p, *q, *r; gint i, enclosed; const gchar *scheme = NULL; const gchar *url_tmp; gchar *url; if( url_in == NULL || ludpp == NULL ) { return LDAP_URL_ERR_PARAM; } *ludpp = NULL; /* pessimistic */ url_tmp = skip_url_prefix( url_in, &enclosed, &scheme ); if ( url_tmp == NULL ) { return LDAP_URL_ERR_BADSCHEME; } assert( scheme ); /* make working copy of the remainder of the URL */ url = LDAP_STRDUP( url_tmp ); if ( url == NULL ) { return LDAP_URL_ERR_MEM; } if ( enclosed ) { p = &url[strlen(url)-1]; if( *p != '>' ) { LDAP_FREE( url ); return LDAP_URL_ERR_BADENCLOSURE; } *p = '\0'; } /* allocate return struct */ ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc )); if ( ludp == NULL ) { LDAP_FREE( url ); return LDAP_URL_ERR_MEM; } ludp->lud_next = NULL; ludp->lud_host = NULL; ludp->lud_port = 0; ludp->lud_dn = NULL; ludp->lud_attrs = NULL; ludp->lud_filter = NULL; ludp->lud_scope = LDAP_SCOPE_DEFAULT; ludp->lud_filter = NULL; ludp->lud_exts = NULL; ludp->lud_scheme = LDAP_STRDUP( scheme ); if ( ludp->lud_scheme == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } /* scan forward for '/' that marks end of hostport and begin. of dn */ p = strchr( url, '/' ); if( p != NULL ) { /* terminate hostport; point to start of dn */ *p++ = '\0'; } /* IPv6 syntax with [ip address]:port */ if ( *url == '[' ) { r = strchr( url, ']' ); if ( r == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADURL; } *r++ = '\0'; q = strchr( r, ':' ); } else { q = strchr( url, ':' ); } if ( q != NULL ) { gchar *next; *q++ = '\0'; ldap_pvt_hex_unescape( q ); if( *q == '\0' ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADURL; } ludp->lud_port = strtol( q, &next, 10 ); if ( next == NULL || next[0] != '\0' ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADURL; } } ldap_pvt_hex_unescape( url ); /* If [ip address]:port syntax, url is [ip and we skip the [ */ ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) ); if( ludp->lud_host == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } /* * Kludge. ldap://111.222.333.444:389??cn=abc,o=company * * On early Novell releases, search references/referrals were returned * in this format, i.e., the dn was kind of in the scope position, * but the required slash is missing. The whole thing is illegal syntax, * but we need to account for it. Fortunately it can't be confused with * anything real. */ if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) { q++; /* ? immediately followed by question */ if( *q == '?') { q++; if( *q != '\0' ) { /* parse dn part */ ldap_pvt_hex_unescape( q ); ludp->lud_dn = LDAP_STRDUP( q ); } else { ludp->lud_dn = LDAP_STRDUP( "" ); } if( ludp->lud_dn == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } } } if( p == NULL ) { LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of dn */ q = strchr( p, '?' ); if( q != NULL ) { /* terminate dn part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse dn part */ ldap_pvt_hex_unescape( p ); ludp->lud_dn = LDAP_STRDUP( p ); } else { ludp->lud_dn = LDAP_STRDUP( "" ); } if( ludp->lud_dn == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } if( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of attributes */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* terminate attributes part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse attributes */ ldap_pvt_hex_unescape( p ); ludp->lud_attrs = ldap_str2charray( p, "," ); if( ludp->lud_attrs == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADATTRS; } } if ( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of scope */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* terminate the scope part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse the scope */ ldap_pvt_hex_unescape( p ); ludp->lud_scope = str2scope( p ); if( ludp->lud_scope == -1 ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADSCOPE; } } if ( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of filter */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* terminate the filter part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse the filter */ ldap_pvt_hex_unescape( p ); if( ! *p ) { /* missing filter */ LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADFILTER; } LDAP_FREE( ludp->lud_filter ); ludp->lud_filter = LDAP_STRDUP( p ); if( ludp->lud_filter == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } } if ( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of extensions */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* extra '?' */ LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADURL; } /* parse the extensions */ ludp->lud_exts = ldap_str2charray( p, "," ); if( ludp->lud_exts == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADEXTS; } for( i=0; ludp->lud_exts[i] != NULL; i++ ) { ldap_pvt_hex_unescape( ludp->lud_exts[i] ); if( *ludp->lud_exts[i] == '!' ) { /* count the number of critical extensions */ ludp->lud_crit_exts++; } } if( i == 0 ) { /* must have 1 or more */ LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADEXTS; } /* no more */ *ludpp = ludp; LDAP_FREE( url ); return LDAP_URL_SUCCESS; } static gint ldap_url_parse( LDAP_CONST gchar *url_in, LDAPURLDesc **ludpp ) { gint rc = ldap_url_parse_ext( url_in, ludpp ); if( rc != LDAP_URL_SUCCESS ) { return rc; } if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) { (*ludpp)->lud_scope = LDAP_SCOPE_BASE; } if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') { LDAP_FREE( (*ludpp)->lud_host ); (*ludpp)->lud_host = NULL; } if ((*ludpp)->lud_port == 0) { if( strcmp((*ludpp)->lud_scheme, "ldap") == 0 ) { (*ludpp)->lud_port = LDAP_PORT; #ifdef LDAP_CONNECTIONLESS } else if( strcmp((*ludpp)->lud_scheme, "cldap") == 0 ) { (*ludpp)->lud_port = LDAP_PORT; #endif } else if( strcmp((*ludpp)->lud_scheme, "ldaps") == 0 ) { (*ludpp)->lud_port = LDAPS_PORT; } } return rc; }