aboutsummaryrefslogblamecommitdiffstats
path: root/modules/addressbook/openldap-extract.h
blob: 29175fb494aee1d5a250415406c84f59b87a16e2 (plain) (tree)























                                                                     

                                                                    























                                                                                             
                                            





































                                                                                        







                                                                           

                  
           
                    
 
         








































                                                                      

                    

  
           
                                                  
 



                        



























































                                                      

                                       
         
 




                                
                            





                                                       

                                                                         
 



                                  










                                                                    
                                                                             












                                                              
                                                                       









                                                           
                                     












                                                     

                                           
 





                      





                                                                    
                                                     












                                                      
                                                                                      




















                                                                             
                                                     















                                                               

                                                                 
 





                      











                                                                        
                                                     





























                                                                
                                                                                              



























                                                                                     
                                                     




























                                                                 


                                           

                                   









                               
                             

                              






















































                                                                           
                                                              








































































































































































































                                                                             
                                                                    
 

                                                                                 
                                             
                 




                    
                                                                    
 

                          
 


                                                                                           
                                                  
                         





                          
                                                                   
 

                          
 


                                                                 


                                                  
                                                                                           
                                      
                         





                          
                                                                          
 

                     
 
                                        




                                                
                               





                                                        
                             
                                                    


































                                                                                  








                                      



                
                    
                


                              

          


                                                                               
                       
























































                                                                                
                                       

























                                                        
 


























                                              
           
                        






                                                 
                                 




                                                                                 
                   


















                                                       

                                                            
 



                         


                                                  
                            









                                                              
                                                                  
 
                            











                                                              

                                                  















                                                    
           
                                                                   





                                         

                                    


                                   
 
                                                





















                                                                
                                  



































                                                                              
                          


















                                                              
                              



                                           
                                   

















                                                                       
                                       













                                                                                
                                                                             
                    
                                                        
                                 
                            
                                           






                                                                 
                                                     






                                                          
                          







                                                           
                          



                                       
                           






                                                 
                                     




                                          
                          









                                                                   
                          



                                               
                           



                                                             
                                                
















                                                              
                          



                                              
                           



                                                 
                                              
















                                                               
                          



                                               
                           


                                           
                             








                                                      
                                                 
















                                                                   
                          








                                                    
                                       




                                            
                                                     

                                                           
                                                  




                                                                     
                       











                                            
           
                                                               
 
                                                      
 
                                       












                                                                        
                                                                  

                                                       
                                                                          

                                                       
                                                                          






                                                        
/* 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

#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;
}