1 /* $NetBSD: password_quality.c,v 1.1.1.1 2011/04/13 18:15:30 elric Exp $ */ 2 3 /* 4 * Copyright (c) 1997-2000, 2003-2005 Kungliga Tekniska Högskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include "kadm5_locl.h" 37 #include "kadm5-pwcheck.h" 38 39 __RCSID("$NetBSD: password_quality.c,v 1.1.1.1 2011/04/13 18:15:30 elric Exp $"); 40 41 #ifdef HAVE_SYS_WAIT_H 42 #include <sys/wait.h> 43 #endif 44 #ifdef HAVE_DLFCN_H 45 #include <dlfcn.h> 46 #endif 47 48 static int 49 min_length_passwd_quality (krb5_context context, 50 krb5_principal principal, 51 krb5_data *pwd, 52 const char *opaque, 53 char *message, 54 size_t length) 55 { 56 uint32_t min_length = krb5_config_get_int_default(context, NULL, 6, 57 "password_quality", 58 "min_length", 59 NULL); 60 61 if (pwd->length < min_length) { 62 strlcpy(message, "Password too short", length); 63 return 1; 64 } else 65 return 0; 66 } 67 68 static const char * 69 min_length_passwd_quality_v0 (krb5_context context, 70 krb5_principal principal, 71 krb5_data *pwd) 72 { 73 static char message[1024]; 74 int ret; 75 76 message[0] = '\0'; 77 78 ret = min_length_passwd_quality(context, principal, pwd, NULL, 79 message, sizeof(message)); 80 if (ret) 81 return message; 82 return NULL; 83 } 84 85 86 static int 87 char_class_passwd_quality (krb5_context context, 88 krb5_principal principal, 89 krb5_data *pwd, 90 const char *opaque, 91 char *message, 92 size_t length) 93 { 94 const char *classes[] = { 95 "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 96 "abcdefghijklmnopqrstuvwxyz", 97 "1234567890", 98 "!@#$%^&*()/?<>,.{[]}\\|'~`\" " 99 }; 100 int i, counter = 0, req_classes; 101 size_t len; 102 char *pw; 103 104 req_classes = krb5_config_get_int_default(context, NULL, 3, 105 "password_quality", 106 "min_classes", 107 NULL); 108 109 len = pwd->length + 1; 110 pw = malloc(len); 111 if (pw == NULL) { 112 strlcpy(message, "out of memory", length); 113 return 1; 114 } 115 strlcpy(pw, pwd->data, len); 116 len = strlen(pw); 117 118 for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) { 119 if (strcspn(pw, classes[i]) < len) 120 counter++; 121 } 122 memset(pw, 0, pwd->length + 1); 123 free(pw); 124 if (counter < req_classes) { 125 snprintf(message, length, 126 "Password doesn't meet complexity requirement.\n" 127 "Add more characters from the following classes:\n" 128 "1. English uppercase characters (A through Z)\n" 129 "2. English lowercase characters (a through z)\n" 130 "3. Base 10 digits (0 through 9)\n" 131 "4. Nonalphanumeric characters (e.g., !, $, #, %%)"); 132 return 1; 133 } 134 return 0; 135 } 136 137 static int 138 external_passwd_quality (krb5_context context, 139 krb5_principal principal, 140 krb5_data *pwd, 141 const char *opaque, 142 char *message, 143 size_t length) 144 { 145 krb5_error_code ret; 146 const char *program; 147 char *p; 148 pid_t child; 149 int status; 150 char reply[1024]; 151 FILE *in = NULL, *out = NULL, *error = NULL; 152 153 if (memchr(pwd->data, '\n', pwd->length) != NULL) { 154 snprintf(message, length, "password contains newline, " 155 "not valid for external test"); 156 return 1; 157 } 158 159 program = krb5_config_get_string(context, NULL, 160 "password_quality", 161 "external_program", 162 NULL); 163 if (program == NULL) { 164 snprintf(message, length, "external password quality " 165 "program not configured"); 166 return 1; 167 } 168 169 ret = krb5_unparse_name(context, principal, &p); 170 if (ret) { 171 strlcpy(message, "out of memory", length); 172 return 1; 173 } 174 175 child = pipe_execv(&in, &out, &error, program, program, p, NULL); 176 if (child < 0) { 177 snprintf(message, length, "external password quality " 178 "program failed to execute for principal %s", p); 179 free(p); 180 return 1; 181 } 182 183 fprintf(in, "principal: %s\n" 184 "new-password: %.*s\n" 185 "end\n", 186 p, (int)pwd->length, (char *)pwd->data); 187 188 fclose(in); 189 190 if (fgets(reply, sizeof(reply), out) == NULL) { 191 192 if (fgets(reply, sizeof(reply), error) == NULL) { 193 snprintf(message, length, "external password quality " 194 "program failed without error"); 195 196 } else { 197 reply[strcspn(reply, "\n")] = '\0'; 198 snprintf(message, length, "External password quality " 199 "program failed: %s", reply); 200 } 201 202 fclose(out); 203 fclose(error); 204 wait_for_process(child); 205 return 1; 206 } 207 reply[strcspn(reply, "\n")] = '\0'; 208 209 fclose(out); 210 fclose(error); 211 212 status = wait_for_process(child); 213 214 if (SE_IS_ERROR(status) || SE_PROCSTATUS(status) != 0) { 215 snprintf(message, length, "external program failed: %s", reply); 216 free(p); 217 return 1; 218 } 219 220 if (strcmp(reply, "APPROVED") != 0) { 221 snprintf(message, length, "%s", reply); 222 free(p); 223 return 1; 224 } 225 226 free(p); 227 228 return 0; 229 } 230 231 232 static kadm5_passwd_quality_check_func_v0 passwd_quality_check = 233 min_length_passwd_quality_v0; 234 235 struct kadm5_pw_policy_check_func builtin_funcs[] = { 236 { "minimum-length", min_length_passwd_quality }, 237 { "character-class", char_class_passwd_quality }, 238 { "external-check", external_passwd_quality }, 239 { NULL } 240 }; 241 struct kadm5_pw_policy_verifier builtin_verifier = { 242 "builtin", 243 KADM5_PASSWD_VERSION_V1, 244 "Heimdal builtin", 245 builtin_funcs 246 }; 247 248 static struct kadm5_pw_policy_verifier **verifiers; 249 static int num_verifiers; 250 251 /* 252 * setup the password quality hook 253 */ 254 255 #ifndef RTLD_NOW 256 #define RTLD_NOW 0 257 #endif 258 259 void 260 kadm5_setup_passwd_quality_check(krb5_context context, 261 const char *check_library, 262 const char *check_function) 263 { 264 #ifdef HAVE_DLOPEN 265 void *handle; 266 void *sym; 267 int *version; 268 const char *tmp; 269 270 if(check_library == NULL) { 271 tmp = krb5_config_get_string(context, NULL, 272 "password_quality", 273 "check_library", 274 NULL); 275 if(tmp != NULL) 276 check_library = tmp; 277 } 278 if(check_function == NULL) { 279 tmp = krb5_config_get_string(context, NULL, 280 "password_quality", 281 "check_function", 282 NULL); 283 if(tmp != NULL) 284 check_function = tmp; 285 } 286 if(check_library != NULL && check_function == NULL) 287 check_function = "passwd_check"; 288 289 if(check_library == NULL) 290 return; 291 handle = dlopen(check_library, RTLD_NOW); 292 if(handle == NULL) { 293 krb5_warnx(context, "failed to open `%s'", check_library); 294 return; 295 } 296 version = (int *) dlsym(handle, "version"); 297 if(version == NULL) { 298 krb5_warnx(context, 299 "didn't find `version' symbol in `%s'", check_library); 300 dlclose(handle); 301 return; 302 } 303 if(*version != KADM5_PASSWD_VERSION_V0) { 304 krb5_warnx(context, 305 "version of loaded library is %d (expected %d)", 306 *version, KADM5_PASSWD_VERSION_V0); 307 dlclose(handle); 308 return; 309 } 310 sym = dlsym(handle, check_function); 311 if(sym == NULL) { 312 krb5_warnx(context, 313 "didn't find `%s' symbol in `%s'", 314 check_function, check_library); 315 dlclose(handle); 316 return; 317 } 318 passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym; 319 #endif /* HAVE_DLOPEN */ 320 } 321 322 #ifdef HAVE_DLOPEN 323 324 static krb5_error_code 325 add_verifier(krb5_context context, const char *check_library) 326 { 327 struct kadm5_pw_policy_verifier *v, **tmp; 328 void *handle; 329 int i; 330 331 handle = dlopen(check_library, RTLD_NOW); 332 if(handle == NULL) { 333 krb5_warnx(context, "failed to open `%s'", check_library); 334 return ENOENT; 335 } 336 v = (struct kadm5_pw_policy_verifier *) dlsym(handle, "kadm5_password_verifier"); 337 if(v == NULL) { 338 krb5_warnx(context, 339 "didn't find `kadm5_password_verifier' symbol " 340 "in `%s'", check_library); 341 dlclose(handle); 342 return ENOENT; 343 } 344 if(v->version != KADM5_PASSWD_VERSION_V1) { 345 krb5_warnx(context, 346 "version of loaded library is %d (expected %d)", 347 v->version, KADM5_PASSWD_VERSION_V1); 348 dlclose(handle); 349 return EINVAL; 350 } 351 for (i = 0; i < num_verifiers; i++) { 352 if (strcmp(v->name, verifiers[i]->name) == 0) 353 break; 354 } 355 if (i < num_verifiers) { 356 krb5_warnx(context, "password verifier library `%s' is already loaded", 357 v->name); 358 dlclose(handle); 359 return 0; 360 } 361 362 tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers)); 363 if (tmp == NULL) { 364 krb5_warnx(context, "out of memory"); 365 dlclose(handle); 366 return 0; 367 } 368 verifiers = tmp; 369 verifiers[num_verifiers] = v; 370 num_verifiers++; 371 372 return 0; 373 } 374 375 #endif 376 377 krb5_error_code 378 kadm5_add_passwd_quality_verifier(krb5_context context, 379 const char *check_library) 380 { 381 #ifdef HAVE_DLOPEN 382 383 if(check_library == NULL) { 384 krb5_error_code ret; 385 char **tmp; 386 387 tmp = krb5_config_get_strings(context, NULL, 388 "password_quality", 389 "policy_libraries", 390 NULL); 391 if(tmp == NULL) 392 return 0; 393 394 while(tmp) { 395 ret = add_verifier(context, *tmp); 396 if (ret) 397 return ret; 398 tmp++; 399 } 400 return 0; 401 } else { 402 return add_verifier(context, check_library); 403 } 404 #else 405 return 0; 406 #endif /* HAVE_DLOPEN */ 407 } 408 409 /* 410 * 411 */ 412 413 static const struct kadm5_pw_policy_check_func * 414 find_func(krb5_context context, const char *name) 415 { 416 const struct kadm5_pw_policy_check_func *f; 417 char *module = NULL; 418 const char *p, *func; 419 int i; 420 421 p = strchr(name, ':'); 422 if (p) { 423 size_t len = p - name + 1; 424 func = p + 1; 425 module = malloc(len); 426 if (module == NULL) 427 return NULL; 428 strlcpy(module, name, len); 429 } else 430 func = name; 431 432 /* Find module in loaded modules first */ 433 for (i = 0; i < num_verifiers; i++) { 434 if (module && strcmp(module, verifiers[i]->name) != 0) 435 continue; 436 for (f = verifiers[i]->funcs; f->name ; f++) 437 if (strcmp(name, f->name) == 0) { 438 if (module) 439 free(module); 440 return f; 441 } 442 } 443 /* Lets try try the builtin modules */ 444 if (module == NULL || strcmp(module, "builtin") == 0) { 445 for (f = builtin_verifier.funcs; f->name ; f++) 446 if (strcmp(func, f->name) == 0) { 447 if (module) 448 free(module); 449 return f; 450 } 451 } 452 if (module) 453 free(module); 454 return NULL; 455 } 456 457 const char * 458 kadm5_check_password_quality (krb5_context context, 459 krb5_principal principal, 460 krb5_data *pwd_data) 461 { 462 const struct kadm5_pw_policy_check_func *proc; 463 static char error_msg[1024]; 464 const char *msg; 465 char **v, **vp; 466 int ret; 467 468 /* 469 * Check if we should use the old version of policy function. 470 */ 471 472 v = krb5_config_get_strings(context, NULL, 473 "password_quality", 474 "policies", 475 NULL); 476 if (v == NULL) { 477 msg = (*passwd_quality_check) (context, principal, pwd_data); 478 krb5_set_error_message(context, 0, "password policy failed: %s", msg); 479 return msg; 480 } 481 482 error_msg[0] = '\0'; 483 484 msg = NULL; 485 for(vp = v; *vp; vp++) { 486 proc = find_func(context, *vp); 487 if (proc == NULL) { 488 msg = "failed to find password verifier function"; 489 krb5_set_error_message(context, 0, "Failed to find password policy " 490 "function: %s", *vp); 491 break; 492 } 493 ret = (proc->func)(context, principal, pwd_data, NULL, 494 error_msg, sizeof(error_msg)); 495 if (ret) { 496 krb5_set_error_message(context, 0, "Password policy " 497 "%s failed with %s", 498 proc->name, error_msg); 499 msg = error_msg; 500 break; 501 } 502 } 503 krb5_config_free_strings(v); 504 505 /* If the default quality check isn't used, lets check that the 506 * old quality function the user have set too */ 507 if (msg == NULL && passwd_quality_check != min_length_passwd_quality_v0) { 508 msg = (*passwd_quality_check) (context, principal, pwd_data); 509 if (msg) 510 krb5_set_error_message(context, 0, "(old) password policy " 511 "failed with %s", msg); 512 513 } 514 return msg; 515 } 516