1 /* $OpenBSD: validate.c,v 1.2 2010/06/29 02:45:46 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 #define OBJ_NAME(obj) ((obj)->names ? SLIST_FIRST((obj)->names)->name : \ 28 (obj)->oid) 29 #define ATTR_NAME(at) OBJ_NAME(at) 30 31 static int 32 validate_required_attributes(struct ber_element *entry, struct object *obj) 33 { 34 struct attr_ptr *ap; 35 struct attr_type *at; 36 37 log_debug("validating required attributes for object %s", 38 OBJ_NAME(obj)); 39 40 if (obj->must == NULL) 41 return LDAP_SUCCESS; 42 43 SLIST_FOREACH(ap, obj->must, next) { 44 at = ap->attr_type; 45 46 if (ldap_find_attribute(entry, at) == NULL) { 47 log_debug("missing required attribute %s", 48 ATTR_NAME(at)); 49 return LDAP_OBJECT_CLASS_VIOLATION; 50 } 51 } 52 53 return LDAP_SUCCESS; 54 } 55 56 static int 57 validate_attribute(struct attr_type *at, struct ber_element *vals) 58 { 59 int nvals = 0; 60 struct ber_element *elm; 61 62 if (vals == NULL) { 63 log_debug("missing values"); 64 return LDAP_OTHER; 65 } 66 67 if (vals->be_type != BER_TYPE_SET) { 68 log_debug("values should be a set"); 69 return LDAP_OTHER; 70 } 71 72 for (elm = vals->be_sub; elm != NULL; elm = elm->be_next) { 73 if (elm->be_type != BER_TYPE_OCTETSTRING) { 74 log_debug("attribute value not an octet-string"); 75 return LDAP_PROTOCOL_ERROR; 76 } 77 78 if (++nvals > 1 && at->single) { 79 log_debug("multiple values for single-valued" 80 " attribute %s", ATTR_NAME(at)); 81 return LDAP_CONSTRAINT_VIOLATION; 82 } 83 } 84 85 /* There must be at least one value in an attribute. */ 86 if (nvals == 0) { 87 log_debug("missing value in attribute %s", ATTR_NAME(at)); 88 return LDAP_CONSTRAINT_VIOLATION; 89 } 90 91 return LDAP_SUCCESS; 92 } 93 94 static const char * 95 attribute_equality(struct attr_type *at) 96 { 97 if (at == NULL) 98 return NULL; 99 if (at->equality != NULL) 100 return at->equality; 101 return attribute_equality(at->sup); 102 } 103 104 /* FIXME: doesn't handle escaped characters. 105 */ 106 static int 107 validate_dn(const char *dn, struct ber_element *entry) 108 { 109 char *copy; 110 char *sup_dn, *na, *dv, *p; 111 struct namespace *ns; 112 struct attr_type *at; 113 struct ber_element *vals; 114 115 if ((copy = strdup(dn)) == NULL) 116 return LDAP_OTHER; 117 118 sup_dn = strchr(copy, ','); 119 if (sup_dn++ == NULL) 120 sup_dn = strrchr(copy, '\0'); 121 122 /* Validate naming attributes and distinguished values in the RDN. 123 */ 124 p = copy; 125 for (;p < sup_dn;) { 126 na = p; 127 p = na + strcspn(na, "="); 128 if (p == na || p >= sup_dn) { 129 free(copy); 130 return LDAP_INVALID_DN_SYNTAX; 131 } 132 *p = '\0'; 133 dv = p + 1; 134 p = dv + strcspn(dv, "+,"); 135 if (p == dv) { 136 free(copy); 137 return LDAP_INVALID_DN_SYNTAX; 138 } 139 *p++ = '\0'; 140 141 log_debug("got naming attribute %s", na); 142 log_debug("got distinguished value %s", dv); 143 if ((at = lookup_attribute(conf->schema, na)) == NULL) { 144 log_debug("attribute %s not defined in schema", na); 145 goto fail; 146 } 147 if (at->usage != USAGE_USER_APP) { 148 log_debug("naming attribute %s is operational", na); 149 goto fail; 150 } 151 if (at->collective) { 152 log_debug("naming attribute %s is collective", na); 153 goto fail; 154 } 155 if (at->obsolete) { 156 log_debug("naming attribute %s is obsolete", na); 157 goto fail; 158 } 159 if (attribute_equality(at) == NULL) { 160 log_debug("naming attribute %s doesn't define equality", 161 na); 162 goto fail; 163 } 164 if ((vals = ldap_find_attribute(entry, at)) == NULL) { 165 log_debug("missing distinguished value for %s", na); 166 goto fail; 167 } 168 if (ldap_find_value(vals->be_next, dv) == NULL) { 169 log_debug("missing distinguished value %s" 170 " in naming attribute %s", dv, na); 171 goto fail; 172 } 173 } 174 175 /* Check that the RDN immediate superior exists, or it is a 176 * top-level namespace. 177 */ 178 if (*sup_dn != '\0') { 179 TAILQ_FOREACH(ns, &conf->namespaces, next) { 180 if (strcmp(dn, ns->suffix) == 0) 181 goto done; 182 } 183 log_debug("checking for presence of superior dn %s", sup_dn); 184 ns = namespace_for_base(sup_dn); 185 if (ns == NULL || !namespace_exists(ns, sup_dn)) { 186 free(copy); 187 return LDAP_NO_SUCH_OBJECT; 188 } 189 } 190 191 done: 192 free(copy); 193 return LDAP_SUCCESS; 194 fail: 195 free(copy); 196 return LDAP_NAMING_VIOLATION; 197 } 198 199 static int 200 validate_object_class(struct ber_element *entry, struct object *obj) 201 { 202 struct obj_ptr *sup; 203 int rc; 204 205 rc = validate_required_attributes(entry, obj); 206 if (rc == LDAP_SUCCESS && obj->sup != NULL) { 207 SLIST_FOREACH(sup, obj->sup, next) { 208 rc = validate_object_class(entry, sup->object); 209 if (rc != LDAP_SUCCESS) 210 break; 211 } 212 } 213 214 return rc; 215 } 216 217 int 218 validate_entry(const char *dn, struct ber_element *entry, int relax) 219 { 220 int rc; 221 char *s; 222 struct ber_element *objclass, *a, *vals; 223 struct object *obj, *structural_obj = NULL; 224 struct attr_type *at; 225 226 if (relax) 227 goto rdn; 228 229 /* There must be an objectClass attribute. 230 */ 231 objclass = ldap_get_attribute(entry, "objectClass"); 232 if (objclass == NULL) { 233 log_debug("missing objectClass attribute"); 234 return LDAP_OBJECT_CLASS_VIOLATION; 235 } 236 237 /* Check objectClass(es) against schema. 238 */ 239 objclass = objclass->be_next; /* skip attribute description */ 240 for (a = objclass->be_sub; a != NULL; a = a->be_next) { 241 if (ber_get_string(a, &s) != 0) 242 return LDAP_INVALID_SYNTAX; 243 if ((obj = lookup_object(conf->schema, s)) == NULL) { 244 log_debug("objectClass %s not defined in schema", s); 245 return LDAP_NAMING_VIOLATION; 246 } 247 log_debug("object class %s has kind %d", s, obj->kind); 248 if (obj->kind == KIND_STRUCTURAL) 249 structural_obj = obj; 250 251 rc = validate_object_class(entry, obj); 252 if (rc != LDAP_SUCCESS) 253 return rc; 254 } 255 256 /* Must have at least one structural object class. 257 */ 258 if (structural_obj == NULL) { 259 log_debug("no structural object class defined"); 260 return LDAP_OBJECT_CLASS_VIOLATION; 261 } 262 263 /* Check all attributes against schema. 264 */ 265 for (a = entry->be_sub; a != NULL; a = a->be_next) { 266 if (ber_scanf_elements(a, "{se{", &s, &vals) != 0) 267 return LDAP_INVALID_SYNTAX; 268 if ((at = lookup_attribute(conf->schema, s)) == NULL) { 269 log_debug("attribute %s not defined in schema", s); 270 return LDAP_NAMING_VIOLATION; 271 } 272 if ((rc = validate_attribute(at, vals)) != LDAP_SUCCESS) 273 return rc; 274 } 275 276 rdn: 277 if ((rc = validate_dn(dn, entry)) != LDAP_SUCCESS) 278 return rc; 279 280 return LDAP_SUCCESS; 281 } 282 283