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