1 /* $OpenBSD: validate.c,v 1.8 2010/09/03 09:39:17 martinh Exp $ */ 2 3 /* 4 * Copyright (c) 2010 Martin Hedenfalk <martin@bzero.se> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/queue.h> 21 22 #include <stdlib.h> 23 #include <string.h> 24 25 #include "ldapd.h" 26 27 static int 28 validate_required_attributes(struct ber_element *entry, struct object *obj) 29 { 30 struct attr_ptr *ap; 31 struct attr_type *at; 32 33 if (obj->must == NULL) 34 return LDAP_SUCCESS; 35 36 SLIST_FOREACH(ap, obj->must, next) { 37 at = ap->attr_type; 38 39 if (ldap_find_attribute(entry, at) == NULL) { 40 log_debug("missing required attribute %s", 41 ATTR_NAME(at)); 42 return LDAP_OBJECT_CLASS_VIOLATION; 43 } 44 } 45 46 return LDAP_SUCCESS; 47 } 48 49 static int 50 validate_attribute(struct attr_type *at, struct ber_element *vals) 51 { 52 int nvals = 0; 53 struct ber_element *elm; 54 char *val; 55 56 if (vals == NULL) { 57 log_debug("missing values"); 58 return LDAP_OTHER; 59 } 60 61 if (vals->be_type != BER_TYPE_SET) { 62 log_debug("values should be a set"); 63 return LDAP_OTHER; 64 } 65 66 for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) { 67 if (ber_get_string(elm, &val) == -1) { 68 log_debug("attribute value not an octet-string"); 69 return LDAP_PROTOCOL_ERROR; 70 } 71 72 if (++nvals > 1 && at->single) { 73 log_debug("multiple values for single-valued" 74 " attribute %s", ATTR_NAME(at)); 75 return LDAP_CONSTRAINT_VIOLATION; 76 } 77 78 if (at->syntax->is_valid != NULL && 79 !at->syntax->is_valid(conf->schema, val, elm->be_len)) { 80 log_debug("%s: invalid syntax", ATTR_NAME(at)); 81 log_debug("syntax = %s", at->syntax->desc); 82 log_debug("value: [%.*s]", elm->be_len, val); 83 return LDAP_INVALID_SYNTAX; 84 } 85 } 86 87 /* There must be at least one value in an attribute. */ 88 if (nvals == 0) { 89 log_debug("missing value in attribute %s", ATTR_NAME(at)); 90 return LDAP_CONSTRAINT_VIOLATION; 91 } 92 93 /* FIXME: validate that values are unique */ 94 95 return LDAP_SUCCESS; 96 } 97 98 static const char * 99 attribute_equality(struct attr_type *at) 100 { 101 if (at == NULL) 102 return NULL; 103 if (at->equality != NULL) 104 return at->equality; 105 return attribute_equality(at->sup); 106 } 107 108 /* FIXME: doesn't handle escaped characters. 109 */ 110 static int 111 validate_dn(const char *dn, struct ber_element *entry) 112 { 113 char *copy; 114 char *sup_dn, *na, *dv, *p; 115 struct namespace *ns; 116 struct attr_type *at; 117 struct ber_element *vals; 118 119 if ((copy = strdup(dn)) == NULL) 120 return LDAP_OTHER; 121 122 sup_dn = strchr(copy, ','); 123 if (sup_dn++ == NULL) 124 sup_dn = strrchr(copy, '\0'); 125 126 /* Validate naming attributes and distinguished values in the RDN. 127 */ 128 p = copy; 129 for (;p < sup_dn;) { 130 na = p; 131 p = na + strcspn(na, "="); 132 if (p == na || p >= sup_dn) { 133 free(copy); 134 return LDAP_INVALID_DN_SYNTAX; 135 } 136 *p = '\0'; 137 dv = p + 1; 138 p = dv + strcspn(dv, "+,"); 139 if (p == dv) { 140 free(copy); 141 return LDAP_INVALID_DN_SYNTAX; 142 } 143 *p++ = '\0'; 144 145 if ((at = lookup_attribute(conf->schema, na)) == NULL) { 146 log_debug("attribute %s not defined in schema", na); 147 goto fail; 148 } 149 if (at->usage != USAGE_USER_APP) { 150 log_debug("naming attribute %s is operational", na); 151 goto fail; 152 } 153 if (at->collective) { 154 log_debug("naming attribute %s is collective", na); 155 goto fail; 156 } 157 if (at->obsolete) { 158 log_debug("naming attribute %s is obsolete", na); 159 goto fail; 160 } 161 if (attribute_equality(at) == NULL) { 162 log_debug("naming attribute %s doesn't define equality", 163 na); 164 goto fail; 165 } 166 if ((vals = ldap_find_attribute(entry, at)) == NULL) { 167 log_debug("missing distinguished value for %s", na); 168 goto fail; 169 } 170 if (ldap_find_value(vals->be_next, dv) == NULL) { 171 log_debug("missing distinguished value %s" 172 " in naming attribute %s", dv, na); 173 goto fail; 174 } 175 } 176 177 /* Check that the RDN immediate superior exists, or it is a 178 * top-level namespace. 179 */ 180 if (*sup_dn != '\0') { 181 TAILQ_FOREACH(ns, &conf->namespaces, next) { 182 if (strcmp(dn, ns->suffix) == 0) 183 goto done; 184 } 185 ns = namespace_for_base(sup_dn); 186 if (ns == NULL || !namespace_exists(ns, sup_dn)) { 187 free(copy); 188 return LDAP_NO_SUCH_OBJECT; 189 } 190 } 191 192 done: 193 free(copy); 194 return LDAP_SUCCESS; 195 fail: 196 free(copy); 197 return LDAP_NAMING_VIOLATION; 198 } 199 200 static int 201 has_attribute(struct attr_type *at, struct attr_list *alist) 202 { 203 struct attr_ptr *ap; 204 205 if (alist == NULL) 206 return 0; 207 208 SLIST_FOREACH(ap, alist, next) { 209 if (at == ap->attr_type) 210 return 1; 211 } 212 return 0; 213 } 214 215 /* Validate that the attribute type is allowed by any object class. 216 */ 217 static int 218 validate_allowed_attribute(struct attr_type *at, struct obj_list *olist) 219 { 220 struct object *obj; 221 struct obj_ptr *optr; 222 223 if (olist == NULL) 224 return LDAP_OBJECT_CLASS_VIOLATION; 225 226 SLIST_FOREACH(optr, olist, next) { 227 obj = optr->object; 228 229 if (has_attribute(at, obj->may) || 230 has_attribute(at, obj->must)) 231 return LDAP_SUCCESS; 232 233 if (validate_allowed_attribute(at, obj->sup) == LDAP_SUCCESS) 234 return LDAP_SUCCESS; 235 } 236 237 return LDAP_OBJECT_CLASS_VIOLATION; 238 } 239 240 static void 241 olist_push(struct obj_list *olist, struct object *obj) 242 { 243 struct obj_ptr *optr, *sup; 244 245 SLIST_FOREACH(optr, olist, next) 246 if (optr->object == obj) 247 return; 248 249 if ((optr = calloc(1, sizeof(*optr))) == NULL) 250 return; 251 optr->object = obj; 252 SLIST_INSERT_HEAD(olist, optr, next); 253 254 /* Expand the list of object classes along the superclass chain. 255 */ 256 if (obj->sup != NULL) 257 SLIST_FOREACH(sup, obj->sup, next) 258 olist_push(olist, sup->object); 259 } 260 261 static void 262 olist_free(struct obj_list *olist) 263 { 264 struct obj_ptr *optr; 265 266 if (olist == NULL) 267 return; 268 269 while ((optr = SLIST_FIRST(olist)) != NULL) { 270 SLIST_REMOVE_HEAD(olist, next); 271 free(optr); 272 } 273 274 free(olist); 275 } 276 277 /* Check if sup is a superior object class to obj. 278 */ 279 static int 280 is_super(struct object *sup, struct object *obj) 281 { 282 struct obj_ptr *optr; 283 284 if (sup == NULL || obj->sup == NULL) 285 return 0; 286 287 SLIST_FOREACH(optr, obj->sup, next) 288 if (optr->object == sup || is_super(sup, optr->object)) 289 return 1; 290 291 return 0; 292 } 293 294 int 295 validate_entry(const char *dn, struct ber_element *entry, int relax) 296 { 297 int rc, extensible = 0; 298 char *s; 299 struct ber_element *objclass, *a, *vals; 300 struct object *obj, *structural_obj = NULL; 301 struct attr_type *at; 302 struct obj_list *olist = NULL; 303 struct obj_ptr *optr, *optr2; 304 305 if (relax) 306 goto rdn; 307 308 /* There must be an objectClass attribute. 309 */ 310 objclass = ldap_get_attribute(entry, "objectClass"); 311 if (objclass == NULL) { 312 log_debug("missing objectClass attribute"); 313 return LDAP_OBJECT_CLASS_VIOLATION; 314 } 315 316 if ((olist = calloc(1, sizeof(*olist))) == NULL) 317 return LDAP_OTHER; 318 SLIST_INIT(olist); 319 320 /* Check objectClass(es) against schema. 321 */ 322 objclass = objclass->be_next; /* skip attribute description */ 323 for (a = objclass->be_sub; a != NULL; a = a->be_next) { 324 if (ber_get_string(a, &s) != 0) { 325 rc = LDAP_INVALID_SYNTAX; 326 goto done; 327 } 328 329 if ((obj = lookup_object(conf->schema, s)) == NULL) { 330 log_debug("objectClass %s not defined in schema", s); 331 rc = LDAP_NAMING_VIOLATION; 332 goto done; 333 } 334 335 if (obj->kind == KIND_STRUCTURAL) { 336 if (structural_obj != NULL) { 337 if (is_super(structural_obj, obj)) 338 structural_obj = obj; 339 else if (!is_super(obj, structural_obj)) { 340 log_debug("multiple structural" 341 " object classes"); 342 rc = LDAP_OBJECT_CLASS_VIOLATION; 343 goto done; 344 } 345 } else 346 structural_obj = obj; 347 } 348 349 olist_push(olist, obj); 350 351 /* RFC4512, section 4.3: 352 * "The 'extensibleObject' auxiliary object class allows 353 * entries that belong to it to hold any user attribute." 354 */ 355 if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0) 356 extensible = 1; 357 } 358 359 /* Must have exactly one structural object class. 360 */ 361 if (structural_obj == NULL) { 362 log_debug("no structural object class defined"); 363 rc = LDAP_OBJECT_CLASS_VIOLATION; 364 goto done; 365 } 366 367 /* "An entry cannot belong to an abstract object class 368 * unless it belongs to a structural or auxiliary class that 369 * inherits from that abstract class." 370 */ 371 SLIST_FOREACH(optr, olist, next) { 372 if (optr->object->kind != KIND_ABSTRACT) 373 continue; 374 375 /* Check the structural object class. */ 376 if (is_super(optr->object, structural_obj)) 377 continue; 378 379 /* Check all auxiliary object classes. */ 380 SLIST_FOREACH(optr2, olist, next) { 381 if (optr2->object->kind != KIND_AUXILIARY) 382 continue; 383 if (is_super(optr->object, optr2->object)) 384 break; 385 } 386 387 if (optr2 == NULL) { 388 /* No subclassed object class found. */ 389 log_debug("abstract class '%s' not subclassed", 390 OBJ_NAME(optr->object)); 391 rc = LDAP_OBJECT_CLASS_VIOLATION; 392 goto done; 393 } 394 } 395 396 /* Check all required attributes. 397 */ 398 SLIST_FOREACH(optr, olist, next) { 399 rc = validate_required_attributes(entry, optr->object); 400 if (rc != LDAP_SUCCESS) 401 goto done; 402 } 403 404 /* Check all attributes against schema. 405 */ 406 for (a = entry->be_sub; a != NULL; a = a->be_next) { 407 if (ber_scanf_elements(a, "{se{", &s, &vals) != 0) { 408 rc = LDAP_INVALID_SYNTAX; 409 goto done; 410 } 411 if ((at = lookup_attribute(conf->schema, s)) == NULL) { 412 log_debug("attribute %s not defined in schema", s); 413 rc = LDAP_NAMING_VIOLATION; 414 goto done; 415 } 416 if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS) 417 goto done; 418 if (!extensible && at->usage == USAGE_USER_APP && 419 (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) { 420 log_debug("%s not allowed by any object class", 421 ATTR_NAME(at)); 422 goto done; 423 } 424 } 425 426 rdn: 427 rc = validate_dn(dn, entry); 428 429 done: 430 olist_free(olist); 431 return rc; 432 } 433 434