ppolicy.c 66.3 KB
Newer Older
1
2
3
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
Kurt Zeilenga's avatar
Kurt Zeilenga committed
4
 * Copyright 2004-2014 The OpenLDAP Foundation.
5
 * Portions Copyright 2004-2005 Howard Chu, Symas Corporation.
Howard Chu's avatar
Howard Chu committed
6
 * Portions Copyright 2004 Hewlett-Packard Company.
7
8
9
10
11
12
13
14
15
16
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
Howard Chu's avatar
Howard Chu committed
17
18
19
20
21
/* ACKNOWLEDGEMENTS:
 * This work was developed by Howard Chu for inclusion in
 * OpenLDAP Software, based on prior work by Neil Dunbar (HP).
 * This work was sponsored by the Hewlett-Packard Company.
 */
22
23
24
25

#include "portable.h"

/* This file implements "Password Policy for LDAP Directories",
26
 * based on draft behera-ldap-password-policy-09
27
28
29
30
31
32
33
 */

#ifdef SLAPD_OVER_PPOLICY

#include <ldap.h>
#include "lutil.h"
#include "slap.h"
34
#ifdef SLAPD_MODULES
35
#define LIBLTDL_DLL_IMPORT	/* Win32: don't re-export libltdl's symbols */
36
#include <ltdl.h>
37
#endif
38
39
40
#include <ac/errno.h>
#include <ac/time.h>
#include <ac/string.h>
41
#include <ac/ctype.h>
42
#include "config.h"
43
44
45
46
47
48
49
50

#ifndef MODULE_NAME_SZ
#define MODULE_NAME_SZ 256
#endif

/* Per-instance configuration information */
typedef struct pp_info {
	struct berval def_policy;	/* DN of default policy subentry */
51
	int use_lockout;		/* send AccountLocked result? */
52
	int hash_passwords;		/* transparently hash cleartext pwds */
53
	int forward_updates;	/* use frontend for policy state updates */
54
55
56
57
58
59
} pp_info;

/* Our per-connection info - note, it is not per-instance, it is 
 * used by all instances
 */
typedef struct pw_conn {
60
	struct berval dn;	/* DN of restricted user */
61
62
63
} pw_conn;

static pw_conn *pwcons;
Howard Chu's avatar
Howard Chu committed
64
static int ppolicy_cid;
65
static int ov_count;
66
67
68
69
70
71
72
73
74
75
76

typedef struct pass_policy {
	AttributeDescription *ad; /* attribute to which the policy applies */
	int pwdMinAge; /* minimum time (seconds) until passwd can change */
	int pwdMaxAge; /* time in seconds until pwd will expire after change */
	int pwdInHistory; /* number of previous passwords kept */
	int pwdCheckQuality; /* 0 = don't check quality, 1 = check if possible,
						   2 = check mandatory; fail if not possible */
	int pwdMinLength; /* minimum number of chars in password */
	int pwdExpireWarning; /* number of seconds that warning controls are
							sent before a password expires */
77
	int pwdGraceAuthNLimit; /* number of times you can log in with an
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
							expired password */
	int pwdLockout; /* 0 = do not lockout passwords, 1 = lock them out */
	int pwdLockoutDuration; /* time in seconds a password is locked out for */
	int pwdMaxFailure; /* number of failed binds allowed before lockout */
	int pwdFailureCountInterval; /* number of seconds before failure
									counts are zeroed */
	int pwdMustChange; /* 0 = users can use admin set password
							1 = users must change password after admin set */
	int pwdAllowUserChange; /* 0 = users cannot change their passwords
								1 = users can change them */
	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 */
} PassPolicy;

typedef struct pw_hist {
	time_t t;	/* timestamp of history entry */
	struct berval pw;	/* old password hash */
	struct berval bv;	/* text of entire entry */
	struct pw_hist *next;
} pw_hist;

/* Operational attributes */
static AttributeDescription *ad_pwdChangedTime, *ad_pwdAccountLockedTime,
104
105
	*ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset,
	*ad_pwdPolicySubentry;
106
107
108
109
110
111
112
113
114
115
116

static struct schema_info {
	char *def;
	AttributeDescription **ad;
} pwd_OpSchema[] = {
	{	"( 1.3.6.1.4.1.42.2.27.8.1.16 "
		"NAME ( 'pwdChangedTime' ) "
		"DESC 'The time the password was last changed' "
		"EQUALITY generalizedTimeMatch "
		"ORDERING generalizedTimeOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
117
		"SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
118
119
120
121
122
123
124
		&ad_pwdChangedTime },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.17 "
		"NAME ( 'pwdAccountLockedTime' ) "
		"DESC 'The time an user account was locked' "
		"EQUALITY generalizedTimeMatch "
		"ORDERING generalizedTimeOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
125
126
		"SINGLE-VALUE "
#if 0
Kurt Zeilenga's avatar
Kurt Zeilenga committed
127
		/* Not until Relax control is released */
128
129
130
		"NO-USER-MODIFICATION "
#endif
		"USAGE directoryOperation )",
131
132
133
134
135
136
137
		&ad_pwdAccountLockedTime },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.19 "
		"NAME ( 'pwdFailureTime' ) "
		"DESC 'The timestamps of the last consecutive authentication failures' "
		"EQUALITY generalizedTimeMatch "
		"ORDERING generalizedTimeOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
138
		"NO-USER-MODIFICATION USAGE directoryOperation )",
139
140
141
142
143
144
		&ad_pwdFailureTime },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.20 "
		"NAME ( 'pwdHistory' ) "
		"DESC 'The history of users passwords' "
		"EQUALITY octetStringMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
145
		"NO-USER-MODIFICATION USAGE directoryOperation )",
146
147
148
149
150
151
		&ad_pwdHistory },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.21 "
		"NAME ( 'pwdGraceUseTime' ) "
		"DESC 'The timestamps of the grace login once the password has expired' "
		"EQUALITY generalizedTimeMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
152
		"NO-USER-MODIFICATION USAGE directoryOperation )",
153
154
155
156
157
158
		&ad_pwdGraceUseTime }, 
	{	"( 1.3.6.1.4.1.42.2.27.8.1.22 "
		"NAME ( 'pwdReset' ) "
		"DESC 'The indication that the password has been reset' "
		"EQUALITY booleanMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
159
		"SINGLE-VALUE USAGE directoryOperation )",
160
161
162
163
164
165
		&ad_pwdReset },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.23 "
		"NAME ( 'pwdPolicySubentry' ) "
		"DESC 'The pwdPolicy subentry in effect for this object' "
		"EQUALITY distinguishedNameMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
166
167
		"SINGLE-VALUE "
#if 0
Kurt Zeilenga's avatar
Kurt Zeilenga committed
168
		/* Not until Relax control is released */
169
170
171
		"NO-USER-MODIFICATION "
#endif
		"USAGE directoryOperation )",
172
173
174
175
176
177
178
		&ad_pwdPolicySubentry },
	{ NULL, NULL }
};

/* User attributes */
static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdInHistory,
	*ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxFailure, 
179
	*ad_pwdGraceAuthNLimit, *ad_pwdExpireWarning, *ad_pwdLockoutDuration,
180
181
182
183
184
185
186
187
188
189
190
191
192
193
	*ad_pwdFailureCountInterval, *ad_pwdCheckModule, *ad_pwdLockout,
	*ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
	*ad_pwdAttribute;

#define TAB(name)	{ #name, &ad_##name }

static struct schema_info pwd_UsSchema[] = {
	TAB(pwdAttribute),
	TAB(pwdMinAge),
	TAB(pwdMaxAge),
	TAB(pwdInHistory),
	TAB(pwdCheckQuality),
	TAB(pwdMinLength),
	TAB(pwdMaxFailure),
194
	TAB(pwdGraceAuthNLimit),
195
196
197
198
199
200
201
202
203
204
205
206
207
	TAB(pwdExpireWarning),
	TAB(pwdLockout),
	TAB(pwdLockoutDuration),
	TAB(pwdFailureCountInterval),
	TAB(pwdCheckModule),
	TAB(pwdMustChange),
	TAB(pwdAllowUserChange),
	TAB(pwdSafeModify),
	{ NULL, NULL }
};

static ldap_pvt_thread_mutex_t chk_syntax_mutex;

208
209
210
211
212
213
214
215
216
217
enum {
	PPOLICY_DEFAULT = 1,
	PPOLICY_HASH_CLEARTEXT,
	PPOLICY_USE_LOCKOUT
};

static ConfigDriver ppolicy_cf_default;

static ConfigTable ppolicycfg[] = {
	{ "ppolicy_default", "policyDN", 2, 2, 0,
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
218
	  ARG_DN|ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT, ppolicy_cf_default,
219
220
221
222
223
224
225
226
227
	  "( OLcfgOvAt:12.1 NAME 'olcPPolicyDefault' "
	  "DESC 'DN of a pwdPolicy object for uncustomized objects' "
	  "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
	{ "ppolicy_hash_cleartext", "on|off", 1, 2, 0,
	  ARG_ON_OFF|ARG_OFFSET|PPOLICY_HASH_CLEARTEXT,
	  (void *)offsetof(pp_info,hash_passwords),
	  "( OLcfgOvAt:12.2 NAME 'olcPPolicyHashCleartext' "
	  "DESC 'Hash passwords on add or modify' "
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
228
229
230
231
232
233
	{ "ppolicy_forward_updates", "on|off", 1, 2, 0,
	  ARG_ON_OFF|ARG_OFFSET,
	  (void *)offsetof(pp_info,forward_updates),
	  "( OLcfgOvAt:12.4 NAME 'olcPPolicyForwardUpdates' "
	  "DESC 'Allow policy state updates to be forwarded via updateref' "
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
	{ "ppolicy_use_lockout", "on|off", 1, 2, 0,
	  ARG_ON_OFF|ARG_OFFSET|PPOLICY_USE_LOCKOUT,
	  (void *)offsetof(pp_info,use_lockout),
	  "( OLcfgOvAt:12.3 NAME 'olcPPolicyUseLockout' "
	  "DESC 'Warn clients with AccountLocked' "
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};

static ConfigOCs ppolicyocs[] = {
	{ "( OLcfgOvOc:12.1 "
	  "NAME 'olcPPolicyConfig' "
	  "DESC 'Password Policy configuration' "
	  "SUP olcOverlayConfig "
	  "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ "
249
	  "olcPPolicyUseLockout $ olcPPolicyForwardUpdates ) )",
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
	  Cft_Overlay, ppolicycfg },
	{ NULL, 0, NULL }
};

static int
ppolicy_cf_default( 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_DEFAULT );
	Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default\n", 0, 0, 0);

	switch ( c->op ) {
	case SLAP_CONFIG_EMIT:
		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n", 0, 0, 0);
		rc = 0;
		if ( !BER_BVISEMPTY( &pi->def_policy )) {
			rc = value_add_one( &c->rvalue_vals,
					    &pi->def_policy );
			if ( rc ) return rc;
			rc = value_add_one( &c->rvalue_nvals,
					    &pi->def_policy );
		}
		break;
	case LDAP_MOD_DELETE:
		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default delete\n", 0, 0, 0);
		if ( pi->def_policy.bv_val ) {
			ber_memfree ( pi->def_policy.bv_val );
			pi->def_policy.bv_val = NULL;
		}
		pi->def_policy.bv_len = 0;
		rc = 0;
		break;
	case SLAP_CONFIG_ADD:
		/* fallthrough to LDAP_MOD_ADD */
	case LDAP_MOD_ADD:
		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n", 0, 0, 0);
289
		if ( pi->def_policy.bv_val ) {
290
			ber_memfree ( pi->def_policy.bv_val );
291
		}
292
		pi->def_policy = c->value_ndn;
293
294
295
		ber_memfree( c->value_dn.bv_val );
		BER_BVZERO( &c->value_dn );
		BER_BVZERO( &c->value_ndn );
296
297
298
299
300
301
302
303
304
		rc = 0;
		break;
	default:
		abort ();
	}

	return rc;
}

305
306
307
static time_t
parse_time( char *atm )
{
308
309
	struct lutil_tm tm;
	struct lutil_timet tt;
310
311
	time_t ret = (time_t)-1;

312
313
314
	if ( lutil_parsetime( atm, &tm ) == 0) {
		lutil_tm2time( &tm, &tt );
		ret = tt.tt_sec;
315
316
	}
	return ret;
317
318
319
320
321
322
323
324
}

static int
account_locked( Operation *op, Entry *e,
		PassPolicy *pp, Modifications **mod ) 
{
	Attribute       *la;

325
	assert(mod != NULL);
326

327
328
329
	if ( !pp->pwdLockout )
		return 0;

330
331
332
333
334
335
336
337
338
339
340
	if ( (la = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) {
		BerVarray vals = la->a_nvals;

		/*
		 * there is a lockout stamp - we now need to know if it's
		 * a valid one.
		 */
		if (vals[0].bv_val != NULL) {
			time_t then, now;
			Modifications *m;

341
342
343
			if (!pp->pwdLockoutDuration)
				return 1;

344
345
346
347
348
349
350
351
352
353
			if ((then = parse_time( vals[0].bv_val )) == (time_t)0)
				return 1;

			now = slap_get_time();

			if (now < then + pp->pwdLockoutDuration)
				return 1;

			m = ch_calloc( sizeof(Modifications), 1 );
			m->sml_op = LDAP_MOD_DELETE;
354
			m->sml_flags = 0;
355
356
357
358
359
360
361
362
363
364
			m->sml_type = ad_pwdAccountLockedTime->ad_cname;
			m->sml_desc = ad_pwdAccountLockedTime;
			m->sml_next = *mod;
			*mod = m;
		}
	}

	return 0;
}

365
366
/* IMPLICIT TAGS, all context-specific */
#define PPOLICY_WARNING 0xa0L	/* constructed + 0 */
367
#define PPOLICY_ERROR 0x81L		/* primitive + 1 */
368
 
369
370
#define PPOLICY_EXPIRE 0x80L	/* primitive + 0 */
#define PPOLICY_GRACE  0x81L	/* primitive + 1 */
371

372
373
static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;

374
static LDAPControl *
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
375
create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err )
376
{
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
377
378
	BerElementBuffer berbuf, bb2;
	BerElement *ber = (BerElement *) &berbuf, *b2 = (BerElement *) &bb2;
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
379
	LDAPControl c = { 0 }, *cp;
380
381
	struct berval bv;

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
382
	BER_BVZERO( &c.ldctl_value );
383
384

	ber_init2( ber, NULL, LBER_USE_DER );
385
	ber_printf( ber, "{" /*}*/ );
386

387
	if ( exptime >= 0 ) {
388
389
390
		ber_init2( b2, NULL, LBER_USE_DER );
		ber_printf( b2, "ti", PPOLICY_EXPIRE, exptime );
		ber_flatten2( b2, &bv, 1 );
391
		(void)ber_free_buf(b2);
392
393
		ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
		ch_free( bv.bv_val );
394
	} else if ( grace > 0 ) {
395
396
397
		ber_init2( b2, NULL, LBER_USE_DER );
		ber_printf( b2, "ti", PPOLICY_GRACE, grace );
		ber_flatten2( b2, &bv, 1 );
398
		(void)ber_free_buf(b2);
399
400
401
402
403
404
405
406
407
		ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
		ch_free( bv.bv_val );
	}

	if (err != PP_noError ) {
		ber_printf( ber, "te", PPOLICY_ERROR, err );
	}
	ber_printf( ber, /*{*/ "N}" );

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
408
	if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
409
		return NULL;
410
	}
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
411
412
413
414
415
416
	cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx );
	cp->ldctl_oid = (char *)ppolicy_ctrl_oid;
	cp->ldctl_iscritical = 0;
	cp->ldctl_value.bv_val = (char *)&cp[1];
	cp->ldctl_value.bv_len = c.ldctl_value.bv_len;
	AC_MEMCPY( cp->ldctl_value.bv_val, c.ldctl_value.bv_val, c.ldctl_value.bv_len );
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
417
	(void)ber_free_buf(ber);
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
418
419
	
	return cp;
420
421
}

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
static LDAPControl **
add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl )
{
	LDAPControl **ctrls, **oldctrls = rs->sr_ctrls;
	int n;

	n = 0;
	if ( oldctrls ) {
		for ( ; oldctrls[n]; n++ )
			;
	}
	n += 2;

	ctrls = op->o_tmpcalloc( sizeof( LDAPControl * ), n, op->o_tmpmemctx );

	n = 0;
	if ( oldctrls ) {
		for ( ; oldctrls[n]; n++ ) {
			ctrls[n] = oldctrls[n];
		}
	}
	ctrls[n] = ctrl;
	ctrls[n+1] = NULL;

	rs->sr_ctrls = ctrls;

	return oldctrls;
}

451
452
453
454
455
456
457
static void
ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	pp_info *pi = on->on_bi.bi_private;
	Attribute *a;
	BerVarray vals;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
458
	int rc;
459
	Entry *pe = NULL;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
460
#if 0
461
	const char *text;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
462
#endif
463
464
465

	memset( pp, 0, sizeof(PassPolicy) );

Howard Chu's avatar
Howard Chu committed
466
467
	pp->ad = slap_schema.si_ad_userPassword;

468
469
470
	/* Users can change their own password by default */
    	pp->pwdAllowUserChange = 1;

471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
	if ((a = attr_find( e->e_attrs, ad_pwdPolicySubentry )) == NULL) {
		/*
		 * entry has no password policy assigned - use default
		 */
		vals = &pi->def_policy;
		if ( !vals->bv_val )
			goto defaultpol;
	} else {
		vals = a->a_nvals;
		if (vals[0].bv_val == NULL) {
			Debug( LDAP_DEBUG_ANY,
				"ppolicy_get: NULL value for policySubEntry\n", 0, 0, 0 );
			goto defaultpol;
		}
	}

	op->o_bd->bd_info = (BackendInfo *)on->on_info;
	rc = be_entry_get_rw( op, vals, NULL, NULL, 0, &pe );
	op->o_bd->bd_info = (BackendInfo *)on;

	if ( rc ) goto defaultpol;

#if 0	/* Only worry about userPassword for now */
	if ((a = attr_find( pe->e_attrs, ad_pwdAttribute )))
		slap_bv2ad( &a->a_vals[0], &pp->ad, &text );
#endif

498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMinAge ) )
			&& lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxAge ) )
			&& lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdInHistory ) )
			&& lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckQuality ) )
			&& lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMinLength ) )
			&& lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxFailure ) )
			&& lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdGraceAuthNLimit ) )
			&& lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdExpireWarning ) )
			&& lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdFailureCountInterval ) )
			&& lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
	if ( ( a = attr_find( pe->e_attrs, ad_pwdLockoutDuration ) )
			&& lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;

	if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckModule ) ) ) {
		strncpy( pp->pwdCheckModule, a->a_vals[0].bv_val,
			sizeof(pp->pwdCheckModule) );
532
533
534
535
		pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0';
	}

	if ((a = attr_find( pe->e_attrs, ad_pwdLockout )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
536
    		pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv );
537
	if ((a = attr_find( pe->e_attrs, ad_pwdMustChange )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
538
    		pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
539
	if ((a = attr_find( pe->e_attrs, ad_pwdAllowUserChange )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
540
	    	pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
541
	if ((a = attr_find( pe->e_attrs, ad_pwdSafeModify )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
542
	    	pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );
543
544
545
546
547
548
549
550
    
	op->o_bd->bd_info = (BackendInfo *)on->on_info;
	be_entry_release_r( op, pe );
	op->o_bd->bd_info = (BackendInfo *)on;

	return;

defaultpol:
Howard Chu's avatar
Howard Chu committed
551
	Debug( LDAP_DEBUG_TRACE,
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
		"ppolicy_get: using default policy\n", 0, 0, 0 );
	return;
}

static int
password_scheme( struct berval *cred, struct berval *sch )
{
	int e;
    
	assert( cred != NULL );

	if (sch) {
		sch->bv_val = NULL;
		sch->bv_len = 0;
	}
    
	if ((cred->bv_len == 0) || (cred->bv_val == NULL) ||
		(cred->bv_val[0] != '{')) return LDAP_OTHER;

	for(e = 1; cred->bv_val[e] && cred->bv_val[e] != '}'; e++);
	if (cred->bv_val[e]) {
Howard Chu's avatar
Howard Chu committed
573
		int rc;
574
		rc = lutil_passwd_scheme( cred->bv_val );
575
576
577
578
579
		if (rc) {
			if (sch) {
				sch->bv_val = cred->bv_val;
				sch->bv_len = e;
			}
580
581
582
583
584
585
586
			return LDAP_SUCCESS;
		}
	}
	return LDAP_OTHER;
}

static int
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
587
check_password_quality( struct berval *cred, PassPolicy *pp, LDAPPasswordPolicyError *err, Entry *e, char **txt )
588
589
{
	int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
590
	char *ptr;
591
592
593
594
	struct berval sch;

	assert( cred != NULL );
	assert( pp != NULL );
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
595
596
	assert( txt != NULL );

597
598
	ptr = cred->bv_val;

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
599
	*txt = NULL;
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636

	if ((cred->bv_len == 0) || (pp->pwdMinLength > cred->bv_len)) {
		rc = LDAP_CONSTRAINT_VIOLATION;
		if ( err ) *err = PP_passwordTooShort;
		return rc;
	}

        /*
         * We need to know if the password is already hashed - if so
         * what scheme is it. The reason being that the "hash" of
         * {cleartext} still allows us to check the password.
         */
	rc = password_scheme( cred, &sch );
	if (rc == LDAP_SUCCESS) {
		if ((sch.bv_val) && (strncasecmp( sch.bv_val, "{cleartext}",
			sch.bv_len ) == 0)) {
			/*
			 * We can check the cleartext "hash"
			 */
			ptr = cred->bv_val + sch.bv_len;
		} else {
			/* everything else, we can't check */
			if (pp->pwdCheckQuality == 2) {
				rc = LDAP_CONSTRAINT_VIOLATION;
				if (err) *err = PP_insufficientPasswordQuality;
				return rc;
			}
			/*
			 * We can't check the syntax of the password, but it's not
			 * mandatory (according to the policy), so we return success.
			 */
		    
			return LDAP_SUCCESS;
		}
	}

	rc = LDAP_SUCCESS;
637

638
	if (pp->pwdCheckModule[0]) {
639
#ifdef SLAPD_MODULES
640
641
642
643
644
645
646
647
648
649
650
		lt_dlhandle mod;
		const char *err;
		
		if ((mod = lt_dlopen( pp->pwdCheckModule )) == NULL) {
			err = lt_dlerror();

			Debug(LDAP_DEBUG_ANY,
			"check_password_quality: lt_dlopen failed: (%s) %s.\n",
				pp->pwdCheckModule, err, 0 );
			ok = LDAP_OTHER; /* internal error */
		} else {
651
652
653
654
655
			/* 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.
			 */
656
			int (*prog)( char *passwd, char **text, Entry *ent );
657
658
659
660
661
662
663
664
665
666

			if ((prog = lt_dlsym( mod, "check_password" )) == NULL) {
				err = lt_dlerror();
			    
				Debug(LDAP_DEBUG_ANY,
					"check_password_quality: lt_dlsym failed: (%s) %s.\n",
					pp->pwdCheckModule, err, 0 );
				ok = LDAP_OTHER;
			} else {
				ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
667
				ok = prog( ptr, txt, e );
668
				ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
669
				if (ok != LDAP_SUCCESS) {
670
671
					Debug(LDAP_DEBUG_ANY,
						"check_password_quality: module error: (%s) %s.[%d]\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
672
						pp->pwdCheckModule, *txt ? *txt : "", ok );
673
				}
674
675
676
677
			}
			    
			lt_dlclose( mod );
		}
678
679
680
681
#else
	Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
		"supported. pwdCheckModule ignored.\n", 0, 0, 0);
#endif /* SLAPD_MODULES */
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
	}
		
		    
	if (ok != LDAP_SUCCESS) {
		rc = LDAP_CONSTRAINT_VIOLATION;
		if (err) *err = PP_insufficientPasswordQuality;
	}
	
	return rc;
}

static int
parse_pwdhistory( struct berval *bv, char **oid, time_t *oldtime, struct berval *oldpw )
{
	char *ptr;
	struct berval nv, npw;
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
698
	ber_len_t i, j;
699
700
701
	
	assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw );

702
703
704
	if ( oid ) {
		*oid = 0;
	}
705
	*oldtime = (time_t)-1;
706
	BER_BVZERO( oldpw );
707
708
709
710
	
	ber_dupbv( &nv, bv );

	/* first get the time field */
711
712
713
714
715
	for ( i = 0; (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
		;
	if ( i == nv.bv_len ) {
		goto exit_failure; /* couldn't locate the '#' separator */
	}
716
717
718
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
	ptr = nv.bv_val;
	*oldtime = parse_time( ptr );
719
720
721
	if (*oldtime == (time_t)-1) {
		goto exit_failure;
	}
722
723

	/* get the OID field */
724
725
726
727
728
	for (ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
		;
	if ( i == nv.bv_len ) {
		goto exit_failure; /* couldn't locate the '#' separator */
	}
729
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
730
731
732
	if ( oid ) {
		*oid = ber_strdup( ptr );
	}
733
734
	
	/* get the length field */
735
736
737
738
739
	for ( ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
		;
	if ( i == nv.bv_len ) {
		goto exit_failure; /* couldn't locate the '#' separator */
	}
740
741
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
	oldpw->bv_len = strtol( ptr, NULL, 10 );
742
743
744
	if (errno == ERANGE) {
		goto exit_failure;
	}
745
746

	/* lastly, get the octets of the string */
747
748
749
750
751
	for ( j = i, ptr = &(nv.bv_val[i]); i < nv.bv_len; i++ )
		;
	if ( i - j != oldpw->bv_len) {
		goto exit_failure; /* length is wrong */
	}
752
753
754
755

	npw.bv_val = ptr;
	npw.bv_len = oldpw->bv_len;
	ber_dupbv( oldpw, &npw );
756
	ber_memfree( nv.bv_val );
757
758
	
	return LDAP_SUCCESS;
759
760
761
762
763

exit_failure:;
	if ( oid && *oid ) {
		ber_memfree(*oid);
		*oid = NULL;
764
	}
765
766
767
768
769
770
	if ( oldpw->bv_val ) {
		ber_memfree( oldpw->bv_val);
		BER_BVZERO( oldpw );
	}
	ber_memfree( nv.bv_val );

771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
	return LDAP_OTHER;
}

static void
add_to_pwd_history( pw_hist **l, time_t t,
                    struct berval *oldpw, struct berval *bv )
{
	pw_hist *p, *p1, *p2;
    
	if (!l) return;

	p = ch_malloc( sizeof( pw_hist ));
	p->pw = *oldpw;
	ber_dupbv( &p->bv, bv );
	p->t = t;
	p->next = NULL;
	
	if (*l == NULL) {
		/* degenerate case */
		*l = p;
		return;
	}
	/*
	 * advance p1 and p2 such that p1 is the node before the
	 * new one, and p2 is the node after it
	 */
	for (p1 = NULL, p2 = *l; p2 && p2->t <= t; p1 = p2, p2=p2->next );
	p->next = p2;
	if (p1 == NULL) { *l = p; return; }
	p1->next = p;
}

#ifndef MAX_PWD_HISTORY_SZ
#define MAX_PWD_HISTORY_SZ 1024
#endif /* MAX_PWD_HISTORY_SZ */

static void
make_pwd_history_value( char *timebuf, struct berval *bv, Attribute *pa )
{
	char str[ MAX_PWD_HISTORY_SZ ];
	int nlen;

	snprintf( str, MAX_PWD_HISTORY_SZ,
814
		  "%s#%s#%lu#", timebuf,
815
		  pa->a_desc->ad_type->sat_syntax->ssyn_oid,
816
		  (unsigned long) pa->a_nvals[0].bv_len );
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
	str[MAX_PWD_HISTORY_SZ-1] = 0;
	nlen = strlen(str);

        /*
         * We have to assume that the string is a string of octets,
         * not readable characters. In reality, yes, it probably is
         * a readable (ie, base64) string, but we can't count on that
         * Hence, while the first 3 fields of the password history
         * are definitely readable (a timestamp, an OID and an integer
         * length), the remaining octets of the actual password
         * are deemed to be binary data.
         */
	AC_MEMCPY( str + nlen, pa->a_nvals[0].bv_val, pa->a_nvals[0].bv_len );
	nlen += pa->a_nvals[0].bv_len;
	bv->bv_val = ch_malloc( nlen + 1 );
	AC_MEMCPY( bv->bv_val, str, nlen );
	bv->bv_val[nlen] = '\0';
	bv->bv_len = nlen;
}

static void
free_pwd_history_list( pw_hist **l )
{
	pw_hist *p;
    
	if (!l) return;
	p = *l;
	while (p) {
		pw_hist *pp = p->next;

		free(p->pw.bv_val);
		free(p->bv.bv_val);
		free(p);
		p = pp;
	}
	*l = NULL;
}

Howard Chu's avatar
Howard Chu committed
855
856
857
typedef struct ppbind {
	slap_overinst *on;
	int send_ctrl;
858
	int set_restrict;
859
	LDAPControl **oldctrls;
Howard Chu's avatar
Howard Chu committed
860
861
862
863
864
	Modifications *mod;
	LDAPPasswordPolicyError pErr;
	PassPolicy pp;
} ppbind;

865
866
867
868
869
870
871
872
873
static void
ctrls_cleanup( Operation *op, SlapReply *rs, LDAPControl **oldctrls )
{
	int n;

	assert( rs->sr_ctrls != NULL );
	assert( rs->sr_ctrls[0] != NULL );

	for ( n = 0; rs->sr_ctrls[n]; n++ ) {
874
		if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid ) {
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
875
			op->o_tmpfree( rs->sr_ctrls[n], op->o_tmpmemctx );
876
877
878
879
880
881
882
883
884
885
886
887
888
889
			rs->sr_ctrls[n] = (LDAPControl *)(-1);
			break;
		}
	}

	if ( rs->sr_ctrls[n] == NULL ) {
		/* missed? */
	}

	op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx );

	rs->sr_ctrls = oldctrls;
}

890
static int
891
892
893
894
895
896
897
898
899
900
901
ppolicy_ctrls_cleanup( Operation *op, SlapReply *rs )
{
	ppbind *ppb = op->o_callback->sc_private;
	if ( ppb->send_ctrl ) {
		ctrls_cleanup( op, rs, ppb->oldctrls );
	}
	return SLAP_CB_CONTINUE;
}

static int
ppolicy_bind_response( Operation *op, SlapReply *rs )
902
{
Howard Chu's avatar
Howard Chu committed
903
904
905
	ppbind *ppb = op->o_callback->sc_private;
	slap_overinst *on = ppb->on;
	Modifications *mod = ppb->mod, *m;
906
	int pwExpired = 0;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
907
	int ngut = -1, warn = -1, age, rc;
908
	Attribute *a;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
909
	time_t now, pwtime = (time_t)-1;
910
911
	struct lutil_tm now_tm;
	struct lutil_timet now_usec;
912
	char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ];
913
914
	char nowstr_usec[ LDAP_LUTIL_GENTIME_BUFSIZE+8 ];
	struct berval timestamp, timestamp_usec;
915
916
917
	BackendInfo *bi = op->o_bd->bd_info;
	Entry *e;

Howard Chu's avatar
Howard Chu committed
918
919
920
921
922
	/* If we already know it's locked, just get on with it */
	if ( ppb->pErr != PP_noError ) {
		goto locked;
	}

923
924
925
926
927
928
929
930
	op->o_bd->bd_info = (BackendInfo *)on->on_info;
	rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
	op->o_bd->bd_info = bi;

	if ( rc != LDAP_SUCCESS ) {
		return SLAP_CB_CONTINUE;
	}

931
932
933
	ldap_pvt_gettime(&now_tm); /* stored for later consideration */
	lutil_tm2time(&now_tm, &now_usec);
	now = now_usec.tt_sec;
934
935
936
	timestamp.bv_val = nowstr;
	timestamp.bv_len = sizeof(nowstr);
	slap_timestamp( &now, &timestamp );
937

938
939
940
941
942
943
944
	/* Separate timestamp for pwdFailureTime with microsecond granularity */
	strcpy(nowstr_usec, nowstr);
	timestamp_usec.bv_val = nowstr_usec;
	timestamp_usec.bv_len = timestamp.bv_len;
	snprintf( timestamp_usec.bv_val + timestamp_usec.bv_len-1, sizeof(".123456Z"), ".%06dZ", now_usec.tt_usec );
	timestamp_usec.bv_len += STRLENOF(".123456");

945
946
947
948
949
	if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) {
		int i = 0, fc = 0;

		m = ch_calloc( sizeof(Modifications), 1 );
		m->sml_op = LDAP_MOD_ADD;
950
		m->sml_flags = 0;
951
952
		m->sml_type = ad_pwdFailureTime->ad_cname;
		m->sml_desc = ad_pwdFailureTime;
953
		m->sml_numvals = 1;
954
		m->sml_values = ch_calloc( sizeof(struct berval), 2 );
955
		m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
956

957
958
		ber_dupbv( &m->sml_values[0], &timestamp_usec );
		ber_dupbv( &m->sml_nvalues[0], &timestamp_usec );
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
		m->sml_next = mod;
		mod = m;

		/*
		 * Count the pwdFailureTimes - if it's
		 * greater than the policy pwdMaxFailure,
		 * then lock the account.
		 */
		if ((a = attr_find( e->e_attrs, ad_pwdFailureTime )) != NULL) {
			for(i=0; a->a_nvals[i].bv_val; i++) {

				/*
				 * If the interval is 0, then failures
				 * stay on the record until explicitly
				 * reset by successful authentication.
				 */
Howard Chu's avatar
Howard Chu committed
975
				if (ppb->pp.pwdFailureCountInterval == 0) {
976
977
978
					fc++;
				} else if (now <=
							parse_time(a->a_nvals[i].bv_val) +
Howard Chu's avatar
Howard Chu committed
979
							ppb->pp.pwdFailureCountInterval) {
980
981
982
983
984
985
986
987
988
989

					fc++;
				}
				/*
				 * We only count those failures
				 * which are not due to expire.
				 */
			}
		}
		
Howard Chu's avatar
Howard Chu committed
990
991
		if ((ppb->pp.pwdMaxFailure > 0) &&
			(fc >= ppb->pp.pwdMaxFailure - 1)) {
992
993
994
995
996
997
998
999

			/*
			 * We subtract 1 from the failure max
			 * because the new failure entry hasn't
			 * made it to the entry yet.
			 */
			m = ch_calloc( sizeof(Modifications), 1 );
			m->sml_op = LDAP_MOD_REPLACE;
1000
			m->sml_flags = 0;
For faster browsing, not all history is shown. View entire blame