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