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