gssapi/mech: -Wcalloc-transposed args
[heimdal.git] / lib / kadm5 / password_quality.c
blob84c1d39979b158e15306a4af1587f1ab6285d8d1
1 /*
2 * Copyright (c) 1997-2000, 2003-2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
34 #include "kadm5_locl.h"
35 #include "kadm5-pwcheck.h"
37 #ifdef HAVE_SYS_WAIT_H
38 #include <sys/wait.h>
39 #endif
41 static int
42 min_length_passwd_quality (krb5_context context,
43 krb5_principal principal,
44 krb5_data *pwd,
45 const char *opaque,
46 char *message,
47 size_t length)
49 uint32_t min_length = krb5_config_get_int_default(context, NULL, 6,
50 "password_quality",
51 "min_length",
52 NULL);
54 if (pwd->length < min_length) {
55 strlcpy(message, "Password too short", length);
56 return 1;
57 } else
58 return 0;
61 static const char *
62 min_length_passwd_quality_v0 (krb5_context context,
63 krb5_principal principal,
64 krb5_data *pwd)
66 static char message[1024];
67 int ret;
69 message[0] = '\0';
71 ret = min_length_passwd_quality(context, principal, pwd, NULL,
72 message, sizeof(message));
73 if (ret)
74 return message;
75 return NULL;
79 static int
80 char_class_passwd_quality (krb5_context context,
81 krb5_principal principal,
82 krb5_data *pwd,
83 const char *opaque,
84 char *message,
85 size_t length)
87 const char *classes[] = {
88 "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
89 "abcdefghijklmnopqrstuvwxyz",
90 "1234567890",
91 " !\"#$%&'()*+,-./:;<=>?@\\]^_`{|}~"
93 int counter = 0, req_classes;
94 size_t i, len;
95 char *pw;
97 req_classes = krb5_config_get_int_default(context, NULL, 3,
98 "password_quality",
99 "min_classes",
100 NULL);
102 len = pwd->length + 1;
103 pw = malloc(len);
104 if (pw == NULL) {
105 strlcpy(message, "out of memory", length);
106 return 1;
108 strlcpy(pw, pwd->data, len);
109 len = strlen(pw);
111 for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) {
112 if (strcspn(pw, classes[i]) < len)
113 counter++;
115 memset(pw, 0, pwd->length + 1);
116 free(pw);
117 if (counter < req_classes) {
118 snprintf(message, length,
119 "Password doesn't meet complexity requirement.\n"
120 "Add more characters from at least %d of the\n"
121 "following classes:\n"
122 "1. English uppercase characters (A through Z)\n"
123 "2. English lowercase characters (a through z)\n"
124 "3. Base 10 digits (0 through 9)\n"
125 "4. Nonalphanumeric characters (e.g., !, $, #, %%)", req_classes);
126 return 1;
128 return 0;
131 static int
132 external_passwd_quality (krb5_context context,
133 krb5_principal principal,
134 krb5_data *pwd,
135 const char *opaque,
136 char *message,
137 size_t length)
139 krb5_error_code ret;
140 const char *program;
141 char *p;
142 pid_t child;
143 int status;
144 char reply[1024];
145 FILE *in = NULL, *out = NULL, *error = NULL;
147 if (memchr(pwd->data, '\n', pwd->length) != NULL) {
148 snprintf(message, length, "password contains newline, "
149 "not valid for external test");
150 return 1;
153 program = krb5_config_get_string(context, NULL,
154 "password_quality",
155 "external_program",
156 NULL);
157 if (program == NULL) {
158 snprintf(message, length, "external password quality "
159 "program not configured");
160 return 1;
163 ret = krb5_unparse_name(context, principal, &p);
164 if (ret) {
165 strlcpy(message, "out of memory", length);
166 return 1;
169 child = pipe_execv(&in, &out, &error, program, program, p, NULL);
170 if (child < 0) {
171 snprintf(message, length, "external password quality "
172 "program failed to execute for principal %s", p);
173 free(p);
174 return 1;
177 fprintf(in, "principal: %s\n"
178 "new-password: %.*s\n"
179 "end\n",
180 p, (int)pwd->length, (char *)pwd->data);
182 fclose(in);
184 if (fgets(reply, sizeof(reply), out) == NULL) {
186 if (fgets(reply, sizeof(reply), error) == NULL) {
187 snprintf(message, length, "external password quality "
188 "program failed without error");
190 } else {
191 reply[strcspn(reply, "\n")] = '\0';
192 snprintf(message, length, "External password quality "
193 "program failed: %s", reply);
196 fclose(out);
197 fclose(error);
198 wait_for_process(child);
199 free(p);
200 return 1;
202 reply[strcspn(reply, "\n")] = '\0';
204 fclose(out);
205 fclose(error);
207 status = wait_for_process(child);
209 if (SE_IS_ERROR(status) || SE_PROCSTATUS(status) != 0) {
210 snprintf(message, length, "external program failed: %s", reply);
211 free(p);
212 return 1;
215 if (strcmp(reply, "APPROVED") != 0) {
216 snprintf(message, length, "%s", reply);
217 free(p);
218 return 1;
221 free(p);
223 return 0;
227 static kadm5_passwd_quality_check_func_v0 passwd_quality_check =
228 min_length_passwd_quality_v0;
230 struct kadm5_pw_policy_check_func builtin_funcs[] = {
231 { "minimum-length", min_length_passwd_quality },
232 { "character-class", char_class_passwd_quality },
233 { "external-check", external_passwd_quality },
234 { NULL, NULL }
236 struct kadm5_pw_policy_verifier builtin_verifier = {
237 "builtin",
238 KADM5_PASSWD_VERSION_V1,
239 "Heimdal builtin",
240 builtin_funcs
243 static struct kadm5_pw_policy_verifier **verifiers;
244 static int num_verifiers;
247 * setup the password quality hook
250 void
251 kadm5_setup_passwd_quality_check(krb5_context context,
252 const char *check_library,
253 const char *check_function)
255 #ifdef HAVE_DLOPEN
256 void *handle;
257 void *sym;
258 int *version;
259 const char *tmp;
261 if(check_library == NULL) {
262 tmp = krb5_config_get_string(context, NULL,
263 "password_quality",
264 "check_library",
265 NULL);
266 if(tmp != NULL)
267 check_library = tmp;
269 if(check_function == NULL) {
270 tmp = krb5_config_get_string(context, NULL,
271 "password_quality",
272 "check_function",
273 NULL);
274 if(tmp != NULL)
275 check_function = tmp;
277 if(check_library != NULL && check_function == NULL)
278 check_function = "passwd_check";
280 if(check_library == NULL)
281 return;
282 handle = dlopen(check_library, RTLD_NOW | RTLD_LOCAL | RTLD_GROUP);
283 if(handle == NULL) {
284 krb5_warnx(context, "failed to open `%s'", check_library);
285 return;
287 version = (int *) dlsym(handle, "version");
288 if(version == NULL) {
289 krb5_warnx(context,
290 "didn't find `version' symbol in `%s'", check_library);
291 dlclose(handle);
292 return;
294 if(*version != KADM5_PASSWD_VERSION_V0) {
295 krb5_warnx(context,
296 "version of loaded library is %d (expected %d)",
297 *version, KADM5_PASSWD_VERSION_V0);
298 dlclose(handle);
299 return;
301 sym = dlsym(handle, check_function);
302 if(sym == NULL) {
303 krb5_warnx(context,
304 "didn't find `%s' symbol in `%s'",
305 check_function, check_library);
306 dlclose(handle);
307 return;
309 passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym;
310 #endif /* HAVE_DLOPEN */
313 #ifdef HAVE_DLOPEN
315 static krb5_error_code
316 add_verifier(krb5_context context, const char *check_library)
318 struct kadm5_pw_policy_verifier *v, **tmp;
319 void *handle;
320 int i;
322 handle = dlopen(check_library, RTLD_NOW | RTLD_LOCAL | RTLD_GROUP);
323 if(handle == NULL) {
324 krb5_warnx(context, "failed to open `%s'", check_library);
325 return ENOENT;
327 v = (struct kadm5_pw_policy_verifier *) dlsym(handle, "kadm5_password_verifier");
328 if(v == NULL) {
329 krb5_warnx(context,
330 "didn't find `kadm5_password_verifier' symbol "
331 "in `%s'", check_library);
332 dlclose(handle);
333 return ENOENT;
335 if(v->version != KADM5_PASSWD_VERSION_V1) {
336 krb5_warnx(context,
337 "version of loaded library is %d (expected %d)",
338 v->version, KADM5_PASSWD_VERSION_V1);
339 dlclose(handle);
340 return EINVAL;
342 for (i = 0; i < num_verifiers; i++) {
343 if (strcmp(v->name, verifiers[i]->name) == 0)
344 break;
346 if (i < num_verifiers) {
347 krb5_warnx(context, "password verifier library `%s' is already loaded",
348 v->name);
349 dlclose(handle);
350 return 0;
353 tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers));
354 if (tmp == NULL) {
355 krb5_warnx(context, "out of memory");
356 dlclose(handle);
357 return 0;
359 verifiers = tmp;
360 verifiers[num_verifiers] = v;
361 num_verifiers++;
363 return 0;
366 #endif
368 krb5_error_code
369 kadm5_add_passwd_quality_verifier(krb5_context context,
370 const char *check_library)
372 #ifdef HAVE_DLOPEN
374 if(check_library == NULL) {
375 krb5_error_code ret = 0;
376 char **strs;
377 char **tmp;
379 strs = krb5_config_get_strings(context, NULL,
380 "password_quality",
381 "policy_libraries",
382 NULL);
383 if (strs == NULL)
384 return 0;
386 for (tmp = strs; *tmp; tmp++) {
387 ret = add_verifier(context, *tmp);
388 if (ret)
389 break;
391 krb5_config_free_strings(strs);
392 return ret;
393 } else {
394 return add_verifier(context, check_library);
396 #else
397 return 0;
398 #endif /* HAVE_DLOPEN */
405 static const struct kadm5_pw_policy_check_func *
406 find_func(krb5_context context, const char *name)
408 const struct kadm5_pw_policy_check_func *f;
409 char *module = NULL;
410 const char *p, *func;
411 int i;
413 p = strchr(name, ':');
414 if (p) {
415 size_t len = p - name + 1;
416 func = p + 1;
417 module = malloc(len);
418 if (module == NULL)
419 return NULL;
420 strlcpy(module, name, len);
421 } else
422 func = name;
424 /* Find module in loaded modules first */
425 for (i = 0; i < num_verifiers; i++) {
426 if (module && strcmp(module, verifiers[i]->name) != 0)
427 continue;
428 for (f = verifiers[i]->funcs; f->name ; f++)
429 if (strcmp(func, f->name) == 0) {
430 if (module)
431 free(module);
432 return f;
435 /* Lets try try the builtin modules */
436 if (module == NULL || strcmp(module, "builtin") == 0) {
437 for (f = builtin_verifier.funcs; f->name ; f++)
438 if (strcmp(func, f->name) == 0) {
439 if (module)
440 free(module);
441 return f;
444 if (module)
445 free(module);
446 return NULL;
449 const char *
450 kadm5_check_password_quality (krb5_context context,
451 krb5_principal principal,
452 krb5_data *pwd_data)
454 const struct kadm5_pw_policy_check_func *proc;
455 static char error_msg[1024];
456 const char *msg;
457 char **v, **vp;
458 int ret;
461 * Check if we should use the old version of policy function.
464 v = krb5_config_get_strings(context, NULL,
465 "password_quality",
466 "policies",
467 NULL);
468 if (v == NULL) {
469 msg = (*passwd_quality_check) (context, principal, pwd_data);
470 if (msg)
471 krb5_set_error_message(context, 0, "password policy failed: %s", msg);
472 return msg;
475 error_msg[0] = '\0';
477 msg = NULL;
478 for(vp = v; *vp; vp++) {
479 proc = find_func(context, *vp);
480 if (proc == NULL) {
481 msg = "failed to find password verifier function";
482 krb5_set_error_message(context, 0, "Failed to find password policy "
483 "function: %s", *vp);
484 break;
486 ret = (proc->func)(context, principal, pwd_data, NULL,
487 error_msg, sizeof(error_msg));
488 if (ret) {
489 krb5_set_error_message(context, 0, "Password policy "
490 "%s failed with %s",
491 proc->name, error_msg);
492 msg = error_msg;
493 break;
496 krb5_config_free_strings(v);
498 /* If the default quality check isn't used, lets check that the
499 * old quality function the user have set too */
500 if (msg == NULL && passwd_quality_check != min_length_passwd_quality_v0) {
501 msg = (*passwd_quality_check) (context, principal, pwd_data);
502 if (msg)
503 krb5_set_error_message(context, 0, "(old) password policy "
504 "failed with %s", msg);
507 return msg;