From 378a5a27de2f7076ce1fe02a39940e3aab420daf Mon Sep 17 00:00:00 2001 From: elad Date: Sat, 18 Feb 2006 10:52:48 +0000 Subject: [PATCH] Some stuff that's been sitting in my tree for too long... Improve the pw_policy(3) API by splitting it to two functions, one to load the policy from /etc/passwd.conf and another to test passwords against the policy. Some bug fixes, more consistent code, and man-page updates. Minor for libutil bumped. --- distrib/sets/lists/base/shl.mi | 6 +- lib/libutil/pw_policy.3 | 158 ++++++++++------ lib/libutil/pw_policy.c | 324 ++++++++++++++++++++------------- lib/libutil/shlib_version | 4 +- 4 files changed, 298 insertions(+), 194 deletions(-) diff --git a/distrib/sets/lists/base/shl.mi b/distrib/sets/lists/base/shl.mi index a2807bfaa6b5..6d3f83e9af62 100644 --- a/distrib/sets/lists/base/shl.mi +++ b/distrib/sets/lists/base/shl.mi @@ -1,4 +1,4 @@ -# $NetBSD: shl.mi,v 1.336 2006/02/13 16:51:35 christos Exp $ +# $NetBSD: shl.mi,v 1.337 2006/02/18 10:52:48 elad Exp $ # Note: libtermcap and libtermlib are hardlinked and share the same version. ./lib/libc.so.12.136 base-sys-shlib ./lib/libcrypt.so.0.2 base-sys-shlib @@ -11,7 +11,7 @@ ./lib/libradius.so.1.0 base-sys-shlib ./lib/libtermcap.so.0.6 base-sys-shlib ./lib/libtermlib.so.0.6 base-sys-shlib -./lib/libutil.so.7.8 base-sys-shlib +./lib/libutil.so.7.9 base-sys-shlib ./lib/libz.so.1.0 base-sys-shlib ./usr/lib/i18n/libBIG5.so.4.4 base-i18n-shlib ./usr/lib/i18n/libEUC.so.4.4 base-i18n-shlib @@ -85,7 +85,7 @@ ./usr/lib/libtermcap.so.0.6 base-sys-shlib ./usr/lib/libtermlib.so.0.6 base-sys-shlib ./usr/lib/libusbhid.so.1.0 base-sys-shlib -./usr/lib/libutil.so.7.8 base-sys-shlib +./usr/lib/libutil.so.7.9 base-sys-shlib ./usr/lib/libwrap.so.0.2 base-net-shlib ./usr/lib/libz.so.1.0 base-sys-shlib ./usr/lib/security/pam_afslog.so.0 base-sys-shlib kerberos,pam diff --git a/lib/libutil/pw_policy.3 b/lib/libutil/pw_policy.3 index 900ce4c9c441..691aa643c912 100644 --- a/lib/libutil/pw_policy.3 +++ b/lib/libutil/pw_policy.3 @@ -1,6 +1,6 @@ -.\" $NetBSD: pw_policy.3,v 1.1 2005/09/14 11:36:52 elad Exp $ +.\" $NetBSD: pw_policy.3,v 1.2 2006/02/18 10:52:48 elad Exp $ .\" -.\" Copyright 2005 Elad Efrat +.\" Copyright 2005, 2006 Elad Efrat .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -23,10 +23,11 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd September 13, 2005 +.Dd February 16, 2006 .Dt PW_POLICY 3 .Os .Sh NAME +.Nm pw_policy_load , .Nm pw_policy_test .Nd password policy enforcement .Sh LIBRARY @@ -34,19 +35,26 @@ .Sh SYNOPSIS .In util.h .Ft int -.Fn pw_policy_test "char *pw" "void *key" "int how" +.Fn pw_policy_load "struct pw_policy *policy" "void *key" "int how" +.Ft int +.Fn pw_policy_test "struct pw_policy *policy" "char *pw" .Sh DESCRIPTION The +.Fn pw_policy_load +and .Fn pw_policy_test -function checks if the password passed in -.Ar pw -follows the system's password policy as specified in +functions are used as an interface to the system's password policy +as specified in .Pa /etc/passwd.conf . .Pp +.Fn pw_policy_load +will load a password policy into +.Ar policy . +.Pp Using .Xr pw_getconf 3 terminology, -.Fn pw_policy_test +.Fn pw_policy_load accepts a .Ar key to be used when searching @@ -59,25 +67,23 @@ To allow calling from various program contexts and using various password policy retrieval schemes, .Ar how tells -.Fn pw_policy_test +.Fn pw_policy_load how to treat .Ar key . .Pp -The value of +Possible values for .Ar how -can be -.Dv PW_POLICY_BYSTRING -to indicate the passed +are: +.Pp +.Bl -tag -width kungfuninja -compact +.It Li PW_POLICY_BYSTRING .Ar key -is to be used as a +is used as a .Ft char * , looking up the string it contains in .Pa /etc/passwd.conf . .Pp -If -.Ar how -is -.Dv PW_POLICY_BYPASSWD , +.It Li PW_POLICY_BYPASSWD .Ar key is used as a .Ft struct passwd * , @@ -86,16 +92,13 @@ first looking up the username in and if no key can be found, it will try the login class in .Ft pw_class . .Pp -Using the value -.Dv PW_POLICY_BYGROUP -for -.Ar how -will use +.It Li PW_POLICY_BYGROUP .Ar key -as a +is used as a .Ft struct group * , looking up the group name in .Ft gr_name . +.El .Pp If .Ar key @@ -106,6 +109,12 @@ or no specified key can be found, the default key, is used. If even the default key can't be found, the password is accepted as no policy is defined. +.Pp +.Fn pw_policy_test +can be used to check if the password in +.Ar pw +is compliant with the policy in +.Ar policy . .Sh BUILT-IN POLICY SYNTAX Available built-in policy options include the following: .Pp @@ -197,55 +206,69 @@ And that the user must change character class every 2 characters: ntoggles = *-2 .Ed .Sh RETURN VALUES -Upon success, meaning the password follows the specified policy, -.Fn pw_policy_test -will return 0. +.Fn pw_policy_load +returns 0 upon success, or any of the below errors upon failure: +.Bl -tag -width Er +.It Bq Er ENOENT +The +.Pa /etc/passwd.conf +is missing. +.It Bq Er EFAULT +NULL pointer passed for policy storage. +.It Bq Er EINVAL +Invalid value for the +.Ar how +parameter or an invalid value in the password policy specification. +.El .Pp .Fn pw_policy_test -will fail and return any of the following if: +returns 0 if the password follows the policy, or any of the following +values: .Bl -tag -width Er .It Bq Er EPERM -The password does not follow the specified policy. +The password does not follow the password policy. .It Bq Er EFAULT -The password string provided is -.Dv NULL . -.It Bq Er ENOENT -.Pa /etc/passwd.conf -could not be found. -.It Bq Er EINVAL -.Ar how -has an invalid value. +NULL pointer was passed as the password. .El +.Pp +In addition, +.Fn pw_policy_load +and +.Fn pw_policy_test +can also return any of the values returned by the called handlers. .Sh FILES .Bl -tag -width /etc/passwd.conf -compact .It Pa /etc/passwd.conf password configuration file. .El .Sh EXAMPLES -To check if -.Ar the_password -follows the system's default password policy: +Initialize a password policy structure: .Bd -literal -offset indent -error = pw_policy_test(the_password, NULL, 0); -if (error == EPERM) - (void)printf("Please refer to the password policy.\en"); +struct pw_policy policy = PW_POLICY_INIT; .Ed .Pp -To check if -.Ar the_password , -entered by a user whose password database entry is in -.Ar pw_entry -follows the specified policy: +Load the system global password policy into +.Ar policy : .Bd -literal -offset indent -error = pw_policy_test(the_password, pw_entry, PW_POLICY_BYPASSWD); +error = pw_policy_load(&policy, NULL, 0); +if (error) + errx(1, "Can't load password policy"); +.Ed +.Pp +Load a policy for a user whose password database entry is in +.Ar pw_entry +into +.Ar policy : +.Bd -literal -offset indent +error = pw_policy_load(&policy, pw_entry, PW_POLICY_BYPASSWD); if (error == EPERM) { - (void)printf("Please refer to the password policy.\en"); + warnx("Please refer to the password policy"); return (EPERM); } .Ed .Pp Note that -.Fn pw_policy_test +.Fn pw_policy_load will first look for a password policy for the username in .Ar pw_entry-\*[Gt]pw_name , if not found, it will try looking for a policy for the login class in @@ -257,16 +280,33 @@ To handle cases where there is no .Pa /etc/passwd.conf , it might be desired to fallback to an internal policy: .Bd -literal -offset indent -error = pw_policy(the_password, NULL, 0); +error = pw_policy_load(&policy, NULL, 0); if (error == ENOENT) { /* No /etc/passwd.conf. Just check minimum length. */ - if (strlen(the_password) \*[Lt] 8) { - (void)printf("Please use at least 8 chars.\en"); - return (EPERM); - } + policy.minlen = 8; } .Ed .Pp +Load the password policy for a group +whose group database entry is in +.Ar grent , +into +.Ar policy : +error = pw_policy_load(&policy, grent, PW_POLICY_BYGROUP); +if (error) + errx(1, "Can't load password policy for \"%s\"", grent-\*[Gt]gr_name); +.Ed +.Pp +Check if +.Ar the_password +follows the policy in +.Ar policy : +.Bd -literal -offset indent +error = pw_policy_test(&policy, the_password); +if (error == EPERM) + warnx("Please refer to the password policy"); +.Ed +.Pp An example for a common default password policy in .Pa /etc/passwd.conf : .Bd -literal -offset indent @@ -288,8 +328,10 @@ A different policy that might be used: .Xr passwd.conf 5 .Sh HISTORY The -.Nm -function first appeared in +.Fn pw_policy_load +and +.Fn pw_policy_test +functions first appeared in .Nx 4.0 . .Sh AUTHORS .An Elad Efrat diff --git a/lib/libutil/pw_policy.c b/lib/libutil/pw_policy.c index c7baa36d2cb8..0aeb0646ad87 100644 --- a/lib/libutil/pw_policy.c +++ b/lib/libutil/pw_policy.c @@ -1,7 +1,7 @@ -/* $NetBSD: pw_policy.c,v 1.2 2005/09/16 22:38:48 elad Exp $ */ +/* $NetBSD: pw_policy.c,v 1.3 2006/02/18 10:52:48 elad Exp $ */ /*- - * Copyright 2005 Elad Efrat + * Copyright 2005, 2006 Elad Efrat * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,26 +41,19 @@ #define PW_POLICY_DEFAULTKEY "pw_policy" -#define PW_GETCONF(buf, len, key, opt) \ - do { \ - memset(buf, 0, len); \ - pw_getconf(buf, len, key, opt); \ - } while (/*CONSTCOND*/0) +#define LOAD_POLICY 0 +#define TEST_POLICY 1 -#define NEED_NONE 0 -#define NEED_ARG 1 -#define NEED_ARGS 2 +#define MINMAX_ERR(min, max, n) ((min == 0 || max == 0) && n != 0) || \ + (min > 0 && min > n) || \ + (max > 0 && max < n) -#define HANDLER_PROTO char *, size_t, void *, void * -#define HANDLER_ARGS char *pw, size_t len, void *arg, void *arg2 -#define HANDLER_SANE(needarg) \ - ((pw != NULL) && (len != 0) && \ - ((/*CONSTCOND*/needarg == NEED_NONE) || \ - (/*CONSTCOND*/needarg == NEED_ARG && arg != NULL) || \ - (/*CONSTCOND*/needarg == NEED_ARGS && arg != NULL && arg2 != NULL))) +#define HANDLER_PROTO struct pw_policy *, int, char *, void *, void * +#define HANDLER_ARGS struct pw_policy *policy, int flag, char *pw, void *arg, void *arg2 static int pw_policy_parse_num(char *, int32_t *); static int pw_policy_parse_range(char *, int32_t *, int32_t *); + static int pw_policy_handle_len(HANDLER_PROTO); static int pw_policy_handle_charclass(HANDLER_PROTO); static int pw_policy_handle_nclasses(HANDLER_PROTO); @@ -68,7 +61,7 @@ static int pw_policy_handle_ntoggles(HANDLER_PROTO); struct pw_policy_handler { const char *name; - int (*handler)(char *, size_t, void *, void *); + int (*handler)(HANDLER_PROTO); void *arg2; }; @@ -87,23 +80,26 @@ static int pw_policy_parse_num(char *s, int32_t *n) { char *endptr = NULL; - unsigned long m; + long l; if (s == NULL || n == NULL) return (EFAULT); - m = strtoul(s, &endptr, 10); - if (*endptr != '\0' || m == ULONG_MAX) - return (EINVAL); + if (s[0] == '*' && s[1] == '\0') { + *n = -1; + return (0); + } - /* - * We receive a signed 32-bit integer, but we only allow - * unsigned 16-bit values. - */ - if (m >= (unsigned long)UINT16_MAX) + l = strtol(s, &endptr, 10); + if (*endptr != '\0') + return (EINVAL); + if (errno == ERANGE && (l == LONG_MIN || l == LONG_MAX)) return (ERANGE); - *n = (int32_t)m; + if (l < 0 || l >= UINT16_MAX) + return (ERANGE); + + *n = (int32_t)l; return (0); } @@ -124,11 +120,11 @@ pw_policy_parse_range(char *range, int32_t *n, int32_t *m) /* Single characters: * = any number, 0 = none. */ if (range[0] == '0' && range[1] == '\0') { - *n = *m = -1; + *n = *m = 0; return (0); } if (range[0] == '*' && range[1] == '\0') { - *n = *m = 0; + *n = *m = -1; return (0); } @@ -152,25 +148,35 @@ pw_policy_parse_range(char *range, int32_t *n, int32_t *m) return (0); } +/*ARGSUSED*/ static int pw_policy_handle_len(HANDLER_ARGS) { - int32_t n = 0, m = 0; + switch (flag) { + case LOAD_POLICY: + /* Here, '0' and '*' mean the same. */ + if (pw_policy_parse_range(arg, &policy->minlen, &policy->maxlen) != 0) + return (EINVAL); - if (!HANDLER_SANE(NEED_ARG)) - return (EFAULT); + if (policy->minlen < 0) + policy->minlen = 0; + if (policy->maxlen < 0) + policy->maxlen = 0; - /* Here, '0' and '*' mean the same. */ - if (pw_policy_parse_range(arg, &n, &m) != 0) - return (EINVAL); + break; - if (n < 0) - n = 0; - if (m < 0) - m = 0; + case TEST_POLICY: { + size_t len; - if ((n && len < n) || (m && len > m)) - return (EPERM); + len = strlen(pw); + + if ((policy->minlen && len < policy->minlen) || + (policy->maxlen && len > policy->maxlen)) + return (EPERM); + + break; + } + } return (0); } @@ -179,147 +185,175 @@ static int pw_policy_handle_charclass(HANDLER_ARGS) { int (*ischarclass)(int); - int32_t n = 0, m = 0, count = 0; + int32_t *n = NULL, *m = NULL, count = 0; - if (!HANDLER_SANE(NEED_ARGS)) - return (EFAULT); + if (arg2 == islower) { + n = &policy->minlower; + m = &policy->maxlower; + } else if (arg2 == isupper) { + n = &policy->minupper; + m = &policy->maxupper; + } else if (arg2 == isdigit) { + n = &policy->mindigits; + m = &policy->maxdigits; + } else if (arg2 == ispunct) { + n = &policy->minpunct; + m = &policy->maxpunct; + } - if (pw_policy_parse_range(arg, &n, &m) != 0) - return (EINVAL); + switch (flag) { + case LOAD_POLICY: + if ((pw_policy_parse_range(arg, n, m) != 0)) + return (EINVAL); - ischarclass = arg2; + break; - if (n == 0 && m == 0) - return (0); + case TEST_POLICY: + ischarclass = arg2; - do { - if (ischarclass((int)*pw++)) - count++; - } while (*pw != '\0'); + do { + if (!isspace((int)*pw) && ischarclass((int)*pw++)) + count++; + } while (*pw != '\0'); - if (n == -1 && count) - return (EPERM); + if (MINMAX_ERR(*n, *m, count)) + return (EPERM); - if ((n >= 0 && count < n) || (m >= 0 && count > m)) - return (EPERM); + break; + } return (0); } +/*ARGSUSED*/ static int pw_policy_handle_nclasses(HANDLER_ARGS) { - int have_lower = 0, have_upper = 0, have_digit = 0, have_punct = 0; - int32_t n = 0, m = 0, nsets = 0; + switch (flag) { + case LOAD_POLICY: + if (pw_policy_parse_range(arg, &policy->minclasses, &policy->maxclasses) != 0) + return (EINVAL); - if (!HANDLER_SANE(NEED_ARG)) - return (EFAULT); + break; - if (pw_policy_parse_range(arg, &n, &m) != 0) - return (EINVAL); + case TEST_POLICY: { + int have_lower = 0, have_upper = 0, have_digit = 0, have_punct = 0; + int32_t nsets = 0; - while (*pw != '\0') { - if (islower((unsigned char)*pw) && !have_lower) { - have_lower = 1; - nsets++; - } else if (isupper((unsigned char)*pw) && !have_upper) { - have_upper = 1; - nsets++; - } else if (isdigit((unsigned char)*pw) && !have_digit) { - have_digit = 1; - nsets++; - } else if (ispunct((unsigned char)*pw) && !have_punct) { - have_punct = 1; - nsets++; + while (*pw != '\0') { + if (islower((unsigned char)*pw) && !have_lower) { + have_lower = 1; + nsets++; + } else if (isupper((unsigned char)*pw) && !have_upper) { + have_upper = 1; + nsets++; + } else if (isdigit((unsigned char)*pw) && !have_digit) { + have_digit = 1; + nsets++; + } else if (ispunct((unsigned char)*pw) && !have_punct) { + have_punct = 1; + nsets++; + } + pw++; } - pw++; - } - if ((n >= 0 && nsets < n) || (m >= 0 && nsets > m)) - return (EPERM); + if (MINMAX_ERR(policy->minclasses, policy->maxclasses, nsets)) + return (EPERM); + + break; + } + } return (0); } +/*ARGSUSED*/ static int pw_policy_handle_ntoggles(HANDLER_ARGS) { - int previous_set = 0, current_set = 0; - int32_t n = 0, m = 0, ntoggles = 0; + switch (flag) { + case LOAD_POLICY: + if (pw_policy_parse_range(arg, &policy->mintoggles, &policy->maxtoggles) != 0) + return (EINVAL); - if (!HANDLER_SANE(NEED_ARG)) - return (EFAULT); + break; - if (pw_policy_parse_range(arg, &n, &m) != 0) - return (EINVAL); + case TEST_POLICY: { + int previous_set = 0, current_set = 0; + int32_t ntoggles = 0, current_ntoggles = 0; #define CHAR_CLASS(c) (islower((unsigned char)(c)) ? 1 : \ - isupper((unsigned char)(c)) ? 2 : \ - isdigit((unsigned char)(c)) ? 3 : \ - ispunct((unsigned char)(c)) ? 4 : \ + isupper((unsigned char)(c)) ? 2 : \ + isdigit((unsigned char)(c)) ? 3 : \ + ispunct((unsigned char)(c)) ? 4 : \ 0) - while (*pw != '\0') { - current_set = CHAR_CLASS(*pw); - if (!current_set) { - return (EINVAL); - } + while (*pw != '\0') { + if (!isspace((int)*pw)) { + current_set = CHAR_CLASS(*pw); + if (!current_set) { + return (EINVAL); + } - if (!previous_set || current_set == previous_set) { - ntoggles++; - } else { - ntoggles = 1; - } + if (!previous_set || current_set == previous_set) { + current_ntoggles++; + } else { + if (current_ntoggles > ntoggles) + ntoggles = current_ntoggles; - previous_set = current_set; - pw++; - } + current_ntoggles = 1; + } + + previous_set = current_set; + } + + pw++; + } #undef CHAR_CLASS - if ((n >= 0 && ntoggles < n) || (m >= 0 && ntoggles > m)) - return (EPERM); + if (MINMAX_ERR(policy->mintoggles, policy->maxtoggles, ntoggles)) + return (EPERM); + + break; + } + } return (0); } int -pw_policy_test(char *pw, void *key, int how) +pw_policy_load(struct pw_policy *policy, void *key, int how) { struct pw_policy_handler *hp; char buf[BUFSIZ]; - size_t pwlen; - /* - * No password provided, fail the test. - */ - if (pw == NULL) - return (EFAULT); - - /* - * If there's no /etc/passwd.conf, we allow the password. - */ + /* If there's no /etc/passwd.conf, don't touch the policy. */ if (access(_PATH_PASSWD_CONF, R_OK) == -1) return (ENOENT); - /* - * No key provided. Use default. - */ + if (policy == NULL) + return (EFAULT); + + /* No key provided. Use default. */ if (key == NULL) { key = __UNCONST(PW_POLICY_DEFAULTKEY); - goto test_policies; + goto load_policies; } + errno = 0; + memset(buf, 0, sizeof(buf)); + switch (how) { case PW_POLICY_BYSTRING: /* * Check for provided key. If non-existant, fallback to * the default. */ - errno = 0; - PW_GETCONF(buf, sizeof(buf), key, "foo"); + pw_getconf(buf, sizeof(buf), key, "foo"); if (errno == ENOTDIR) key = __UNCONST(PW_POLICY_DEFAULTKEY); + break; case PW_POLICY_BYPASSWD: { @@ -330,16 +364,17 @@ pw_policy_test(char *pw, void *key, int how) * try a policy for the login class. If can't find any, * fallback to the default. */ - errno = 0; - PW_GETCONF(buf, sizeof(buf), pentry->pw_name, "foo"); + pw_getconf(buf, sizeof(buf), pentry->pw_name, "foo"); if (errno == ENOTDIR) { - PW_GETCONF(buf, sizeof(buf), pentry->pw_class, "foo"); + memset(buf, 0, sizeof(buf)); + pw_getconf(buf, sizeof(buf), pentry->pw_class, "foo"); if (errno == ENOTDIR) key = __UNCONST(PW_POLICY_DEFAULTKEY); else key = pentry->pw_class; } else key = pentry->pw_name; + break; } @@ -350,12 +385,12 @@ pw_policy_test(char *pw, void *key, int how) * Check for policy for given group. If can't find any, * fallback to the default. */ - errno = 0; - PW_GETCONF(buf, sizeof(buf), gentry->gr_name, "foo"); + pw_getconf(buf, sizeof(buf), gentry->gr_name, "foo"); if (errno == ENOTDIR) key = __UNCONST(PW_POLICY_DEFAULTKEY); else key = gentry->gr_name; + break; } @@ -367,13 +402,40 @@ pw_policy_test(char *pw, void *key, int how) return (EINVAL); } - test_policies: - pwlen = strlen(pw); + load_policies: + hp = &handlers[0]; + while (hp->name != NULL) { + int error; + + memset(buf, 0, sizeof(buf)); + pw_getconf(buf, sizeof(buf), key, hp->name); + if (*buf) { + error = hp->handler(policy, LOAD_POLICY, NULL, buf, + hp->arg2); + if (error) + return (error); + } + + hp++; + } + + return (0); +} + +int +pw_policy_test(struct pw_policy *policy, char *pw) +{ + struct pw_policy_handler *hp; + + if (policy == NULL) + return (0); + + if (pw == NULL) + return (EFAULT); hp = &handlers[0]; while (hp->name != NULL) { - PW_GETCONF(buf, sizeof(buf), key, hp->name); - if (*buf && hp->handler(pw, pwlen, buf, hp->arg2) != 0) + if (hp->handler(policy, TEST_POLICY, pw, NULL, hp->arg2) != 0) return (EPERM); hp++; diff --git a/lib/libutil/shlib_version b/lib/libutil/shlib_version index 81f20554fb5f..641f054a3255 100644 --- a/lib/libutil/shlib_version +++ b/lib/libutil/shlib_version @@ -1,5 +1,5 @@ -# $NetBSD: shlib_version,v 1.38 2005/12/20 21:32:20 christos Exp $ +# $NetBSD: shlib_version,v 1.39 2006/02/18 10:52:48 elad Exp $ # Remember to update distrib/sets/lists/base/shl.* when changing # major=7 -minor=8 +minor=9