]> git.sur5r.net Git - openldap/commitdiff
Initial incomplete and broken version.
authorJulio Sánchez Fernández <jsanchez@openldap.org>
Mon, 24 May 1999 01:38:57 +0000 (01:38 +0000)
committerJulio Sánchez Fernández <jsanchez@openldap.org>
Mon, 24 May 1999 01:38:57 +0000 (01:38 +0000)
include/ldap_schema.h [new file with mode: 0644]
libraries/libldap/schema.c [new file with mode: 0644]

diff --git a/include/ldap_schema.h b/include/ldap_schema.h
new file mode 100644 (file)
index 0000000..251c63e
--- /dev/null
@@ -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_cdefs.h>
+
+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 (file)
index 0000000..6ff1313
--- /dev/null
@@ -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 <ac/ctype.h>
+#include <ac/string.h>
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#include <lber.h>
+#include <ldap.h>
+#include <ldap_schema.h>
+#include <stdio.h>
+
+/*
+ * 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;
+               }
+       }
+}
+
+