1
/** BEGIN COPYRIGHT BLOCK
2
* This Program is free software; you can redistribute it and/or modify it under
3
* the terms of the GNU General Public License as published by the Free Software
4
* Foundation; version 2 of the License.
6
* This Program is distributed in the hope that it will be useful, but WITHOUT
7
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
8
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
* You should have received a copy of the GNU General Public License along with
11
* this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
12
* Place, Suite 330, Boston, MA 02111-1307 USA.
14
* In addition, as a special exception, Red Hat, Inc. gives You the additional
15
* right to link the code of this Program with code not covered under the GNU
16
* General Public License ("Non-GPL Code") and to distribute linked combinations
17
* including the two, subject to the limitations in this paragraph. Non-GPL Code
18
* permitted under this exception must only link to the code of this Program
19
* through those well defined interfaces identified in the file named EXCEPTION
20
* found in the source code files (the "Approved Interfaces"). The files of
21
* Non-GPL Code may instantiate templates or use macros or inline functions from
22
* the Approved Interfaces without causing the resulting work to be covered by
23
* the GNU General Public License. Only Red Hat, Inc. may make changes or
24
* additions to the list of Approved Interfaces. You must obey the GNU General
25
* Public License in all respects for all of the Program code and other code used
26
* in conjunction with the Program except the Non-GPL Code covered by this
27
* exception. If you modify this file, you may extend this exception to your
28
* version of the file, but you are not obligated to do so. If you do not wish to
29
* provide this exception without modification, you must delete this exception
30
* statement from your version and license this file solely under the GPL without
34
* Copyright (C) 2005 Red Hat, Inc.
35
* All rights reserved.
36
* END COPYRIGHT BLOCK **/
43
* Password Modify - LDAP Extended Operation.
47
* This plugin implements the "Password Modify - LDAP3"
48
* extended operation for LDAP. The plugin function is called by
49
* the server if an LDAP client request contains the OID:
50
* "1.3.6.1.4.1.4203.1.11.1".
56
#include <private/pprio.h>
64
#include "slapi-plugin.h"
67
/* Type of connection for this operation;*/
68
#define LDAP_EXTOP_PASSMOD_CONN_SECURE
70
/* Uncomment the following line FOR TESTING: allows non-SSL connections to use the password change extended op */
71
/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */
73
/* ber tags for the PasswdModifyRequestValue sequence */
74
#define LDAP_EXTOP_PASSMOD_TAG_USERID 0x80U
75
#define LDAP_EXTOP_PASSMOD_TAG_OLDPWD 0x81U
76
#define LDAP_EXTOP_PASSMOD_TAG_NEWPWD 0x82U
78
/* ber tags for the PasswdModifyResponseValue sequence */
79
#define LDAP_EXTOP_PASSMOD_TAG_GENPWD 0x80U
81
/* number of bytes used for random password generation */
82
#define LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN 8
84
/* number of random bytes needed to generate password */
85
#define LDAP_EXTOP_PASSMOD_RANDOM_BYTES 6
88
Slapi_PluginDesc passwdopdesc = { "passwd_modify_plugin", VENDOR, DS_PACKAGE_VERSION,
89
"Password Modify extended operation plugin" };
91
/* Check SLAPI_USERPWD_ATTR attribute of the directory entry
92
* return 0, if the userpassword attribute contains the given pwd value
93
* return -1, if userPassword attribute is absent for given Entry
94
* return LDAP_INVALID_CREDENTIALS,if userPassword attribute and given pwd don't match
96
static int passwd_check_pwd(Slapi_Entry *targetEntry, const char *pwd){
97
int rc = LDAP_SUCCESS;
98
Slapi_Attr *attr = NULL;
102
LDAPDebug( LDAP_DEBUG_TRACE, "=> passwd_check_pwd\n", 0, 0, 0 );
104
slapi_value_init_string(&cv,pwd);
106
if ( (rc = slapi_entry_attr_find( targetEntry, SLAPI_USERPWD_ATTR, &attr )) == 0 )
107
{ /* we have found the userPassword attribute and it has some value */
108
bvals = attr_get_present_values( attr );
109
if ( slapi_pw_find_sv( bvals, &cv ) != 0 )
111
rc = LDAP_INVALID_CREDENTIALS;
116
LDAPDebug( LDAP_DEBUG_TRACE, "<= passwd_check_pwd: %d\n", rc, 0, 0 );
118
/* if the userPassword attribute is absent then rc is -1 */
123
/* Searches the dn in directory,
124
* If found : fills in slapi_entry structure and returns 0
125
* If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT
128
passwd_modify_getEntry( const char *dn, Slapi_Entry **e2 ) {
129
int search_result = 0;
131
LDAPDebug( LDAP_DEBUG_TRACE, "=> passwd_modify_getEntry\n", 0, 0, 0 );
132
slapi_sdn_init_dn_byref( &sdn, dn );
133
if ((search_result = slapi_search_internal_get_entry( &sdn, NULL, e2,
134
plugin_get_default_component_id())) != LDAP_SUCCESS ){
135
LDAPDebug (LDAP_DEBUG_TRACE, "passwd_modify_getEntry: No such entry-(%s), err (%d)\n",
136
dn, search_result, 0);
139
slapi_sdn_done( &sdn );
140
LDAPDebug( LDAP_DEBUG_TRACE, "<= passwd_modify_getEntry: %d\n", search_result, 0, 0 );
141
return search_result;
145
/* Construct Mods pblock and perform the modify operation
146
* Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT
149
passwd_apply_mods(Slapi_PBlock *pb_orig, const Slapi_DN *sdn, Slapi_Mods *mods,
150
LDAPControl **req_controls, LDAPControl ***resp_controls)
153
LDAPControl **req_controls_copy = NULL;
154
LDAPControl **pb_resp_controls = NULL;
157
LDAPDebug( LDAP_DEBUG_TRACE, "=> passwd_apply_mods\n", 0, 0, 0 );
159
if (mods && (slapi_mods_get_num_mods(mods) > 0))
161
/* We need to dup the request controls since the original
162
* pblock owns the ones that have been passed in. */
164
slapi_add_controls(&req_controls_copy, req_controls, 1);
168
slapi_modify_internal_set_pb_ext (&pb, sdn,
169
slapi_mods_get_ldapmods_byref(mods),
170
req_controls_copy, NULL, /* UniqueID */
171
plugin_get_default_component_id(), /* PluginID */
174
/* We copy the connection from the original pblock into the
175
* pblock we use for the internal modify operation. We do
176
* this to allow the password policy code to be able to tell
177
* that the password change was initiated by the user who
178
* sent the extended operation instead of always assuming
179
* that it was done by the root DN. */
180
pb.pb_conn = pb_orig->pb_conn;
182
ret =slapi_modify_internal_pb (&pb);
184
/* We now clean up the connection that we copied into the
185
* new pblock. We want to leave it untouched. */
188
slapi_pblock_get(&pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
190
/* Retreive and duplicate the response controls since they will be
191
* destroyed along with the pblock used for the internal operation. */
192
slapi_pblock_get(&pb, SLAPI_RESCONTROLS, &pb_resp_controls);
193
if (pb_resp_controls) {
194
slapi_add_controls(resp_controls, pb_resp_controls, 1);
197
if (ret != LDAP_SUCCESS){
198
LDAPDebug(LDAP_DEBUG_TRACE, "WARNING: passwordPolicy modify error %d on entry '%s'\n",
199
ret, slapi_sdn_get_dn(sdn), 0);
205
LDAPDebug( LDAP_DEBUG_TRACE, "<= passwd_apply_mods: %d\n", ret, 0, 0 );
212
/* Modify the userPassword attribute field of the entry */
213
static int passwd_modify_userpassword(Slapi_PBlock *pb_orig, Slapi_Entry *targetEntry,
214
const char *newPasswd, LDAPControl **req_controls, LDAPControl ***resp_controls)
219
LDAPDebug( LDAP_DEBUG_TRACE, "=> passwd_modify_userpassword\n", 0, 0, 0 );
221
slapi_mods_init (&smods, 0);
222
slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, SLAPI_USERPWD_ATTR, newPasswd);
225
ret = passwd_apply_mods(pb_orig, slapi_entry_get_sdn_const(targetEntry),
226
&smods, req_controls, resp_controls);
228
slapi_mods_done(&smods);
230
LDAPDebug( LDAP_DEBUG_TRACE, "<= passwd_modify_userpassword: %d\n", ret, 0, 0 );
235
/* Generate a new, basic random password */
236
static int passwd_modify_generate_basic_passwd( int passlen, char **genpasswd )
240
int datalen = LDAP_EXTOP_PASSMOD_RANDOM_BYTES;
242
if ( genpasswd == NULL ) {
243
return LDAP_OPERATIONS_ERROR;
247
datalen = passlen * 3 / 4 + 1;
250
data = slapi_ch_calloc( datalen, 1 );
252
/* get random bytes from NSS */
253
PK11_GenerateRandom( (unsigned char *)data, datalen );
255
/* b64 encode the random bytes to get a password made up
256
* of printable characters. */
257
enc = PL_Base64Encode( data, datalen, NULL );
259
/* This will get freed by the caller */
260
*genpasswd = slapi_ch_malloc( 1 + passlen );
262
/* trim the password to the proper length */
263
PL_strncpyz( *genpasswd, enc, passlen + 1 );
265
slapi_ch_free_string( &data );
266
slapi_ch_free_string( &enc );
271
/* Generate a new, password-policy-based random password */
272
static int passwd_modify_generate_policy_passwd( passwdPolicy *pwpolicy,
273
char **genpasswd, char **errMesg )
275
unsigned char *data = NULL;
285
int my_policy[idx_end];
289
} chr_table[] = { /* NOTE: the above enum order */
290
{ 65, 26 }, /* [ A - Z ] */
291
{ 97, 26 }, /* [ a - z ] */
292
{ 48, 10 }, /* [ 0 - 9 ] */
293
{ 58, 7 } /* [ : - @ ] */
295
#define gen_policy_pw_getchar(n, idx) \
296
( chr_table[(idx)].chr_start + (n) % chr_table[(idx)].chr_range )
299
if ( genpasswd == NULL ) {
300
return LDAP_OPERATIONS_ERROR;
303
my_policy[idx_mindigits] = pwpolicy->pw_mindigits;
304
my_policy[idx_minuppers] = pwpolicy->pw_minuppers;
305
my_policy[idx_minlowers] = pwpolicy->pw_minlowers;
306
my_policy[idx_minspecials] = pwpolicy->pw_minspecials;
308
/* if only minalphas is set, divide it into minuppers and minlowers. */
309
if ( pwpolicy->pw_minalphas > 0 &&
310
( my_policy[idx_minuppers] == 0 && my_policy[idx_minlowers] == 0 )) {
311
unsigned int x = (unsigned int)time(NULL);
312
my_policy[idx_minuppers] = slapi_rand_r(&x) % pwpolicy->pw_minalphas;
313
my_policy[idx_minlowers] = pwpolicy->pw_minalphas - my_policy[idx_minuppers];
316
if ( pwpolicy->pw_mincategories ) {
318
for ( i = 0; i < idx_end; i++ ) {
319
if ( my_policy[i] > 0 ) {
323
if ( pwpolicy->pw_mincategories > categories ) {
324
categories = pwpolicy->pw_mincategories;
325
for ( i = 0; i < idx_end; i++ ) {
326
if ( my_policy[i] == 0 ) {
327
/* force to add a policy to match the pw_mincategories */
330
if ( --categories == 0 ) {
334
if ( categories > 0 ) {
335
/* password generator does not support passwordMin8Bit */
336
LDAPDebug( LDAP_DEBUG_ANY,
337
"Unable to generate a password that meets the current "
338
"password syntax rules. A minimum categories setting "
339
"of %d is not supported with random password generation.\n",
340
pwpolicy->pw_mincategories, 0, 0 );
341
*errMesg = "Unable to generate new random password. Please contact the Administrator.";
342
return LDAP_CONSTRAINT_VIOLATION;
347
/* get the password length */
349
for ( i = 0; i < idx_end; i++ ) {
350
tmplen += my_policy[i];
353
if ( passlen < pwpolicy->pw_minlength ) {
354
passlen = pwpolicy->pw_minlength;
356
if ( passlen < LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN ) {
357
passlen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN;
360
data = (unsigned char *)slapi_ch_calloc( passlen, 1 );
362
/* get random bytes from NSS */
363
PK11_GenerateRandom( data, passlen );
365
/* if password length is longer the sum of my_policy's,
366
let them share the burden */
367
if ( passlen > tmplen ) {
368
unsigned int x = (unsigned int)time(NULL);
369
int delta = passlen - tmplen;
370
for ( i = 0; i < delta; i++ ) {
371
my_policy[(x = slapi_rand_r(&x)) % idx_end]++;
375
/* This will get freed by the caller */
376
*genpasswd = slapi_ch_malloc( 1 + passlen );
378
for ( i = 0; i < passlen; i++ ) {
379
int idx = data[i] % idx_end;
381
/* choose a category based on the random value */
382
while ( my_policy[idx] <= 0 ) {
383
if ( ++idx == idx_end ) {
384
idx = 0; /* if no rule is found, default is uppercase */
392
(*genpasswd)[i] = gen_policy_pw_getchar(data[i], idx);
394
(*genpasswd)[passlen] = '\0';
396
slapi_ch_free( (void **)&data );
401
/* Generate a new random password */
402
static int passwd_modify_generate_passwd( passwdPolicy *pwpolicy,
403
char **genpasswd, char **errMesg )
406
int passlen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN;
407
int rval = LDAP_SUCCESS;
409
if ( genpasswd == NULL ) {
410
return LDAP_OPERATIONS_ERROR;
412
if ( pwpolicy->pw_min8bit > 0 ) {
413
LDAPDebug( LDAP_DEBUG_ANY, "Unable to generate a password that meets "
414
"the current password syntax rules. 8-bit syntax "
415
"restrictions are not supported with random password "
416
"generation.\n", 0, 0, 0 );
417
*errMesg = "Unable to generate new random password. Please contact the Administrator.";
418
return LDAP_CONSTRAINT_VIOLATION;
421
if ( pwpolicy->pw_minalphas || pwpolicy->pw_minuppers ||
422
pwpolicy->pw_minlowers || pwpolicy->pw_mindigits ||
423
pwpolicy->pw_minspecials || pwpolicy->pw_maxrepeats ||
424
pwpolicy->pw_mincategories > 2 ) {
425
rval = passwd_modify_generate_policy_passwd( pwpolicy, genpasswd,
428
/* find out the minimum length to fulfill the passwd policy
430
minalphalen = pwpolicy->pw_minuppers + pwpolicy->pw_minlowers;
431
if ( minalphalen < pwpolicy->pw_minalphas ) {
432
minalphalen = pwpolicy->pw_minalphas;
434
passlen = minalphalen + pwpolicy->pw_mindigits +
435
pwpolicy->pw_minspecials + pwpolicy->pw_min8bit;
436
if ( passlen < pwpolicy->pw_minlength ) {
437
passlen = pwpolicy->pw_minlength;
439
if ( passlen < LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN ) {
440
passlen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN;
442
rval = passwd_modify_generate_basic_passwd( passlen, genpasswd );
449
/* Password Modify Extended operation plugin function */
451
passwd_modify_extop( Slapi_PBlock *pb )
455
Slapi_DN *bindSDN = NULL;
456
char *authmethod = NULL;
458
const char *dn = NULL;
460
char *oldPasswd = NULL;
461
char *newPasswd = NULL;
462
char *errMesg = NULL;
463
int ret=0, rc=0, sasl_ssf=0, local_ssf=0, need_pwpolicy_ctrl=0;
465
ber_len_t len=(ber_len_t)-1;
466
struct berval *extop_value = NULL;
467
struct berval *gen_passwd = NULL;
468
BerElement *ber = NULL;
469
BerElement *response_ber = NULL;
470
Slapi_Entry *targetEntry=NULL;
471
Connection *conn = NULL;
472
LDAPControl **req_controls = NULL;
473
LDAPControl **resp_controls = NULL;
474
passwdPolicy *pwpolicy = NULL;
475
Slapi_DN *target_sdn = NULL;
476
Slapi_Entry *referrals = NULL;
479
LDAPDebug( LDAP_DEBUG_TRACE, "=> passwd_modify_extop\n", 0, 0, 0 );
481
/* Before going any further, we'll make sure that the right extended operation plugin
482
* has been called: i.e., the OID shipped whithin the extended operation request must
483
* match this very plugin's OID: EXTOP_PASSWD_OID. */
484
if ( slapi_pblock_get( pb, SLAPI_EXT_OP_REQ_OID, &oid ) != 0 ) {
485
errMesg = "Could not get OID value from request.\n";
486
rc = LDAP_OPERATIONS_ERROR;
487
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_extop",
489
goto free_and_return;
491
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_extop",
492
"Received extended operation request with OID %s\n", oid );
495
if ( strcasecmp( oid, EXTOP_PASSWD_OID ) != 0) {
496
errMesg = "Request OID does not match Passwd OID.\n";
497
rc = LDAP_OPERATIONS_ERROR;
498
goto free_and_return;
500
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_extop",
501
"Password Modify extended operation request confirmed.\n" );
504
/* Now , at least we know that the request was indeed a Password Modify one. */
506
#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE
507
/* Allow password modify only for SSL/TLS established connections and
508
* connections using SASL privacy layers */
510
if ( slapi_pblock_get(pb, SLAPI_CONN_SASL_SSF, &sasl_ssf) != 0) {
511
errMesg = "Could not get SASL SSF from connection\n";
512
rc = LDAP_OPERATIONS_ERROR;
513
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_extop",
515
goto free_and_return;
518
if ( slapi_pblock_get(pb, SLAPI_CONN_LOCAL_SSF, &local_ssf) != 0) {
519
errMesg = "Could not get local SSF from connection\n";
520
rc = LDAP_OPERATIONS_ERROR;
521
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_extop",
523
goto free_and_return;
526
if ( ((conn->c_flags & CONN_FLAG_SSL) != CONN_FLAG_SSL) &&
527
(sasl_ssf <= 1) && (local_ssf <= 1)) {
528
errMesg = "Operation requires a secure connection.\n";
529
rc = LDAP_CONFIDENTIALITY_REQUIRED;
530
goto free_and_return;
534
/* Get the ber value of the extended operation */
535
slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
537
if (!BV_HAS_DATA(extop_value))
539
/* The request field wasn't provided. We'll
540
* now try to determine the userid and verify
541
* knowledge of the old password via other
547
if ((ber = ber_init(extop_value)) == NULL)
549
errMesg = "PasswdModify Request decode failed.\n";
550
rc = LDAP_PROTOCOL_ERROR;
551
goto free_and_return;
554
/* Format of request to parse
556
* PasswdModifyRequestValue ::= SEQUENCE {
557
* userIdentity [0] OCTET STRING OPTIONAL
558
* oldPasswd [1] OCTET STRING OPTIONAL
559
* newPasswd [2] OCTET STRING OPTIONAL }
561
* The request value field is optional. If it is
562
* provided, at least one field must be filled in.
566
if ( ber_scanf( ber, "{") == LBER_ERROR )
568
/* The request field wasn't provided. We'll
569
* now try to determine the userid and verify
570
* knowledge of the old password via other
575
tag = ber_peek_tag( ber, &len);
579
/* identify userID field by tags */
580
if (tag == LDAP_EXTOP_PASSMOD_TAG_USERID )
583
if ( ber_scanf( ber, "a", &rawdn) == LBER_ERROR ) {
584
slapi_ch_free_string(&rawdn);
585
LDAPDebug( LDAP_DEBUG_ANY, "ber_scanf failed :{\n", 0, 0, 0 );
586
errMesg = "ber_scanf failed at userID parse.\n";
587
rc = LDAP_PROTOCOL_ERROR;
588
goto free_and_return;
591
/* Check if we should be performing strict validation. */
592
if (config_get_dn_validate_strict()) {
593
/* check that the dn is formatted correctly */
594
rc = slapi_dn_syntax_check(pb, rawdn, 1);
595
if (rc) { /* syntax check failed */
596
op_shared_log_error_access(pb, "EXT", rawdn?rawdn:"",
597
"strict: invalid target dn");
598
errMesg = "invalid target dn.\n";
599
slapi_ch_free_string(&rawdn);
600
rc = LDAP_INVALID_SYNTAX;
601
goto free_and_return;
604
tag = ber_peek_tag(ber, &len);
607
/* identify oldPasswd field by tags */
608
if (tag == LDAP_EXTOP_PASSMOD_TAG_OLDPWD ) {
609
if ( ber_scanf( ber, "a", &oldPasswd ) == LBER_ERROR ) {
610
slapi_ch_free_string(&oldPasswd);
611
LDAPDebug( LDAP_DEBUG_ANY, "ber_scanf failed :{\n", 0, 0, 0 );
612
errMesg = "ber_scanf failed at oldPasswd parse.\n";
613
rc = LDAP_PROTOCOL_ERROR;
614
goto free_and_return;
616
tag = ber_peek_tag( ber, &len);
619
/* identify newPasswd field by tags */
620
if (tag == LDAP_EXTOP_PASSMOD_TAG_NEWPWD )
622
if ( ber_scanf( ber, "a", &newPasswd ) == LBER_ERROR ) {
623
slapi_ch_free_string(&newPasswd);
624
LDAPDebug( LDAP_DEBUG_ANY, "ber_scanf failed :{\n", 0, 0, 0 );
625
errMesg = "ber_scanf failed at newPasswd parse.\n";
626
rc = LDAP_PROTOCOL_ERROR;
627
goto free_and_return;
632
/* Uncomment for debugging, otherwise we don't want to leak the password values into the log... */
633
/* LDAPDebug( LDAP_DEBUG_ARGS, "passwd: dn (%s), oldPasswd (%s) ,newPasswd (%s)\n",
634
dn, oldPasswd, newPasswd); */
637
slapi_pblock_get( pb, SLAPI_CONN_DN, &bindDN );
639
/* If the connection is bound anonymously, we must refuse to process this operation. */
640
if (bindDN == NULL || *bindDN == '\0') {
641
/* Refuse the operation because they're bound anonymously */
642
errMesg = "Anonymous Binds are not allowed.\n";
643
rc = LDAP_INSUFFICIENT_ACCESS;
644
goto free_and_return;
647
/* Find and set the target DN. */
648
if (rawdn && *rawdn != '\0') {
649
target_sdn = slapi_sdn_new_dn_passin(rawdn);
650
} else { /* We already checked (bindDN && *bindDN != '\0') above */
651
target_sdn = bindSDN = slapi_sdn_new_normdn_byref(bindDN);
653
dn = slapi_sdn_get_ndn(target_sdn);
654
if (dn == NULL || *dn == '\0') {
655
/* Refuse the operation because they're bound anonymously */
656
errMesg = "Invalid dn.\n";
657
rc = LDAP_INVALID_DN_SYNTAX;
658
goto free_and_return;
660
slapi_pblock_set(pb, SLAPI_TARGET_SDN, target_sdn);
662
/* Check if we need to send any referrals. */
663
if (slapi_dn_write_needs_referral(target_sdn, &referrals)) {
665
goto free_and_return;
668
if (oldPasswd == NULL || *oldPasswd == '\0') {
669
/* If user is authenticated, they already gave their password during
670
* the bind operation (or used sasl or client cert auth or OS creds) */
671
slapi_pblock_get(pb, SLAPI_CONN_AUTHMETHOD, &authmethod);
672
if (!authmethod || !strcmp(authmethod, SLAPD_AUTH_NONE)) {
673
errMesg = "User must be authenticated to the directory server.\n";
674
rc = LDAP_INSUFFICIENT_ACCESS;
675
goto free_and_return;
679
/* Fetch the password policy. We need this in case we need to
680
* generate a password as well as for some policy checks. */
681
pwpolicy = new_passwdPolicy( pb, dn );
683
/* A new password was not supplied in the request, so we need to generate
684
* a random one and return it to the user in a response.
686
if (newPasswd == NULL || *newPasswd == '\0') {
688
/* Do a free of newPasswd here to be safe, otherwise we may leak 1 byte */
689
slapi_ch_free_string( &newPasswd );
691
/* Generate a new password */
692
rval = passwd_modify_generate_passwd( pwpolicy, &newPasswd, &errMesg );
694
if (rval != LDAP_SUCCESS) {
696
errMesg = "Error generating new password.\n";
697
rc = LDAP_OPERATIONS_ERROR;
698
goto free_and_return;
701
/* Make sure a passwd was actually generated */
702
if (newPasswd == NULL || *newPasswd == '\0') {
703
errMesg = "Error generating new password.\n";
704
rc = LDAP_OPERATIONS_ERROR;
705
goto free_and_return;
709
* Create the response message since we generated a new password.
711
* PasswdModifyResponseValue ::= SEQUENCE {
712
* genPasswd [0] OCTET STRING OPTIONAL }
714
if ( (response_ber = ber_alloc()) == NULL ) {
716
goto free_and_return;
719
if ( LBER_ERROR == ( ber_printf( response_ber, "{ts}",
720
LDAP_EXTOP_PASSMOD_TAG_GENPWD, newPasswd ) ) ) {
721
ber_free( response_ber, 1 );
722
rc = LDAP_ENCODING_ERROR;
723
goto free_and_return;
726
ber_flatten(response_ber, &gen_passwd);
728
/* We're done with response_ber now, so free it */
729
ber_free( response_ber, 1 );
732
slapi_pblock_set( pb, SLAPI_ORIGINAL_TARGET, (void *)dn );
734
/* Now we have the DN, look for the entry */
735
ret = passwd_modify_getEntry(dn, &targetEntry);
736
/* If we can't find the entry, then that's an error */
738
/* Couldn't find the entry, fail */
739
errMesg = "No such Entry exists.\n" ;
740
rc = LDAP_NO_SUCH_OBJECT ;
741
goto free_and_return;
744
/* First thing to do is to ask access control if the bound identity has
745
rights to modify the userpassword attribute on this entry. If not, then
746
we fail immediately with insufficient access. This means that we don't
747
leak any useful information to the client such as current password
751
operation_set_target_spec (pb->pb_op, slapi_entry_get_sdn(targetEntry));
752
slapi_pblock_set( pb, SLAPI_REQUESTOR_ISROOT, &pb->pb_op->o_isroot );
754
/* In order to perform the access control check , we need to select a backend (even though
755
* we don't actually need it otherwise).
758
Slapi_Backend *be = NULL;
760
be = slapi_mapping_tree_find_backend_for_sdn(slapi_entry_get_sdn(targetEntry));
762
errMesg = "Failed to find backend for target entry";
763
rc = LDAP_OPERATIONS_ERROR;
764
goto free_and_return;
766
slapi_pblock_set(pb, SLAPI_BACKEND, be);
769
/* Check if the pwpolicy control is present */
770
slapi_pblock_get( pb, SLAPI_PWPOLICY, &need_pwpolicy_ctrl );
772
ret = slapi_access_allowed ( pb, targetEntry, SLAPI_USERPWD_ATTR, NULL, SLAPI_ACL_WRITE );
773
if ( ret != LDAP_SUCCESS ) {
774
if (need_pwpolicy_ctrl) {
775
slapi_pwpolicy_make_response_control ( pb, -1, -1, LDAP_PWPOLICY_PWDMODNOTALLOWED );
777
errMesg = "Insufficient access rights\n";
778
rc = LDAP_INSUFFICIENT_ACCESS;
779
goto free_and_return;
782
/* Now we have the entry which we want to modify
783
* They gave us a password (old), check it against the target entry
784
* Is the old password valid ?
786
if (oldPasswd && *oldPasswd) {
787
ret = passwd_check_pwd(targetEntry, oldPasswd);
789
/* No, then we fail this operation */
790
errMesg = "Invalid oldPasswd value.\n";
792
goto free_and_return;
796
/* Check if password policy allows users to change their passwords. We need to do
797
* this here since the normal modify code doesn't perform this check for
798
* internal operations. */
799
if (!pb->pb_op->o_isroot && !pb->pb_conn->c_needpw && !pwpolicy->pw_change) {
800
if (NULL == bindSDN) {
801
bindSDN = slapi_sdn_new_normdn_byref(bindDN);
803
/* Is this a user modifying their own password? */
804
if (slapi_sdn_compare(bindSDN, slapi_entry_get_sdn(targetEntry))==0) {
805
if (need_pwpolicy_ctrl) {
806
slapi_pwpolicy_make_response_control ( pb, -1, -1, LDAP_PWPOLICY_PWDMODNOTALLOWED );
808
errMesg = "User is not allowed to change password\n";
809
rc = LDAP_UNWILLING_TO_PERFORM;
810
goto free_and_return;
814
/* Fetch any present request controls so we can use them when
815
* performing the modify operation. */
816
slapi_pblock_get(pb, SLAPI_REQCONTROLS, &req_controls);
818
/* Now we're ready to make actual password change */
819
ret = passwd_modify_userpassword(pb, targetEntry, newPasswd, req_controls, &resp_controls);
821
/* Set the response controls if necessary. We want to do this now
822
* so it is set for both the success and failure cases. The pblock
823
* will now own the controls. */
825
slapi_pblock_set(pb, SLAPI_RESCONTROLS, resp_controls);
828
if (ret != LDAP_SUCCESS) {
829
/* Failed to modify the password, e.g. because password policy, etc. */
830
errMesg = "Failed to update password\n";
832
goto free_and_return;
835
if (gen_passwd && (gen_passwd->bv_val != '\0')) {
836
/* Set the reponse to let the user know the generated password */
837
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, gen_passwd);
840
LDAPDebug( LDAP_DEBUG_TRACE, "<= passwd_modify_extop: %d\n", rc, 0, 0 );
842
/* Free anything that we allocated above */
844
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_extop",
845
"%s", errMesg ? errMesg : "success" );
847
if ((rc == LDAP_REFERRAL) && (referrals)) {
848
send_referrals_from_entry(pb, referrals);
850
send_ldap_result( pb, rc, NULL, errMesg, 0, NULL );
853
slapi_ch_free_string(&oldPasswd);
854
slapi_ch_free_string(&newPasswd);
855
/* Either this is the same pointer that we allocated and set above,
856
* or whoever used it should have freed it and allocated a new
857
* value that we need to free here */
858
slapi_pblock_get( pb, SLAPI_ORIGINAL_TARGET, &otdn );
860
slapi_ch_free_string(&otdn);
862
slapi_pblock_get(pb, SLAPI_TARGET_SDN, &target_sdn);
863
if (bindSDN != target_sdn) {
864
slapi_sdn_free(&bindSDN);
866
/* slapi_pblock_get SLAPI_CONN_DN does strdup */
867
slapi_ch_free_string(&bindDN);
868
slapi_sdn_free(&target_sdn);
869
slapi_pblock_set(pb, SLAPI_TARGET_SDN, NULL);
870
slapi_pblock_set( pb, SLAPI_ORIGINAL_TARGET, NULL );
871
slapi_ch_free_string(&authmethod);
872
slapi_entry_free(referrals);
874
if ( targetEntry != NULL ){
875
slapi_entry_free (targetEntry);
883
/* We can free the generated password bval now */
884
ber_bvfree(gen_passwd);
886
return( SLAPI_PLUGIN_EXTENDED_SENT_RESULT );
888
}/* passwd_modify_extop */
891
static char *passwd_oid_list[] = {
897
static char *passwd_name_list[] = {
898
"passwd_modify_extop",
903
/* Initialization function */
904
int passwd_modify_init( Slapi_PBlock *pb )
908
/* Get the arguments appended to the plugin extendedop directive. The first argument
909
* (after the standard arguments for the directive) should contain the OID of the
910
* extended operation.
913
if ( slapi_pblock_get( pb, SLAPI_PLUGIN_ARGV, &argv ) != 0 ) {
914
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_init", "Could not get argv\n" );
918
/* Compare the OID specified in the configuration file against the Passwd OID. */
920
if ( argv == NULL || strcmp( argv[0], EXTOP_PASSWD_OID ) != 0 ) {
921
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_init",
922
"OID is missing or is not %s\n", EXTOP_PASSWD_OID );
925
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_init",
926
"Registering plug-in for Password Modify extended op %s.\n", argv[0] /* oid */);
929
/* Register the plug-in function as an extended operation
930
* plug-in function that handles the operation identified by
931
* OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server
933
if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
934
slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&passwdopdesc ) != 0 ||
935
slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *) passwd_modify_extop ) != 0 ||
936
slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, passwd_oid_list ) != 0 ||
937
slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, passwd_name_list ) != 0 ) {
939
slapi_log_error( SLAPI_LOG_PLUGIN, "passwd_modify_init",
940
"Failed to set plug-in version, function, and OID.\n" );
947
int passwd_modify_register_plugin()
949
slapi_register_plugin( "extendedop", 1 /* Enabled */, "passwd_modify_init",
950
passwd_modify_init, "Password Modify extended operation",
951
passwd_oid_list, NULL );