155682Smarkm/* 2233294Sstas * Copyright (c) 1997-2000, 2003-2005 Kungliga Tekniska H��gskolan 3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden). 4233294Sstas * All rights reserved. 555682Smarkm * 6233294Sstas * Redistribution and use in source and binary forms, with or without 7233294Sstas * modification, are permitted provided that the following conditions 8233294Sstas * are met: 955682Smarkm * 10233294Sstas * 1. Redistributions of source code must retain the above copyright 11233294Sstas * notice, this list of conditions and the following disclaimer. 1255682Smarkm * 13233294Sstas * 2. Redistributions in binary form must reproduce the above copyright 14233294Sstas * notice, this list of conditions and the following disclaimer in the 15233294Sstas * documentation and/or other materials provided with the distribution. 1655682Smarkm * 17233294Sstas * 3. Neither the name of the Institute nor the names of its contributors 18233294Sstas * may be used to endorse or promote products derived from this software 19233294Sstas * without specific prior written permission. 2055682Smarkm * 21233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24233294Sstas * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31233294Sstas * SUCH DAMAGE. 3255682Smarkm */ 3355682Smarkm 3455682Smarkm#include "kadm5_locl.h" 35178825Sdfr#include "kadm5-pwcheck.h" 3655682Smarkm 37178825Sdfr#ifdef HAVE_SYS_WAIT_H 38178825Sdfr#include <sys/wait.h> 39178825Sdfr#endif 4055682Smarkm#ifdef HAVE_DLFCN_H 4155682Smarkm#include <dlfcn.h> 4255682Smarkm#endif 4355682Smarkm 44178825Sdfrstatic int 45178825Sdfrmin_length_passwd_quality (krb5_context context, 46178825Sdfr krb5_principal principal, 47178825Sdfr krb5_data *pwd, 48178825Sdfr const char *opaque, 49178825Sdfr char *message, 50178825Sdfr size_t length) 51178825Sdfr{ 52178825Sdfr uint32_t min_length = krb5_config_get_int_default(context, NULL, 6, 53178825Sdfr "password_quality", 54178825Sdfr "min_length", 55178825Sdfr NULL); 56178825Sdfr 57178825Sdfr if (pwd->length < min_length) { 58178825Sdfr strlcpy(message, "Password too short", length); 59178825Sdfr return 1; 60178825Sdfr } else 61178825Sdfr return 0; 62178825Sdfr} 63178825Sdfr 6455682Smarkmstatic const char * 65178825Sdfrmin_length_passwd_quality_v0 (krb5_context context, 66178825Sdfr krb5_principal principal, 67178825Sdfr krb5_data *pwd) 6855682Smarkm{ 69178825Sdfr static char message[1024]; 70178825Sdfr int ret; 71178825Sdfr 72178825Sdfr message[0] = '\0'; 73178825Sdfr 74178825Sdfr ret = min_length_passwd_quality(context, principal, pwd, NULL, 75178825Sdfr message, sizeof(message)); 76178825Sdfr if (ret) 77178825Sdfr return message; 78178825Sdfr return NULL; 7955682Smarkm} 8055682Smarkm 8155682Smarkm 82178825Sdfrstatic int 83178825Sdfrchar_class_passwd_quality (krb5_context context, 84178825Sdfr krb5_principal principal, 85178825Sdfr krb5_data *pwd, 86178825Sdfr const char *opaque, 87178825Sdfr char *message, 88178825Sdfr size_t length) 89178825Sdfr{ 90178825Sdfr const char *classes[] = { 91178825Sdfr "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 92178825Sdfr "abcdefghijklmnopqrstuvwxyz", 93178825Sdfr "1234567890", 94178825Sdfr "!@#$%^&*()/?<>,.{[]}\\|'~`\" " 95178825Sdfr }; 96233294Sstas int counter = 0, req_classes; 97233294Sstas size_t i, len; 98178825Sdfr char *pw; 9955682Smarkm 100178825Sdfr req_classes = krb5_config_get_int_default(context, NULL, 3, 101178825Sdfr "password_quality", 102178825Sdfr "min_classes", 103178825Sdfr NULL); 10455682Smarkm 105178825Sdfr len = pwd->length + 1; 106178825Sdfr pw = malloc(len); 107178825Sdfr if (pw == NULL) { 108178825Sdfr strlcpy(message, "out of memory", length); 109178825Sdfr return 1; 110178825Sdfr } 111178825Sdfr strlcpy(pw, pwd->data, len); 112178825Sdfr len = strlen(pw); 11355682Smarkm 114178825Sdfr for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) { 115178825Sdfr if (strcspn(pw, classes[i]) < len) 116178825Sdfr counter++; 117178825Sdfr } 118178825Sdfr memset(pw, 0, pwd->length + 1); 119178825Sdfr free(pw); 120178825Sdfr if (counter < req_classes) { 121178825Sdfr snprintf(message, length, 122178825Sdfr "Password doesn't meet complexity requirement.\n" 123178825Sdfr "Add more characters from the following classes:\n" 124178825Sdfr "1. English uppercase characters (A through Z)\n" 125178825Sdfr "2. English lowercase characters (a through z)\n" 126178825Sdfr "3. Base 10 digits (0 through 9)\n" 127178825Sdfr "4. Nonalphanumeric characters (e.g., !, $, #, %%)"); 128178825Sdfr return 1; 129178825Sdfr } 130178825Sdfr return 0; 131178825Sdfr} 13255682Smarkm 133178825Sdfrstatic int 134178825Sdfrexternal_passwd_quality (krb5_context context, 135178825Sdfr krb5_principal principal, 136178825Sdfr krb5_data *pwd, 137178825Sdfr const char *opaque, 138178825Sdfr char *message, 139178825Sdfr size_t length) 140178825Sdfr{ 141178825Sdfr krb5_error_code ret; 142178825Sdfr const char *program; 143178825Sdfr char *p; 144178825Sdfr pid_t child; 145178825Sdfr int status; 146178825Sdfr char reply[1024]; 147178825Sdfr FILE *in = NULL, *out = NULL, *error = NULL; 148178825Sdfr 149233294Sstas if (memchr(pwd->data, '\n', pwd->length) != NULL) { 150178825Sdfr snprintf(message, length, "password contains newline, " 151178825Sdfr "not valid for external test"); 152178825Sdfr return 1; 153178825Sdfr } 154178825Sdfr 155178825Sdfr program = krb5_config_get_string(context, NULL, 156178825Sdfr "password_quality", 157178825Sdfr "external_program", 158178825Sdfr NULL); 159178825Sdfr if (program == NULL) { 160178825Sdfr snprintf(message, length, "external password quality " 161178825Sdfr "program not configured"); 162178825Sdfr return 1; 163178825Sdfr } 164178825Sdfr 165178825Sdfr ret = krb5_unparse_name(context, principal, &p); 166178825Sdfr if (ret) { 167178825Sdfr strlcpy(message, "out of memory", length); 168178825Sdfr return 1; 169178825Sdfr } 170178825Sdfr 171233294Sstas child = pipe_execv(&in, &out, &error, program, program, p, NULL); 172178825Sdfr if (child < 0) { 173178825Sdfr snprintf(message, length, "external password quality " 174178825Sdfr "program failed to execute for principal %s", p); 175178825Sdfr free(p); 176178825Sdfr return 1; 177178825Sdfr } 178178825Sdfr 179178825Sdfr fprintf(in, "principal: %s\n" 180178825Sdfr "new-password: %.*s\n" 181178825Sdfr "end\n", 182178825Sdfr p, (int)pwd->length, (char *)pwd->data); 183233294Sstas 184178825Sdfr fclose(in); 185178825Sdfr 186178825Sdfr if (fgets(reply, sizeof(reply), out) == NULL) { 187178825Sdfr 188178825Sdfr if (fgets(reply, sizeof(reply), error) == NULL) { 189178825Sdfr snprintf(message, length, "external password quality " 190178825Sdfr "program failed without error"); 191178825Sdfr 192178825Sdfr } else { 193178825Sdfr reply[strcspn(reply, "\n")] = '\0'; 194178825Sdfr snprintf(message, length, "External password quality " 195178825Sdfr "program failed: %s", reply); 196178825Sdfr } 197178825Sdfr 198178825Sdfr fclose(out); 199178825Sdfr fclose(error); 200233294Sstas wait_for_process(child); 201178825Sdfr return 1; 202178825Sdfr } 203178825Sdfr reply[strcspn(reply, "\n")] = '\0'; 204178825Sdfr 205178825Sdfr fclose(out); 206178825Sdfr fclose(error); 207178825Sdfr 208233294Sstas status = wait_for_process(child); 209233294Sstas 210233294Sstas if (SE_IS_ERROR(status) || SE_PROCSTATUS(status) != 0) { 211178825Sdfr snprintf(message, length, "external program failed: %s", reply); 212178825Sdfr free(p); 213178825Sdfr return 1; 214178825Sdfr } 215178825Sdfr 216178825Sdfr if (strcmp(reply, "APPROVED") != 0) { 217178825Sdfr snprintf(message, length, "%s", reply); 218178825Sdfr free(p); 219178825Sdfr return 1; 220178825Sdfr } 221178825Sdfr 222178825Sdfr free(p); 223178825Sdfr 224178825Sdfr return 0; 225178825Sdfr} 226178825Sdfr 227178825Sdfr 228233294Sstasstatic kadm5_passwd_quality_check_func_v0 passwd_quality_check = 229178825Sdfr min_length_passwd_quality_v0; 230178825Sdfr 231178825Sdfrstruct kadm5_pw_policy_check_func builtin_funcs[] = { 232178825Sdfr { "minimum-length", min_length_passwd_quality }, 233178825Sdfr { "character-class", char_class_passwd_quality }, 234178825Sdfr { "external-check", external_passwd_quality }, 235233294Sstas { NULL, NULL } 236178825Sdfr}; 237178825Sdfrstruct kadm5_pw_policy_verifier builtin_verifier = { 238233294Sstas "builtin", 239233294Sstas KADM5_PASSWD_VERSION_V1, 240178825Sdfr "Heimdal builtin", 241178825Sdfr builtin_funcs 242178825Sdfr}; 243178825Sdfr 244178825Sdfrstatic struct kadm5_pw_policy_verifier **verifiers; 245178825Sdfrstatic int num_verifiers; 246178825Sdfr 24755682Smarkm/* 24855682Smarkm * setup the password quality hook 24955682Smarkm */ 25055682Smarkm 251178825Sdfr#ifndef RTLD_NOW 252178825Sdfr#define RTLD_NOW 0 253178825Sdfr#endif 254178825Sdfr 25555682Smarkmvoid 25655682Smarkmkadm5_setup_passwd_quality_check(krb5_context context, 25755682Smarkm const char *check_library, 25855682Smarkm const char *check_function) 25955682Smarkm{ 26055682Smarkm#ifdef HAVE_DLOPEN 26155682Smarkm void *handle; 26255682Smarkm void *sym; 26355682Smarkm int *version; 26455682Smarkm const char *tmp; 26555682Smarkm 26655682Smarkm if(check_library == NULL) { 267233294Sstas tmp = krb5_config_get_string(context, NULL, 268233294Sstas "password_quality", 269233294Sstas "check_library", 27055682Smarkm NULL); 27155682Smarkm if(tmp != NULL) 27255682Smarkm check_library = tmp; 27355682Smarkm } 27455682Smarkm if(check_function == NULL) { 275233294Sstas tmp = krb5_config_get_string(context, NULL, 276233294Sstas "password_quality", 277233294Sstas "check_function", 27855682Smarkm NULL); 27955682Smarkm if(tmp != NULL) 28055682Smarkm check_function = tmp; 28155682Smarkm } 28255682Smarkm if(check_library != NULL && check_function == NULL) 28355682Smarkm check_function = "passwd_check"; 28455682Smarkm 28555682Smarkm if(check_library == NULL) 28655682Smarkm return; 287178825Sdfr handle = dlopen(check_library, RTLD_NOW); 28855682Smarkm if(handle == NULL) { 28955682Smarkm krb5_warnx(context, "failed to open `%s'", check_library); 29055682Smarkm return; 29155682Smarkm } 292233294Sstas version = (int *) dlsym(handle, "version"); 29355682Smarkm if(version == NULL) { 29455682Smarkm krb5_warnx(context, 29555682Smarkm "didn't find `version' symbol in `%s'", check_library); 29655682Smarkm dlclose(handle); 29755682Smarkm return; 29855682Smarkm } 299178825Sdfr if(*version != KADM5_PASSWD_VERSION_V0) { 30055682Smarkm krb5_warnx(context, 30155682Smarkm "version of loaded library is %d (expected %d)", 302178825Sdfr *version, KADM5_PASSWD_VERSION_V0); 30355682Smarkm dlclose(handle); 30455682Smarkm return; 30555682Smarkm } 30655682Smarkm sym = dlsym(handle, check_function); 30755682Smarkm if(sym == NULL) { 308233294Sstas krb5_warnx(context, 309233294Sstas "didn't find `%s' symbol in `%s'", 31055682Smarkm check_function, check_library); 31155682Smarkm dlclose(handle); 31255682Smarkm return; 31355682Smarkm } 314178825Sdfr passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym; 31555682Smarkm#endif /* HAVE_DLOPEN */ 31655682Smarkm} 31755682Smarkm 318178825Sdfr#ifdef HAVE_DLOPEN 319178825Sdfr 320178825Sdfrstatic krb5_error_code 321178825Sdfradd_verifier(krb5_context context, const char *check_library) 322178825Sdfr{ 323178825Sdfr struct kadm5_pw_policy_verifier *v, **tmp; 324178825Sdfr void *handle; 325178825Sdfr int i; 326178825Sdfr 327178825Sdfr handle = dlopen(check_library, RTLD_NOW); 328178825Sdfr if(handle == NULL) { 329178825Sdfr krb5_warnx(context, "failed to open `%s'", check_library); 330178825Sdfr return ENOENT; 331178825Sdfr } 332233294Sstas v = (struct kadm5_pw_policy_verifier *) dlsym(handle, "kadm5_password_verifier"); 333178825Sdfr if(v == NULL) { 334178825Sdfr krb5_warnx(context, 335178825Sdfr "didn't find `kadm5_password_verifier' symbol " 336178825Sdfr "in `%s'", check_library); 337178825Sdfr dlclose(handle); 338178825Sdfr return ENOENT; 339178825Sdfr } 340178825Sdfr if(v->version != KADM5_PASSWD_VERSION_V1) { 341178825Sdfr krb5_warnx(context, 342178825Sdfr "version of loaded library is %d (expected %d)", 343178825Sdfr v->version, KADM5_PASSWD_VERSION_V1); 344178825Sdfr dlclose(handle); 345178825Sdfr return EINVAL; 346178825Sdfr } 347178825Sdfr for (i = 0; i < num_verifiers; i++) { 348178825Sdfr if (strcmp(v->name, verifiers[i]->name) == 0) 349178825Sdfr break; 350178825Sdfr } 351178825Sdfr if (i < num_verifiers) { 352178825Sdfr krb5_warnx(context, "password verifier library `%s' is already loaded", 353178825Sdfr v->name); 354178825Sdfr dlclose(handle); 355178825Sdfr return 0; 356178825Sdfr } 357178825Sdfr 358178825Sdfr tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers)); 359178825Sdfr if (tmp == NULL) { 360178825Sdfr krb5_warnx(context, "out of memory"); 361178825Sdfr dlclose(handle); 362178825Sdfr return 0; 363178825Sdfr } 364178825Sdfr verifiers = tmp; 365178825Sdfr verifiers[num_verifiers] = v; 366178825Sdfr num_verifiers++; 367178825Sdfr 368178825Sdfr return 0; 369178825Sdfr} 370178825Sdfr 371178825Sdfr#endif 372178825Sdfr 373178825Sdfrkrb5_error_code 374178825Sdfrkadm5_add_passwd_quality_verifier(krb5_context context, 375178825Sdfr const char *check_library) 376178825Sdfr{ 377178825Sdfr#ifdef HAVE_DLOPEN 378178825Sdfr 379178825Sdfr if(check_library == NULL) { 380178825Sdfr krb5_error_code ret; 381178825Sdfr char **tmp; 382178825Sdfr 383233294Sstas tmp = krb5_config_get_strings(context, NULL, 384233294Sstas "password_quality", 385233294Sstas "policy_libraries", 386178825Sdfr NULL); 387233294Sstas if(tmp == NULL || *tmp == NULL) 388178825Sdfr return 0; 389178825Sdfr 390233294Sstas while (*tmp) { 391178825Sdfr ret = add_verifier(context, *tmp); 392178825Sdfr if (ret) 393178825Sdfr return ret; 394178825Sdfr tmp++; 395178825Sdfr } 396233294Sstas return 0; 397233294Sstas } else { 398233294Sstas return add_verifier(context, check_library); 399178825Sdfr } 400178825Sdfr#else 401178825Sdfr return 0; 402178825Sdfr#endif /* HAVE_DLOPEN */ 403178825Sdfr} 404178825Sdfr 405178825Sdfr/* 406178825Sdfr * 407178825Sdfr */ 408178825Sdfr 409178825Sdfrstatic const struct kadm5_pw_policy_check_func * 410178825Sdfrfind_func(krb5_context context, const char *name) 411178825Sdfr{ 412178825Sdfr const struct kadm5_pw_policy_check_func *f; 413178825Sdfr char *module = NULL; 414178825Sdfr const char *p, *func; 415178825Sdfr int i; 416178825Sdfr 417178825Sdfr p = strchr(name, ':'); 418178825Sdfr if (p) { 419233294Sstas size_t len = p - name + 1; 420178825Sdfr func = p + 1; 421233294Sstas module = malloc(len); 422178825Sdfr if (module == NULL) 423178825Sdfr return NULL; 424233294Sstas strlcpy(module, name, len); 425178825Sdfr } else 426178825Sdfr func = name; 427178825Sdfr 428178825Sdfr /* Find module in loaded modules first */ 429178825Sdfr for (i = 0; i < num_verifiers; i++) { 430178825Sdfr if (module && strcmp(module, verifiers[i]->name) != 0) 431178825Sdfr continue; 432178825Sdfr for (f = verifiers[i]->funcs; f->name ; f++) 433233294Sstas if (strcmp(func, f->name) == 0) { 434178825Sdfr if (module) 435178825Sdfr free(module); 436178825Sdfr return f; 437178825Sdfr } 438178825Sdfr } 439178825Sdfr /* Lets try try the builtin modules */ 440178825Sdfr if (module == NULL || strcmp(module, "builtin") == 0) { 441178825Sdfr for (f = builtin_verifier.funcs; f->name ; f++) 442178825Sdfr if (strcmp(func, f->name) == 0) { 443178825Sdfr if (module) 444178825Sdfr free(module); 445178825Sdfr return f; 446178825Sdfr } 447178825Sdfr } 448178825Sdfr if (module) 449178825Sdfr free(module); 450178825Sdfr return NULL; 451178825Sdfr} 452178825Sdfr 45355682Smarkmconst char * 45455682Smarkmkadm5_check_password_quality (krb5_context context, 45555682Smarkm krb5_principal principal, 45655682Smarkm krb5_data *pwd_data) 45755682Smarkm{ 458178825Sdfr const struct kadm5_pw_policy_check_func *proc; 459178825Sdfr static char error_msg[1024]; 460178825Sdfr const char *msg; 461178825Sdfr char **v, **vp; 462178825Sdfr int ret; 463178825Sdfr 464178825Sdfr /* 465178825Sdfr * Check if we should use the old version of policy function. 466178825Sdfr */ 467178825Sdfr 468233294Sstas v = krb5_config_get_strings(context, NULL, 469233294Sstas "password_quality", 470233294Sstas "policies", 471178825Sdfr NULL); 472178825Sdfr if (v == NULL) { 473178825Sdfr msg = (*passwd_quality_check) (context, principal, pwd_data); 474234027Sstas if (msg) 475234027Sstas krb5_set_error_message(context, 0, "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"; 486233294Sstas krb5_set_error_message(context, 0, "Failed to find password policy " 487233294Sstas "function: %s", *vp); 488178825Sdfr break; 489178825Sdfr } 490178825Sdfr ret = (proc->func)(context, principal, pwd_data, NULL, 491178825Sdfr error_msg, sizeof(error_msg)); 492178825Sdfr if (ret) { 493233294Sstas krb5_set_error_message(context, 0, "Password policy " 494233294Sstas "%s failed with %s", 495233294Sstas 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) 507233294Sstas krb5_set_error_message(context, 0, "(old) password policy " 508233294Sstas "failed with %s", msg); 509233294Sstas 510178825Sdfr } 511178825Sdfr return msg; 51255682Smarkm} 513