155682Smarkm/* 2178825Sdfr * Copyright (c) 1997-2000, 2003-2005 Kungliga Tekniska H�gskolan 355682Smarkm * (Royal Institute of Technology, Stockholm, Sweden). 455682Smarkm * All rights reserved. 555682Smarkm * 655682Smarkm * Redistribution and use in source and binary forms, with or without 755682Smarkm * modification, are permitted provided that the following conditions 855682Smarkm * are met: 955682Smarkm * 1055682Smarkm * 1. Redistributions of source code must retain the above copyright 1155682Smarkm * notice, this list of conditions and the following disclaimer. 1255682Smarkm * 1355682Smarkm * 2. Redistributions in binary form must reproduce the above copyright 1455682Smarkm * notice, this list of conditions and the following disclaimer in the 1555682Smarkm * documentation and/or other materials provided with the distribution. 1655682Smarkm * 1755682Smarkm * 3. Neither the name of the Institute nor the names of its contributors 1855682Smarkm * may be used to endorse or promote products derived from this software 1955682Smarkm * without specific prior written permission. 2055682Smarkm * 2155682Smarkm * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 2255682Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2455682Smarkm * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 2555682Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2655682Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2755682Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2855682Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2955682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3055682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3155682Smarkm * SUCH DAMAGE. 3255682Smarkm */ 3355682Smarkm 3455682Smarkm#include "kadm5_locl.h" 35178825Sdfr#include "kadm5-pwcheck.h" 3655682Smarkm 37178825SdfrRCSID("$Id: password_quality.c 17595 2006-05-30 21:51:55Z lha $"); 3855682Smarkm 39178825Sdfr#ifdef HAVE_SYS_WAIT_H 40178825Sdfr#include <sys/wait.h> 41178825Sdfr#endif 4255682Smarkm#ifdef HAVE_DLFCN_H 4355682Smarkm#include <dlfcn.h> 4455682Smarkm#endif 4555682Smarkm 46178825Sdfrstatic int 47178825Sdfrmin_length_passwd_quality (krb5_context context, 48178825Sdfr krb5_principal principal, 49178825Sdfr krb5_data *pwd, 50178825Sdfr const char *opaque, 51178825Sdfr char *message, 52178825Sdfr size_t length) 53178825Sdfr{ 54178825Sdfr uint32_t min_length = krb5_config_get_int_default(context, NULL, 6, 55178825Sdfr "password_quality", 56178825Sdfr "min_length", 57178825Sdfr NULL); 58178825Sdfr 59178825Sdfr if (pwd->length < min_length) { 60178825Sdfr strlcpy(message, "Password too short", length); 61178825Sdfr return 1; 62178825Sdfr } else 63178825Sdfr return 0; 64178825Sdfr} 65178825Sdfr 6655682Smarkmstatic const char * 67178825Sdfrmin_length_passwd_quality_v0 (krb5_context context, 68178825Sdfr krb5_principal principal, 69178825Sdfr krb5_data *pwd) 7055682Smarkm{ 71178825Sdfr static char message[1024]; 72178825Sdfr int ret; 73178825Sdfr 74178825Sdfr message[0] = '\0'; 75178825Sdfr 76178825Sdfr ret = min_length_passwd_quality(context, principal, pwd, NULL, 77178825Sdfr message, sizeof(message)); 78178825Sdfr if (ret) 79178825Sdfr return message; 80178825Sdfr return NULL; 8155682Smarkm} 8255682Smarkm 8355682Smarkm 84178825Sdfrstatic int 85178825Sdfrchar_class_passwd_quality (krb5_context context, 86178825Sdfr krb5_principal principal, 87178825Sdfr krb5_data *pwd, 88178825Sdfr const char *opaque, 89178825Sdfr char *message, 90178825Sdfr size_t length) 91178825Sdfr{ 92178825Sdfr const char *classes[] = { 93178825Sdfr "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 94178825Sdfr "abcdefghijklmnopqrstuvwxyz", 95178825Sdfr "1234567890", 96178825Sdfr "!@#$%^&*()/?<>,.{[]}\\|'~`\" " 97178825Sdfr }; 98178825Sdfr int i, counter = 0, req_classes; 99178825Sdfr size_t len; 100178825Sdfr char *pw; 10155682Smarkm 102178825Sdfr req_classes = krb5_config_get_int_default(context, NULL, 3, 103178825Sdfr "password_quality", 104178825Sdfr "min_classes", 105178825Sdfr NULL); 10655682Smarkm 107178825Sdfr len = pwd->length + 1; 108178825Sdfr pw = malloc(len); 109178825Sdfr if (pw == NULL) { 110178825Sdfr strlcpy(message, "out of memory", length); 111178825Sdfr return 1; 112178825Sdfr } 113178825Sdfr strlcpy(pw, pwd->data, len); 114178825Sdfr len = strlen(pw); 11555682Smarkm 116178825Sdfr for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) { 117178825Sdfr if (strcspn(pw, classes[i]) < len) 118178825Sdfr counter++; 119178825Sdfr } 120178825Sdfr memset(pw, 0, pwd->length + 1); 121178825Sdfr free(pw); 122178825Sdfr if (counter < req_classes) { 123178825Sdfr snprintf(message, length, 124178825Sdfr "Password doesn't meet complexity requirement.\n" 125178825Sdfr "Add more characters from the following classes:\n" 126178825Sdfr "1. English uppercase characters (A through Z)\n" 127178825Sdfr "2. English lowercase characters (a through z)\n" 128178825Sdfr "3. Base 10 digits (0 through 9)\n" 129178825Sdfr "4. Nonalphanumeric characters (e.g., !, $, #, %%)"); 130178825Sdfr return 1; 131178825Sdfr } 132178825Sdfr return 0; 133178825Sdfr} 13455682Smarkm 135178825Sdfrstatic int 136178825Sdfrexternal_passwd_quality (krb5_context context, 137178825Sdfr krb5_principal principal, 138178825Sdfr krb5_data *pwd, 139178825Sdfr const char *opaque, 140178825Sdfr char *message, 141178825Sdfr size_t length) 142178825Sdfr{ 143178825Sdfr krb5_error_code ret; 144178825Sdfr const char *program; 145178825Sdfr char *p; 146178825Sdfr pid_t child; 147178825Sdfr int status; 148178825Sdfr char reply[1024]; 149178825Sdfr FILE *in = NULL, *out = NULL, *error = NULL; 150178825Sdfr 151178825Sdfr if (memchr(pwd->data, pwd->length, '\n') != NULL) { 152178825Sdfr snprintf(message, length, "password contains newline, " 153178825Sdfr "not valid for external test"); 154178825Sdfr return 1; 155178825Sdfr } 156178825Sdfr 157178825Sdfr program = krb5_config_get_string(context, NULL, 158178825Sdfr "password_quality", 159178825Sdfr "external_program", 160178825Sdfr NULL); 161178825Sdfr if (program == NULL) { 162178825Sdfr snprintf(message, length, "external password quality " 163178825Sdfr "program not configured"); 164178825Sdfr return 1; 165178825Sdfr } 166178825Sdfr 167178825Sdfr ret = krb5_unparse_name(context, principal, &p); 168178825Sdfr if (ret) { 169178825Sdfr strlcpy(message, "out of memory", length); 170178825Sdfr return 1; 171178825Sdfr } 172178825Sdfr 173178825Sdfr child = pipe_execv(&in, &out, &error, program, p, NULL); 174178825Sdfr if (child < 0) { 175178825Sdfr snprintf(message, length, "external password quality " 176178825Sdfr "program failed to execute for principal %s", p); 177178825Sdfr free(p); 178178825Sdfr return 1; 179178825Sdfr } 180178825Sdfr 181178825Sdfr fprintf(in, "principal: %s\n" 182178825Sdfr "new-password: %.*s\n" 183178825Sdfr "end\n", 184178825Sdfr p, (int)pwd->length, (char *)pwd->data); 185178825Sdfr 186178825Sdfr fclose(in); 187178825Sdfr 188178825Sdfr if (fgets(reply, sizeof(reply), out) == NULL) { 189178825Sdfr 190178825Sdfr if (fgets(reply, sizeof(reply), error) == NULL) { 191178825Sdfr snprintf(message, length, "external password quality " 192178825Sdfr "program failed without error"); 193178825Sdfr 194178825Sdfr } else { 195178825Sdfr reply[strcspn(reply, "\n")] = '\0'; 196178825Sdfr snprintf(message, length, "External password quality " 197178825Sdfr "program failed: %s", reply); 198178825Sdfr } 199178825Sdfr 200178825Sdfr fclose(out); 201178825Sdfr fclose(error); 202178825Sdfr waitpid(child, &status, 0); 203178825Sdfr return 1; 204178825Sdfr } 205178825Sdfr reply[strcspn(reply, "\n")] = '\0'; 206178825Sdfr 207178825Sdfr fclose(out); 208178825Sdfr fclose(error); 209178825Sdfr 210178825Sdfr if (waitpid(child, &status, 0) < 0) { 211178825Sdfr snprintf(message, length, "external program failed: %s", reply); 212178825Sdfr free(p); 213178825Sdfr return 1; 214178825Sdfr } 215178825Sdfr if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { 216178825Sdfr snprintf(message, length, "external program failed: %s", reply); 217178825Sdfr free(p); 218178825Sdfr return 1; 219178825Sdfr } 220178825Sdfr 221178825Sdfr if (strcmp(reply, "APPROVED") != 0) { 222178825Sdfr snprintf(message, length, "%s", reply); 223178825Sdfr free(p); 224178825Sdfr return 1; 225178825Sdfr } 226178825Sdfr 227178825Sdfr free(p); 228178825Sdfr 229178825Sdfr return 0; 230178825Sdfr} 231178825Sdfr 232178825Sdfr 233178825Sdfrstatic kadm5_passwd_quality_check_func_v0 passwd_quality_check = 234178825Sdfr min_length_passwd_quality_v0; 235178825Sdfr 236178825Sdfrstruct kadm5_pw_policy_check_func builtin_funcs[] = { 237178825Sdfr { "minimum-length", min_length_passwd_quality }, 238178825Sdfr { "character-class", char_class_passwd_quality }, 239178825Sdfr { "external-check", external_passwd_quality }, 240178825Sdfr { NULL } 241178825Sdfr}; 242178825Sdfrstruct kadm5_pw_policy_verifier builtin_verifier = { 243178825Sdfr "builtin", 244178825Sdfr KADM5_PASSWD_VERSION_V1, 245178825Sdfr "Heimdal builtin", 246178825Sdfr builtin_funcs 247178825Sdfr}; 248178825Sdfr 249178825Sdfrstatic struct kadm5_pw_policy_verifier **verifiers; 250178825Sdfrstatic int num_verifiers; 251178825Sdfr 25255682Smarkm/* 25355682Smarkm * setup the password quality hook 25455682Smarkm */ 25555682Smarkm 256178825Sdfr#ifndef RTLD_NOW 257178825Sdfr#define RTLD_NOW 0 258178825Sdfr#endif 259178825Sdfr 26055682Smarkmvoid 26155682Smarkmkadm5_setup_passwd_quality_check(krb5_context context, 26255682Smarkm const char *check_library, 26355682Smarkm const char *check_function) 26455682Smarkm{ 26555682Smarkm#ifdef HAVE_DLOPEN 26655682Smarkm void *handle; 26755682Smarkm void *sym; 26855682Smarkm int *version; 26955682Smarkm const char *tmp; 27055682Smarkm 27155682Smarkm if(check_library == NULL) { 27255682Smarkm tmp = krb5_config_get_string(context, NULL, 27355682Smarkm "password_quality", 27455682Smarkm "check_library", 27555682Smarkm NULL); 27655682Smarkm if(tmp != NULL) 27755682Smarkm check_library = tmp; 27855682Smarkm } 27955682Smarkm if(check_function == NULL) { 28055682Smarkm tmp = krb5_config_get_string(context, NULL, 28155682Smarkm "password_quality", 28255682Smarkm "check_function", 28355682Smarkm NULL); 28455682Smarkm if(tmp != NULL) 28555682Smarkm check_function = tmp; 28655682Smarkm } 28755682Smarkm if(check_library != NULL && check_function == NULL) 28855682Smarkm check_function = "passwd_check"; 28955682Smarkm 29055682Smarkm if(check_library == NULL) 29155682Smarkm return; 292178825Sdfr handle = dlopen(check_library, RTLD_NOW); 29355682Smarkm if(handle == NULL) { 29455682Smarkm krb5_warnx(context, "failed to open `%s'", check_library); 29555682Smarkm return; 29655682Smarkm } 29755682Smarkm version = dlsym(handle, "version"); 29855682Smarkm if(version == NULL) { 29955682Smarkm krb5_warnx(context, 30055682Smarkm "didn't find `version' symbol in `%s'", check_library); 30155682Smarkm dlclose(handle); 30255682Smarkm return; 30355682Smarkm } 304178825Sdfr if(*version != KADM5_PASSWD_VERSION_V0) { 30555682Smarkm krb5_warnx(context, 30655682Smarkm "version of loaded library is %d (expected %d)", 307178825Sdfr *version, KADM5_PASSWD_VERSION_V0); 30855682Smarkm dlclose(handle); 30955682Smarkm return; 31055682Smarkm } 31155682Smarkm sym = dlsym(handle, check_function); 31255682Smarkm if(sym == NULL) { 31355682Smarkm krb5_warnx(context, 31455682Smarkm "didn't find `%s' symbol in `%s'", 31555682Smarkm check_function, check_library); 31655682Smarkm dlclose(handle); 31755682Smarkm return; 31855682Smarkm } 319178825Sdfr passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym; 32055682Smarkm#endif /* HAVE_DLOPEN */ 32155682Smarkm} 32255682Smarkm 323178825Sdfr#ifdef HAVE_DLOPEN 324178825Sdfr 325178825Sdfrstatic krb5_error_code 326178825Sdfradd_verifier(krb5_context context, const char *check_library) 327178825Sdfr{ 328178825Sdfr struct kadm5_pw_policy_verifier *v, **tmp; 329178825Sdfr void *handle; 330178825Sdfr int i; 331178825Sdfr 332178825Sdfr handle = dlopen(check_library, RTLD_NOW); 333178825Sdfr if(handle == NULL) { 334178825Sdfr krb5_warnx(context, "failed to open `%s'", check_library); 335178825Sdfr return ENOENT; 336178825Sdfr } 337178825Sdfr v = dlsym(handle, "kadm5_password_verifier"); 338178825Sdfr if(v == NULL) { 339178825Sdfr krb5_warnx(context, 340178825Sdfr "didn't find `kadm5_password_verifier' symbol " 341178825Sdfr "in `%s'", check_library); 342178825Sdfr dlclose(handle); 343178825Sdfr return ENOENT; 344178825Sdfr } 345178825Sdfr if(v->version != KADM5_PASSWD_VERSION_V1) { 346178825Sdfr krb5_warnx(context, 347178825Sdfr "version of loaded library is %d (expected %d)", 348178825Sdfr v->version, KADM5_PASSWD_VERSION_V1); 349178825Sdfr dlclose(handle); 350178825Sdfr return EINVAL; 351178825Sdfr } 352178825Sdfr for (i = 0; i < num_verifiers; i++) { 353178825Sdfr if (strcmp(v->name, verifiers[i]->name) == 0) 354178825Sdfr break; 355178825Sdfr } 356178825Sdfr if (i < num_verifiers) { 357178825Sdfr krb5_warnx(context, "password verifier library `%s' is already loaded", 358178825Sdfr v->name); 359178825Sdfr dlclose(handle); 360178825Sdfr return 0; 361178825Sdfr } 362178825Sdfr 363178825Sdfr tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers)); 364178825Sdfr if (tmp == NULL) { 365178825Sdfr krb5_warnx(context, "out of memory"); 366178825Sdfr dlclose(handle); 367178825Sdfr return 0; 368178825Sdfr } 369178825Sdfr verifiers = tmp; 370178825Sdfr verifiers[num_verifiers] = v; 371178825Sdfr num_verifiers++; 372178825Sdfr 373178825Sdfr return 0; 374178825Sdfr} 375178825Sdfr 376178825Sdfr#endif 377178825Sdfr 378178825Sdfrkrb5_error_code 379178825Sdfrkadm5_add_passwd_quality_verifier(krb5_context context, 380178825Sdfr const char *check_library) 381178825Sdfr{ 382178825Sdfr#ifdef HAVE_DLOPEN 383178825Sdfr 384178825Sdfr if(check_library == NULL) { 385178825Sdfr krb5_error_code ret; 386178825Sdfr char **tmp; 387178825Sdfr 388178825Sdfr tmp = krb5_config_get_strings(context, NULL, 389178825Sdfr "password_quality", 390178825Sdfr "policy_libraries", 391178825Sdfr NULL); 392178825Sdfr if(tmp == NULL) 393178825Sdfr return 0; 394178825Sdfr 395178825Sdfr while(tmp) { 396178825Sdfr ret = add_verifier(context, *tmp); 397178825Sdfr if (ret) 398178825Sdfr return ret; 399178825Sdfr tmp++; 400178825Sdfr } 401178825Sdfr } 402178825Sdfr return add_verifier(context, check_library); 403178825Sdfr#else 404178825Sdfr return 0; 405178825Sdfr#endif /* HAVE_DLOPEN */ 406178825Sdfr} 407178825Sdfr 408178825Sdfr/* 409178825Sdfr * 410178825Sdfr */ 411178825Sdfr 412178825Sdfrstatic const struct kadm5_pw_policy_check_func * 413178825Sdfrfind_func(krb5_context context, const char *name) 414178825Sdfr{ 415178825Sdfr const struct kadm5_pw_policy_check_func *f; 416178825Sdfr char *module = NULL; 417178825Sdfr const char *p, *func; 418178825Sdfr int i; 419178825Sdfr 420178825Sdfr p = strchr(name, ':'); 421178825Sdfr if (p) { 422178825Sdfr func = p + 1; 423178825Sdfr module = strndup(name, p - name); 424178825Sdfr if (module == NULL) 425178825Sdfr return NULL; 426178825Sdfr } else 427178825Sdfr func = name; 428178825Sdfr 429178825Sdfr /* Find module in loaded modules first */ 430178825Sdfr for (i = 0; i < num_verifiers; i++) { 431178825Sdfr if (module && strcmp(module, verifiers[i]->name) != 0) 432178825Sdfr continue; 433178825Sdfr for (f = verifiers[i]->funcs; f->name ; f++) 434178825Sdfr if (strcmp(name, f->name) == 0) { 435178825Sdfr if (module) 436178825Sdfr free(module); 437178825Sdfr return f; 438178825Sdfr } 439178825Sdfr } 440178825Sdfr /* Lets try try the builtin modules */ 441178825Sdfr if (module == NULL || strcmp(module, "builtin") == 0) { 442178825Sdfr for (f = builtin_verifier.funcs; f->name ; f++) 443178825Sdfr if (strcmp(func, f->name) == 0) { 444178825Sdfr if (module) 445178825Sdfr free(module); 446178825Sdfr return f; 447178825Sdfr } 448178825Sdfr } 449178825Sdfr if (module) 450178825Sdfr free(module); 451178825Sdfr return NULL; 452178825Sdfr} 453178825Sdfr 45455682Smarkmconst char * 45555682Smarkmkadm5_check_password_quality (krb5_context context, 45655682Smarkm krb5_principal principal, 45755682Smarkm krb5_data *pwd_data) 45855682Smarkm{ 459178825Sdfr const struct kadm5_pw_policy_check_func *proc; 460178825Sdfr static char error_msg[1024]; 461178825Sdfr const char *msg; 462178825Sdfr char **v, **vp; 463178825Sdfr int ret; 464178825Sdfr 465178825Sdfr /* 466178825Sdfr * Check if we should use the old version of policy function. 467178825Sdfr */ 468178825Sdfr 469178825Sdfr v = krb5_config_get_strings(context, NULL, 470178825Sdfr "password_quality", 471178825Sdfr "policies", 472178825Sdfr NULL); 473178825Sdfr if (v == NULL) { 474178825Sdfr msg = (*passwd_quality_check) (context, principal, pwd_data); 475178825Sdfr krb5_set_error_string(context, "password policy failed: %s", msg); 476178825Sdfr return msg; 477178825Sdfr } 478178825Sdfr 479178825Sdfr error_msg[0] = '\0'; 480178825Sdfr 481178825Sdfr msg = NULL; 482178825Sdfr for(vp = v; *vp; vp++) { 483178825Sdfr proc = find_func(context, *vp); 484178825Sdfr if (proc == NULL) { 485178825Sdfr msg = "failed to find password verifier function"; 486178825Sdfr krb5_set_error_string(context, "Failed to find password policy " 487178825Sdfr "function: %s", *vp); 488178825Sdfr break; 489178825Sdfr } 490178825Sdfr ret = (proc->func)(context, principal, pwd_data, NULL, 491178825Sdfr error_msg, sizeof(error_msg)); 492178825Sdfr if (ret) { 493178825Sdfr krb5_set_error_string(context, "Password policy " 494178825Sdfr "%s failed with %s", 495178825Sdfr proc->name, error_msg); 496178825Sdfr msg = error_msg; 497178825Sdfr break; 498178825Sdfr } 499178825Sdfr } 500178825Sdfr krb5_config_free_strings(v); 501178825Sdfr 502178825Sdfr /* If the default quality check isn't used, lets check that the 503178825Sdfr * old quality function the user have set too */ 504178825Sdfr if (msg == NULL && passwd_quality_check != min_length_passwd_quality_v0) { 505178825Sdfr msg = (*passwd_quality_check) (context, principal, pwd_data); 506178825Sdfr if (msg) 507178825Sdfr krb5_set_error_string(context, "(old) password policy " 508178825Sdfr "failed with %s", msg); 509178825Sdfr 510178825Sdfr } 511178825Sdfr return msg; 51255682Smarkm} 513