ppolicy.c 93.4 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-2021 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 "slap-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
	int disable_write;
59
	int send_netscape_controls;	/* send netscape password controls */
60
	ldap_pvt_thread_mutex_t pwdFailureTime_mutex;
61
62
63
64
65
66
} pp_info;

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

static pw_conn *pwcons;
Howard Chu's avatar
Howard Chu committed
71
static int ppolicy_cid;
72
static int account_usability_cid;
73
static int ov_count;
74
75
76
77
78

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 */
79
80
	int pwdMaxIdle; /* number of seconds since last successful bind before
					   passwd gets locked out */
81
82
83
84
	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 */
85
	int pwdMaxLength; /* maximum number of chars in password */
86
87
	int pwdExpireWarning; /* number of seconds that warning controls are
							sent before a password expires */
88
89
	int pwdGraceExpiry; /* number of seconds after expiry grace logins are
						   valid */
90
	int pwdGraceAuthNLimit; /* number of times you can log in with an
91
92
93
							expired password */
	int pwdLockout; /* 0 = do not lockout passwords, 1 = lock them out */
	int pwdLockoutDuration; /* time in seconds a password is locked out for */
94
95
	int pwdMinDelay; /* base bind delay in seconds on failure */
	int pwdMaxDelay; /* maximum bind delay in seconds */
96
	int pwdMaxFailure; /* number of failed binds allowed before lockout */
97
	int pwdMaxRecordedFailure;	/* number of failed binds to store */
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 */
109
110
	struct berval pwdCheckModuleArg; /* Optional argument to the password check
										module */
111
112
113
114
115
116
117
118
119
120
121
} 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,
122
	*ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset,
123
	*ad_pwdPolicySubentry, *ad_pwdStartTime, *ad_pwdEndTime,
124
	*ad_pwdLastSuccess, *ad_pwdAccountTmpLockoutEnd;
125

126
127
/* Policy attributes */
static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdMaxIdle,
128
	*ad_pwdInHistory, *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxLength,
129
	*ad_pwdMaxFailure, *ad_pwdGraceExpiry, *ad_pwdGraceAuthNLimit,
130
131
	*ad_pwdExpireWarning, *ad_pwdMinDelay, *ad_pwdMaxDelay,
	*ad_pwdLockoutDuration, *ad_pwdFailureCountInterval,
132
133
134
	*ad_pwdCheckModule, *ad_pwdCheckModuleArg, *ad_pwdLockout,
	*ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
	*ad_pwdAttribute, *ad_pwdMaxRecordedFailure;
135

136
137
138
139
140
141
142
143
144
145
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 "
146
		"SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
147
148
149
150
151
152
153
		&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 "
154
155
		"SINGLE-VALUE "
#if 0
Kurt Zeilenga's avatar
Kurt Zeilenga committed
156
		/* Not until Relax control is released */
157
158
159
		"NO-USER-MODIFICATION "
#endif
		"USAGE directoryOperation )",
160
161
162
163
164
165
166
		&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 "
167
		"NO-USER-MODIFICATION USAGE directoryOperation )",
168
169
170
171
172
173
		&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 "
174
		"NO-USER-MODIFICATION USAGE directoryOperation )",
175
176
177
178
179
180
		&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 "
181
		"NO-USER-MODIFICATION USAGE directoryOperation )",
182
183
184
185
186
187
		&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 "
188
		"SINGLE-VALUE USAGE directoryOperation )",
189
190
191
192
193
194
		&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 "
195
196
		"SINGLE-VALUE "
#if 0
Kurt Zeilenga's avatar
Kurt Zeilenga committed
197
		/* Not until Relax control is released */
198
199
200
		"NO-USER-MODIFICATION "
#endif
		"USAGE directoryOperation )",
201
		&ad_pwdPolicySubentry },
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
	{	"( 1.3.6.1.4.1.42.2.27.8.1.27 "
		"NAME ( 'pwdStartTime' ) "
		"DESC 'The time the password becomes enabled' "
		"EQUALITY generalizedTimeMatch "
		"ORDERING generalizedTimeOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
		"SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
		&ad_pwdStartTime },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.28 "
		"NAME ( 'pwdEndTime' ) "
		"DESC 'The time the password becomes disabled' "
		"EQUALITY generalizedTimeMatch "
		"ORDERING generalizedTimeOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
		"SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
		&ad_pwdEndTime },
218
	/* Defined in schema_prep.c now
219
220
221
222
223
224
225
226
	{	"( 1.3.6.1.4.1.42.2.27.8.1.29 "
		"NAME ( 'pwdLastSuccess' ) "
		"DESC 'The timestamp of the last successful authentication' "
		"EQUALITY generalizedTimeMatch "
		"ORDERING generalizedTimeOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
		"SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
		&ad_pwdLastSuccess },
227
	*/
228
229
230
231
232
233
234
235
236
237
238
239
240
	{	"( 1.3.6.1.4.1.42.2.27.8.1.33 "
		"NAME ( 'pwdAccountTmpLockoutEnd' ) "
		"DESC 'Temporary lockout end' "
		"EQUALITY generalizedTimeMatch "
		"ORDERING generalizedTimeOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
		"SINGLE-VALUE "
#if 0
		/* Not until Relax control is released */
		"NO-USER-MODIFICATION "
#endif
		"USAGE directoryOperation )",
		&ad_pwdAccountTmpLockoutEnd },
241
242
243
244
245
246
247
248
249
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

	{	"( 1.3.6.1.4.1.42.2.27.8.1.1 "
		"NAME ( 'pwdAttribute' ) "
		"EQUALITY objectIdentifierMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
		&ad_pwdAttribute },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.2 "
		"NAME ( 'pwdMinAge' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMinAge },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.3 "
		"NAME ( 'pwdMaxAge' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMaxAge },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.4 "
		"NAME ( 'pwdInHistory' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdInHistory },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.5 "
		"NAME ( 'pwdCheckQuality' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdCheckQuality },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.6 "
		"NAME ( 'pwdMinLength' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMinLength },
282
283
284
285
286
287
	{	"( 1.3.6.1.4.1.42.2.27.8.1.31 "
		"NAME ( 'pwdMaxLength' ) "
		"EQUALITY integerMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMaxLength },
288
289
290
291
292
293
294
295
296
297
298
299
300
301
	{	"( 1.3.6.1.4.1.42.2.27.8.1.7 "
		"NAME ( 'pwdExpireWarning' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdExpireWarning },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.8 "
		"NAME ( 'pwdGraceAuthNLimit' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdGraceAuthNLimit },
302
303
304
305
306
307
	{	"( 1.3.6.1.4.1.42.2.27.8.1.30 "
		"NAME ( 'pwdGraceExpiry' ) "
		"EQUALITY integerMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdGraceExpiry },
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
	{	"( 1.3.6.1.4.1.42.2.27.8.1.9 "
		"NAME ( 'pwdLockout' ) "
		"EQUALITY booleanMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
		"SINGLE-VALUE )",
		&ad_pwdLockout },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.10 "
		"NAME ( 'pwdLockoutDuration' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdLockoutDuration },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.11 "
		"NAME ( 'pwdMaxFailure' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMaxFailure },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.12 "
		"NAME ( 'pwdFailureCountInterval' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdFailureCountInterval },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.13 "
		"NAME ( 'pwdMustChange' ) "
		"EQUALITY booleanMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
		"SINGLE-VALUE )",
		&ad_pwdMustChange },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.14 "
		"NAME ( 'pwdAllowUserChange' ) "
		"EQUALITY booleanMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
		"SINGLE-VALUE )",
		&ad_pwdAllowUserChange },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.15 "
		"NAME ( 'pwdSafeModify' ) "
		"EQUALITY booleanMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
		"SINGLE-VALUE )",
		&ad_pwdSafeModify },
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
	{	"( 1.3.6.1.4.1.42.2.27.8.1.24 "
		"NAME ( 'pwdMinDelay' ) "
		"EQUALITY integerMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMinDelay },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.25 "
		"NAME ( 'pwdMaxDelay' ) "
		"EQUALITY integerMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMaxDelay },
	{	"( 1.3.6.1.4.1.42.2.27.8.1.26 "
		"NAME ( 'pwdMaxIdle' ) "
		"EQUALITY integerMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMaxIdle },
371
372
373
374
375
376
377
378
379
380
381
382
383
384
	{	"( 1.3.6.1.4.1.42.2.27.8.1.32 "
		"NAME ( 'pwdMaxRecordedFailure' ) "
		"EQUALITY integerMatch "
		"ORDERING integerOrderingMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
		"SINGLE-VALUE )",
		&ad_pwdMaxRecordedFailure },
	{	"( 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 "
		"DESC 'Loadable module that instantiates check_password() function' "
		"SINGLE-VALUE )",
		&ad_pwdCheckModule },
385
386
387
388
389
390
391
	{	"( 1.3.6.1.4.1.4754.1.99.2 "
		"NAME ( 'pwdCheckModuleArg' ) "
		"EQUALITY octetStringMatch "
		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
		"DESC 'Argument to pass to check_password() function' "
		"SINGLE-VALUE )",
		&ad_pwdCheckModuleArg },
392

393
394
395
	{ NULL, NULL }
};

396
397
398
399
400
static char *pwd_ocs[] = {
	"( 1.3.6.1.4.1.4754.2.99.1 "
		"NAME 'pwdPolicyChecker' "
		"SUP top "
		"AUXILIARY "
401
		"MAY ( pwdCheckModule $ pwdCheckModuleArg ) )" ,
402
403
404
405
406
407
	"( 1.3.6.1.4.1.42.2.27.8.2.1 "
		"NAME 'pwdPolicy' "
		"SUP top "
		"AUXILIARY "
		"MUST ( pwdAttribute ) "
		"MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $ "
408
409
		"pwdMinLength $ pwdMaxLength $ pwdExpireWarning $ "
		"pwdGraceAuthNLimit $ pwdGraceExpiry $ pwdLockout $ "
410
411
		"pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ "
		"pwdMustChange $ pwdAllowUserChange $ pwdSafeModify $ "
412
		"pwdMinDelay $ pwdMaxDelay $ pwdMaxIdle $ "
413
414
		"pwdMaxRecordedFailure ) )",
	NULL
415
416
417
418
};

static ldap_pvt_thread_mutex_t chk_syntax_mutex;

419
420
421
enum {
	PPOLICY_DEFAULT = 1,
	PPOLICY_HASH_CLEARTEXT,
422
423
	PPOLICY_USE_LOCKOUT,
	PPOLICY_DISABLE_WRITE,
424
425
426
427
428
429
};

static ConfigDriver ppolicy_cf_default;

static ConfigTable ppolicycfg[] = {
	{ "ppolicy_default", "policyDN", 2, 2, 0,
430
	  ARG_DN|ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT, ppolicy_cf_default,
431
432
	  "( OLcfgOvAt:12.1 NAME 'olcPPolicyDefault' "
	  "DESC 'DN of a pwdPolicy object for uncustomized objects' "
433
	  "EQUALITY distinguishedNameMatch "
434
435
436
437
438
439
	  "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' "
440
	  "EQUALITY booleanMatch "
441
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
442
443
444
445
446
	{ "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' "
447
	  "EQUALITY booleanMatch "
448
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
449
450
451
452
453
	{ "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' "
454
	  "EQUALITY booleanMatch "
455
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
456
457
458
459
460
	{ "ppolicy_disable_write", "on|off", 1, 2, 0,
	  ARG_ON_OFF|ARG_OFFSET|PPOLICY_DISABLE_WRITE,
	  (void *)offsetof(pp_info,disable_write),
	  "( OLcfgOvAt:12.5 NAME 'olcPPolicyDisableWrite' "
	  "DESC 'Prevent all policy overlay writes' "
461
462
463
464
465
466
467
468
	  "EQUALITY booleanMatch "
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
	{ "ppolicy_send_netscape_controls", "on|off", 1, 2, 0,
	  ARG_ON_OFF|ARG_OFFSET,
	  (void *)offsetof(pp_info,send_netscape_controls),
	  "( OLcfgOvAt:12.6 NAME 'olcPPolicySendNetscapeControls' "
	  "DESC 'Send Netscape policy controls' "
	  "EQUALITY booleanMatch "
469
	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
470
471
472
473
474
475
476
477
478
	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};

static ConfigOCs ppolicyocs[] = {
	{ "( OLcfgOvOc:12.1 "
	  "NAME 'olcPPolicyConfig' "
	  "DESC 'Password Policy configuration' "
	  "SUP olcOverlayConfig "
	  "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ "
479
	  "olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ "
480
	  "olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls ) )",
481
482
483
484
485
486
487
488
489
490
491
492
	  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 );
493
	Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default\n" );
494
495
496

	switch ( c->op ) {
	case SLAP_CONFIG_EMIT:
497
		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n" );
498
499
500
501
502
503
504
505
506
507
		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:
508
		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default delete\n" );
509
510
511
512
513
514
515
516
		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:
Josh Soref's avatar
Josh Soref committed
517
		/* fallthru to LDAP_MOD_ADD */
518
	case LDAP_MOD_ADD:
519
		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n" );
520
		if ( pi->def_policy.bv_val ) {
521
			ber_memfree ( pi->def_policy.bv_val );
522
		}
523
		pi->def_policy = c->value_ndn;
524
525
526
		ber_memfree( c->value_dn.bv_val );
		BER_BVZERO( &c->value_dn );
		BER_BVZERO( &c->value_ndn );
527
528
529
530
531
532
533
534
535
		rc = 0;
		break;
	default:
		abort ();
	}

	return rc;
}

536
537
538
static time_t
parse_time( char *atm )
{
539
540
	struct lutil_tm tm;
	struct lutil_timet tt;
541
542
	time_t ret = (time_t)-1;

543
544
545
	if ( lutil_parsetime( atm, &tm ) == 0) {
		lutil_tm2time( &tm, &tt );
		ret = tt.tt_sec;
546
547
	}
	return ret;
548
549
550
551
552
553
554
555
}

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

556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
	if ( (la = attr_find( e->e_attrs, ad_pwdStartTime )) != NULL ) {
		BerVarray vals = la->a_nvals;
		time_t then, now = op->o_time;

		/*
		 * Password has a defined start of validity
		 */
		if ( vals[0].bv_val != NULL ) {
			if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
				return 1;
			}
			if ( now < then ) {
				return 1;
			}
		}
	}

	if ( (la = attr_find( e->e_attrs, ad_pwdEndTime )) != NULL ) {
		BerVarray vals = la->a_nvals;
		time_t then, now = op->o_time;

		/*
		 * Password has a defined end of validity
		 */
		if ( vals[0].bv_val != NULL ) {
			if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
				return 1;
			}
			if ( then <= now ) {
				return 1;
			}
		}
	}

590
591
592
	if ( !pp->pwdLockout )
		return 0;

593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
	if ( (la = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) {
		BerVarray vals = la->a_nvals;
		time_t then, now = op->o_time;

		/*
		 * We have temporarily locked the account after a failure
		 */
		if ( vals[0].bv_val != NULL ) {
			if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
				return 1;
			}
			if ( now < then ) {
				return 1;
			}
		}
	}

610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
	/* Only check if database maintains lastbind */
	if ( pp->pwdMaxIdle && SLAP_LASTBIND( op->o_bd ) ) {
		time_t lastbindtime = (time_t)-1;

		la = attr_find( e->e_attrs, ad_pwdLastSuccess );
		if ( la == NULL ) {
			la = attr_find( e->e_attrs, ad_pwdChangedTime );
		}
		if ( la != NULL ) {
			lastbindtime = parse_time( la->a_nvals[0].bv_val );
		}

		if ( lastbindtime != (time_t)-1 &&
				op->o_time > lastbindtime + pp->pwdMaxIdle ) {
			return 1;
		}
	}

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
	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;

			if ((then = parse_time( vals[0].bv_val )) == (time_t)0)
				return 1;

			now = slap_get_time();

644
645
646
647
648
649
650
			/* Still in the future? not yet in effect */
			if (now < then)
				return 0;

			if (!pp->pwdLockoutDuration)
				return 1;

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

654
655
656
657
658
659
660
661
662
			if ( mod != NULL ) {
				m = ch_calloc( sizeof(Modifications), 1 );
				m->sml_op = LDAP_MOD_DELETE;
				m->sml_flags = 0;
				m->sml_type = ad_pwdAccountLockedTime->ad_cname;
				m->sml_desc = ad_pwdAccountLockedTime;
				m->sml_next = *mod;
				*mod = m;
			}
663
664
665
666
667
668
		}
	}

	return 0;
}

669
670
/* IMPLICIT TAGS, all context-specific */
#define PPOLICY_WARNING 0xa0L	/* constructed + 0 */
671
#define PPOLICY_ERROR 0x81L		/* primitive + 1 */
672
 
673
674
#define PPOLICY_EXPIRE 0x80L	/* primitive + 0 */
#define PPOLICY_GRACE  0x81L	/* primitive + 1 */
675

Howard Chu's avatar
Howard Chu committed
676
static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;
677
static const char ppolicy_account_ctrl_oid[] = LDAP_CONTROL_X_ACCOUNT_USABILITY;
678
679
static const char ppolicy_pwd_expired_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRED;
static const char ppolicy_pwd_expiring_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRING;
Howard Chu's avatar
Howard Chu committed
680

681
static LDAPControl *
682
create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err )
683
{
684
685
	BerElementBuffer berbuf, bb2;
	BerElement *ber = (BerElement *) &berbuf, *b2 = (BerElement *) &bb2;
686
	LDAPControl c = { 0 }, *cp;
687
	struct berval bv;
Howard Chu's avatar
Howard Chu committed
688
	int rc;
689

690
	BER_BVZERO( &c.ldctl_value );
691
692

	ber_init2( ber, NULL, LBER_USE_DER );
693
	ber_printf( ber, "{" /*}*/ );
694

695
	if ( exptime >= 0 ) {
696
697
		ber_init2( b2, NULL, LBER_USE_DER );
		ber_printf( b2, "ti", PPOLICY_EXPIRE, exptime );
Howard Chu's avatar
Howard Chu committed
698
		rc = ber_flatten2( b2, &bv, 1 );
699
		(void)ber_free_buf(b2);
Howard Chu's avatar
Howard Chu committed
700
701
702
703
		if (rc == -1) {
			cp = NULL;
			goto fail;
		}
704
705
		ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
		ch_free( bv.bv_val );
706
	} else if ( grace >= 0 ) {
707
708
		ber_init2( b2, NULL, LBER_USE_DER );
		ber_printf( b2, "ti", PPOLICY_GRACE, grace );
Howard Chu's avatar
Howard Chu committed
709
		rc = ber_flatten2( b2, &bv, 1 );
710
		(void)ber_free_buf(b2);
Howard Chu's avatar
Howard Chu committed
711
712
713
714
		if (rc == -1) {
			cp = NULL;
			goto fail;
		}
715
716
717
718
719
720
721
722
723
		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}" );

724
	if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
725
		return NULL;
726
	}
727
728
729
730
731
732
	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
733
fail:
734
	(void)ber_free_buf(ber);
735
736
	
	return cp;
737
738
}

739
740
741
static LDAPControl *
create_passexpiry( Operation *op, int expired, int warn )
{
742
	LDAPControl *cp;
743
744
745
746
747
	char buf[sizeof("-2147483648")];
	struct berval bv = { .bv_val = buf, .bv_len = sizeof(buf) };

	bv.bv_len = snprintf( bv.bv_val, bv.bv_len, "%d", warn );

748
	cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
749
750
751
752
753
754
755
	if ( expired ) {
		cp->ldctl_oid = (char *)ppolicy_pwd_expired_oid;
	} else {
		cp->ldctl_oid = (char *)ppolicy_pwd_expiring_oid;
	}
	cp->ldctl_iscritical = 0;
	cp->ldctl_value.bv_val = (char *)&cp[1];
756
757
	cp->ldctl_value.bv_len = bv.bv_len;
	AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
758
759
760
	return cp;
}

761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
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;
}

790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
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
static void
add_account_control(
	Operation *op,
	SlapReply *rs,
	int available,
	int remaining,
	LDAPAccountUsabilityMoreInfo *more_info )
{
	BerElementBuffer berbuf;
	BerElement *ber = (BerElement *) &berbuf;
	LDAPControl c = { 0 }, *cp = NULL, **ctrls;
	int i = 0;

	BER_BVZERO( &c.ldctl_value );

	ber_init2( ber, NULL, LBER_USE_DER );

	if ( available ) {
		ber_put_int( ber, remaining, LDAP_TAG_X_ACCOUNT_USABILITY_AVAILABLE );
	} else {
		assert( more_info != NULL );

		ber_start_seq( ber, LDAP_TAG_X_ACCOUNT_USABILITY_NOT_AVAILABLE );
		ber_put_boolean( ber, more_info->inactive, LDAP_TAG_X_ACCOUNT_USABILITY_INACTIVE );
		ber_put_boolean( ber, more_info->reset, LDAP_TAG_X_ACCOUNT_USABILITY_RESET );
		ber_put_boolean( ber, more_info->expired, LDAP_TAG_X_ACCOUNT_USABILITY_EXPIRED );
		ber_put_int( ber, more_info->remaining_grace, LDAP_TAG_X_ACCOUNT_USABILITY_REMAINING_GRACE );
		ber_put_int( ber, more_info->seconds_before_unlock, LDAP_TAG_X_ACCOUNT_USABILITY_UNTIL_UNLOCK );
		ber_put_seq( ber );
	}

	if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
		goto fail;
	}

	if ( rs->sr_ctrls != NULL ) {
		for ( ; rs->sr_ctrls[ i ] != NULL; i++ ) /* Count */;
	}

	ctrls = op->o_tmprealloc( rs->sr_ctrls, sizeof(LDAPControl *)*( i + 2 ), op->o_tmpmemctx );
	if ( ctrls == NULL ) {
		goto fail;
	}

	cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx );
	cp->ldctl_oid = (char *)ppolicy_account_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 );

	ctrls[ i ] = cp;
	ctrls[ i + 1 ] = NULL;
	rs->sr_ctrls = ctrls;

fail:
	(void)ber_free_buf(ber);
}

849
850
851
852
853
854
855
856
857
858
859
860
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;
}


861
static int
862
863
864
865
866
867
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;
868
	int rc = LDAP_SUCCESS;
869
	Entry *pe = NULL;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
870
#if 0
871
	const char *text;
Hallvard Furuseth's avatar
Hallvard Furuseth committed
872
#endif
873

874
	ppolicy_get_default( pp );
875

876
877
878
879
880
881
882
883
884
885
886
	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,
887
				"ppolicy_get: NULL value for policySubEntry\n" );
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
			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

903
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMinAge ) )
904
905
			&& lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
906
		goto defaultpol;
907
	}
908
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxAge ) )
909
910
			&& lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
911
		goto defaultpol;
912
	}
913
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxIdle ) )
914
915
			&& lutil_atoi( &pp->pwdMaxIdle, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
916
		goto defaultpol;
917
	}
918
	if ( ( a = attr_find( pe->e_attrs, ad_pwdInHistory ) )
919
920
			&& lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
921
		goto defaultpol;
922
	}
923
	if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckQuality ) )
924
925
			&& lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
926
		goto defaultpol;
927
	}
928
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMinLength ) )
929
930
			&& lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
931
		goto defaultpol;
932
	}
933
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxLength ) )
934
935
			&& lutil_atoi( &pp->pwdMaxLength, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
936
		goto defaultpol;
937
	}
938
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxFailure ) )
939
940
			&& lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
941
		goto defaultpol;
942
	}
943
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxRecordedFailure ) )
944
945
			&& lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
946
		goto defaultpol;
947
	}
948
	if ( ( a = attr_find( pe->e_attrs, ad_pwdGraceExpiry ) )
949
950
			&& lutil_atoi( &pp->pwdGraceExpiry, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
951
		goto defaultpol;
952
	}
953
	if ( ( a = attr_find( pe->e_attrs, ad_pwdGraceAuthNLimit ) )
954
955
			&& lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
956
		goto defaultpol;
957
	}
958
	if ( ( a = attr_find( pe->e_attrs, ad_pwdExpireWarning ) )
959
960
			&& lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
961
		goto defaultpol;
962
	}
963
	if ( ( a = attr_find( pe->e_attrs, ad_pwdFailureCountInterval ) )
964
965
			&& lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
966
		goto defaultpol;
967
	}
968
	if ( ( a = attr_find( pe->e_attrs, ad_pwdLockoutDuration ) )
969
970
			&& lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
971
		goto defaultpol;
972
	}
973
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMinDelay ) )
974
975
			&& lutil_atoi( &pp->pwdMinDelay, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
976
		goto defaultpol;
977
	}
978
	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxDelay ) )
979
980
			&& lutil_atoi( &pp->pwdMaxDelay, a->a_vals[0].bv_val ) != 0 ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
981
		goto defaultpol;
982
	}
983
984
985
986

	if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckModule ) ) ) {
		strncpy( pp->pwdCheckModule, a->a_vals[0].bv_val,
			sizeof(pp->pwdCheckModule) );
987
988
		pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0';
	}
989
990
991
	if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckModuleArg ) ) ) {
		ber_dupbv_x( &pp->pwdCheckModuleArg, &a->a_vals[0], op->o_tmpmemctx );
	}
992
993

	if ((a = attr_find( pe->e_attrs, ad_pwdLockout )))
Ondřej Kuzník's avatar
Ondřej Kuzník committed
994
		pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv );
995
	if ((a = attr_find( pe->e_attrs, ad_pwdMustChange )))
Ondřej Kuzník's avatar
Ondřej Kuzník committed
996
		pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
997
	if ((a = attr_find( pe->e_attrs, ad_pwdAllowUserChange )))
Ondřej Kuzník's avatar
Ondřej Kuzník committed
998
		pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
999
	if ((a = attr_find( pe->e_attrs, ad_pwdSafeModify )))
Ondřej Kuzník's avatar
Ondřej Kuzník committed
1000
1001
		pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );

1002
1003
	if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure )
		pp->pwdMaxRecordedFailure = pp->pwdMaxFailure;
1004
	if ( !pp->pwdMaxRecordedFailure && pp->pwdMinDelay )
1005
1006
		pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;

1007
1008
1009
1010
1011
1012
	if ( pp->pwdMinDelay && !pp->pwdMaxDelay ) {
		Debug( LDAP_DEBUG_ANY, "ppolicy_get: pwdMinDelay was set but pwdMaxDelay wasn't, "
				"assuming they are equal\n" );
		pp->pwdMaxDelay = pp->pwdMinDelay;
	}

1013
1014
1015
1016
	op->o_bd->bd_info = (BackendInfo *)on->on_info;
	be_entry_release_r( op, pe );
	op->o_bd->bd_info = (BackendInfo *)on;

1017
	return LDAP_SUCCESS;
1018
1019

defaultpol:
Ryan Tandy's avatar
Ryan Tandy committed
1020
1021
1022
1023
1024
1025
	if ( pe ) {
		op->o_bd->bd_info = (BackendInfo *)on->on_info;
		be_entry_release_r( op, pe );
		op->o_bd->bd_info = (BackendInfo *)on;
	}

1026
1027
1028
1029
1030
1031
1032
1033
	if ( rc && !BER_BVISNULL( vals ) ) {
		Debug( LDAP_DEBUG_ANY,
			"ppolicy_get: policy subentry %s missing or invalid\n",
			vals->bv_val );
	} else {
		Debug( LDAP_DEBUG_TRACE,
			"ppolicy_get: using default policy\n" );
	}
1034
1035
1036

	ppolicy_get_default( pp );

1037
	return -1;
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
}

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
1057
		int rc;
1058
		rc = lutil_passwd_scheme( cred->bv_val );
1059
1060
1061
1062
1063
		if (rc) {
			if (sch) {
				sch->bv_val = cred->bv_val;
				sch->bv_len = e;
			}
1064
1065
1066
1067
1068
1069
1070
			return LDAP_SUCCESS;
		}
	}
	return LDAP_OTHER;
}

static int
1071
check_password_quality( struct berval *cred, PassPolicy *pp, LDAPPasswordPolicyError *err, Entry *e, char **txt )
1072
1073
{
	int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
1074
	char *ptr;
1075
1076
1077
1078
	struct berval sch;

	assert( cred != NULL );
	assert( pp != NULL );
Howard Chu's avatar
Howard Chu committed
1079
1080
	assert( txt != NULL );

1081
1082
	ptr = cred->bv_val;

Howard Chu's avatar
Howard Chu committed
1083
	*txt = NULL;
1084
1085
1086
1087
1088
1089
1090

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

1091
1092
1093
1094
1095
1096
	if ( pp->pwdMaxLength && cred->bv_len > pp->pwdMaxLength ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
		if ( err ) *err = PP_passwordTooLong;
		return rc;
	}

1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
        /*
         * 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;
1127

1128
	if (pp->pwdCheckModule[0]) {
1129
#ifdef SLAPD_MODULES
1130
1131
1132
1133
1134
1135
1136
1137
		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",
1138
				pp->pwdCheckModule, err );
1139
1140
			ok = LDAP_OTHER; /* internal error */
		} else {
1141
1142
1143
1144
1145
			/* 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.
			 */
1146
			int (*prog)( char *passwd, char **text, Entry *ent, struct berval *arg );
1147
1148
1149

			if ((prog = lt_dlsym( mod, "check_password" )) == NULL) {
				err = lt_dlerror();
1150

1151
1152
				Debug(LDAP_DEBUG_ANY,
					"check_password_quality: lt_dlsym failed: (%s) %s.\n",
1153
					pp->pwdCheckModule, err );
1154
1155
				ok = LDAP_OTHER;
			} else {
1156
1157
1158
1159
1160
				struct berval *arg = NULL;
				if ( !BER_BVISNULL( &pp->pwdCheckModuleArg ) ) {
					arg = &pp->pwdCheckModuleArg;
				}

1161
				ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
1162
				ok = prog( ptr, txt, e, arg );
1163
				ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
1164
				if (ok != LDAP_SUCCESS) {
1165
1166
					Debug(LDAP_DEBUG_ANY,
						"check_password_quality: module error: (%s) %s.[%d]\n",
1167
						pp->pwdCheckModule, *txt ? *txt : "", ok );
1168
				}
1169
1170
1171
1172
			}
			    
			lt_dlclose( mod );
		}
1173
1174
#else
	Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
1175
		"supported. pwdCheckModule ignored.\n" );
1176
#endif /* SLAPD_MODULES */
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
	}
		
		    
	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;
1193
	ber_len_t i, j;
1194
1195
1196
	
	assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw );

1197
1198
1199
	if ( oid ) {
		*oid = 0;
	}
1200
	*oldtime = (time_t)-1;
1201
	BER_BVZERO( oldpw );
1202
1203
1204
1205
	
	ber_dupbv( &nv, bv );

	/* first get the time field */
1206
1207
1208
1209
1210
	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 */
	}
1211
1212
1213
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
	ptr = nv.bv_val;
	*oldtime = parse_time( ptr );
1214
1215
1216
	if (*oldtime == (time_t)-1) {
		goto exit_failure;
	}
1217
1218

	/* get the OID field */
1219
1220
1221
1222
1223
	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 */
	}
1224
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
1225
1226
1227
	if ( oid ) {
		*oid = ber_strdup( ptr );
	}
1228
1229
	
	/* get the length field */
1230
1231
1232
1233
1234
	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 */
	}
1235
1236
	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
	oldpw->bv_len = strtol( ptr, NULL, 10 );
1237
1238
1239
	if (errno == ERANGE) {
		goto exit_failure;
	}
1240
1241

	/* lastly, get the octets of the string */
1242
1243
1244
1245
1246
	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 */
	}
1247
1248
1249
1250

	npw.bv_val = ptr;
	npw.bv_len = oldpw->bv_len;
	ber_dupbv( oldpw, &npw );
1251
	ber_memfree( nv.bv_val );
1252
1253
	
	return LDAP_SUCCESS;
1254
1255
1256
1257
1258

exit_failure:;
	if ( oid && *oid ) {
		ber_memfree(*oid);
		*oid = NULL;
1259
	}
1260
1261
1262
1263
1264
1265
	if ( oldpw->bv_val ) {
		ber_memfree( oldpw->bv_val);
		BER_BVZERO( oldpw );
	}
	ber_memfree( nv.bv_val );

1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
	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,
1309
		  "%s#%s#%lu#", timebuf,
1310
		  pa->a_desc->ad_type->sat_syntax->ssyn_oid,
1311
		  (unsigned long) pa->a_nvals[0].bv_len );
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345