/*
password policy module is called with:
- int check_password (char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
+ int check_password (char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
*pPasswd: new password
- **ppErrStr: pointer to the string containing the error message
+ *ppErrmsg: pointer to a struct berval containing space for an error message of length bv_len
*e: pointer to the current user entry
*pArg: pointer to a struct berval holding the value of pwdCheckModuleArg attr
*/
#endif
static int
-realloc_error_message(char **target, int curlen, int nextlen)
+realloc_error_message(const char *orig, char **target, int curlen, int nextlen)
{
if (curlen < nextlen + MEMORY_MARGIN) {
ppm_log(LOG_WARNING,
"ppm: Reallocating szErrStr from %d to %d", curlen,
nextlen + MEMORY_MARGIN);
- ber_memfree(*target);
+ if (*target != orig)
+ ber_memfree(*target);
curlen = nextlen + MEMORY_MARGIN;
*target = (char *) ber_memalloc(curlen);
}
int
-check_password(char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
+check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
{
Entry *pEntry = e;
(*(struct berval*)pwdCheckModuleArg).bv_val);
#endif
- char *szErrStr = (char *) ber_memalloc(MEM_INIT_SZ);
- int mem_len = MEM_INIT_SZ;
+ char *origmsg = ppErrmsg->bv_val;
+ char *szErrStr = origmsg;
+ int mem_len = ppErrmsg->bv_len;
int numParam = 0; // Number of params in current configuration
int useCracklib;
}
if (nQuality < minQuality) {
- mem_len = realloc_error_message(&szErrStr, mem_len,
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
strlen(PASSWORD_QUALITY_SZ) +
strlen(pEntry->e_nname.bv_val) + 4);
sprintf(szErrStr, PASSWORD_QUALITY_SZ, pEntry->e_nname.bv_val,
if ((nbInClass[i] < fileConf[i].min) &&
strlen(fileConf[i].value.sVal) != 0) {
// constraint is not satisfied... goto fail
- mem_len = realloc_error_message(&szErrStr, mem_len,
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
strlen(PASSWORD_CRITERIA) +
strlen(pEntry->e_nname.bv_val) +
2 + PARAM_MAX_LEN);
// Password checking done, now loocking for forbiddenChars criteria
if (nForbiddenChars > 0) { // at least 1 forbidden char... goto fail
- mem_len = realloc_error_message(&szErrStr, mem_len,
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
strlen(PASSWORD_FORBIDDENCHARS) +
strlen(pEntry->e_nname.bv_val) + 2 +
VALUE_MAX_LEN);
// Too much consecutive characters of the same class
ppm_log(LOG_NOTICE, "ppm: Too much consecutive chars for class %s",
fileConf[i].param);
- mem_len = realloc_error_message(&szErrStr, mem_len,
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
strlen(PASSWORD_MAXCONSECUTIVEPERCLASS) +
strlen(pEntry->e_nname.bv_val) + 2 +
PARAM_MAX_LEN);
if (( fd = fopen ( cracklibDictFiles[j], "r")) == NULL ) {
ppm_log(LOG_NOTICE, "ppm: Error while reading %s file",
cracklibDictFiles[j]);
- mem_len = realloc_error_message(&szErrStr, mem_len,
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
strlen(GENERIC_ERROR));
sprintf(szErrStr, GENERIC_ERROR);
goto fail;
if ( res != NULL ) {
ppm_log(LOG_NOTICE, "ppm: cracklib does not validate password for entry %s",
pEntry->e_nname.bv_val);
- mem_len = realloc_error_message(&szErrStr, mem_len,
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
strlen(PASSWORD_CRACKLIB) +
strlen(pEntry->e_nname.bv_val));
sprintf(szErrStr, PASSWORD_CRACKLIB, pEntry->e_nname.bv_val);
if (checkRDN == 1 && containsRDN(pPasswd, pEntry->e_nname.bv_val))
// RDN check enabled and a token from RDN is found in password: goto fail
{
- mem_len = realloc_error_message(&szErrStr, mem_len,
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
strlen(RDN_TOKEN_FOUND) +
strlen(pEntry->e_nname.bv_val));
sprintf(szErrStr, RDN_TOKEN_FOUND, pEntry->e_nname.bv_val);
goto fail;
}
- *ppErrStr = strdup("");
- ber_memfree(szErrStr);
+ szErrStr[0] = '\0';
return (LDAP_SUCCESS);
fail:
- *ppErrStr = strdup(szErrStr);
- ber_memfree(szErrStr);
+ ppErrmsg->bv_val = szErrStr;
+ ppErrmsg->bv_len = mem_len;
return (EXIT_FAILURE);
}
and password policy expiring (2.16.840.1.113730.3.4.5) controls when
appropriate. The controls are not sent for bind requests where the Password
policy control has already been requested. Default is not to send the controls.
+.TP
+.B ppolicy_check_module <path>
+Specify the path of a loadable module containing a
+.B check_password()
+function for additional password quality checks. The use of this module
+is described further below in the description of the
+.B pwdPolicyChecker
+objectclass.
+
+Note: The user-defined loadable module must be in
+.B slapd's
+standard executable search PATH, or an absolute path must be provided.
+
+Note: Use of a
+.B ppolicy_check_module
+is a non-standard extension to the LDAP password
+policy proposal.
+
.SH OBJECT CLASS
The
NAME 'pwdPolicyChecker'
AUXILIARY
SUP top
- MAY ( pwdCheckModule $ pwdCheckModuleArg ) )
+ MAY ( pwdCheckModule $ pwdCheckModuleArg $ pwdUseCheckModule ) )
.RE
.P
Every account that should be subject to password policy control should
is zero (0) or one (1)) or refuse it (if
.B pwdCheckQuality
is two (2)). If the number of characters should be enforced with regards
-to a particular encoding, the use of an appropriate pwdCheckModule is
-required.
+to a particular encoding, the use of an appropriate
+.B ppolicy_check_module
+is required.
.LP
.RS 4
( 1.3.6.1.4.1.42.2.27.8.1.6
is zero (0) or one (1)) or refuse it (if
.B pwdCheckQuality
is two (2)). If the number of characters should be enforced with regards
-to a particular encoding, the use of an appropriate pwdCheckModule is
-required.
+to a particular encoding, the use of an appropriate
+.B ppolicy_check_module
+is required.
.LP
.RS 4
( 1.3.6.1.4.1.42.2.27.8.1.31
SINGLE\-VALUE )
.RE
-.BR pwdCheckModule / pwdCheckModuleArg
+.BR pwdUseCheckModule / pwdCheckModuleArg
.P
-This attribute names a user-defined loadable module that must
+The
+.B pwdUseCheckModule
+attribute enables use of a loadable module previously configured with
+.B ppolicy_check_module
+for the current policy. The module must
instantiate the check_password() function. This function
will be called to further check a new password if
.B pwdCheckQuality
.RS 4
int
.I check_password
-(char *pPasswd, char **ppErrStr, Entry *pEntry, struct berval *pArg);
+(char *pPasswd, struct berval *pErrmsg, Entry *pEntry, struct berval *pArg);
.RE
The
.B pPasswd
parameter contains the clear-text user password, the
-.B ppErrStr
-parameter contains a double pointer that allows the function
+.B pErrmsg
+parameter points to a
+.B struct berval
+containing space
to return human-readable details about any error it encounters.
+The
+.B bv_len
+field must contain the size of the space provided
+by the
+.B bv_val
+field.
The
.B pEntry
in the effective password policy, if set, otherwise NULL.
If
-.B ppErrStr
+.B pErrmsg
is NULL, then
.I funcName
-must NOT attempt to use it/them.
+must NOT attempt to use it.
A return value of LDAP_SUCCESS from the called
function indicates that the password is ok, any other value
indicates that the password is unacceptable. If the password is
unacceptable, the server will return an error to the client, and
-.B ppErrStr
+.B pErrmsg
may be used to return a human-readable textual explanation of the
-error. The error string must be dynamically allocated as it will
+error. If the space passed in by the caller is too small, the function
+may replace it with a dynamically allocated buffer, which will
be free()'d by slapd.
+
+The
+.B pwdCheckModule
+attribute is now obsolete and is ignored.
+
.LP
.RS 4
( 1.3.6.1.4.1.4754.1.99.1
NAME 'pwdCheckModule'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ OBSOLETE
SINGLE\-VALUE )
( 1.3.6.1.4.1.4754.1.99.2
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
DESC 'Argument to pass to check_password() function'
SINGLE\-VALUE )
+
+( 1.3.6.1.4.1.4754.1.99.3
+ NAME 'pwdUseCheckModule'
+ EQUALITY booleanMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+ SINGLE\-VALUE )
.RE
-.P
-Note:
-The user-defined loadable module named by
-.B pwdCheckModule
-must be in
-.B slapd's
-standard executable search PATH.
-.P
-Note:
-.B pwdCheckModule
-is a non-standard extension to the LDAP password
-policy proposal.
.SH OPERATIONAL ATTRIBUTES
.P
#include <ac/ctype.h>
#include "slap-config.h"
-#ifndef MODULE_NAME_SZ
-#define MODULE_NAME_SZ 256
-#endif
-
#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE
#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE 5
#endif
+ /* External password quality checking function.
+ * The error message must have a preallocated buffer and size
+ * passed in. Module can still allocate a buffer for
+ * it if the provided one is too small.
+ */
+typedef int (check_func)( char *passwd, struct berval *errmsg, Entry *ent, struct berval *arg );
+#define ERRBUFSIZ 256
+
/* Per-instance configuration information */
typedef struct pp_info {
struct berval def_policy; /* DN of default policy subentry */
int forward_updates; /* use frontend for policy state updates */
int disable_write;
int send_netscape_controls; /* send netscape password controls */
+ char *pwdCheckModule; /* name of module to dynamically
+ load to check password */
+ lt_dlhandle pwdCheckHandle; /* handle from lt_dlopen */
+ check_func *pwdCheckFunc;
ldap_pvt_thread_mutex_t pwdFailureTime_mutex;
} pp_info;
int pwdSafeModify; /* 0 = old password doesn't need to come
with password change request
1 = password change must supply existing pwd */
- char pwdCheckModule[MODULE_NAME_SZ]; /* name of module to dynamically
- load to check password */
+ int pwdUseCheckModule; /* 0 = do not use password check module, 1 = use */
struct berval pwdCheckModuleArg; /* Optional argument to the password check
module */
} PassPolicy;
*ad_pwdMaxFailure, *ad_pwdGraceExpiry, *ad_pwdGraceAuthNLimit,
*ad_pwdExpireWarning, *ad_pwdMinDelay, *ad_pwdMaxDelay,
*ad_pwdLockoutDuration, *ad_pwdFailureCountInterval,
- *ad_pwdCheckModule, *ad_pwdCheckModuleArg, *ad_pwdLockout,
+ *ad_pwdCheckModule, *ad_pwdCheckModuleArg, *ad_pwdUseCheckModule, *ad_pwdLockout,
*ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
*ad_pwdAttribute, *ad_pwdMaxRecordedFailure;
"NAME ( 'pwdCheckModule' ) "
"EQUALITY caseExactIA5Match "
"SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 "
- "DESC 'Loadable module that instantiates check_password() function' "
+ "DESC 'Obsolete, no longer used' "
+ "OBSOLETE "
"SINGLE-VALUE )",
&ad_pwdCheckModule },
{ "( 1.3.6.1.4.1.4754.1.99.2 "
"DESC 'Argument to pass to check_password() function' "
"SINGLE-VALUE )",
&ad_pwdCheckModuleArg },
+ { "( 1.3.6.1.4.1.4754.1.99.3 "
+ "NAME ( 'pwdUseCheckModule' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "DESC 'Toggle use of the loaded pwdCheckModule' "
+ "SINGLE-VALUE )",
+ &ad_pwdUseCheckModule },
{ NULL, NULL }
};
"NAME 'pwdPolicyChecker' "
"SUP top "
"AUXILIARY "
- "MAY ( pwdCheckModule $ pwdCheckModuleArg ) )" ,
+ "MAY ( pwdCheckModule $ pwdCheckModuleArg $ pwdUseCheckModule ) )" ,
"( 1.3.6.1.4.1.42.2.27.8.2.1 "
"NAME 'pwdPolicy' "
"SUP top "
PPOLICY_HASH_CLEARTEXT,
PPOLICY_USE_LOCKOUT,
PPOLICY_DISABLE_WRITE,
+ PPOLICY_CHECK_MODULE,
};
-static ConfigDriver ppolicy_cf_default;
+static ConfigDriver ppolicy_cf_default, ppolicy_cf_checkmod;
static ConfigTable ppolicycfg[] = {
{ "ppolicy_default", "policyDN", 2, 2, 0,
"DESC 'Send Netscape policy controls' "
"EQUALITY booleanMatch "
"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_check_module", "path", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|PPOLICY_CHECK_MODULE, ppolicy_cf_checkmod,
+ "( OLcfgOvAt:12.7 NAME 'olcPPolicyCheckModule' "
+ "DESC 'Loadable module that instantiates check_password() function' "
+ "EQUALITY caseExactIA5Match "
+ "SYNTAX OMsIA5String "
+ "SINGLE-VALUE )", NULL, NULL },
{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};
"SUP olcOverlayConfig "
"MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ "
"olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ "
- "olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls ) )",
+ "olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls $ "
+ "olcPPolicyCheckModule ) )",
Cft_Overlay, ppolicycfg },
{ NULL, 0, NULL }
};
return rc;
}
+static int
+ppolicy_cf_checkmod( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ pp_info *pi = (pp_info *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ assert ( c->type == PPOLICY_CHECK_MODULE );
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_checkmod\n" );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ if ( pi->pwdCheckModule ) {
+ c->value_string = ch_strdup( pi->pwdCheckModule );
+ rc = 0;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( pi->pwdCheckHandle ) {
+ lt_dlclose( pi->pwdCheckHandle );
+ pi->pwdCheckHandle = NULL;
+ pi->pwdCheckFunc = NULL;
+ }
+ ch_free( pi->pwdCheckModule );
+ pi->pwdCheckModule = NULL;
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ /* fallthru to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ pi->pwdCheckHandle = lt_dlopen( c->value_string );
+ if ( pi->pwdCheckHandle == NULL ) {
+ const char *dlerr = lt_dlerror();
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> lt_dlopen(%s) failed: %s",
+ c->argv[0], c->value_string, dlerr );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ } else {
+ if (( pi->pwdCheckFunc = lt_dlsym( pi->pwdCheckHandle, "check_password" )) == NULL) {
+ const char *dlerr = lt_dlerror();
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> lt_dlsym(%s) failed: %s",
+ c->argv[0], c->value_string, dlerr );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ } else {
+ pi->pwdCheckModule = c->value_string;
+ rc = 0;
+ }
+ }
+ break;
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+
static time_t
parse_time( char *atm )
{
}
ad = ad_pwdCheckModule;
- if ( (a = attr_find( pe->e_attrs, ad )) ) {
- strncpy( pp->pwdCheckModule, a->a_vals[0].bv_val,
- sizeof(pp->pwdCheckModule) );
- pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0';
+ if ( attr_find( pe->e_attrs, ad )) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_get: "
+ "WARNING: Ignoring OBSOLETE attribute %s in policy %s.\n",
+ ad->ad_cname.bv_val, pe->e_name.bv_val );
}
+ ad = ad_pwdUseCheckModule;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdUseCheckModule = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
ad = ad_pwdCheckModuleArg;
if ( (a = attr_find( pe->e_attrs, ad )) ) {
ber_dupbv_x( &pp->pwdCheckModuleArg, &a->a_vals[0], op->o_tmpmemctx );
}
static int
-check_password_quality( struct berval *cred, PassPolicy *pp, LDAPPasswordPolicyError *err, Entry *e, char **txt )
+check_password_quality( struct berval *cred, pp_info *pi, PassPolicy *pp, LDAPPasswordPolicyError *err,
+ Entry *e, struct berval *errmsg )
{
int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
char *ptr;
assert( cred != NULL );
assert( pp != NULL );
- assert( txt != NULL );
+ assert( errmsg != NULL );
- ptr = cred->bv_val;
+ ptr = errmsg->bv_val;
+ *ptr = '\0';
- *txt = NULL;
+ ptr = cred->bv_val;
if ((cred->bv_len == 0) || (pp->pwdMinLength > cred->bv_len)) {
rc = LDAP_CONSTRAINT_VIOLATION;
rc = LDAP_SUCCESS;
- if (pp->pwdCheckModule[0]) {
+ if (pp->pwdUseCheckModule) {
#ifdef SLAPD_MODULES
- lt_dlhandle mod;
- const char *err;
-
- if ((mod = lt_dlopen( pp->pwdCheckModule )) == NULL) {
- err = lt_dlerror();
+ check_func *prog;
+ if ( !pi->pwdCheckFunc ) {
Debug(LDAP_DEBUG_ANY,
- "check_password_quality: lt_dlopen failed: (%s) %s.\n",
- pp->pwdCheckModule, err );
- ok = LDAP_OTHER; /* internal error */
+ "check_password_quality: no CheckModule loaded\n" );
+ ok = LDAP_OTHER;
} else {
- /* FIXME: the error message ought to be passed thru a
- * struct berval, with preallocated buffer and size
- * passed in. Module can still allocate a buffer for
- * it if the provided one is too small.
- */
- int (*prog)( char *passwd, char **text, Entry *ent, struct berval *arg );
-
- if ((prog = lt_dlsym( mod, "check_password" )) == NULL) {
- err = lt_dlerror();
+ struct berval *arg = NULL;
+ if ( !BER_BVISNULL( &pp->pwdCheckModuleArg ) ) {
+ arg = &pp->pwdCheckModuleArg;
+ }
+ ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
+ ok = pi->pwdCheckFunc( ptr, errmsg, e, arg );
+ ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
+ if (ok != LDAP_SUCCESS) {
Debug(LDAP_DEBUG_ANY,
- "check_password_quality: lt_dlsym failed: (%s) %s.\n",
- pp->pwdCheckModule, err );
- ok = LDAP_OTHER;
- } else {
- struct berval *arg = NULL;
- if ( !BER_BVISNULL( &pp->pwdCheckModuleArg ) ) {
- arg = &pp->pwdCheckModuleArg;
- }
-
- ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
- ok = prog( ptr, txt, e, arg );
- ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
- if (ok != LDAP_SUCCESS) {
- Debug(LDAP_DEBUG_ANY,
- "check_password_quality: module error: (%s) %s.[%d]\n",
- pp->pwdCheckModule, *txt ? *txt : "", ok );
- }
+ "check_password_quality: module error: (%s) %s.[%d]\n",
+ pi->pwdCheckModule, errmsg->bv_val ? errmsg->bv_val : "", ok );
}
-
- lt_dlclose( mod );
}
#else
- Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
- "supported. pwdCheckModule ignored.\n" );
+ Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
+ "supported. pwdCheckModule ignored.\n" );
#endif /* SLAPD_MODULES */
}
-
-
+
if (ok != LDAP_SUCCESS) {
rc = LDAP_CONSTRAINT_VIOLATION;
if (err) *err = PP_insufficientPasswordQuality;
}
-
+
return rc;
}
struct berval *bv = &(pa->a_vals[0]);
int rc, send_ctrl = 0;
LDAPPasswordPolicyError pErr = PP_noError;
- char *txt;
+ char errbuf[ERRBUFSIZ];
+ struct berval errmsg = BER_BVC( errbuf );
/* Did we receive a password policy request control? */
if ( op->o_ctrlflag[ppolicy_cid] ) {
send_ctrl = 1;
}
- rc = check_password_quality( bv, &pp, &pErr, op->ora_e, &txt );
+ rc = check_password_quality( bv, pi, &pp, &pErr, op->ora_e, &errmsg );
if (rc != LDAP_SUCCESS) {
+ char *txt = errmsg.bv_val;
LDAPControl **oldctrls = NULL;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
if ( send_ctrl ) {
ctrl = create_passcontrol( op, -1, -1, pErr );
oldctrls = add_passcontrol( op, rs, ctrl );
}
- send_ldap_error( op, rs, rc, txt ? txt : "Password fails quality checking policy" );
- if ( txt ) {
+ send_ldap_error( op, rs, rc, txt && txt[0] ? txt : "Password fails quality checking policy" );
+ if ( txt != errbuf ) {
free( txt );
}
if ( send_ctrl ) {
*ml, *delmod, *addmod;
Attribute *pa, *ha, at;
const char *txt;
+ char errbuf[ERRBUFSIZ];
pw_hist *tl = NULL, *p;
int zapReset, send_ctrl = 0, free_txt = 0;
Entry *e;
bv = newpw.bv_val ? &newpw : &addmod->sml_values[0];
if (pp.pwdCheckQuality > 0) {
+ struct berval errmsg = BER_BVC( errbuf );
- rc = check_password_quality( bv, &pp, &pErr, e, (char **)&txt );
+ rc = check_password_quality( bv, pi, &pp, &pErr, e, &errmsg );
if (rc != LDAP_SUCCESS) {
rs->sr_err = rc;
- if ( txt ) {
+ txt = errmsg.bv_val;
+ if ( txt && txt[0] ) {
rs->sr_text = txt;
- free_txt = 1;
+ if ( txt != errbuf )
+ free_txt = 1;
} else {
rs->sr_text = "Password fails quality checking policy";
}