(external_passwd_quality): Improve messages. git-svn-id: svn://svn.h5l.se/heimdal/trunk/heimdal@15106 ec53bebd-3082-4978-b11e-865c3cabbd6b
		
			
				
	
	
		
			497 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			497 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 1997-2000, 2003-2005 Kungliga Tekniska Högskolan
 | 
						|
 * (Royal Institute of Technology, Stockholm, Sweden). 
 | 
						|
 * All rights reserved. 
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without 
 | 
						|
 * modification, are permitted provided that the following conditions 
 | 
						|
 * are met: 
 | 
						|
 *
 | 
						|
 * 1. Redistributions of source code must retain the above copyright 
 | 
						|
 *    notice, this list of conditions and the following disclaimer. 
 | 
						|
 *
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright 
 | 
						|
 *    notice, this list of conditions and the following disclaimer in the 
 | 
						|
 *    documentation and/or other materials provided with the distribution. 
 | 
						|
 *
 | 
						|
 * 3. Neither the name of the Institute nor the names of its contributors 
 | 
						|
 *    may be used to endorse or promote products derived from this software 
 | 
						|
 *    without specific prior written permission. 
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
 | 
						|
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 | 
						|
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
 | 
						|
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 | 
						|
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
 | 
						|
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 | 
						|
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 | 
						|
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
 | 
						|
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 | 
						|
 * SUCH DAMAGE. 
 | 
						|
 */
 | 
						|
 | 
						|
#include "kadm5_locl.h"
 | 
						|
#include "kadm5-pwcheck.h"
 | 
						|
 | 
						|
RCSID("$Id$");
 | 
						|
 | 
						|
#ifdef HAVE_SYS_WAIT_H
 | 
						|
#include <sys/wait.h>
 | 
						|
#endif
 | 
						|
#ifdef HAVE_DLFCN_H
 | 
						|
#include <dlfcn.h>
 | 
						|
#endif
 | 
						|
 | 
						|
static int
 | 
						|
min_length_passwd_quality (krb5_context context,
 | 
						|
			   krb5_principal principal,
 | 
						|
			   krb5_data *pwd,
 | 
						|
			   const char *opaque,
 | 
						|
			   char *message,
 | 
						|
			   size_t length)
 | 
						|
{
 | 
						|
    u_int32_t min_length = krb5_config_get_int_default(context, NULL, 6,
 | 
						|
						       "password_quality",
 | 
						|
						       "min_length",
 | 
						|
						       NULL);
 | 
						|
 | 
						|
    if (pwd->length < min_length) {
 | 
						|
	strlcpy(message, "Password too short", length);
 | 
						|
	return 1;
 | 
						|
    } else
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const char *
 | 
						|
min_length_passwd_quality_v0 (krb5_context context,
 | 
						|
			      krb5_principal principal,
 | 
						|
			      krb5_data *pwd)
 | 
						|
{
 | 
						|
    static char message[1024];
 | 
						|
    int ret;
 | 
						|
 | 
						|
    message[0] = '\0';
 | 
						|
 | 
						|
    ret = min_length_passwd_quality(context, principal, pwd, NULL,
 | 
						|
				    message, sizeof(message));
 | 
						|
    if (ret)
 | 
						|
	return message;
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
char_class_passwd_quality (krb5_context context,
 | 
						|
			   krb5_principal principal,
 | 
						|
			   krb5_data *pwd,
 | 
						|
			   const char *opaque,
 | 
						|
			   char *message,
 | 
						|
			   size_t length)
 | 
						|
{
 | 
						|
    char *classes[] = {
 | 
						|
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
 | 
						|
	"abcdefghijklmnopqrstuvwxyz",
 | 
						|
	"1234567890",
 | 
						|
	"!@#$%^&*()/?<>,.{[]}\\|'~`\" "
 | 
						|
    };
 | 
						|
    int i, counter = 0, req_classes;
 | 
						|
    size_t len;
 | 
						|
    char *pw;
 | 
						|
 | 
						|
    req_classes = krb5_config_get_int_default(context, NULL, 3,
 | 
						|
					      "password_quality",
 | 
						|
					      "min_classes",
 | 
						|
					      NULL);
 | 
						|
 | 
						|
    len = pwd->length + 1;
 | 
						|
    pw = malloc(len);
 | 
						|
    if (pw == NULL) {
 | 
						|
	strlcpy(message, "out of memory", length);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    strlcpy(pw, pwd->data, len);
 | 
						|
    len = strlen(pw);
 | 
						|
 | 
						|
    for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) {
 | 
						|
	if (strcspn(pw, classes[i]) < len)
 | 
						|
	    counter++;
 | 
						|
    }
 | 
						|
    memset(pw, 0, pwd->length + 1);
 | 
						|
    free(pw);
 | 
						|
    if (counter < req_classes) {
 | 
						|
	snprintf(message, length,
 | 
						|
	    "Password doesn't meet complexity requirement.\n"
 | 
						|
	    "Add more characters from the following classes:\n"
 | 
						|
	    "1. English uppercase characters (A through Z)\n"
 | 
						|
	    "2. English lowercase characters (a through z)\n"
 | 
						|
	    "3. Base 10 digits (0 through 9)\n"
 | 
						|
	    "4. Nonalphanumeric characters (e.g., !, $, #, %%)");
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
external_passwd_quality (krb5_context context,
 | 
						|
			 krb5_principal principal,
 | 
						|
			 krb5_data *pwd,
 | 
						|
			 const char *opaque,
 | 
						|
			 char *message,
 | 
						|
			 size_t length)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    const char *program;
 | 
						|
    char *p;
 | 
						|
    pid_t child;
 | 
						|
    int status;
 | 
						|
    char reply[1024];
 | 
						|
    FILE *in = NULL, *out = NULL, *error = NULL;
 | 
						|
 | 
						|
    if (memchr(pwd->data, pwd->length, '\n') != NULL) {
 | 
						|
	snprintf(message, length, "password contains newline, "
 | 
						|
		 "not valid for external test");
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    program = krb5_config_get_string(context, NULL,
 | 
						|
				     "password_quality",
 | 
						|
				     "external_program",
 | 
						|
				     NULL);
 | 
						|
    if (program == NULL) {
 | 
						|
	snprintf(message, length, "external password quality "
 | 
						|
		 "program not configured");
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = krb5_unparse_name(context, principal, &p);
 | 
						|
    if (ret) {
 | 
						|
	strlcpy(message, "out of memory", length);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    child = pipe_execv(&in, &out, &error, program, p, NULL);
 | 
						|
    if (child < 0) {
 | 
						|
	snprintf(message, length, "external password quality "
 | 
						|
		 "program failed to execute for principal %s", p);
 | 
						|
	free(p);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    fprintf(in, "principal: %s\n"
 | 
						|
	    "new-password: %.*s\n"
 | 
						|
	    "end\n",
 | 
						|
	    p, (int)pwd->length, (char *)pwd->data);
 | 
						|
    
 | 
						|
    fclose(in);
 | 
						|
 | 
						|
    if (fgets(reply, sizeof(reply), out) == NULL) {
 | 
						|
 | 
						|
	if (fgets(reply, sizeof(reply), error) == NULL) {
 | 
						|
	    snprintf(message, length, "external password quality "
 | 
						|
		     "program failed without error");
 | 
						|
 | 
						|
	} else {
 | 
						|
	    reply[strcspn(reply, "\n")] = '\0';
 | 
						|
	    snprintf(message, length, "External password quality "
 | 
						|
		     "program failed: %s", reply);
 | 
						|
	}
 | 
						|
 | 
						|
	fclose(out);
 | 
						|
	fclose(error);
 | 
						|
	waitpid(child, &status, 0);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    reply[strcspn(reply, "\n")] = '\0';
 | 
						|
 | 
						|
    fclose(out);
 | 
						|
    fclose(error);
 | 
						|
 | 
						|
    if (waitpid(child, &status, 0) < 0) {
 | 
						|
	snprintf(message, length, "external program failed: %s", reply);
 | 
						|
	free(p);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
 | 
						|
	snprintf(message, length, "external program failed: %s", reply);
 | 
						|
	free(p);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    if (strcmp(reply, "APPROVED") != 0) {
 | 
						|
	snprintf(message, length, "%s", reply);
 | 
						|
	free(p);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    free(p);
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static kadm5_passwd_quality_check_func_v0 passwd_quality_check = 
 | 
						|
	min_length_passwd_quality_v0;
 | 
						|
 | 
						|
struct kadm5_pw_policy_check_func builtin_funcs[] = {
 | 
						|
    { "minimum-length", min_length_passwd_quality },
 | 
						|
    { "character-class", char_class_passwd_quality },
 | 
						|
    { "external-check", external_passwd_quality },
 | 
						|
    { NULL }
 | 
						|
};
 | 
						|
struct kadm5_pw_policy_verifier builtin_verifier = {
 | 
						|
    "builtin", 
 | 
						|
    KADM5_PASSWD_VERSION_V1, 
 | 
						|
    "Heimdal builtin",
 | 
						|
    builtin_funcs
 | 
						|
};
 | 
						|
 | 
						|
static struct kadm5_pw_policy_verifier **verifiers;
 | 
						|
static int num_verifiers;
 | 
						|
 | 
						|
/*
 | 
						|
 * setup the password quality hook
 | 
						|
 */
 | 
						|
 | 
						|
#ifndef RTLD_NOW
 | 
						|
#define RTLD_NOW 0
 | 
						|
#endif
 | 
						|
 | 
						|
void
 | 
						|
kadm5_setup_passwd_quality_check(krb5_context context,
 | 
						|
				 const char *check_library,
 | 
						|
				 const char *check_function)
 | 
						|
{
 | 
						|
#ifdef HAVE_DLOPEN
 | 
						|
    void *handle;
 | 
						|
    void *sym;
 | 
						|
    int *version;
 | 
						|
    const char *tmp;
 | 
						|
 | 
						|
    if(check_library == NULL) {
 | 
						|
	tmp = krb5_config_get_string(context, NULL, 
 | 
						|
				     "password_quality", 
 | 
						|
				     "check_library", 
 | 
						|
				     NULL);
 | 
						|
	if(tmp != NULL)
 | 
						|
	    check_library = tmp;
 | 
						|
    }
 | 
						|
    if(check_function == NULL) {
 | 
						|
	tmp = krb5_config_get_string(context, NULL, 
 | 
						|
				     "password_quality", 
 | 
						|
				     "check_function", 
 | 
						|
				     NULL);
 | 
						|
	if(tmp != NULL)
 | 
						|
	    check_function = tmp;
 | 
						|
    }
 | 
						|
    if(check_library != NULL && check_function == NULL)
 | 
						|
	check_function = "passwd_check";
 | 
						|
 | 
						|
    if(check_library == NULL)
 | 
						|
	return;
 | 
						|
    handle = dlopen(check_library, RTLD_NOW);
 | 
						|
    if(handle == NULL) {
 | 
						|
	krb5_warnx(context, "failed to open `%s'", check_library);
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    version = dlsym(handle, "version");
 | 
						|
    if(version == NULL) {
 | 
						|
	krb5_warnx(context,
 | 
						|
		   "didn't find `version' symbol in `%s'", check_library);
 | 
						|
	dlclose(handle);
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    if(*version != KADM5_PASSWD_VERSION_V0) {
 | 
						|
	krb5_warnx(context,
 | 
						|
		   "version of loaded library is %d (expected %d)",
 | 
						|
		   *version, KADM5_PASSWD_VERSION_V0);
 | 
						|
	dlclose(handle);
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    sym = dlsym(handle, check_function);
 | 
						|
    if(sym == NULL) {
 | 
						|
	krb5_warnx(context, 
 | 
						|
		   "didn't find `%s' symbol in `%s'", 
 | 
						|
		   check_function, check_library);
 | 
						|
	dlclose(handle);
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym;
 | 
						|
#endif /* HAVE_DLOPEN */
 | 
						|
}
 | 
						|
 | 
						|
#ifdef HAVE_DLOPEN
 | 
						|
 | 
						|
static krb5_error_code
 | 
						|
add_verifier(krb5_context context, const char *check_library)
 | 
						|
{
 | 
						|
    struct kadm5_pw_policy_verifier *v, **tmp;
 | 
						|
    void *handle;
 | 
						|
    int i;
 | 
						|
 | 
						|
    handle = dlopen(check_library, RTLD_NOW);
 | 
						|
    if(handle == NULL) {
 | 
						|
	krb5_warnx(context, "failed to open `%s'", check_library);
 | 
						|
	return ENOENT;
 | 
						|
    }
 | 
						|
    v = dlsym(handle, "kadm5_password_verifier");
 | 
						|
    if(v == NULL) {
 | 
						|
	krb5_warnx(context,
 | 
						|
		   "didn't find `kadm5_password_verifier' symbol "
 | 
						|
		   "in `%s'", check_library);
 | 
						|
	dlclose(handle);
 | 
						|
	return ENOENT;
 | 
						|
    }
 | 
						|
    if(v->version != KADM5_PASSWD_VERSION_V1) {
 | 
						|
	krb5_warnx(context,
 | 
						|
		   "version of loaded library is %d (expected %d)",
 | 
						|
		   v->version, KADM5_PASSWD_VERSION_V1);
 | 
						|
	dlclose(handle);
 | 
						|
	return EINVAL;
 | 
						|
    }
 | 
						|
    for (i = 0; i < num_verifiers; i++) {
 | 
						|
	if (strcmp(v->name, verifiers[i]->name) == 0)
 | 
						|
	    break;
 | 
						|
    }
 | 
						|
    if (i < num_verifiers) {
 | 
						|
	krb5_warnx(context, "password verifier library `%s' is already loaded",
 | 
						|
		   v->name);
 | 
						|
	dlclose(handle);
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers));
 | 
						|
    if (tmp == NULL) {
 | 
						|
	krb5_warnx(context, "out of memory");
 | 
						|
	dlclose(handle);
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
    verifiers = tmp;
 | 
						|
    verifiers[num_verifiers] = v;
 | 
						|
    num_verifiers++;
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
#endif
 | 
						|
 | 
						|
krb5_error_code
 | 
						|
kadm5_add_passwd_quality_verifier(krb5_context context,
 | 
						|
				  const char *check_library)
 | 
						|
{
 | 
						|
#ifdef HAVE_DLOPEN
 | 
						|
 | 
						|
    if(check_library == NULL) {
 | 
						|
	krb5_error_code ret;
 | 
						|
	char **tmp;
 | 
						|
 | 
						|
	tmp = krb5_config_get_strings(context, NULL, 
 | 
						|
				      "password_quality", 
 | 
						|
				      "policy_libraries", 
 | 
						|
				      NULL);
 | 
						|
	if(tmp == NULL)
 | 
						|
	    return 0;
 | 
						|
 | 
						|
	while(tmp) {
 | 
						|
	    ret = add_verifier(context, *tmp);
 | 
						|
	    if (ret)
 | 
						|
		return ret;
 | 
						|
	    tmp++;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    return add_verifier(context, check_library);
 | 
						|
#else
 | 
						|
    return 0;
 | 
						|
#endif /* HAVE_DLOPEN */
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
static struct kadm5_pw_policy_check_func *
 | 
						|
find_func(krb5_context context, const char *name)
 | 
						|
{
 | 
						|
    struct kadm5_pw_policy_check_func *f;
 | 
						|
    char *module = NULL;
 | 
						|
    const char *p, *func;
 | 
						|
    int i;
 | 
						|
 | 
						|
    p = strchr(name, ':');
 | 
						|
    if (p) {
 | 
						|
	func = p + 1;
 | 
						|
	module = strndup(name, p - name);
 | 
						|
	if (module == NULL)
 | 
						|
	    return NULL;
 | 
						|
    } else
 | 
						|
	func = name;
 | 
						|
 | 
						|
    /* Find module in loaded modules first */
 | 
						|
    for (i = 0; i < num_verifiers; i++) {
 | 
						|
	if (module && strcmp(module, verifiers[i]->name) != 0)
 | 
						|
	    continue;
 | 
						|
	for (f = verifiers[i]->funcs; f->name ; f++)
 | 
						|
	    if (strcmp(name, f->name) == 0) {
 | 
						|
		if (module)
 | 
						|
		    free(module);
 | 
						|
		return f;
 | 
						|
	    }
 | 
						|
    }
 | 
						|
    /* Lets try try the builtin modules */
 | 
						|
    if (module == NULL || strcmp(module, "builtin") == 0) {
 | 
						|
	for (f = builtin_verifier.funcs; f->name ; f++)
 | 
						|
	    if (strcmp(func, f->name) == 0) {
 | 
						|
		if (module)
 | 
						|
		    free(module);
 | 
						|
		return f;
 | 
						|
	    }
 | 
						|
    }
 | 
						|
    if (module)
 | 
						|
	free(module);
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
const char *
 | 
						|
kadm5_check_password_quality (krb5_context context,
 | 
						|
			      krb5_principal principal,
 | 
						|
			      krb5_data *pwd_data)
 | 
						|
{
 | 
						|
    struct kadm5_pw_policy_check_func *proc;
 | 
						|
    static char error_msg[1024];
 | 
						|
    const char *msg;
 | 
						|
    char **v, **vp;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    v = krb5_config_get_strings(context, NULL, 
 | 
						|
				"password_quality", 
 | 
						|
				"policies", 
 | 
						|
				NULL);
 | 
						|
    if (v == NULL)
 | 
						|
	return (*passwd_quality_check) (context, principal, pwd_data);
 | 
						|
 | 
						|
    error_msg[0] = '\0';
 | 
						|
 | 
						|
    msg = NULL;
 | 
						|
    for(vp = v; *vp; vp++) {
 | 
						|
	proc = find_func(context, *vp);
 | 
						|
	if (proc == NULL) {
 | 
						|
	    msg = "failed to find password verifier function";
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
	ret = (proc->func)(context, principal, pwd_data, NULL,
 | 
						|
			   error_msg, sizeof(error_msg));
 | 
						|
	if (ret) {
 | 
						|
	    msg = error_msg;
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    krb5_config_free_strings(v);
 | 
						|
 | 
						|
    /* If the default quality check isn't used, lets check that the
 | 
						|
     * old quality function the user have set too */
 | 
						|
    if (msg == NULL && passwd_quality_check != min_length_passwd_quality_v0)
 | 
						|
	msg = (*passwd_quality_check) (context, principal, pwd_data);
 | 
						|
 | 
						|
    return msg;
 | 
						|
}
 |