509 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			509 lines
		
	
	
		
			13 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"
 | 
						|
 | 
						|
#ifdef HAVE_SYS_WAIT_H
 | 
						|
#include <sys/wait.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)
 | 
						|
{
 | 
						|
    uint32_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)
 | 
						|
{
 | 
						|
    const char *classes[] = {
 | 
						|
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
 | 
						|
	"abcdefghijklmnopqrstuvwxyz",
 | 
						|
	"1234567890",
 | 
						|
	" !\"#$%&'()*+,-./:;<=>?@\\]^_`{|}~"
 | 
						|
    };
 | 
						|
    int counter = 0, req_classes;
 | 
						|
    size_t i, 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 at least %d of the\n"
 | 
						|
            "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., !, $, #, %%)", req_classes);
 | 
						|
	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, '\n', pwd->length) != 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, 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);
 | 
						|
	wait_for_process(child);
 | 
						|
	free(p);
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    reply[strcspn(reply, "\n")] = '\0';
 | 
						|
 | 
						|
    fclose(out);
 | 
						|
    fclose(error);
 | 
						|
 | 
						|
    status = wait_for_process(child);
 | 
						|
 | 
						|
    if (SE_IS_ERROR(status) || SE_PROCSTATUS(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, 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
 | 
						|
 */
 | 
						|
 | 
						|
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 | RTLD_LOCAL | RTLD_GROUP);
 | 
						|
    if(handle == NULL) {
 | 
						|
	krb5_warnx(context, "failed to open `%s'", check_library);
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    version = (int *) 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 | RTLD_LOCAL | RTLD_GROUP);
 | 
						|
    if(handle == NULL) {
 | 
						|
	krb5_warnx(context, "failed to open `%s'", check_library);
 | 
						|
	return ENOENT;
 | 
						|
    }
 | 
						|
    v = (struct kadm5_pw_policy_verifier *) 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 = 0;
 | 
						|
        char **strs;
 | 
						|
	char **tmp;
 | 
						|
 | 
						|
	strs = krb5_config_get_strings(context, NULL,
 | 
						|
				       "password_quality",
 | 
						|
				       "policy_libraries",
 | 
						|
				       NULL);
 | 
						|
	if (strs == NULL)
 | 
						|
	    return 0;
 | 
						|
 | 
						|
	for (tmp = strs; *tmp; tmp++) {
 | 
						|
	    ret = add_verifier(context, *tmp);
 | 
						|
	    if (ret)
 | 
						|
		break;
 | 
						|
	}
 | 
						|
        krb5_config_free_strings(strs);
 | 
						|
	return ret;
 | 
						|
    } else {
 | 
						|
	return add_verifier(context, check_library);
 | 
						|
    }
 | 
						|
#else
 | 
						|
    return 0;
 | 
						|
#endif /* HAVE_DLOPEN */
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
static const struct kadm5_pw_policy_check_func *
 | 
						|
find_func(krb5_context context, const char *name)
 | 
						|
{
 | 
						|
    const struct kadm5_pw_policy_check_func *f;
 | 
						|
    char *module = NULL;
 | 
						|
    const char *p, *func;
 | 
						|
    int i;
 | 
						|
 | 
						|
    p = strchr(name, ':');
 | 
						|
    if (p) {
 | 
						|
	size_t len = p - name + 1;
 | 
						|
	func = p + 1;
 | 
						|
	module = malloc(len);
 | 
						|
	if (module == NULL)
 | 
						|
	    return NULL;
 | 
						|
	strlcpy(module, name, len);
 | 
						|
    } 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(func, 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)
 | 
						|
{
 | 
						|
    const struct kadm5_pw_policy_check_func *proc;
 | 
						|
    static char error_msg[1024];
 | 
						|
    const char *msg;
 | 
						|
    char **v, **vp;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    /*
 | 
						|
     * Check if we should use the old version of policy function.
 | 
						|
     */
 | 
						|
 | 
						|
    v = krb5_config_get_strings(context, NULL,
 | 
						|
				"password_quality",
 | 
						|
				"policies",
 | 
						|
				NULL);
 | 
						|
    if (v == NULL) {
 | 
						|
	msg = (*passwd_quality_check) (context, principal, pwd_data);
 | 
						|
	if (msg)
 | 
						|
	    krb5_set_error_message(context, 0, "password policy failed: %s", msg);
 | 
						|
	return msg;
 | 
						|
    }
 | 
						|
 | 
						|
    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";
 | 
						|
	    krb5_set_error_message(context, 0, "Failed to find password policy "
 | 
						|
				   "function: %s", *vp);
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
	ret = (proc->func)(context, principal, pwd_data, NULL,
 | 
						|
			   error_msg, sizeof(error_msg));
 | 
						|
	if (ret) {
 | 
						|
	    krb5_set_error_message(context, 0, "Password policy "
 | 
						|
				   "%s failed with %s",
 | 
						|
				   proc->name, error_msg);
 | 
						|
	    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);
 | 
						|
	if (msg)
 | 
						|
	    krb5_set_error_message(context, 0, "(old) password policy "
 | 
						|
				   "failed with %s", msg);
 | 
						|
 | 
						|
    }
 | 
						|
    return msg;
 | 
						|
}
 |