ppolicy.c 70.5 KB
Newer Older
1
2
3
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
4
 * Copyright 2004-2020 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

#ifndef MODULE_NAME_SZ
#define MODULE_NAME_SZ 256
#endif

48
49
50
51
#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE
#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE	5
#endif

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

/* Our per-connection info - note, it is not per-instance, it is 
 * used by all instances
 */
typedef struct pw_conn {
64
	struct berval dn;	/* DN of restricted user */
65
66
67
} pw_conn;

static pw_conn *pwcons;
Howard Chu's avatar
Howard Chu committed
68
static int ppolicy_cid;
69
static int ov_count;
70
71
72
73
74
75
76
77
78
79
80

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 */
81
	int pwdGraceAuthNLimit; /* number of times you can log in with an
82
83
84
85
							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 */
86
	int pwdMaxRecordedFailure;	/* number of failed binds to store */
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
	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,
109
110
	*ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset,
	*ad_pwdPolicySubentry;
111
112
113
114
115
116
117
118
119
120
121

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 "
122
		"SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
123
124
125
126
127
128
129
		&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 "
130
131
		"SINGLE-VALUE "
#if 0
Kurt Zeilenga's avatar
Kurt Zeilenga committed
132
		/* Not until Relax control is released */
133
134
135
		"NO-USER-MODIFICATION "
#endif
		"USAGE directoryOperation )",
136
137
138
139
140
141
142
		&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 "
143
		"NO-USER-MODIFICATION USAGE directoryOperation )",
144
145
146
147
148
149
		&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 "
150
		"NO-USER-MODIFICATION USAGE directoryOperation )",
151
152
153
154
155
156
		&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 "
157
		"NO-USER-MODIFICATION USAGE directoryOperation )",
158
159
160
161
162
163
		&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 "
164
		"SINGLE-VALUE USAGE directoryOperation )",
165
166
167
168
169
170
		&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 "
171
172
		"SINGLE-VALUE "
#if 0
Kurt Zeilenga's avatar
Kurt Zeilenga committed
173
		/* Not until Relax control is released */
174
175
176
		"NO-USER-MODIFICATION "
#endif
		"USAGE directoryOperation )",
177
178
179
180
181
182
183
		&ad_pwdPolicySubentry },
	{ NULL, NULL }
};

/* User attributes */
static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdInHistory,
	*ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxFailure, 
184
	*ad_pwdGraceAuthNLimit, *ad_pwdExpireWarning, *ad_pwdLockoutDuration,
185
186
	*ad_pwdFailureCountInterval, *ad_pwdCheckModule, *ad_pwdLockout,
	*ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
187
	*ad_pwdAttribute, *ad_pwdMaxRecordedFailure;
188
189
190
191
192
193
194
195
196
197
198

#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),
Howard Chu's avatar
Howard Chu committed
199
	TAB(pwdMaxRecordedFailure),
200
	TAB(pwdGraceAuthNLimit),
201
202
203
204
205
206
207
208
209
210
211
212
213
	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;

214
215
216
217
218
219
220
221
222
223
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
224
	  ARG_DN|ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT, ppolicy_cf_default,
225
226
227
228
229
230
231
232
233
	  "( 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 },
234
235
236
237
238
239
	{ "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 },
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
	{ "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 $ "
255
	  "olcPPolicyUseLockout $ olcPPolicyForwardUpdates ) )",
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
289
290
291
292
293
294
	  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);
295
		if ( pi->def_policy.bv_val ) {
296
			ber_memfree ( pi->def_policy.bv_val );
297
		}
298
		pi->def_policy = c->value_ndn;
299
300
301
		ber_memfree( c->value_dn.bv_val );
		BER_BVZERO( &c->value_dn );
		BER_BVZERO( &c->value_ndn );
302
303
304
305
306
307
308
309
310
		rc = 0;
		break;
	default:
		abort ();
	}

	return rc;
}

311
312
313
static time_t
parse_time( char *atm )
{
314
315
	struct lutil_tm tm;
	struct lutil_timet tt;
316
317
	time_t ret = (time_t)-1;

318
319
320
	if ( lutil_parsetime( atm, &tm ) == 0) {
		lutil_tm2time( &tm, &tt );
		ret = tt.tt_sec;
321
322
	}
	return ret;
323
324
325
326
327
328
329
330
}

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

331
	assert(mod != NULL);
332

333
334
335
	if ( !pp->pwdLockout )
		return 0;

336
337
338
339
340
341
342
343
344
345
346
	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;

347
348
349
			if (!pp->pwdLockoutDuration)
				return 1;

350
351
352
353
354
355
356
357
358
359
			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;
360
			m->sml_flags = 0;
361
362
363
364
365
366
367
368
369
370
			m->sml_type = ad_pwdAccountLockedTime->ad_cname;
			m->sml_desc = ad_pwdAccountLockedTime;
			m->sml_next = *mod;
			*mod = m;
		}
	}

	return 0;
}

371
372
/* IMPLICIT TAGS, all context-specific */
#define PPOLICY_WARNING 0xa0L	/* constructed + 0 */
373
#define PPOLICY_ERROR 0x81L		/* primitive + 1 */
374
 
375
376
#define PPOLICY_EXPIRE 0x80L	/* primitive + 0 */
#define PPOLICY_GRACE  0x81L	/* primitive + 1 */
377

378
379
static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;

380
static LDAPControl *
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
381
create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err )
382
{
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
383
384
	BerElementBuffer berbuf, bb2;
	BerElement *ber = (BerElement *) &berbuf, *b2 = (BerElement *) &bb2;
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
385
	LDAPControl c = { 0 }, *cp;
386
	struct berval bv;
Howard Chu's avatar
Howard Chu committed
387
	int rc;
388

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
389
	BER_BVZERO( &c.ldctl_value );
390
391

	ber_init2( ber, NULL, LBER_USE_DER );
392
	ber_printf( ber, "{" /*}*/ );
393

394
	if ( exptime >= 0 ) {
395
396
		ber_init2( b2, NULL, LBER_USE_DER );
		ber_printf( b2, "ti", PPOLICY_EXPIRE, exptime );
Howard Chu's avatar
Howard Chu committed
397
		rc = ber_flatten2( b2, &bv, 1 );
398
		(void)ber_free_buf(b2);
Howard Chu's avatar
Howard Chu committed
399
400
401
402
		if (rc == -1) {
			cp = NULL;
			goto fail;
		}
403
404
		ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
		ch_free( bv.bv_val );
405
	} else if ( grace > 0 ) {
406
407
		ber_init2( b2, NULL, LBER_USE_DER );
		ber_printf( b2, "ti", PPOLICY_GRACE, grace );
Howard Chu's avatar
Howard Chu committed
408
		rc = ber_flatten2( b2, &bv, 1 );
409
		(void)ber_free_buf(b2);
Howard Chu's avatar
Howard Chu committed
410
411
412
413
		if (rc == -1) {
			cp = NULL;
			goto fail;
		}
414
415
416
417
418
419
420
421
422
		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
423
	if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
424
		return NULL;
425
	}
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
426
427
428
429
430
431
	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 );
Howard Chu's avatar
Howard Chu committed
432
fail:
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
433
	(void)ber_free_buf(ber);
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
434
435
	
	return cp;
436
437
}

438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
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;
}

467
468
469
470
471
472
473
474
475
static void
ppolicy_get_default( PassPolicy *pp )
{
	memset( pp, 0, sizeof(PassPolicy) );

	pp->ad = slap_schema.si_ad_userPassword;

	/* Users can change their own password by default */
	pp->pwdAllowUserChange = 1;
Howard Chu's avatar
Howard Chu committed
476
477
	if ( !pp->pwdMaxRecordedFailure )
		pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;
478
479
480
}


481
482
483
484
485
486
487
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
488
	int rc;
489
	Entry *pe = NULL;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
490
#if 0
491
	const char *text;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
492
#endif
493

494
	ppolicy_get_default( pp );
495

496
497
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
	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

523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
	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;
541
542
543
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxRecordedFailure ) )
			&& lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 )
		goto defaultpol;
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
	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) );
560
561
562
563
		pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0';
	}

	if ((a = attr_find( pe->e_attrs, ad_pwdLockout )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
564
    		pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv );
565
	if ((a = attr_find( pe->e_attrs, ad_pwdMustChange )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
566
    		pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
567
	if ((a = attr_find( pe->e_attrs, ad_pwdAllowUserChange )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
568
	    	pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
569
	if ((a = attr_find( pe->e_attrs, ad_pwdSafeModify )))
Pierangelo Masarati's avatar
Pierangelo Masarati committed
570
	    	pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );
571
    
572
573
574
575
576
	if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure )
		pp->pwdMaxRecordedFailure = pp->pwdMaxFailure;
	if ( !pp->pwdMaxRecordedFailure )
		pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;

577
578
579
580
581
582
583
	op->o_bd->bd_info = (BackendInfo *)on->on_info;
	be_entry_release_r( op, pe );
	op->o_bd->bd_info = (BackendInfo *)on;

	return;

defaultpol:
Ryan Tandy's avatar
Ryan Tandy committed
584
585
586
587
588
589
	if ( pe ) {
		op->o_bd->bd_info = (BackendInfo *)on->on_info;
		be_entry_release_r( op, pe );
		op->o_bd->bd_info = (BackendInfo *)on;
	}

Howard Chu's avatar
Howard Chu committed
590
	Debug( LDAP_DEBUG_TRACE,
591
		"ppolicy_get: using default policy\n", 0, 0, 0 );
592
593
594

	ppolicy_get_default( pp );

595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
	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
615
		int rc;
616
		rc = lutil_passwd_scheme( cred->bv_val );
617
618
619
620
621
		if (rc) {
			if (sch) {
				sch->bv_val = cred->bv_val;
				sch->bv_len = e;
			}
622
623
624
625
626
627
628
			return LDAP_SUCCESS;
		}
	}
	return LDAP_OTHER;
}

static int
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
629
check_password_quality( struct berval *cred, PassPolicy *pp, LDAPPasswordPolicyError *err, Entry *e, char **txt )
630
631
{
	int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
632
	char *ptr;
633
634
635
636
	struct berval sch;

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

639
640
	ptr = cred->bv_val;

Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
641
	*txt = NULL;
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678

	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;
679

680
	if (pp->pwdCheckModule[0]) {
681
#ifdef SLAPD_MODULES
682
683
684
685
686
687
688
689
690
691
692
		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 {
693
694
695
696
697
			/* 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.
			 */
698
			int (*prog)( char *passwd, char **text, Entry *ent );
699
700
701
702
703
704
705
706
707
708

			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
709
				ok = prog( ptr, txt, e );
710
				ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
711
				if (ok != LDAP_SUCCESS) {
712
713
					Debug(LDAP_DEBUG_ANY,
						"check_password_quality: module error: (%s) %s.[%d]\n",
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
714
						pp->pwdCheckModule, *txt ? *txt : "", ok );
715
				}
716
717
718
719
			}
			    
			lt_dlclose( mod );
		}
720
721
722
723
#else
	Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
		"supported. pwdCheckModule ignored.\n", 0, 0, 0);
#endif /* SLAPD_MODULES */
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
	}
		
		    
	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
740
	ber_len_t i, j;
741
742
743
	
	assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw );

744
745
746
	if ( oid ) {
		*oid = 0;
	}
747
	*oldtime = (time_t)-1;
748
	BER_BVZERO( oldpw );
749
750
751
752
	
	ber_dupbv( &nv, bv );

	/* first get the time field */
753
754
755
756
757
	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 */
	}
758
759
760
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
	ptr = nv.bv_val;
	*oldtime = parse_time( ptr );
761
762
763
	if (*oldtime == (time_t)-1) {
		goto exit_failure;
	}
764
765

	/* get the OID field */
766
767
768
769
770
	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 */
	}
771
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
772
773
774
	if ( oid ) {
		*oid = ber_strdup( ptr );
	}
775
776
	
	/* get the length field */
777
778
779
780
781
	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 */
	}
782
783
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
	oldpw->bv_len = strtol( ptr, NULL, 10 );
784
785
786
	if (errno == ERANGE) {
		goto exit_failure;
	}
787
788

	/* lastly, get the octets of the string */
789
790
791
792
793
	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 */
	}
794
795
796
797

	npw.bv_val = ptr;
	npw.bv_len = oldpw->bv_len;
	ber_dupbv( oldpw, &npw );
798
	ber_memfree( nv.bv_val );
799
800
	
	return LDAP_SUCCESS;
801
802
803
804
805

exit_failure:;
	if ( oid && *oid ) {
		ber_memfree(*oid);
		*oid = NULL;
806
	}
807
808
809
810
811
812
	if ( oldpw->bv_val ) {
		ber_memfree( oldpw->bv_val);
		BER_BVZERO( oldpw );
	}
	ber_memfree( nv.bv_val );

813
814
815
816
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
855
	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,
856
		  "%s#%s#%lu#", timebuf,
857
		  pa->a_desc->ad_type->sat_syntax->ssyn_oid,
858
		  (unsigned long) pa->a_nvals[0].bv_len );
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
	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
897
898
899
typedef struct ppbind {
	slap_overinst *on;
	int send_ctrl;
900
	int set_restrict;
901
	LDAPControl **oldctrls;
Howard Chu's avatar
Howard Chu committed
902
903
904
905
906
	Modifications *mod;
	LDAPPasswordPolicyError pErr;
	PassPolicy pp;
} ppbind;

907
908
909
910
911
912
913
914
915
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++ ) {
916
		if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid ) {
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
917
			op->o_tmpfree( rs->sr_ctrls[n], op->o_tmpmemctx );
918
919
920
921
922
923
924
925
926
927
928
929
930
931
			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;
}

932
static int
933
934
935
936
937
938
939
940
941
942
943
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 )
944
{
Howard Chu's avatar
Howard Chu committed
945
946
947
	ppbind *ppb = op->o_callback->sc_private;
	slap_overinst *on = ppb->on;
	Modifications *mod = ppb->mod, *m;
948
	int pwExpired = 0;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
949
	int ngut = -1, warn = -1, age, rc;
950
	Attribute *a;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
951
	time_t now, pwtime = (time_t)-1;
952
953
	struct lutil_tm now_tm;
	struct lutil_timet now_usec;
954
	char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ];
955
956
	char nowstr_usec[ LDAP_LUTIL_GENTIME_BUFSIZE+8 ];
	struct berval timestamp, timestamp_usec;
957
958
959
	BackendInfo *bi = op->o_bd->bd_info;
	Entry *e;

Howard Chu's avatar
Howard Chu committed
960
961
962
963
964
	/* If we already know it's locked, just get on with it */
	if ( ppb->pErr != PP_noError ) {
		goto locked;
	}

965
966
967
968
969
970
971
972
	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;
	}

973
974
975
	ldap_pvt_gettime(&now_tm); /* stored for later consideration */
	lutil_tm2time(&now_tm, &now_usec);
	now = now_usec.tt_sec;
976
977
978
	timestamp.bv_val = nowstr;
	timestamp.bv_len = sizeof(nowstr);
	slap_timestamp( &now, &timestamp );
979

980
981
982
983
984
985
986
	/* 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");

987
988
989
990
991
	if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) {
		int i = 0, fc = 0;

		m = ch_calloc( sizeof(Modifications), 1 );
		m->sml_op = LDAP_MOD_ADD;
992
		m->sml_flags = 0;
993
994
		m->sml_type = ad_pwdFailureTime->ad_cname;
		m->sml_desc = ad_pwdFailureTime;
995
		m->sml_numvals = 1;
996
		m->sml_values = ch_calloc( sizeof(struct berval), 2 );
997
		m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
998

999
1000
		ber_dupbv( &m->sml_values[0], &timestamp_usec );
		ber_dupbv( &m->sml_nvalues[0], &timestamp_usec );
For faster browsing, not all history is shown. View entire blame