/* 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 <http://www.openldap.org/>.
*
* 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
* <http://www.OpenLDAP.org/license.html>.
*/
#include <string.h>
#include <assert.h>
/* 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
#ifndef LDAP_TYPE_OR_VALUE_EXISTS
#define LDAP_TYPE_OR_VALUE_EXISTS 0x14
#endif
#ifndef LDAP_SCOPE_DEFAULT
#define LDAP_SCOPE_DEFAULT -1
#endif
#ifndef LDAP_OPT_SUCCESS
#define LDAP_OPT_SUCCESS 0x00
#endif
#ifndef LDAP_INSUFFICIENT_ACCESS
#define LDAP_INSUFFICIENT_ACCESS 0x32
#endif
#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;
}