1 /* $OpenBSD: validate.c,v 1.13 2021/12/20 13:18:29 claudio 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 #include "log.h" 27 28 static int 29 validate_required_attributes(struct ber_element *entry, struct object *obj) 30 { 31 struct attr_ptr *ap; 32 struct attr_type *at; 33 34 if (obj->must == NULL) 35 return LDAP_SUCCESS; 36 37 SLIST_FOREACH(ap, obj->must, next) { 38 at = ap->attr_type; 39 40 if (ldap_find_attribute(entry, at) == NULL) { 41 log_debug("missing required attribute %s", 42 ATTR_NAME(at)); 43 return LDAP_OBJECT_CLASS_VIOLATION; 44 } 45 } 46 47 return LDAP_SUCCESS; 48 } 49 50 static int 51 validate_attribute(struct attr_type *at, struct ber_element *vals) 52 { 53 int nvals = 0; 54 struct ber_element *elm; 55 char *val; 56 57 if (vals == NULL) { 58 log_debug("missing values"); 59 return LDAP_OTHER; 60 } 61 62 if (vals->be_type != BER_TYPE_SET) { 63 log_debug("values should be a set"); 64 return LDAP_OTHER; 65 } 66 67 for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) { 68 if (ober_get_string(elm, &val) == -1) { 69 log_debug("attribute value not an octet-string"); 70 return LDAP_PROTOCOL_ERROR; 71 } 72 73 if (++nvals > 1 && at->single) { 74 log_debug("multiple values for single-valued" 75 " attribute %s", ATTR_NAME(at)); 76 return LDAP_CONSTRAINT_VIOLATION; 77 } 78 79 if (at->syntax->is_valid != NULL && 80 !at->syntax->is_valid(conf->schema, val, elm->be_len)) { 81 log_debug("%s: invalid syntax", ATTR_NAME(at)); 82 log_debug("syntax = %s", at->syntax->desc); 83 log_debug("value: [%.*s]", (int)elm->be_len, val); 84 return LDAP_INVALID_SYNTAX; 85 } 86 } 87 88 /* There must be at least one value in an attribute. */ 89 if (nvals == 0) { 90 log_debug("missing value in attribute %s", ATTR_NAME(at)); 91 return LDAP_CONSTRAINT_VIOLATION; 92 } 93 94 /* FIXME: validate that values are unique */ 95 96 return LDAP_SUCCESS; 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 (at->equality == 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 static void 253 olist_free(struct obj_list *olist) 254 { 255 struct obj_ptr *optr; 256 257 if (olist == NULL) 258 return; 259 260 while ((optr = SLIST_FIRST(olist)) != NULL) { 261 SLIST_REMOVE_HEAD(olist, next); 262 free(optr); 263 } 264 265 free(olist); 266 } 267 268 /* Check if sup is a superior object class to obj. 269 */ 270 static int 271 is_super(struct object *sup, struct object *obj) 272 { 273 struct obj_ptr *optr; 274 275 if (sup == NULL || obj->sup == NULL) 276 return 0; 277 278 SLIST_FOREACH(optr, obj->sup, next) 279 if (optr->object == sup || is_super(sup, optr->object)) 280 return 1; 281 282 return 0; 283 } 284 285 int 286 validate_entry(const char *dn, struct ber_element *entry, int relax) 287 { 288 int rc, extensible = 0; 289 char *s; 290 struct ber_element *objclass, *a, *vals; 291 struct object *obj, *structural_obj = NULL; 292 struct attr_type *at; 293 struct obj_list *olist = NULL; 294 struct obj_ptr *optr, *optr2; 295 296 if (relax) 297 goto rdn; 298 299 /* There must be an objectClass attribute. 300 */ 301 objclass = ldap_get_attribute(entry, "objectClass"); 302 if (objclass == NULL) { 303 log_debug("missing objectClass attribute"); 304 return LDAP_OBJECT_CLASS_VIOLATION; 305 } 306 307 if ((olist = calloc(1, sizeof(*olist))) == NULL) 308 return LDAP_OTHER; 309 SLIST_INIT(olist); 310 311 /* Check objectClass(es) against schema. 312 */ 313 objclass = objclass->be_next; /* skip attribute description */ 314 for (a = objclass->be_sub; a != NULL; a = a->be_next) { 315 if (ober_get_string(a, &s) != 0) { 316 log_debug("invalid ObjectClass encoding"); 317 rc = LDAP_INVALID_SYNTAX; 318 goto done; 319 } 320 321 if ((obj = lookup_object(conf->schema, s)) == NULL) { 322 log_debug("objectClass %s not defined in schema", s); 323 rc = LDAP_NAMING_VIOLATION; 324 goto done; 325 } 326 327 if (obj->kind == KIND_STRUCTURAL) { 328 if (structural_obj != NULL) { 329 if (is_super(structural_obj, obj)) 330 structural_obj = obj; 331 else if (!is_super(obj, structural_obj)) { 332 log_debug("multiple structural" 333 " object classes"); 334 rc = LDAP_OBJECT_CLASS_VIOLATION; 335 goto done; 336 } 337 } else 338 structural_obj = obj; 339 } 340 341 olist_push(olist, obj); 342 343 /* RFC4512, section 4.3: 344 * "The 'extensibleObject' auxiliary object class allows 345 * entries that belong to it to hold any user attribute." 346 */ 347 if (strcmp(obj->oid, "1.3.6.1.4.1.1466.101.120.111") == 0) 348 extensible = 1; 349 } 350 351 /* Must have exactly one structural object class. 352 */ 353 if (structural_obj == NULL) { 354 log_debug("no structural object class defined"); 355 rc = LDAP_OBJECT_CLASS_VIOLATION; 356 goto done; 357 } 358 359 /* "An entry cannot belong to an abstract object class 360 * unless it belongs to a structural or auxiliary class that 361 * inherits from that abstract class." 362 */ 363 SLIST_FOREACH(optr, olist, next) { 364 if (optr->object->kind != KIND_ABSTRACT) 365 continue; 366 367 /* Check the structural object class. */ 368 if (is_super(optr->object, structural_obj)) 369 continue; 370 371 /* Check all auxiliary object classes. */ 372 SLIST_FOREACH(optr2, olist, next) { 373 if (optr2->object->kind != KIND_AUXILIARY) 374 continue; 375 if (is_super(optr->object, optr2->object)) 376 break; 377 } 378 379 if (optr2 == NULL) { 380 /* No subclassed object class found. */ 381 log_debug("abstract class '%s' not subclassed", 382 OBJ_NAME(optr->object)); 383 rc = LDAP_OBJECT_CLASS_VIOLATION; 384 goto done; 385 } 386 } 387 388 /* Check all required attributes. 389 */ 390 SLIST_FOREACH(optr, olist, next) { 391 rc = validate_required_attributes(entry, optr->object); 392 if (rc != LDAP_SUCCESS) 393 goto done; 394 } 395 396 /* Check all attributes against schema. 397 */ 398 for (a = entry->be_sub; a != NULL; a = a->be_next) { 399 if (ober_scanf_elements(a, "{se{", &s, &vals) != 0) { 400 log_debug("invalid attribute encoding"); 401 rc = LDAP_INVALID_SYNTAX; 402 goto done; 403 } 404 if ((at = lookup_attribute(conf->schema, s)) == NULL) { 405 log_debug("attribute %s not defined in schema", s); 406 rc = LDAP_NAMING_VIOLATION; 407 goto done; 408 } 409 if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS) 410 goto done; 411 if (!extensible && at->usage == USAGE_USER_APP && 412 (rc = validate_allowed_attribute(at, olist)) != LDAP_SUCCESS) { 413 log_debug("%s not allowed by any object class", 414 ATTR_NAME(at)); 415 goto done; 416 } 417 } 418 419 rdn: 420 rc = validate_dn(dn, entry); 421 422 done: 423 olist_free(olist); 424 return rc; 425 } 426 427