diff options
author | Martin Hedenfal <martinh@cvs.openbsd.org> | 2010-09-03 09:39:18 +0000 |
---|---|---|
committer | Martin Hedenfal <martinh@cvs.openbsd.org> | 2010-09-03 09:39:18 +0000 |
commit | c51fd5b8a2f165a9fbe744c02af75eaa9dcffd59 (patch) | |
tree | 4380bf8580594d310b651f4fcce701d5445159fb | |
parent | f195a78f3fe24722ab2c8b29fa650aca07c86bf2 (diff) |
Implement attribute syntaxes from RFC4517. This adds validation to the most
common attribute types. Specialized attribute types like Delivery Method or
Teletex Terminal Identifier are recognized for completeness, but not
validated.
-rw-r--r-- | usr.sbin/ldapd/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/ldapd/schema.c | 42 | ||||
-rw-r--r-- | usr.sbin/ldapd/schema.h | 16 | ||||
-rw-r--r-- | usr.sbin/ldapd/syntax.c | 351 | ||||
-rw-r--r-- | usr.sbin/ldapd/validate.c | 13 |
5 files changed, 410 insertions, 16 deletions
diff --git a/usr.sbin/ldapd/Makefile b/usr.sbin/ldapd/Makefile index efba6758031..f3ee37b6df2 100644 --- a/usr.sbin/ldapd/Makefile +++ b/usr.sbin/ldapd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.7 2010/09/01 17:34:15 martinh Exp $ +# $OpenBSD: Makefile,v 1.8 2010/09/03 09:39:17 martinh Exp $ PROG= ldapd MAN= ldapd.8 ldapd.conf.5 @@ -6,7 +6,7 @@ SRCS= ber.c log.c control.c \ util.c ldapd.c ldape.c conn.c attributes.c namespace.c \ btree.c filter.c search.c parse.y \ auth.c modify.c index.c ssl.c ssl_privsep.c \ - validate.c uuid.c schema.c imsgev.c + validate.c uuid.c schema.c imsgev.c syntax.c LDADD= -levent -lssl -lcrypto -lz -lutil DPADD= ${LIBEVENT} ${LIBCRYPTO} ${LIBSSL} ${LIBZ} ${LIBUTIL} diff --git a/usr.sbin/ldapd/schema.c b/usr.sbin/ldapd/schema.c index a73f020c3f4..2daca246015 100644 --- a/usr.sbin/ldapd/schema.c +++ b/usr.sbin/ldapd/schema.c @@ -1,4 +1,4 @@ -/* $OpenBSD: schema.c,v 1.9 2010/09/01 18:30:48 martinh Exp $ */ +/* $OpenBSD: schema.c,v 1.10 2010/09/03 09:39:17 martinh Exp $ */ /* * Copyright (c) 2010 Martin Hedenfalk <martinh@openbsd.org> @@ -122,7 +122,8 @@ lookup_object(struct schema *schema, char *oid_or_name) return lookup_object_by_name(schema, oid_or_name); } -/* Looks up a symbolic OID, optionally with a suffix OID, so if +/* + * 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 @@ -130,7 +131,7 @@ lookup_object(struct schema *schema, char *oid_or_name) * Returned string must be freed by the caller. * Modifies the name argument. */ -static char * +char * lookup_symbolic_oid(struct schema *schema, char *name) { struct symoid *symoid, find; @@ -154,8 +155,7 @@ lookup_symbolic_oid(struct schema *schema, char *name) if (colon == NULL) return strdup(symoid->oid); - /* Expand SYMBOL:OID. - */ + /* Expand SYMBOL:OID. */ sz = strlen(symoid->oid) + 1 + strlen(colon + 1) + 1; if ((oid = malloc(sz)) == NULL) { log_warnx("malloc"); @@ -169,7 +169,8 @@ lookup_symbolic_oid(struct schema *schema, char *name) return oid; } -/* Push a symbol-OID pair on the tree. Name and OID must be valid pointers +/* + * Push a symbol-OID pair on the tree. Name and OID must be valid pointers * during the lifetime of the tree. */ static struct symoid * @@ -666,7 +667,7 @@ fail: static int schema_parse_attributetype(struct schema *schema) { - struct attr_type *attr = NULL, *prev; + struct attr_type *attr = NULL, *prev, *sup; struct name_list *xnames; char *kw = NULL, *arg = NULL; int token, ret = 0, c; @@ -733,15 +734,23 @@ schema_parse_attributetype(struct schema *schema) if (schema_lex(schema, &attr->substr) != STRING) goto fail; } else if (strcasecmp(kw, "SYNTAX") == 0) { - if (schema_lex(schema, &attr->syntax) != STRING || - !is_oidstr(attr->syntax)) + 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) { @@ -777,6 +786,19 @@ schema_parse_attributetype(struct schema *schema) free(kw); } + /* 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; + } + return 0; fail: @@ -1200,7 +1222,7 @@ schema_dump_attribute(struct attr_type *at, char *buf, size_t size) if (at->syntax != NULL) if (strlcat(buf, " SYNTAX ", size) >= size || - strlcat(buf, at->syntax, size) >= size) + strlcat(buf, at->syntax->oid, size) >= size) return -1; if (at->single && strlcat(buf, " SINGLE-VALUE", size) >= size) diff --git a/usr.sbin/ldapd/schema.h b/usr.sbin/ldapd/schema.h index d00dfaa14e3..e5c305d0dcd 100644 --- a/usr.sbin/ldapd/schema.h +++ b/usr.sbin/ldapd/schema.h @@ -1,4 +1,4 @@ -/* $OpenBSD: schema.h,v 1.4 2010/07/02 05:23:40 martinh Exp $ */ +/* $OpenBSD: schema.h,v 1.5 2010/09/03 09:39:17 martinh Exp $ */ /* * Copyright (c) 2010 Martin Hedenfalk <martinh@openbsd.org> @@ -36,6 +36,14 @@ struct name { }; SLIST_HEAD(name_list, name); +struct schema; +struct syntax { + char *oid; + char *desc; + int (*is_valid)(struct schema *schema, char *value, + size_t len); +}; + struct attr_type { RB_ENTRY(attr_type) link; char *oid; @@ -46,7 +54,7 @@ struct attr_type { char *equality; char *ordering; char *substr; - char *syntax; + const struct syntax *syntax; int single; int collective; int immutable; /* no-user-modification */ @@ -141,7 +149,11 @@ struct attr_type *lookup_attribute(struct schema *schema, char *oid_or_name); struct object *lookup_object_by_oid(struct schema *schema, char *oid); struct object *lookup_object_by_name(struct schema *schema, char *name); struct object *lookup_object(struct schema *schema, char *oid_or_name); +char *lookup_symbolic_oid(struct schema *schema, char *name); int is_oidstr(const char *oidstr); +/* syntax.c */ +const struct syntax *syntax_lookup(const char *oid); + #endif diff --git a/usr.sbin/ldapd/syntax.c b/usr.sbin/ldapd/syntax.c new file mode 100644 index 00000000000..69a46388015 --- /dev/null +++ b/usr.sbin/ldapd/syntax.c @@ -0,0 +1,351 @@ +/* $OpenBSD: syntax.c,v 1.1 2010/09/03 09:39:17 martinh Exp $ */ + +/* + * Copyright (c) 2010 Martin Hedenfalk <martin@bzero.se> + * + * 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 <sys/queue.h> +#include <sys/tree.h> + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "schema.h" +#include "uuid.h" + +#define SYNTAX_DECL(TYPE) \ + static int syntax_is_##TYPE(struct schema *schema, char *value, size_t len) + +SYNTAX_DECL(bit_string); +SYNTAX_DECL(boolean); +SYNTAX_DECL(country); +SYNTAX_DECL(directory_string); +SYNTAX_DECL(dn); +SYNTAX_DECL(gentime); +SYNTAX_DECL(ia5_string); +SYNTAX_DECL(integer); +SYNTAX_DECL(numeric_string); +SYNTAX_DECL(octet_string); +SYNTAX_DECL(oid); +SYNTAX_DECL(printable_string); +SYNTAX_DECL(utctime); +SYNTAX_DECL(uuid); + +static struct syntax syntaxes[] = { + /* + * Keep these sorted. + */ + { "1.3.6.1.1.1.0.0", "NIS netgroup triple", NULL }, + { "1.3.6.1.1.1.0.1", "Boot parameter", NULL }, + { "1.3.6.1.1.16.1", "UUID", syntax_is_uuid }, + { "1.3.6.1.4.1.1466.115.121.1.11", "Country String", syntax_is_country }, + { "1.3.6.1.4.1.1466.115.121.1.12", "DN", syntax_is_dn }, + { "1.3.6.1.4.1.1466.115.121.1.14", "Delivery Method", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.15", "Directory String", syntax_is_directory_string }, + { "1.3.6.1.4.1.1466.115.121.1.16", "DIT Content Rule Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.17", "DIT Structure Rule Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.21", "Enhanced Guide", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.22", "Facsimile Telephone Number", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.23", "Fax", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.24", "Generalized Time", syntax_is_gentime }, + { "1.3.6.1.4.1.1466.115.121.1.25", "Guide", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.26", "IA5 String", syntax_is_ia5_string }, + { "1.3.6.1.4.1.1466.115.121.1.27", "INTEGER", syntax_is_integer }, + { "1.3.6.1.4.1.1466.115.121.1.28", "JPEG", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.3", "Attribute Type Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.30", "Matching Rule Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.31", "Matching Rule Use Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.34", "Name And Optional UID", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.35", "Name Form Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.36", "Numeric String", syntax_is_numeric_string }, + { "1.3.6.1.4.1.1466.115.121.1.37", "Object Class Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.38", "OID", syntax_is_oid }, + { "1.3.6.1.4.1.1466.115.121.1.39", "Other Mailbox", syntax_is_ia5_string }, + { "1.3.6.1.4.1.1466.115.121.1.40", "Octet String", syntax_is_octet_string }, + { "1.3.6.1.4.1.1466.115.121.1.41", "Postal Address", syntax_is_directory_string }, + { "1.3.6.1.4.1.1466.115.121.1.44", "Printable String", syntax_is_printable_string }, + { "1.3.6.1.4.1.1466.115.121.1.45", "Subtree Specification", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.5", "Binary", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.50", "Telephone Number", syntax_is_printable_string }, + { "1.3.6.1.4.1.1466.115.121.1.51", "Teletex Terminal Identifier", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.52", "Telex Number", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.53", "UTC Time", syntax_is_utctime }, + { "1.3.6.1.4.1.1466.115.121.1.54", "LDAP Syntax Description", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.58", "Substring Assertion", NULL }, + { "1.3.6.1.4.1.1466.115.121.1.6", "Bit String", syntax_is_bit_string }, + { "1.3.6.1.4.1.1466.115.121.1.7", "Boolean", syntax_is_boolean }, + { "1.3.6.1.4.1.1466.115.121.1.8", "Certificate", NULL }, + +}; + +static int +syntax_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct syntax *)e)->oid)); +} + +const struct syntax * +syntax_lookup(const char *oid) +{ + return bsearch(oid, syntaxes, sizeof(syntaxes)/sizeof(syntaxes[0]), + sizeof(syntaxes[0]), syntax_cmp); +} + +/* + * A value of the Octet String syntax is a sequence of zero, one, or + * more arbitrary octets. + */ +static int +syntax_is_octet_string(struct schema *schema, char *value, size_t len) +{ + return 1; +} + +/* + * A string of one or more arbitrary UTF-8 characters. + */ +static int +syntax_is_directory_string(struct schema *schema, char *value, size_t len) +{ + /* FIXME: validate UTF-8 characters. */ + return len >= 1 && *value != '\0'; +} + +/* + * A value of the Printable String syntax is a string of one or more + * latin alphabetic, numeric, and selected punctuation characters as + * specified by the <PrintableCharacter> rule in Section 3.2. + * + * PrintableCharacter = ALPHA / DIGIT / SQUOTE / LPAREN / RPAREN / + * PLUS / COMMA / HYPHEN / DOT / EQUALS / + * SLASH / COLON / QUESTION / SPACE + */ +static int +syntax_is_printable_string(struct schema *schema, char *value, size_t len) +{ + static char *special = "'()+,-.=/:? "; + char *p; + + for (p = value; len > 0 && *p != '\0'; p++, len--) { + if (!isalnum(*p) && strchr(special, *p) == NULL) + return 0; + } + + return (p != value); +} + +/* + * A value of the IA5 String syntax is a string of zero, one, or more + * characters from International Alphabet 5 (IA5). + * IA5String = *(%x00-7F) + */ +static int +syntax_is_ia5_string(struct schema *schema, char *value, size_t len) +{ + char *p; + + for (p = value; *p != '\0'; p++) { + if ((unsigned char)*p > 0x7F) + return 0; + } + + return 1; +} + +/* + * A value of the Integer syntax is a whole number of unlimited magnitude. + * Integer = ( HYPHEN LDIGIT *DIGIT ) / number + * number = DIGIT / ( LDIGIT 1*DIGIT ) + */ +static int +syntax_is_integer(struct schema *schema, char *value, size_t len) +{ + if (*value == '-') + value++; + if (*value == '0') + return value[1] == '\0'; + for (value++; *value != '\0'; value++) + if (!isdigit(*value)) + return 0; + return 1; +} + +static int +syntax_is_dn(struct schema *schema, char *value, size_t len) +{ + if (!syntax_is_directory_string(schema, value, len)) + return 0; + + /* FIXME: DN syntax not implemented */ + + return 1; +} + +static int +syntax_is_oid(struct schema *schema, char *value, size_t len) +{ + char *symoid = NULL; + + if (len == 0 || *value == '\0') + return 0; + if (is_oidstr(value)) + return 1; + + /* + * Check for a symbolic OID: object class, attribute type or symoid. + */ + if (lookup_object_by_name(schema, value) != NULL || + lookup_attribute_by_name(schema, value) != NULL || + (symoid = lookup_symbolic_oid(schema, value)) != NULL) { + free(symoid); + return 1; + } + + return 0; +} + +static int +syntax_is_uuid(struct schema *schema, char *value, size_t len) +{ + int i; + + if (len != 36) + return 0; + +#define IS_XDIGITS(n, c) \ + do { \ + for (i = 0; i < (n); i++) \ + if (!isxdigit(*value++)) \ + return 0; \ + if (*value++ != (c)) \ + return 0; \ + } while(0) + + IS_XDIGITS(8, '-'); + IS_XDIGITS(4, '-'); + IS_XDIGITS(4, '-'); + IS_XDIGITS(4, '-'); + IS_XDIGITS(12, '\0'); + + return 1; +} + +/* + * NumericString = 1*(DIGIT / SPACE) + */ +static int +syntax_is_numeric_string(struct schema *schema, char *value, size_t len) +{ + char *p; + + for (p = value; *p != '\0'; p++) + if (!isdigit(*p) || *p != ' ') + return 0; + + return p != value; +} + +static int +syntax_is_time(struct schema *schema, char *value, size_t len, int gen) +{ + int n; + char *p = value; + +#define CHECK_RANGE(min, max) \ + if (!isdigit(p[0]) || !isdigit(p[1])) \ + return 0; \ + n = (p[0] - '0') * 10 + (p[1] - '0'); \ + if (n < min || n > max) \ + return 0; \ + p += 2; + + if (gen) + CHECK_RANGE(0, 99) /* century */ + CHECK_RANGE(0, 99) /* year */ + CHECK_RANGE(1, 12) /* month */ + CHECK_RANGE(1, 31) /* day */ + /* FIXME: should check number of days in month */ + CHECK_RANGE(0, 23); /* hour */ + + if (!gen || isdigit(*p)) { + CHECK_RANGE(0, 59); /* minute */ + if (!gen && isdigit(*p)) + CHECK_RANGE(0, 59+gen); /* second or leap-second */ + if (*p == '\0') + return 1; + } + /* fraction */ + if (!gen && ((*p == ',' || *p == '.') && !isdigit(*++p))) + return 0; + + if (*p == '-' || *p == '+') { + ++p; + CHECK_RANGE(0, 23); /* hour */ + if (!gen || isdigit(*p)) + CHECK_RANGE(0, 59); /* minute */ + } else if (*p++ != 'Z') + return 0; + + return *p == '\0'; +} + +static int +syntax_is_gentime(struct schema *schema, char *value, size_t len) +{ + return syntax_is_time(schema, value, len, 1); +} + +static int +syntax_is_utctime(struct schema *schema, char *value, size_t len) +{ + return syntax_is_time(schema, value, len, 0); +} + +static int +syntax_is_country(struct schema *schema, char *value, size_t len) +{ + if (len != 2) + return 0; + return syntax_is_printable_string(schema, value, len); +} + +static int +syntax_is_bit_string(struct schema *schema, char *value, size_t len) +{ + if (*value++ != '\'') + return 0; + + for (; *value != '\0'; value++) { + if (*value == '\'') + break; + if (*value != '0' && *value != '1') + return 0; + } + + if (++*value != 'B') + return 0; + + return *value == '\0'; +} + +static int +syntax_is_boolean(struct schema *schema, char *value, size_t len) +{ + return strcmp(value, "TRUE") == 0 || strcmp(value, "FALSE") == 0; +} + diff --git a/usr.sbin/ldapd/validate.c b/usr.sbin/ldapd/validate.c index a1cc215e470..8df2c710180 100644 --- a/usr.sbin/ldapd/validate.c +++ b/usr.sbin/ldapd/validate.c @@ -1,4 +1,4 @@ -/* $OpenBSD: validate.c,v 1.7 2010/07/01 06:15:55 martinh Exp $ */ +/* $OpenBSD: validate.c,v 1.8 2010/09/03 09:39:17 martinh Exp $ */ /* * Copyright (c) 2010 Martin Hedenfalk <martin@bzero.se> @@ -51,6 +51,7 @@ validate_attribute(struct attr_type *at, struct ber_element *vals) { int nvals = 0; struct ber_element *elm; + char *val; if (vals == NULL) { log_debug("missing values"); @@ -63,7 +64,7 @@ validate_attribute(struct attr_type *at, struct ber_element *vals) } for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) { - if (elm->be_type != BER_TYPE_OCTETSTRING) { + if (ber_get_string(elm, &val) == -1) { log_debug("attribute value not an octet-string"); return LDAP_PROTOCOL_ERROR; } @@ -73,6 +74,14 @@ validate_attribute(struct attr_type *at, struct ber_element *vals) " attribute %s", ATTR_NAME(at)); return LDAP_CONSTRAINT_VIOLATION; } + + if (at->syntax->is_valid != NULL && + !at->syntax->is_valid(conf->schema, val, elm->be_len)) { + log_debug("%s: invalid syntax", ATTR_NAME(at)); + log_debug("syntax = %s", at->syntax->desc); + log_debug("value: [%.*s]", elm->be_len, val); + return LDAP_INVALID_SYNTAX; + } } /* There must be at least one value in an attribute. */ |