mirror of
https://github.com/openbsd/src.git
synced 2024-12-22 07:27:59 -08:00
c1e33626a4
ok miod@ martijn@
1356 lines
30 KiB
C
1356 lines
30 KiB
C
/* $OpenBSD: schema.c,v 1.20 2022/10/12 11:57:40 jsg Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2010 Martin Hedenfalk <martinh@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
|
|
#include "ldapd.h"
|
|
#include "log.h"
|
|
|
|
#define ERROR -1
|
|
#define STRING 1
|
|
|
|
static int
|
|
attr_oid_cmp(struct attr_type *a, struct attr_type *b)
|
|
{
|
|
return strcasecmp(a->oid, b->oid);
|
|
}
|
|
|
|
static int
|
|
obj_oid_cmp(struct object *a, struct object *b)
|
|
{
|
|
return strcasecmp(a->oid, b->oid);
|
|
}
|
|
|
|
static int
|
|
oidname_cmp(struct oidname *a, struct oidname *b)
|
|
{
|
|
return strcasecmp(a->on_name, b->on_name);
|
|
}
|
|
|
|
static int
|
|
symoid_cmp(struct symoid *a, struct symoid *b)
|
|
{
|
|
return strcasecmp(a->name, b->name);
|
|
}
|
|
|
|
RB_GENERATE(attr_type_tree, attr_type, link, attr_oid_cmp);
|
|
RB_GENERATE(object_tree, object, link, obj_oid_cmp);
|
|
RB_GENERATE(oidname_tree, oidname, link, oidname_cmp);
|
|
RB_GENERATE(symoid_tree, symoid, link, symoid_cmp);
|
|
|
|
static struct attr_list *push_attr(struct attr_list *alist, struct attr_type *a);
|
|
static struct obj_list *push_obj(struct obj_list *olist, struct object *obj);
|
|
static struct name_list *push_name(struct name_list *nl, char *name);
|
|
int is_oidstr(const char *oidstr);
|
|
|
|
struct attr_type *
|
|
lookup_attribute_by_name(struct schema *schema, char *name)
|
|
{
|
|
struct oidname *on, find;
|
|
|
|
find.on_name = name;
|
|
on = RB_FIND(oidname_tree, &schema->attr_names, &find);
|
|
|
|
if (on)
|
|
return on->on_attr_type;
|
|
return NULL;
|
|
}
|
|
|
|
struct attr_type *
|
|
lookup_attribute_by_oid(struct schema *schema, char *oid)
|
|
{
|
|
struct attr_type find;
|
|
|
|
find.oid = oid;
|
|
return RB_FIND(attr_type_tree, &schema->attr_types, &find);
|
|
}
|
|
|
|
struct attr_type *
|
|
lookup_attribute(struct schema *schema, char *oid_or_name)
|
|
{
|
|
if (is_oidstr(oid_or_name))
|
|
return lookup_attribute_by_oid(schema, oid_or_name);
|
|
return lookup_attribute_by_name(schema, oid_or_name);
|
|
}
|
|
|
|
struct object *
|
|
lookup_object_by_oid(struct schema *schema, char *oid)
|
|
{
|
|
struct object find;
|
|
|
|
find.oid = oid;
|
|
return RB_FIND(object_tree, &schema->objects, &find);
|
|
}
|
|
|
|
struct object *
|
|
lookup_object_by_name(struct schema *schema, char *name)
|
|
{
|
|
struct oidname *on, find;
|
|
|
|
find.on_name = name;
|
|
on = RB_FIND(oidname_tree, &schema->object_names, &find);
|
|
|
|
if (on)
|
|
return on->on_object;
|
|
return NULL;
|
|
}
|
|
|
|
struct object *
|
|
lookup_object(struct schema *schema, char *oid_or_name)
|
|
{
|
|
if (is_oidstr(oid_or_name))
|
|
return lookup_object_by_oid(schema, oid_or_name);
|
|
return lookup_object_by_name(schema, oid_or_name);
|
|
}
|
|
|
|
/*
|
|
* Looks up a symbolic OID, optionally with a suffix OID, so if
|
|
* SYMBOL = 1.2.3.4
|
|
* then
|
|
* SYMBOL:5.6 = 1.2.3.4.5.6
|
|
*
|
|
* Returned string must be freed by the caller.
|
|
* Modifies the name argument.
|
|
*/
|
|
char *
|
|
lookup_symbolic_oid(struct schema *schema, char *name)
|
|
{
|
|
struct symoid *symoid, find;
|
|
char *colon, *oid;
|
|
size_t sz;
|
|
|
|
colon = strchr(name, ':');
|
|
if (colon != NULL) {
|
|
if (!is_oidstr(colon + 1)) {
|
|
log_warnx("invalid OID after colon: %s", colon + 1);
|
|
return NULL;
|
|
}
|
|
*colon = '\0';
|
|
}
|
|
|
|
find.name = name;
|
|
symoid = RB_FIND(symoid_tree, &schema->symbolic_oids, &find);
|
|
if (symoid == NULL)
|
|
return NULL;
|
|
|
|
if (colon == NULL)
|
|
return strdup(symoid->oid);
|
|
|
|
/* Expand SYMBOL:OID. */
|
|
sz = strlen(symoid->oid) + 1 + strlen(colon + 1) + 1;
|
|
if ((oid = malloc(sz)) == NULL) {
|
|
log_warnx("malloc");
|
|
return NULL;
|
|
}
|
|
|
|
strlcpy(oid, symoid->oid, sz);
|
|
strlcat(oid, ".", sz);
|
|
strlcat(oid, colon + 1, sz);
|
|
|
|
return oid;
|
|
}
|
|
|
|
/*
|
|
* Push a symbol-OID pair on the tree. Name and OID must be valid pointers
|
|
* during the lifetime of the tree.
|
|
*/
|
|
static struct symoid *
|
|
push_symbolic_oid(struct schema *schema, char *name, char *oid)
|
|
{
|
|
struct symoid *symoid, find;
|
|
|
|
find.name = name;
|
|
symoid = RB_FIND(symoid_tree, &schema->symbolic_oids, &find);
|
|
|
|
if (symoid == NULL) {
|
|
symoid = calloc(1, sizeof(*symoid));
|
|
if (symoid == NULL) {
|
|
log_warnx("calloc");
|
|
return NULL;
|
|
}
|
|
|
|
symoid->name = name;
|
|
RB_INSERT(symoid_tree, &schema->symbolic_oids, symoid);
|
|
}
|
|
|
|
free(symoid->oid);
|
|
symoid->oid = oid;
|
|
|
|
return symoid;
|
|
}
|
|
|
|
static struct attr_list *
|
|
push_attr(struct attr_list *alist, struct attr_type *a)
|
|
{
|
|
struct attr_ptr *aptr;
|
|
|
|
if (alist == NULL) {
|
|
if ((alist = calloc(1, sizeof(*alist))) == NULL) {
|
|
log_warn("calloc");
|
|
return NULL;
|
|
}
|
|
SLIST_INIT(alist);
|
|
}
|
|
|
|
if ((aptr = calloc(1, sizeof(*aptr))) == NULL) {
|
|
log_warn("calloc");
|
|
free(alist);
|
|
return NULL;
|
|
}
|
|
aptr->attr_type = a;
|
|
SLIST_INSERT_HEAD(alist, aptr, next);
|
|
|
|
return alist;
|
|
}
|
|
|
|
static struct obj_list *
|
|
push_obj(struct obj_list *olist, struct object *obj)
|
|
{
|
|
struct obj_ptr *optr;
|
|
|
|
if (olist == NULL) {
|
|
if ((olist = calloc(1, sizeof(*olist))) == NULL) {
|
|
log_warn("calloc");
|
|
return NULL;
|
|
}
|
|
SLIST_INIT(olist);
|
|
}
|
|
|
|
if ((optr = calloc(1, sizeof(*optr))) == NULL) {
|
|
log_warn("calloc");
|
|
free(olist);
|
|
return NULL;
|
|
}
|
|
optr->object = obj;
|
|
SLIST_INSERT_HEAD(olist, optr, next);
|
|
|
|
return olist;
|
|
}
|
|
|
|
int
|
|
is_oidstr(const char *oidstr)
|
|
{
|
|
struct ber_oid oid;
|
|
return (ober_string2oid(oidstr, &oid) == 0);
|
|
}
|
|
|
|
static struct name_list *
|
|
push_name(struct name_list *nl, char *name)
|
|
{
|
|
struct name *n;
|
|
|
|
if (nl == NULL) {
|
|
if ((nl = calloc(1, sizeof(*nl))) == NULL) {
|
|
log_warn("calloc");
|
|
return NULL;
|
|
}
|
|
SLIST_INIT(nl);
|
|
}
|
|
if ((n = calloc(1, sizeof(*n))) == NULL) {
|
|
log_warn("calloc");
|
|
free(nl);
|
|
return NULL;
|
|
}
|
|
n->name = name;
|
|
SLIST_INSERT_HEAD(nl, n, next);
|
|
|
|
return nl;
|
|
}
|
|
|
|
static int
|
|
schema_getc(struct schema *schema, int quotec)
|
|
{
|
|
int c, next;
|
|
|
|
if (schema->pushback_index)
|
|
return (schema->pushback_buffer[--schema->pushback_index]);
|
|
|
|
if (quotec) {
|
|
if ((c = getc(schema->fp)) == EOF) {
|
|
log_warnx("reached end of file while parsing "
|
|
"quoted string");
|
|
return EOF;
|
|
}
|
|
return (c);
|
|
}
|
|
|
|
while ((c = getc(schema->fp)) == '\\') {
|
|
next = getc(schema->fp);
|
|
if (next != '\n') {
|
|
c = next;
|
|
break;
|
|
}
|
|
schema->lineno++;
|
|
}
|
|
|
|
return (c);
|
|
}
|
|
|
|
static int
|
|
schema_ungetc(struct schema *schema, int c)
|
|
{
|
|
if (c == EOF)
|
|
return EOF;
|
|
|
|
if (schema->pushback_index < SCHEMA_MAXPUSHBACK-1)
|
|
return (schema->pushback_buffer[schema->pushback_index++] = c);
|
|
else
|
|
return (EOF);
|
|
}
|
|
|
|
static int
|
|
findeol(struct schema *schema)
|
|
{
|
|
int c;
|
|
|
|
/* skip to either EOF or the first real EOL */
|
|
while (1) {
|
|
if (schema->pushback_index)
|
|
c = schema->pushback_buffer[--schema->pushback_index];
|
|
else
|
|
c = schema_getc(schema, 0);
|
|
if (c == '\n') {
|
|
schema->lineno++;
|
|
break;
|
|
}
|
|
if (c == EOF)
|
|
break;
|
|
}
|
|
return (ERROR);
|
|
}
|
|
|
|
static int
|
|
schema_lex(struct schema *schema, char **kw)
|
|
{
|
|
char buf[8096];
|
|
char *p;
|
|
int quotec, next, c;
|
|
|
|
if (kw)
|
|
*kw = NULL;
|
|
|
|
top:
|
|
p = buf;
|
|
while ((c = schema_getc(schema, 0)) == ' ' || c == '\t')
|
|
; /* nothing */
|
|
|
|
if (c == '#')
|
|
while ((c = schema_getc(schema, 0)) != '\n' && c != EOF)
|
|
; /* nothing */
|
|
|
|
switch (c) {
|
|
case '\'':
|
|
case '"':
|
|
quotec = c;
|
|
while (1) {
|
|
if ((c = schema_getc(schema, quotec)) == EOF)
|
|
return (0);
|
|
if (c == '\n') {
|
|
schema->lineno++;
|
|
continue;
|
|
} else if (c == '\\') {
|
|
if ((next = schema_getc(schema, quotec)) == EOF)
|
|
return (0);
|
|
if (next == quotec || c == ' ' || c == '\t')
|
|
c = next;
|
|
else if (next == '\n')
|
|
continue;
|
|
else
|
|
schema_ungetc(schema, next);
|
|
} else if (c == quotec) {
|
|
*p = '\0';
|
|
break;
|
|
}
|
|
if (p + 1 >= buf + sizeof(buf) - 1) {
|
|
log_warnx("string too long");
|
|
return (findeol(schema));
|
|
}
|
|
*p++ = (char)c;
|
|
}
|
|
if (kw != NULL && (*kw = strdup(buf)) == NULL)
|
|
fatal("schema_lex: strdup");
|
|
return (STRING);
|
|
}
|
|
|
|
#define allowed_in_string(x) \
|
|
(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
|
|
x != '{' && x != '}' && x != '<' && x != '>' && \
|
|
x != '!' && x != '=' && x != '/' && x != '#' && \
|
|
x != ','))
|
|
|
|
if (isalnum(c) || c == ':' || c == '_' || c == '*') {
|
|
do {
|
|
*p++ = c;
|
|
if ((size_t)(p-buf) >= sizeof(buf)) {
|
|
log_warnx("string too long");
|
|
return (findeol(schema));
|
|
}
|
|
} while ((c = schema_getc(schema, 0)) != EOF && (allowed_in_string(c)));
|
|
schema_ungetc(schema, c);
|
|
*p = '\0';
|
|
if (kw != NULL && (*kw = strdup(buf)) == NULL)
|
|
fatal("schema_lex: strdup");
|
|
return STRING;
|
|
}
|
|
if (c == '\n') {
|
|
schema->lineno++;
|
|
goto top;
|
|
}
|
|
if (c == EOF)
|
|
return (0);
|
|
return (c);
|
|
}
|
|
|
|
struct schema *
|
|
schema_new(void)
|
|
{
|
|
struct schema *schema;
|
|
|
|
if ((schema = calloc(1, sizeof(*schema))) == NULL)
|
|
return NULL;
|
|
|
|
RB_INIT(&schema->attr_types);
|
|
RB_INIT(&schema->attr_names);
|
|
RB_INIT(&schema->objects);
|
|
RB_INIT(&schema->object_names);
|
|
RB_INIT(&schema->symbolic_oids);
|
|
|
|
return schema;
|
|
}
|
|
|
|
static void
|
|
schema_err(struct schema *schema, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *msg;
|
|
|
|
va_start(ap, fmt);
|
|
if (vasprintf(&msg, fmt, ap) == -1)
|
|
fatal("vasprintf");
|
|
va_end(ap);
|
|
logit(LOG_CRIT, "%s:%d: %s", schema->filename, schema->lineno, msg);
|
|
free(msg);
|
|
|
|
schema->error++;
|
|
}
|
|
|
|
static int
|
|
schema_link_attr_name(struct schema *schema, const char *name, struct attr_type *attr)
|
|
{
|
|
struct oidname *oidname, *prev;
|
|
|
|
if ((oidname = calloc(1, sizeof(*oidname))) == NULL) {
|
|
log_warn("calloc");
|
|
return -1;
|
|
}
|
|
|
|
oidname->on_name = name;
|
|
oidname->on_attr_type = attr;
|
|
prev = RB_INSERT(oidname_tree, &schema->attr_names, oidname);
|
|
if (prev != NULL) {
|
|
schema_err(schema, "attribute type name '%s'"
|
|
" already defined for oid %s",
|
|
name, prev->on_attr_type->oid);
|
|
free(oidname);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
schema_link_attr_names(struct schema *schema, struct attr_type *attr)
|
|
{
|
|
struct name *name;
|
|
|
|
SLIST_FOREACH(name, attr->names, next) {
|
|
if (schema_link_attr_name(schema, name->name, attr) != 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
schema_link_obj_name(struct schema *schema, const char *name, struct object *obj)
|
|
{
|
|
struct oidname *oidname, *prev;
|
|
|
|
if ((oidname = calloc(1, sizeof(*oidname))) == NULL) {
|
|
log_warn("calloc");
|
|
return -1;
|
|
}
|
|
|
|
oidname->on_name = name;
|
|
oidname->on_object = obj;
|
|
prev = RB_INSERT(oidname_tree, &schema->object_names, oidname);
|
|
if (prev != NULL) {
|
|
schema_err(schema, "object class name '%s'"
|
|
" already defined for oid %s",
|
|
name, prev->on_object->oid);
|
|
free(oidname);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
schema_link_obj_names(struct schema *schema, struct object *obj)
|
|
{
|
|
struct name *name;
|
|
|
|
SLIST_FOREACH(name, obj->names, next) {
|
|
if (schema_link_obj_name(schema, name->name, obj) != 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct name_list *
|
|
schema_parse_names(struct schema *schema)
|
|
{
|
|
struct name_list *nlist = NULL;
|
|
char *kw;
|
|
int token;
|
|
|
|
token = schema_lex(schema, &kw);
|
|
if (token == STRING)
|
|
return push_name(NULL, kw);
|
|
|
|
if (token != '(')
|
|
goto fail;
|
|
|
|
for (;;) {
|
|
token = schema_lex(schema, &kw);
|
|
if (token == ')')
|
|
break;
|
|
if (token != STRING)
|
|
goto fail;
|
|
nlist = push_name(nlist, kw);
|
|
}
|
|
|
|
return nlist;
|
|
|
|
fail:
|
|
free(kw);
|
|
/* FIXME: leaks nlist here */
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
schema_free_name_list(struct name_list *nlist)
|
|
{
|
|
struct name *name;
|
|
|
|
while ((name = SLIST_FIRST(nlist)) != NULL) {
|
|
SLIST_REMOVE_HEAD(nlist, next);
|
|
free(name->name);
|
|
free(name);
|
|
}
|
|
free(nlist);
|
|
}
|
|
|
|
static struct attr_list *
|
|
schema_parse_attrlist(struct schema *schema)
|
|
{
|
|
struct attr_list *alist = NULL;
|
|
struct attr_type *attr;
|
|
char *kw;
|
|
int token, want_dollar = 0;
|
|
|
|
token = schema_lex(schema, &kw);
|
|
if (token == STRING) {
|
|
if ((attr = lookup_attribute(schema, kw)) == NULL) {
|
|
schema_err(schema, "undeclared attribute type '%s'", kw);
|
|
goto fail;
|
|
}
|
|
free(kw);
|
|
return push_attr(NULL, attr);
|
|
}
|
|
|
|
if (token != '(')
|
|
goto fail;
|
|
|
|
for (;;) {
|
|
token = schema_lex(schema, &kw);
|
|
if (token == ')')
|
|
break;
|
|
if (token == '$') {
|
|
if (!want_dollar)
|
|
goto fail;
|
|
want_dollar = 0;
|
|
continue;
|
|
}
|
|
if (token != STRING)
|
|
goto fail;
|
|
if ((attr = lookup_attribute(schema, kw)) == NULL) {
|
|
schema_err(schema, "%s: no such attribute", kw);
|
|
goto fail;
|
|
}
|
|
alist = push_attr(alist, attr);
|
|
free(kw);
|
|
want_dollar = 1;
|
|
}
|
|
|
|
return alist;
|
|
|
|
fail:
|
|
free(kw);
|
|
/* FIXME: leaks alist here */
|
|
return NULL;
|
|
}
|
|
|
|
static struct obj_list *
|
|
schema_parse_objlist(struct schema *schema)
|
|
{
|
|
struct obj_list *olist = NULL;
|
|
struct object *obj;
|
|
char *kw;
|
|
int token, want_dollar = 0;
|
|
|
|
token = schema_lex(schema, &kw);
|
|
if (token == STRING) {
|
|
if ((obj = lookup_object(schema, kw)) == NULL) {
|
|
schema_err(schema, "undeclared object class '%s'", kw);
|
|
goto fail;
|
|
}
|
|
free(kw);
|
|
return push_obj(NULL, obj);
|
|
}
|
|
|
|
if (token != '(')
|
|
goto fail;
|
|
|
|
for (;;) {
|
|
token = schema_lex(schema, &kw);
|
|
if (token == ')')
|
|
break;
|
|
if (token == '$') {
|
|
if (!want_dollar)
|
|
goto fail;
|
|
want_dollar = 0;
|
|
continue;
|
|
}
|
|
if (token != STRING)
|
|
goto fail;
|
|
if ((obj = lookup_object(schema, kw)) == NULL)
|
|
goto fail;
|
|
olist = push_obj(olist, obj);
|
|
want_dollar = 1;
|
|
}
|
|
|
|
return olist;
|
|
|
|
fail:
|
|
free(kw);
|
|
/* FIXME: leaks olist here */
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
schema_validate_match_rule(struct schema *schema, struct attr_type *at,
|
|
const struct match_rule *mrule, enum match_rule_type type)
|
|
{
|
|
int i;
|
|
|
|
if (mrule == NULL)
|
|
return 0;
|
|
|
|
if ((mrule->type & type) != type) {
|
|
schema_err(schema, "%s: bad matching rule '%s'",
|
|
ATTR_NAME(at), mrule->name);
|
|
return -1;
|
|
}
|
|
|
|
/* Is this matching rule compatible with the attribute syntax? */
|
|
if (strcmp(mrule->syntax_oid, at->syntax->oid) == 0)
|
|
return 0;
|
|
|
|
/* Check any alternative syntaxes for compatibility. */
|
|
for (i = 0; mrule->alt_syntax_oids && mrule->alt_syntax_oids[i]; i++)
|
|
if (strcmp(mrule->alt_syntax_oids[i], at->syntax->oid) == 0)
|
|
return 0;
|
|
|
|
schema_err(schema, "%s: inappropriate matching rule '%s' for syntax [%s]",
|
|
ATTR_NAME(at), mrule->name, at->syntax->oid);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
schema_parse_attributetype(struct schema *schema)
|
|
{
|
|
struct attr_type *attr = NULL, *prev, *sup;
|
|
struct name_list *xnames;
|
|
char *kw = NULL, *arg = NULL;
|
|
int token, ret = 0, c;
|
|
|
|
if (schema_lex(schema, NULL) != '(')
|
|
goto fail;
|
|
|
|
if (schema_lex(schema, &kw) != STRING)
|
|
goto fail;
|
|
|
|
if ((attr = calloc(1, sizeof(*attr))) == NULL) {
|
|
log_warn("calloc");
|
|
goto fail;
|
|
}
|
|
attr->usage = USAGE_USER_APP;
|
|
|
|
if (is_oidstr(kw))
|
|
attr->oid = kw;
|
|
else {
|
|
attr->oid = lookup_symbolic_oid(schema, kw);
|
|
if (attr->oid == NULL)
|
|
goto fail;
|
|
free(kw);
|
|
}
|
|
kw = NULL;
|
|
|
|
prev = RB_INSERT(attr_type_tree, &schema->attr_types, attr);
|
|
if (prev != NULL) {
|
|
schema_err(schema, "attribute type %s already defined", attr->oid);
|
|
goto fail;
|
|
}
|
|
|
|
while (ret == 0) {
|
|
token = schema_lex(schema, &kw);
|
|
if (token == ')')
|
|
break;
|
|
else if (token != STRING)
|
|
goto fail;
|
|
if (strcasecmp(kw, "NAME") == 0) {
|
|
attr->names = schema_parse_names(schema);
|
|
if (attr->names == NULL)
|
|
goto fail;
|
|
schema_link_attr_names(schema, attr);
|
|
} else if (strcasecmp(kw, "DESC") == 0) {
|
|
if (schema_lex(schema, &attr->desc) != STRING)
|
|
goto fail;
|
|
} else if (strcasecmp(kw, "OBSOLETE") == 0) {
|
|
attr->obsolete = 1;
|
|
} else if (strcasecmp(kw, "SUP") == 0) {
|
|
if (schema_lex(schema, &arg) != STRING)
|
|
goto fail;
|
|
if ((attr->sup = lookup_attribute(schema, arg)) == NULL) {
|
|
schema_err(schema, "%s: no such attribute", arg);
|
|
goto fail;
|
|
}
|
|
free(arg);
|
|
} else if (strcasecmp(kw, "EQUALITY") == 0) {
|
|
if (schema_lex(schema, &arg) != STRING)
|
|
goto fail;
|
|
if ((attr->equality = match_rule_lookup(arg)) == NULL) {
|
|
schema_err(schema, "%s: unknown matching rule",
|
|
arg);
|
|
goto fail;
|
|
}
|
|
free(arg);
|
|
} else if (strcasecmp(kw, "ORDERING") == 0) {
|
|
if (schema_lex(schema, &arg) != STRING)
|
|
goto fail;
|
|
if ((attr->ordering = match_rule_lookup(arg)) == NULL) {
|
|
schema_err(schema, "%s: unknown matching rule",
|
|
arg);
|
|
goto fail;
|
|
}
|
|
free(arg);
|
|
} else if (strcasecmp(kw, "SUBSTR") == 0) {
|
|
if (schema_lex(schema, &arg) != STRING)
|
|
goto fail;
|
|
if ((attr->substr = match_rule_lookup(arg)) == NULL) {
|
|
schema_err(schema, "%s: unknown matching rule",
|
|
arg);
|
|
goto fail;
|
|
}
|
|
free(arg);
|
|
} else if (strcasecmp(kw, "SYNTAX") == 0) {
|
|
if (schema_lex(schema, &arg) != STRING ||
|
|
!is_oidstr(arg))
|
|
goto fail;
|
|
|
|
if ((attr->syntax = syntax_lookup(arg)) == NULL) {
|
|
schema_err(schema, "syntax not supported: %s",
|
|
arg);
|
|
goto fail;
|
|
}
|
|
|
|
if ((c = schema_getc(schema, 0)) == '{') {
|
|
if (schema_lex(schema, NULL) != STRING ||
|
|
schema_lex(schema, NULL) != '}')
|
|
goto fail;
|
|
} else
|
|
schema_ungetc(schema, c);
|
|
free(arg);
|
|
} else if (strcasecmp(kw, "SINGLE-VALUE") == 0) {
|
|
attr->single = 1;
|
|
} else if (strcasecmp(kw, "COLLECTIVE") == 0) {
|
|
attr->collective = 1;
|
|
} else if (strcasecmp(kw, "NO-USER-MODIFICATION") == 0) {
|
|
attr->immutable = 1;
|
|
} else if (strcasecmp(kw, "USAGE") == 0) {
|
|
if (schema_lex(schema, &arg) != STRING)
|
|
goto fail;
|
|
if (strcasecmp(arg, "dSAOperation") == 0)
|
|
attr->usage = USAGE_DSA_OP;
|
|
else if (strcasecmp(arg, "directoryOperation") == 0)
|
|
attr->usage = USAGE_DIR_OP;
|
|
else if (strcasecmp(arg, "distributedOperation") == 0)
|
|
attr->usage = USAGE_DIST_OP;
|
|
else if (strcasecmp(arg, "userApplications") == 0)
|
|
attr->usage = USAGE_USER_APP;
|
|
else {
|
|
schema_err(schema, "invalid usage '%s'", arg);
|
|
goto fail;
|
|
}
|
|
free(arg);
|
|
} else if (strncmp(kw, "X-", 2) == 0) {
|
|
/* unknown extension, eat argument(s) */
|
|
xnames = schema_parse_names(schema);
|
|
if (xnames == NULL)
|
|
goto fail;
|
|
schema_free_name_list(xnames);
|
|
} else {
|
|
schema_err(schema, "syntax error at token '%s'", kw);
|
|
goto fail;
|
|
}
|
|
free(kw);
|
|
kw = NULL;
|
|
}
|
|
|
|
/* Check that a syntax is defined, either directly or
|
|
* indirectly via a superior attribute type.
|
|
*/
|
|
sup = attr->sup;
|
|
while (attr->syntax == NULL && sup != NULL) {
|
|
attr->syntax = sup->syntax;
|
|
sup = sup->sup;
|
|
}
|
|
if (attr->syntax == NULL) {
|
|
schema_err(schema, "%s: no syntax defined", ATTR_NAME(attr));
|
|
goto fail;
|
|
}
|
|
|
|
/* If the attribute type doesn't explicitly define equality, check
|
|
* if any superior attribute type does.
|
|
*/
|
|
sup = attr->sup;
|
|
while (attr->equality == NULL && sup != NULL) {
|
|
attr->equality = sup->equality;
|
|
sup = sup->sup;
|
|
}
|
|
/* Same thing with ordering matching rule. */
|
|
sup = attr->sup;
|
|
while (attr->ordering == NULL && sup != NULL) {
|
|
attr->ordering = sup->ordering;
|
|
sup = sup->sup;
|
|
}
|
|
/* ...and substring matching rule. */
|
|
sup = attr->sup;
|
|
while (attr->substr == NULL && sup != NULL) {
|
|
attr->substr = sup->substr;
|
|
sup = sup->sup;
|
|
}
|
|
|
|
if (schema_validate_match_rule(schema, attr, attr->equality, MATCH_EQUALITY) != 0 ||
|
|
schema_validate_match_rule(schema, attr, attr->ordering, MATCH_ORDERING) != 0 ||
|
|
schema_validate_match_rule(schema, attr, attr->substr, MATCH_SUBSTR) != 0)
|
|
goto fail;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
free(kw);
|
|
if (attr != NULL) {
|
|
if (attr->oid != NULL) {
|
|
RB_REMOVE(attr_type_tree, &schema->attr_types, attr);
|
|
free(attr->oid);
|
|
}
|
|
free(attr->desc);
|
|
free(attr);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
schema_parse_objectclass(struct schema *schema)
|
|
{
|
|
struct object *obj = NULL, *prev;
|
|
struct obj_ptr *optr;
|
|
struct name_list *xnames;
|
|
char *kw = NULL;
|
|
int token, ret = 0;
|
|
|
|
if (schema_lex(schema, NULL) != '(')
|
|
goto fail;
|
|
|
|
if (schema_lex(schema, &kw) != STRING)
|
|
goto fail;
|
|
|
|
if ((obj = calloc(1, sizeof(*obj))) == NULL) {
|
|
log_warn("calloc");
|
|
goto fail;
|
|
}
|
|
obj->kind = KIND_STRUCTURAL;
|
|
|
|
if (is_oidstr(kw))
|
|
obj->oid = kw;
|
|
else {
|
|
obj->oid = lookup_symbolic_oid(schema, kw);
|
|
if (obj->oid == NULL)
|
|
goto fail;
|
|
free(kw);
|
|
}
|
|
kw = NULL;
|
|
|
|
prev = RB_INSERT(object_tree, &schema->objects, obj);
|
|
if (prev != NULL) {
|
|
schema_err(schema, "object class %s already defined", obj->oid);
|
|
goto fail;
|
|
}
|
|
|
|
while (ret == 0) {
|
|
token = schema_lex(schema, &kw);
|
|
if (token == ')')
|
|
break;
|
|
else if (token != STRING)
|
|
goto fail;
|
|
if (strcasecmp(kw, "NAME") == 0) {
|
|
obj->names = schema_parse_names(schema);
|
|
if (obj->names == NULL)
|
|
goto fail;
|
|
schema_link_obj_names(schema, obj);
|
|
} else if (strcasecmp(kw, "DESC") == 0) {
|
|
if (schema_lex(schema, &obj->desc) != STRING)
|
|
goto fail;
|
|
} else if (strcasecmp(kw, "OBSOLETE") == 0) {
|
|
obj->obsolete = 1;
|
|
} else if (strcasecmp(kw, "SUP") == 0) {
|
|
obj->sup = schema_parse_objlist(schema);
|
|
if (obj->sup == NULL)
|
|
goto fail;
|
|
} else if (strcasecmp(kw, "ABSTRACT") == 0) {
|
|
obj->kind = KIND_ABSTRACT;
|
|
} else if (strcasecmp(kw, "STRUCTURAL") == 0) {
|
|
obj->kind = KIND_STRUCTURAL;
|
|
} else if (strcasecmp(kw, "AUXILIARY") == 0) {
|
|
obj->kind = KIND_AUXILIARY;
|
|
} else if (strcasecmp(kw, "MUST") == 0) {
|
|
obj->must = schema_parse_attrlist(schema);
|
|
if (obj->must == NULL)
|
|
goto fail;
|
|
} else if (strcasecmp(kw, "MAY") == 0) {
|
|
obj->may = schema_parse_attrlist(schema);
|
|
if (obj->may == NULL)
|
|
goto fail;
|
|
} else if (strncasecmp(kw, "X-", 2) == 0) {
|
|
/* unknown extension, eat argument(s) */
|
|
xnames = schema_parse_names(schema);
|
|
if (xnames == NULL)
|
|
goto fail;
|
|
schema_free_name_list(xnames);
|
|
} else {
|
|
schema_err(schema, "syntax error at token '%s'", kw);
|
|
goto fail;
|
|
}
|
|
free(kw);
|
|
kw = NULL;
|
|
}
|
|
|
|
/* Verify the subclassing is allowed.
|
|
*
|
|
* Structural object classes cannot subclass auxiliary object classes.
|
|
* Auxiliary object classes cannot subclass structural object classes.
|
|
* Abstract object classes cannot derive from structural or auxiliary
|
|
* object classes.
|
|
*/
|
|
if (obj->sup != NULL) {
|
|
SLIST_FOREACH(optr, obj->sup, next) {
|
|
if (obj->kind == KIND_STRUCTURAL &&
|
|
optr->object->kind == KIND_AUXILIARY) {
|
|
log_warnx("structural object class '%s' cannot"
|
|
" subclass auxiliary object class '%s'",
|
|
OBJ_NAME(obj), OBJ_NAME(optr->object));
|
|
goto fail;
|
|
}
|
|
|
|
if (obj->kind == KIND_AUXILIARY &&
|
|
optr->object->kind == KIND_STRUCTURAL) {
|
|
log_warnx("auxiliary object class '%s' cannot"
|
|
" subclass structural object class '%s'",
|
|
OBJ_NAME(obj), OBJ_NAME(optr->object));
|
|
goto fail;
|
|
}
|
|
|
|
if (obj->kind == KIND_ABSTRACT &&
|
|
optr->object->kind != KIND_ABSTRACT) {
|
|
log_warnx("abstract object class '%s' cannot"
|
|
" subclass non-abstract object class '%s'",
|
|
OBJ_NAME(obj), OBJ_NAME(optr->object));
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
free(kw);
|
|
if (obj != NULL) {
|
|
if (obj->oid != NULL) {
|
|
RB_REMOVE(object_tree, &schema->objects, obj);
|
|
free(obj->oid);
|
|
}
|
|
free(obj->desc);
|
|
free(obj);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
schema_parse_objectidentifier(struct schema *schema)
|
|
{
|
|
char *symname = NULL, *symoid = NULL;
|
|
char *oid = NULL;
|
|
|
|
if (schema_lex(schema, &symname) != STRING)
|
|
goto fail;
|
|
if (schema_lex(schema, &symoid) != STRING)
|
|
goto fail;
|
|
|
|
if (is_oidstr(symoid)) {
|
|
oid = symoid;
|
|
symoid = NULL;
|
|
} else if ((oid = lookup_symbolic_oid(schema, symoid)) == NULL)
|
|
goto fail;
|
|
|
|
if (push_symbolic_oid(schema, symname, oid) == NULL)
|
|
goto fail;
|
|
|
|
free(symoid);
|
|
return 0;
|
|
|
|
fail:
|
|
free(symname);
|
|
free(symoid);
|
|
free(oid);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
schema_parse(struct schema *schema, const char *filename)
|
|
{
|
|
char *kw;
|
|
int token, ret = 0;
|
|
|
|
log_debug("parsing schema file '%s'", filename);
|
|
|
|
if ((schema->fp = fopen(filename, "r")) == NULL) {
|
|
log_warn("%s", filename);
|
|
return -1;
|
|
}
|
|
schema->filename = filename;
|
|
schema->lineno = 1;
|
|
|
|
while (ret == 0) {
|
|
token = schema_lex(schema, &kw);
|
|
if (token == STRING) {
|
|
if (strcasecmp(kw, "attributetype") == 0)
|
|
ret = schema_parse_attributetype(schema);
|
|
else if (strcasecmp(kw, "objectclass") == 0)
|
|
ret = schema_parse_objectclass(schema);
|
|
else if (strcasecmp(kw, "objectidentifier") == 0)
|
|
ret = schema_parse_objectidentifier(schema);
|
|
else {
|
|
schema_err(schema, "syntax error at '%s'", kw);
|
|
ret = -1;
|
|
}
|
|
if (ret == -1 && schema->error == 0)
|
|
schema_err(schema, "syntax error");
|
|
free(kw);
|
|
} else if (token == 0) { /* EOF */
|
|
break;
|
|
} else {
|
|
schema_err(schema, "syntax error");
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
fclose(schema->fp);
|
|
schema->fp = NULL;
|
|
schema->filename = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
schema_dump_names(const char *desc, struct name_list *nlist,
|
|
char *buf, size_t size)
|
|
{
|
|
struct name *name;
|
|
|
|
if (nlist == NULL || SLIST_EMPTY(nlist))
|
|
return 0;
|
|
|
|
if (strlcat(buf, " ", size) >= size ||
|
|
strlcat(buf, desc, size) >= size)
|
|
return -1;
|
|
|
|
name = SLIST_FIRST(nlist);
|
|
if (SLIST_NEXT(name, next) == NULL) {
|
|
/* single name, no parenthesis */
|
|
if (strlcat(buf, " '", size) >= size ||
|
|
strlcat(buf, name->name, size) >= size ||
|
|
strlcat(buf, "'", size) >= size)
|
|
return -1;
|
|
} else {
|
|
if (strlcat(buf, " ( ", size) >= size)
|
|
return -1;
|
|
SLIST_FOREACH(name, nlist, next)
|
|
if (strlcat(buf, "'", size) >= size ||
|
|
strlcat(buf, name->name, size) >= size ||
|
|
strlcat(buf, "' ", size) >= size)
|
|
return -1;
|
|
if (strlcat(buf, ")", size) >= size)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
schema_dump_attrlist(const char *desc, struct attr_list *alist,
|
|
char *buf, size_t size)
|
|
{
|
|
struct attr_ptr *aptr;
|
|
|
|
if (alist == NULL || SLIST_EMPTY(alist))
|
|
return 0;
|
|
|
|
if (strlcat(buf, " ", size) >= size ||
|
|
strlcat(buf, desc, size) >= size)
|
|
return -1;
|
|
|
|
aptr = SLIST_FIRST(alist);
|
|
if (SLIST_NEXT(aptr, next) == NULL) {
|
|
/* single attribute, no parenthesis */
|
|
if (strlcat(buf, " ", size) >= size ||
|
|
strlcat(buf, ATTR_NAME(aptr->attr_type), size) >= size)
|
|
return -1;
|
|
} else {
|
|
if (strlcat(buf, " ( ", size) >= size)
|
|
return -1;
|
|
SLIST_FOREACH(aptr, alist, next) {
|
|
if (strlcat(buf, ATTR_NAME(aptr->attr_type),
|
|
size) >= size ||
|
|
strlcat(buf, " ", size) >= size)
|
|
return -1;
|
|
if (SLIST_NEXT(aptr, next) != NULL &&
|
|
strlcat(buf, "$ ", size) >= size)
|
|
return -1;
|
|
}
|
|
if (strlcat(buf, ")", size) >= size)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
schema_dump_objlist(const char *desc, struct obj_list *olist,
|
|
char *buf, size_t size)
|
|
{
|
|
struct obj_ptr *optr;
|
|
|
|
if (olist == NULL || SLIST_EMPTY(olist))
|
|
return 0;
|
|
|
|
if (strlcat(buf, " ", size) >= size ||
|
|
strlcat(buf, desc, size) >= size)
|
|
return -1;
|
|
|
|
optr = SLIST_FIRST(olist);
|
|
if (SLIST_NEXT(optr, next) == NULL) {
|
|
/* single attribute, no parenthesis */
|
|
if (strlcat(buf, " ", size) >= size ||
|
|
strlcat(buf, OBJ_NAME(optr->object), size) >= size)
|
|
return -1;
|
|
} else {
|
|
if (strlcat(buf, " ( ", size) >= size)
|
|
return -1;
|
|
SLIST_FOREACH(optr, olist, next) {
|
|
if (strlcat(buf, OBJ_NAME(optr->object), size) >= size ||
|
|
strlcat(buf, " ", size) >= size)
|
|
return -1;
|
|
if (SLIST_NEXT(optr, next) != NULL &&
|
|
strlcat(buf, "$ ", size) >= size)
|
|
return -1;
|
|
}
|
|
if (strlcat(buf, ")", size) >= size)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
schema_dump_object(struct object *obj, char *buf, size_t size)
|
|
{
|
|
if (strlcpy(buf, "( ", size) >= size ||
|
|
strlcat(buf, obj->oid, size) >= size)
|
|
return -1;
|
|
|
|
if (schema_dump_names("NAME", obj->names, buf, size) != 0)
|
|
return -1;
|
|
|
|
if (obj->desc != NULL)
|
|
if (strlcat(buf, " DESC '", size) >= size ||
|
|
strlcat(buf, obj->desc, size) >= size ||
|
|
strlcat(buf, "'", size) >= size)
|
|
return -1;
|
|
|
|
switch (obj->kind) {
|
|
case KIND_STRUCTURAL:
|
|
if (strlcat(buf, " STRUCTURAL", size) >= size)
|
|
return -1;
|
|
break;
|
|
case KIND_ABSTRACT:
|
|
if (strlcat(buf, " ABSTRACT", size) >= size)
|
|
return -1;
|
|
break;
|
|
case KIND_AUXILIARY:
|
|
if (strlcat(buf, " AUXILIARY", size) >= size)
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
if (schema_dump_objlist("SUP", obj->sup, buf, size) != 0)
|
|
return -1;
|
|
|
|
if (obj->obsolete && strlcat(buf, " OBSOLETE", size) >= size)
|
|
return -1;
|
|
|
|
if (schema_dump_attrlist("MUST", obj->must, buf, size) != 0)
|
|
return -1;
|
|
|
|
if (schema_dump_attrlist("MAY", obj->may, buf, size) != 0)
|
|
return -1;
|
|
|
|
if (strlcat(buf, " )", size) >= size)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
schema_dump_attribute(struct attr_type *at, char *buf, size_t size)
|
|
{
|
|
if (strlcpy(buf, "( ", size) >= size ||
|
|
strlcat(buf, at->oid, size) >= size)
|
|
return -1;
|
|
|
|
if (schema_dump_names("NAME", at->names, buf, size) != 0)
|
|
return -1;
|
|
|
|
if (at->desc != NULL)
|
|
if (strlcat(buf, " DESC '", size) >= size ||
|
|
strlcat(buf, at->desc, size) >= size ||
|
|
strlcat(buf, "'", size) >= size)
|
|
return -1;
|
|
|
|
if (at->obsolete && strlcat(buf, " OBSOLETE", size) >= size)
|
|
return -1;
|
|
|
|
if (at->sup != NULL)
|
|
if (strlcat(buf, " SUP ", size) >= size ||
|
|
strlcat(buf, ATTR_NAME(at->sup), size) >= size)
|
|
return -1;
|
|
|
|
if (at->equality != NULL)
|
|
if (strlcat(buf, " EQUALITY ", size) >= size ||
|
|
strlcat(buf, at->equality->name, size) >= size)
|
|
return -1;
|
|
|
|
if (at->ordering != NULL)
|
|
if (strlcat(buf, " ORDERING ", size) >= size ||
|
|
strlcat(buf, at->ordering->name, size) >= size)
|
|
return -1;
|
|
|
|
if (at->substr != NULL)
|
|
if (strlcat(buf, " SUBSTR ", size) >= size ||
|
|
strlcat(buf, at->substr->name, size) >= size)
|
|
return -1;
|
|
|
|
if (at->syntax != NULL)
|
|
if (strlcat(buf, " SYNTAX ", size) >= size ||
|
|
strlcat(buf, at->syntax->oid, size) >= size)
|
|
return -1;
|
|
|
|
if (at->single && strlcat(buf, " SINGLE-VALUE", size) >= size)
|
|
return -1;
|
|
|
|
if (at->collective && strlcat(buf, " COLLECTIVE", size) >= size)
|
|
return -1;
|
|
|
|
if (at->immutable && strlcat(buf, " NO-USER-MODIFICATION", size) >= size)
|
|
return -1;
|
|
|
|
switch (at->usage) {
|
|
case USAGE_USER_APP:
|
|
/* User application usage is the default. */
|
|
break;
|
|
case USAGE_DIR_OP:
|
|
if (strlcat(buf, " USAGE directoryOperation", size) >= size)
|
|
return -1;
|
|
break;
|
|
case USAGE_DIST_OP:
|
|
if (strlcat(buf, " USAGE distributedOperation", size) >= size)
|
|
return -1;
|
|
break;
|
|
case USAGE_DSA_OP:
|
|
if (strlcat(buf, " USAGE dSAOperation", size) >= size)
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
if (strlcat(buf, " )", size) >= size)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
schema_dump_match_rule(struct match_rule *mr, char *buf, size_t size)
|
|
{
|
|
if (strlcpy(buf, "( ", size) >= size ||
|
|
strlcat(buf, mr->oid, size) >= size ||
|
|
strlcat(buf, " NAME '", size) >= size ||
|
|
strlcat(buf, mr->name, size) >= size ||
|
|
strlcat(buf, "' SYNTAX ", size) >= size ||
|
|
strlcat(buf, mr->syntax_oid, size) >= size ||
|
|
strlcat(buf, " )", size) >= size)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|