From: Julio Sánchez Fernández Date: Mon, 24 May 1999 01:38:57 +0000 (+0000) Subject: Initial incomplete and broken version. X-Git-Tag: OPENLDAP_SLAPD_BACK_LDAP~13 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=b2b3581bc1b5dfcd973c06d0e66848840f07b73d;p=openldap Initial incomplete and broken version. --- diff --git a/include/ldap_schema.h b/include/ldap_schema.h new file mode 100644 index 0000000000..251c63ee19 --- /dev/null +++ b/include/ldap_schema.h @@ -0,0 +1,71 @@ +/* + * Copyright 1999 The OpenLDAP Foundation, Redwood City, California, USA + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted only + * as authorized by the OpenLDAP Public License. A copy of this + * license is available at http://www.OpenLDAP.org/license.html or + * in file LICENSE in the top-level directory of the distribution. + */ +/* + * ldap-schema.h - Header for basic schema handling functions that can be + * used by both clients and servers. + */ + +#ifndef _LDAP_SCHEMA_H +#define _LDAP_SCHEMA_H 1 + +#include + +LDAP_BEGIN_DECL + +/* Codes for parsing errors */ + +#define SCHEMA_ERR_OUTOFMEM 1 +#define SCHEMA_ERR_UNEXPTOKEN 2 +#define SCHEMA_ERR_NOLEFTPAREN 3 +#define SCHEMA_ERR_NORIGHTPAREN 4 +#define SCHEMA_ERR_NODIGIT 5 +#define SCHEMA_ERR_BADNAME 6 +#define SCHEMA_ERR_BADDESC 7 +#define SCHEMA_ERR_BADSUP 8 +#define SCHEMA_ERR_DUPOPT 9 + +typedef struct ldap_attributetype { + char *at_oid; /* REQUIRED */ + char **at_names; /* OPTIONAL */ + char *at_desc; /* OPTIONAL */ + int at_obsolete; /* 0=no, 1=yes */ + char *at_sup_oid; /* OPTIONAL */ + char *at_equality_oid; /* OPTIONAL */ + char *at_ordering_oid; /* OPTIONAL */ + char *at_substr_oid; /* OPTIONAL */ + char *at_syntax_oid; /* OPTIONAL */ + int at_syntax_len; /* OPTIONAL */ + int at_single_value; /* 0=no, 1=yes */ + int at_collective; /* 0=no, 1=yes */ + int at_no_user_mod; /* 0=no, 1=yes */ + int at_usage; /* 0=userApplications, 1=directoryOperation, + 2=distributedOperation, 3=dSAOperation */ +} LDAP_ATTRIBUTE_TYPE; + +typedef struct ldap_objectclass { + char *oc_oid; /* REQUIRED */ + char **oc_names; /* OPTIONAL */ + char *oc_desc; /* OPTIONAL */ + int oc_obsolete; /* 0=no, 1=yes */ + char **oc_sup_oids; /* OPTIONAL */ + int oc_kind; /* 0=ABSTRACT, 1=STRUCTURAL, 2=AUXILIARY */ + char **oc_at_oids_must; /* OPTIONAL */ + char **oc_at_oids_may; /* MAY */ +} LDAP_OBJECT_CLASS; + +LDAP_F(LDAP_OBJECT_CLASS *) ldap_str2objectclass LDAP_P(( char * s, int * code, char ** errp )); +LDAP_F(LDAP_ATTRIBUTE_TYPE) ldap_str2attributetype LDAP_P(( char * sval, char ** errp )); +LDAP_F( char *) ldap_objectclass2str LDAP_P(( LDAP_OBJECT_CLASS * oc )); +LDAP_F( char *) ldap_attributetype2str LDAP_P(( LDAP_ATTRIBUTE_TYPE * at )); + +LDAP_END_DECL + +#endif + diff --git a/libraries/libldap/schema.c b/libraries/libldap/schema.c new file mode 100644 index 0000000000..6ff131316e --- /dev/null +++ b/libraries/libldap/schema.c @@ -0,0 +1,914 @@ +/* + * Copyright 1999 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + * + * schema.c: parsing routines used by servers and clients to process + * schema definitions + */ + +#include "portable.h" + +#include +#include +#ifdef HAVE_MALLOC_H +#include +#endif +#include +#include +#include +#include + +/* + * When pretty printing the entities we will be appending to a buffer. + * Since checking for overflow, realloc'ing and checking if no error + * is extremely boring, we will use a pretection layer that will let + * us blissfully ignore the error until the end. This layer is + * implemented with the help of the next type. + */ + +typedef struct safe_string { + char * val; + int size; + int pos; + int at_whsp; +} safe_string; + +static safe_string * +new_safe_string(int size) +{ + safe_string * ss; + + ss = malloc(sizeof(safe_string)); + if ( !ss ) + return(NULL); + ss->size = size; + ss->pos = 0; + ss->val = malloc(size); + ss->at_whsp = 0; + if ( !ss->val ) { + free(ss); + return(NULL); + } + return ss; +} + +void +safe_string_free(safe_string * ss) +{ + if ( !ss ) + return; + ldap_memfree(ss->val); + ldap_memfree(ss); +} + +static char * +safe_string_val(safe_string * ss) +{ + return(ss->val); +} + +static int +append_to_safe_string(safe_string * ss, char * s) +{ + int l = strlen(s); + char * temp; + + /* + * Some runaway process is trying to append to a string that + * overflowed and we could not extend. + */ + if ( !ss->val ) + return -1; + + /* We always make sure there is at least one position available */ + if ( ss->pos + l >= ss->size-1 ) { + ss->size *= 2; + temp = realloc(ss->val, ss->size); + if ( !temp ) { + /* Trouble, out of memory */ + free(ss->val); + return -1; + } + ss->val = temp; + } + strncpy(&ss->val[ss->pos], s, l); + ss->pos += l; + if ( ss->pos > 0 && ss->val[ss->pos-1] == ' ' ) + ss->at_whsp = 1; + else + ss->at_whsp = 0; + + return 0; +} + +static int +print_literal(safe_string *ss, char *s) +{ + return(append_to_safe_string(ss,s)); +} + +static int +print_whsp(safe_string *ss) +{ + if ( ss->at_whsp ) + return(append_to_safe_string(ss,"")); + else + return(append_to_safe_string(ss," ")); +} + +static int +print_numericoid(safe_string *ss, char *s) +{ + return(append_to_safe_string(ss,s)); +} + +/* This one is identical to print_qdescr */ +static int +print_qdstring(safe_string *ss, char *s) +{ + print_whsp(ss); + print_literal(ss,"'"); + append_to_safe_string(ss,s); + print_literal(ss,"'"); + return(print_whsp(ss)); +} + +static int +print_qdescr(safe_string *ss, char *s) +{ + print_whsp(ss); + print_literal(ss,"'"); + append_to_safe_string(ss,s); + print_literal(ss,"'"); + return(print_whsp(ss)); +} + +static int +print_qdescrlist(safe_string *ss, char **sa) +{ + char **sp; + int ret = 0; + + for (sp=sa; *sp; sp++) { + ret = print_qdescr(ss,*sp); + } + /* If the list was empty, we return zero that is potentially + * incorrect, but since we will still appending things, the + * overflow will be detected later. Maybe FIX. + */ + return(ret); +} + +static int +print_qdescrs(safe_string *ss, char **sa) +{ + /* The only way to represent an empty list is as a qdescrlist + * so, if the list is empty we treat it as a long list. + * Really, this is what the syntax mandates. + */ + if ( !sa[0] || ( sa[0] && sa[1] ) ) { + print_whsp(ss); + print_literal(ss,"("); + print_qdescrlist(ss,sa); + print_literal(ss,")"); + return(print_whsp(ss)); + } else { + return(print_qdescr(ss,*sa)); + } +} + +static int +print_woid(safe_string *ss, char *s) +{ + print_whsp(ss); + append_to_safe_string(ss,s); + return print_whsp(ss); +} + +static int +print_oidlist(safe_string *ss, char **sa) +{ + char **sp; + + for (sp=sa; *(sp+1); sp++) { + print_woid(ss,*sp); + print_literal(ss,"$"); + } + return(print_woid(ss,*sp)); +} + +static int +print_oids(safe_string *ss, char **sa) +{ + if ( sa[0] && sa[1] ) { + print_literal(ss,"("); + print_oidlist(ss,sa); + print_whsp(ss); + return(print_literal(ss,")")); + } else { + return(print_woid(ss,*sa)); + } +} + +static int +print_noidlen(safe_string *ss, char *s, int l) +{ + char buf[64]; + int ret; + + ret = print_numericoid(ss,s); + if ( l ) { + sprintf(buf,"{%d}",l); + ret = print_literal(ss,buf); + } + return(ret); +} + +char * +ldap_objectclass2str( LDAP_OBJECT_CLASS * oc ) +{ + safe_string * ss; + char * retstring; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("); + print_whsp(ss); + + print_numericoid(ss, oc->oc_oid); + print_whsp(ss); + + if ( oc->oc_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,oc->oc_names); + } + + if ( oc->oc_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,oc->oc_desc); + } + + if ( oc->oc_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + if ( oc->oc_sup_oids ) { + print_literal(ss,"SUP"); + print_oids(ss,oc->oc_sup_oids); + } + + switch (oc->oc_kind) { + case 0: + print_literal(ss,"ABSTRACT"); + break; + case 1: + print_literal(ss,"STRUCTURAL"); + break; + case 2: + print_literal(ss,"AUXILIARY"); + break; + default: + print_literal(ss,"KIND-UNKNOWN"); + break; + } + print_whsp(ss); + + if ( oc->oc_at_oids_must ) { + print_literal(ss,"MUST"); + print_whsp(ss); + print_oids(ss,oc->oc_at_oids_must); + print_whsp(ss); + } + + if ( oc->oc_at_oids_may ) { + print_literal(ss,"MAY"); + print_whsp(ss); + print_oids(ss,oc->oc_at_oids_may); + print_whsp(ss); + } + + print_whsp(ss); + print_literal(ss,")"); + + retstring = safe_string_val(ss); + safe_string_free(ss); + return(retstring); +} + +char * +ldap_attributetype2str( LDAP_ATTRIBUTE_TYPE * at ) +{ + safe_string * ss; + char * retstring; + + ss = new_safe_string(256); + if ( !ss ) + return NULL; + + print_literal(ss,"("); + print_whsp(ss); + + print_numericoid(ss, at->at_oid); + print_whsp(ss); + + if ( at->at_names ) { + print_literal(ss,"NAME"); + print_qdescrs(ss,at->at_names); + } + + if ( at->at_desc ) { + print_literal(ss,"DESC"); + print_qdstring(ss,at->at_desc); + } + + if ( at->at_obsolete ) { + print_literal(ss, "OBSOLETE"); + print_whsp(ss); + } + + if ( at->at_sup_oid ) { + print_literal(ss,"SUP"); + print_woid(ss,at->at_sup_oid); + } + + if ( at->at_equality_oid ) { + print_literal(ss,"EQUALITY"); + print_woid(ss,at->at_equality_oid); + } + + if ( at->at_ordering_oid ) { + print_literal(ss,"ORDERING"); + print_woid(ss,at->at_ordering_oid); + } + + if ( at->at_substr_oid ) { + print_literal(ss,"SUBSTR"); + print_woid(ss,at->at_substr_oid); + } + + if ( at->at_syntax_oid ) { + print_literal(ss,"SYNTAX"); + print_noidlen(ss,at->at_syntax_oid,at->at_syntax_len); + } + + if ( at->at_single_value ) { + print_literal(ss,"SINGLE-VALUE"); + print_whsp(ss); + } + + if ( at->at_collective ) { + print_literal(ss,"COLLECTIVE"); + print_whsp(ss); + } + + if ( at->at_no_user_mod ) { + print_literal(ss,"NO-USER-MODIFICATION"); + print_whsp(ss); + } + + if ( at->at_usage ) { + print_literal(ss,"USAGE"); + print_whsp(ss); + switch (at->at_usage) { + case 1: + print_literal(ss,"directoryOperation"); + break; + case 2: + print_literal(ss,"distributedOperation"); + break; + case 3: + print_literal(ss,"dSAOperation"); + break; + default: + print_literal(ss,"UNKNOWN"); + break; + } + } + + print_whsp(ss); + print_literal(ss,")"); + + retstring = safe_string_val(ss); + safe_string_free(ss); + return(retstring); +} + +/* + * This is ripped from servers/slapd/charray.c that should be promoted + * to -lldap or something so that it is used everywhere. + */ +static void +charray_free( char **array ) +{ + char **a; + + if ( array == NULL ) { + return; + } + + for ( a = array; *a != NULL; a++ ) { + if ( *a != NULL ) { + free( *a ); + } + } + free( (char *) array ); +} + +#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 { + int type; + char *sval; +}; + +static int +get_token(char ** sp, char ** token_val) +{ + int kind; + char * p; + char * q; + char * res; + + 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 = 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 ( !isspace(**sp) && **sp != '\0' ) + (*sp)++; + q = *sp; + res = 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(char **sp) +{ + while (isspace(**sp)) + (*sp)++; +} + +/* TBC:!! + * General note for all parsers: to guarantee the algorithm halts they + * must always advance the pointer even when an error is found. For + * this one is not that important since an error here is fatal at the + * upper layers, but it is a simple strategy that will not get in + * endless loops. + */ + +/* Parse a sequence of dot-separated decimal strings */ +static char * +parse_numericoid(char **sp, int *code) +{ + char * res; + char * start = *sp; + int len; + + /* Each iteration of this loops gets one decimal string */ + while (**sp) { + if ( !isdigit(**sp) ) { + /* Initial char is not a digit or char after dot is not a digit */ + *code = SCHEMA_ERR_NODIGIT; + return NULL; + } + (*sp)++; + while ( isdigit(**sp) ) + (*sp)++; + if ( **sp != '.' ) + break; + /* Otherwise, gobble the dot and loop again */ + (*sp)++; + } + /* At this point, *sp points at the char past the numericoid. Perfect. */ + len = *sp - start; + res = malloc(len+1); + if (!res) { + *code = SCHEMA_ERR_OUTOFMEM; + return(NULL); + } + strncpy(res,start,len); + res[len] = '\0'; + return(res); +} + +/* Parse a qdescr or a list of them enclosed in () */ +static char ** +parse_qdescrs(char **sp, int *code) +{ + char ** res; + char ** res1; + int kind; + char * sval; + int size; + int 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 = calloc(3,sizeof(char *)); + if ( !res ) { + *code = SCHEMA_ERR_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 = realloc(res,size*sizeof(char *)); + if ( !res1 ) { + charray_free(res); + *code = SCHEMA_ERR_OUTOFMEM; + return(NULL); + } + res = res1; + } + res[pos] = sval; + pos++; + parse_whsp(sp); + } else { + charray_free(res); + *code = SCHEMA_ERR_UNEXPTOKEN; + return(NULL); + } + } + res[pos] = NULL; + parse_whsp(sp); + return(res); + } else if ( kind == TK_QDESCR ) { + res = calloc(2,sizeof(char *)); + if ( !res ) { + *code = SCHEMA_ERR_OUTOFMEM; + return NULL; + } + res[0] = sval; + res[1] = NULL; + parse_whsp(sp); + return res; + } else { + *code = SCHEMA_ERR_BADNAME; + return NULL; + } +} + +/* Parse a woid or a $-separated list of them enclosed in () */ +static char ** +parse_oids(char **sp, int *code) +{ + char ** res; + char ** res1; + int kind; + char * sval; + int size; + int pos; + + /* + * Strictly speaking, doing this here accepts whsp before the + * ( at the begining of an oidlist, but his 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 = calloc(3,sizeof(char *)); + if ( !res ) { + *code = SCHEMA_ERR_OUTOFMEM; + return NULL; + } + pos = 0; + parse_whsp(sp); + kind = get_token(sp,&sval); + if ( kind == TK_BAREWORD ) { + res[pos] = sval; + pos++; + } else { + *code = SCHEMA_ERR_UNEXPTOKEN; + charray_free(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 ) { + if ( pos == size-2 ) { + size++; + res1 = realloc(res,size*sizeof(char *)); + if ( !res1 ) { + charray_free(res); + *code = SCHEMA_ERR_OUTOFMEM; + return(NULL); + } + res = res1; + } + res[pos] = sval; + pos++; + } else { + *code = SCHEMA_ERR_UNEXPTOKEN; + charray_free(res); + return NULL; + } + parse_whsp(sp); + } else { + *code = SCHEMA_ERR_UNEXPTOKEN; + charray_free(res); + return NULL; + } + } + res[pos] = NULL; + parse_whsp(sp); + return(res); + } else if ( kind == TK_BAREWORD ) { + res = calloc(2,sizeof(char *)); + if ( !res ) { + *code = SCHEMA_ERR_OUTOFMEM; + return NULL; + } + res[0] = sval; + res[1] = NULL; + parse_whsp(sp); + return res; + } else { + *code = SCHEMA_ERR_BADNAME; + return NULL; + } +} + +static void +free_oc(LDAP_OBJECT_CLASS * oc) +{ + ldap_memfree(oc->oc_oid); + charray_free(oc->oc_names); + ldap_memfree(oc->oc_desc); + charray_free(oc->oc_sup_oids); + charray_free(oc->oc_at_oids_must); + charray_free(oc->oc_at_oids_may); + ldap_memfree(oc); +} + +LDAP_OBJECT_CLASS * +ldap_str2objectclass( char * s, int * code, char ** errp ) +{ + int kind; + char * ss = s; + char * sval; + int seen_name = 0; + int seen_desc = 0; + int seen_obsolete = 0; + int seen_sup = 0; + int seen_kind = 0; + int seen_must = 0; + int seen_may = 0; + LDAP_OBJECT_CLASS * oc; + + *errp = s; + oc = calloc(1,sizeof(LDAP_OBJECT_CLASS)); + + if ( !oc ) { + *code = SCHEMA_ERR_OUTOFMEM; + return NULL; + } + + kind = get_token(&ss,&sval); + if ( kind != TK_LEFTPAREN ) { + *code = SCHEMA_ERR_NOLEFTPAREN; + free_oc(oc); + return NULL; + } + + parse_whsp(&ss); + oc->oc_oid = parse_numericoid(&ss,code); + if ( !oc->oc_oid ) { + *errp = ss; + free_oc(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 = SCHEMA_ERR_NORIGHTPAREN; + *errp = ss; + free_oc(oc); + return NULL; + case TK_RIGHTPAREN: + return oc; + case TK_BAREWORD: + if ( !strcmp(sval,"NAME") ) { + if ( seen_name ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_name = 1; + oc->oc_names = parse_qdescrs(&ss,code); + if ( !oc->oc_names ) { + if ( *code != SCHEMA_ERR_OUTOFMEM ) + *code = SCHEMA_ERR_BADNAME; + *errp = ss; + free_oc(oc); + return NULL; + } + } else if ( !strcmp(sval,"DESC") ) { + if ( seen_desc ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_desc = 1; + parse_whsp(&ss); + kind = get_token(&ss,&sval); + if ( kind != TK_QDSTRING ) { + *code = SCHEMA_ERR_UNEXPTOKEN; + *errp = ss; + free(oc); + return NULL; + } + oc->oc_desc = sval; + parse_whsp(&ss); + } else if ( !strcmp(sval,"OBSOLETE") ) { + if ( seen_obsolete ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_obsolete = 1; + oc->oc_obsolete = 1; + parse_whsp(&ss); + } else if ( !strcmp(sval,"SUP") ) { + if ( seen_sup ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_sup = 1; + /* Netscape DS is broken or I have not + understood the syntax. */ + /* oc->oc_sup_oids = parse_oids(&ss,code); */ + oc->oc_sup_oids = parse_qdescrs(&ss,code); + if ( !oc->oc_sup_oids ) { + *errp = ss; + free_oc(oc); + return NULL; + } + } else if ( !strcmp(sval,"ABSTRACT") ) { + if ( seen_kind ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_kind = 1; + oc->oc_kind = 0; + parse_whsp(&ss); + } else if ( !strcmp(sval,"STRUCTURAL") ) { + if ( seen_kind ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_kind = 1; + oc->oc_kind = 1; + parse_whsp(&ss); + } else if ( !strcmp(sval,"AUXILIARY") ) { + if ( seen_kind ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_kind = 1; + oc->oc_kind = 2; + parse_whsp(&ss); + } else if ( !strcmp(sval,"MUST") ) { + if ( seen_must ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_must = 1; + oc->oc_at_oids_must = parse_oids(&ss,code); + if ( !oc->oc_at_oids_must ) { + *errp = ss; + free_oc(oc); + return NULL; + } + parse_whsp(&ss); + } else if ( !strcmp(sval,"MAY") ) { + if ( seen_may ) { + *code = SCHEMA_ERR_DUPOPT; + *errp = ss; + free_oc(oc); + return(NULL); + } + seen_may = 1; + oc->oc_at_oids_may = parse_oids(&ss,code); + if ( !oc->oc_at_oids_may ) { + *errp = ss; + free_oc(oc); + return NULL; + } + parse_whsp(&ss); + } else { + *code = SCHEMA_ERR_UNEXPTOKEN; + *errp = ss; + free_oc(oc); + return NULL; + } + break; + default: + *code = SCHEMA_ERR_UNEXPTOKEN; + *errp = ss; + free_oc(oc); + return NULL; + } + } +} + +