1 /* $OpenBSD: validate.c,v 1.9 2010/09/03 09:53:24 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 /* FIXME: doesn't handle escaped characters. 99 */ 100 static int 101 validate_dn(const char *dn, struct ber_element *entry) 102 { 103 char *copy; 104 char *sup_dn, *na, *dv, *p; 105 struct namespace *ns; 106 struct attr_type *at; 107 struct ber_element *vals; 108 109 if ((copy = strdup(dn)) == NULL) 110 return LDAP_OTHER; 111 112 sup_dn = strchr(copy, ','); 113 if (sup_dn++ == NULL) 114 sup_dn = strrchr(copy, '\0'); 115 116 /* Validate naming attributes and distinguished values in the RDN. 117 */ 118 p = copy; 119 for (;p < sup_dn;) { 120 na = p; 121 p = na + strcspn(na, "="); 122 if (p == na || p >= sup_dn) { 123 free(copy); 124 return LDAP_INVALID_DN_SYNTAX; 125 } 126 *p = '\0'; 127 dv = p + 1; 128 p = dv + strcspn(dv, "+,"); 129 if (p == dv) { 130 free(copy); 131 return LDAP_INVALID_DN_SYNTAX; 132 } 133 *p++ = '\0'; 134 135 if ((at = lookup_attribute(conf->schema, na)) == NULL) { 136 log_debug("attribute %s not defined in schema", na); 137 goto fail; 138 } 139 if (at->usage != USAGE_USER_APP) { 140 log_debug("naming attribute %s is operational", na); 141 goto fail; 142 } 143 if (at->collective) { 144 log_debug("naming attribute %s is collective", na); 145 goto fail; 146 } 147 if (at->obsolete) { 148 log_debug("naming attribute %s is obsolete", na); 149 goto fail; 150 } 151 if (at->equality == NULL) { 152 log_debug("naming attribute %s doesn't define equality", 153 na); 154 goto fail; 155 } 156 if ((vals = ldap_find_attribute(entry, at)) == NULL) { 157 log_debug("missing distinguished value for %s", na); 158 goto fail; 159 } 160 if (ldap_find_value(vals->be_next, dv) == NULL) { 161 log_debug("missing distinguished value %s" 162 " in naming attribute %s", dv, na); 163 goto fail; 164 } 165 } 166 167 /* Check that the RDN immediate superior exists, or it is a 168 * top-level namespace. 169 */ 170 if (*sup_dn != '\0') { 171 TAILQ_FOREACH(ns, &conf->namespaces, next) { 172 if (strcmp(dn, ns->suffix) == 0) 173 goto done; 174 } 175 ns = namespace_for_base(sup_dn); 176 if (ns == NULL || !namespace_exists(ns, sup_dn)) { 177 free(copy); 178 return LDAP_NO_SUCH_OBJECT; 179 } 180 } 181 182 done: 183 free(copy); 184 return LDAP_SUCCESS; 185 fail: 186 free(copy); 187 return LDAP_NAMING_VIOLATION; 188 } 189 190 static int 191 has_attribute(struct attr_type *at, struct attr_list *alist) 192 { 193 struct attr_ptr *ap; 194 195 if (alist == NULL) 196 return 0; 197 198 SLIST_FOREACH(ap, alist, next) { 199 if (at == ap->attr_type) 200 return 1; 201 } 202 return 0; 203 } 204 205 /* Validate that the attribute type is allowed by any object class. 206 */ 207 static int 208 validate_allowed_attribute(struct attr_type *at, struct obj_list *olist) 209 { 210 struct object *obj; 211 struct obj_ptr *optr; 212 213 if (olist == NULL) 214 return LDAP_OBJECT_CLASS_VIOLATION; 215 216 SLIST_FOREACH(optr, olist, next) { 217 obj = optr->object; 218 219 if (has_attribute(at, obj->may) || 220 has_attribute(at, obj->must)) 221 return LDAP_SUCCESS; 222 223 if (validate_allowed_attribute(at, obj->sup) == LDAP_SUCCESS) 224 return LDAP_SUCCESS; 225 } 226 227 return LDAP_OBJECT_CLASS_VIOLATION; 228 } 229 230 static void 231 olist_push(struct obj_list *olist, struct object *obj) 232 { 233 struct obj_ptr *optr, *sup; 234 235 SLIST_FOREACH(optr, olist, next) 236 if (optr->object == obj) 237 return; 238 239 if ((optr = calloc(1, sizeof(*optr))) == NULL) 240 return; 241 optr->object = obj; 242 SLIST_INSERT_HEAD(olist, optr, next); 243 244 /* Expand the list of object classes along the superclass chain. 245 */ 246 if (obj->sup != NULL) 247 SLIST_FOREACH(sup, obj->sup, next) 248 olist_push(olist, sup->object); 249 } 250 251 static void 252 olist_free(struct obj_list *olist) 253 { 254 struct obj_ptr *optr; 255 256 if (olist == NULL) 257 return; 258 259 while ((optr = SLIST_FIRST(olist)) != NULL) { 260 SLIST_REMOVE_HEAD(olist, next); 261 free(optr); 262 } 263 264 free(olist); 265 } 266 267 /* Check if sup is a superior object class to obj. 268 */ 269 static int 270 is_super(struct object *sup, struct object *obj) 271 { 272 struct obj_ptr *optr; 273 274 if (sup == NULL || obj->sup == NULL) 275 return 0; 276 277 SLIST_FOREACH(optr, obj->sup, next) 278 if (optr->object == sup || is_super(sup, optr->object)) 279 return 1; 280 281 return 0; 282 } 283 284 int 285 validate_entry(const char *dn, struct ber_element *entry, int relax) 286 { 287 int rc, extensible = 0; 288 char *s; 289 struct ber_element *objclass, *a, *vals; 290 struct object *obj, *structural_obj = NULL; 291 struct attr_type *at; 292 struct obj_list *olist = NULL; 293 struct obj_ptr *optr, *optr2; 294 295 if (relax) 296 goto rdn; 297 298 /* There must be an objectClass attribute. 299 */ 300 objclass = ldap_get_attribute(entry, "objectClass"); 301 if (objclass == NULL) { 302 log_debug("missing objectClass attribute"); 303 return LDAP_OBJECT_CLASS_VIOLATION; 304 } 305 306 if ((olist = calloc(1, sizeof(*olist))) == NULL) 307 return LDAP_OTHER; 308 SLIST_INIT(olist); 309 310 /* Check objectClass(es) against schema. 311 */ 312 objclass = objclass->be_next; /* skip attribute description */ 313 for (a = objclass->be_sub; a != NULL; a = a->be_next) { 314 if (ber_get_string(a, &s) != 0) { 315 rc = LDAP_INVALID_SYNTAX; 316 goto done; 317 } 318 319 if ((obj = lookup_object(conf->schema, s)) == NULL) { 320 log_debug("objectClass %s not defined in schema", s); 321 rc = LDAP_NAMING_VIOLATION; 322 goto done; 323 } 324 325 if (obj->kind == KIND_STRUCTURAL) { 326 if (structural_obj != NULL) { 327 if (is_super(structural_obj, obj)) 328 structural_obj = obj; 329 else if (!is_super(obj, structural_obj)) { 330 log_debug("multiple structural" 331 " object classes"); 332 rc = LDAP_OBJECT_CLASS_VIOLATION; 333 goto done; 334 } 335 } else 336 structural_obj = obj; 337 } 338 339 olist_push(olist, obj); 340 341 /* RFC4512, section 4.3: 342 * "The 'extensibleObject' auxiliary object class allows 343 * entries that belong to it to hold any user attribute." 344 */ 345 if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0) 346 extensible = 1; 347 } 348 349 /* Must have exactly one structural object class. 350 */ 351 if (structural_obj == NULL) { 352 log_debug("no structural object class defined"); 353 rc = LDAP_OBJECT_CLASS_VIOLATION; 354 goto done; 355 } 356 357 /* "An entry cannot belong to an abstract object class 358 * unless it belongs to a structural or auxiliary class that 359 * inherits from that abstract class." 360 */ 361 SLIST_FOREACH(optr, olist, next) { 362 if (optr->object->kind != KIND_ABSTRACT) 363 continue; 364 365 /* Check the structural object class. */ 366 if (is_super(optr->object, structural_obj)) 367 continue; 368 369 /* Check all auxiliary object classes. */ 370 SLIST_FOREACH(optr2, olist, next) { 371 if (optr2->object->kind != KIND_AUXILIARY) 372 continue; 373 if (is_super(optr->object, optr2->object)) 374 break; 375 } 376 377 if (optr2 == NULL) { 378 /* No subclassed object class found. */ 379 log_debug("abstract class '%s' not subclassed", 380 OBJ_NAME(optr->object)); 381 rc = LDAP_OBJECT_CLASS_VIOLATION; 382 goto done; 383 } 384 } 385 386 /* Check all required attributes. 387 */ 388 SLIST_FOREACH(optr, olist, next) { 389 rc = validate_required_attributes(entry, optr->object); 390 if (rc != LDAP_SUCCESS) 391 goto done; 392 } 393 394 /* Check all attributes against schema. 395 */ 396 for (a = entry->be_sub; a != NULL; a = a->be_next) { 397 if (ber_scanf_elements(a, "{se{", &s, &vals) != 0) { 398 rc = LDAP_INVALID_SYNTAX; 399 goto done; 400 } 401 if ((at = lookup_attribute(conf->schema, s)) == NULL) { 402 log_debug("attribute %s not defined in schema", s); 403 rc = LDAP_NAMING_VIOLATION; 404 goto done; 405 } 406 if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS) 407 goto done; 408 if (!extensible && at->usage == USAGE_USER_APP && 409 (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) { 410 log_debug("%s not allowed by any object class", 411 ATTR_NAME(at)); 412 goto done; 413 } 414 } 415 416 rdn: 417 rc = validate_dn(dn, entry); 418 419 done: 420 olist_free(olist); 421 return rc; 422 } 423 424