From 3a19ee45dcad094634c946112b62d1a3c14799ea Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Tue, 4 Apr 2017 22:27:19 -0400
Subject: [PATCH] Issue 49210 - Fix regression when checking is password min
 age should be checked

Bug Description:  If a plugin makes an internal modification of userpassword
                  the connection structure in the pblock is null, and it was
                  being dereferenced.

Fix Description:  These internal operations do not need to have the password
                  policy checks done.  They are intended to be unrestricted.
                  So we only need to check password policy on client connections.
                  The fix frist hecks if the connection structy is present,
                  only then it continues.

                  Revised test script to include the tool: ldappasswd

https://pagure.io/389-ds-base/issue/49210

Reviewed by: ?
---
 dirsrvtests/tests/tickets/ticket49039_test.py | 136 ++++++++++++++++++++++++++
 ldap/servers/slapd/modify.c                   |   2 +-
 2 files changed, 137 insertions(+), 1 deletion(-)

diff --git a/dirsrvtests/tests/tickets/ticket49039_test.py b/dirsrvtests/tests/tickets/ticket49039_test.py
index e6d4c03..2053314 100644
--- a/dirsrvtests/tests/tickets/ticket49039_test.py
+++ b/dirsrvtests/tests/tickets/ticket49039_test.py
@@ -2,6 +2,7 @@ import time
 import ldap
 import logging
 import pytest
+import os
 from lib389 import Entry
 from lib389._constants import *
 from lib389.properties import *
@@ -9,6 +10,7 @@ from lib389.tasks import *
 from lib389.utils import *
 from lib389.topologies import topology_st as topo
 
+
 DEBUGGING = os.getenv("DEBUGGING", default=False)
 if DEBUGGING:
     logging.getLogger(__name__).setLevel(logging.DEBUG)
@@ -19,11 +21,112 @@ log = logging.getLogger(__name__)
 USER_DN = 'uid=user,dc=example,dc=com'
 
 
+def ssl_init(topo, restart=True):
+    """ Generate self signed cert and import it to the DS cert db.
+    """
+    ENCRYPTION_DN = 'cn=encryption,cn=config'
+    RSA = 'RSA'
+    RSA_DN = 'cn=%s,%s' % (RSA, ENCRYPTION_DN)
+    SERVERCERT = 'Server-Cert'
+
+    log.info("\n######################### Initializing SSL Setup ######################\n")
+    onss_version = os.popen("rpm -q nss | awk -F'-' '{print $2}'", "r")
+    global nss_version
+    nss_version = onss_version.readline()
+
+    conf_dir = topo.standalone.confdir
+
+    log.info("\n######################### Checking existing certs ######################\n")
+    os.system('certutil -L -d %s -n "CA certificate"' % conf_dir)
+    os.system('certutil -L -d %s -n "%s"' % (conf_dir, SERVERCERT))
+
+    log.info("\n######################### Create a password file ######################\n")
+    pwdfile = '%s/pwdfile.txt' % (conf_dir)
+    opasswd = os.popen("(ps -ef ; w ) | sha1sum | awk '{print $1}'", "r")
+    passwd = opasswd.readline()
+    pwdfd = open(pwdfile, "w")
+    pwdfd.write(passwd)
+    pwdfd.close()
+
+    log.info("\n######################### Create a noise file ######################\n")
+    noisefile = '%s/noise.txt' % (conf_dir)
+    noise = os.popen("(w ; ps -ef ; date ) | sha1sum | awk '{print $1}'", "r")
+    noisewdfd = open(noisefile, "w")
+    noisewdfd.write(noise.readline())
+    noisewdfd.close()
+    time.sleep(1)
+
+    log.info("\n######################### Create key3.db and cert8.db database ######################\n")
+    os.system("ls %s" % pwdfile)
+    os.system("cat %s" % pwdfile)
+    os.system('certutil -N -d %s -f %s' % (conf_dir, pwdfile))
+
+    log.info("\n######################### Creating encryption key for CA ######################\n")
+    os.system('certutil -G -d %s -z %s -f %s' % (conf_dir, noisefile, pwdfile))
+
+    log.info("\n######################### Creating self-signed CA certificate ######################\n")
+    os.system(
+        '( echo y ; echo ; echo y ) | certutil -S -n "CA certificate" -s "cn=CAcert" -x -t "CT,," -m 1000 -v 120 -d %s -z %s -f %s -2' %
+        (conf_dir, noisefile, pwdfile))
+
+    log.info("\n######################### Exporting the CA certificate to cacert.asc ######################\n")
+    cafile = '%s/cacert.asc' % conf_dir
+    catxt = os.popen('certutil -L -d %s -n "CA certificate" -a' % conf_dir)
+    cafd = open(cafile, "w")
+    while True:
+        line = catxt.readline()
+        if (line == ''):
+            break
+        cafd.write(line)
+    cafd.close()
+
+    log.info("\n######################### Generate the server certificate ######################\n")
+    ohostname = os.popen('hostname --fqdn', "r")
+    myhostname = ohostname.readline()
+    os.system(
+        'certutil -S -n "%s" -s "cn=%s,ou=389 Directory Server" -c "CA certificate" -t "u,u,u" -m 1001 -v 120 -d %s -z %s -f %s' % (
+        SERVERCERT, myhostname.rstrip(), conf_dir, noisefile, pwdfile))
+
+    log.info("\n######################### Create the pin file ######################\n")
+    pinfile = '%s/pin.txt' % (conf_dir)
+    pintxt = 'Internal (Software) Token:%s' % passwd
+    pinfd = open(pinfile, "w")
+    pinfd.write(pintxt)
+    pinfd.close()
+    time.sleep(1)
+
+    log.info("\n######################### Enable SSL in the directory server with all ciphers ######################\n")
+    topo.standalone.simple_bind_s(DN_DM, PASSWORD)
+    topo.standalone.modify_s(ENCRYPTION_DN, [(ldap.MOD_REPLACE, 'nsSSL3', 'off'),
+                                                    (ldap.MOD_REPLACE, 'nsTLS1', 'on'),
+                                                    (ldap.MOD_REPLACE, 'nsSSLClientAuth', 'allowed'),
+                                                    (ldap.MOD_REPLACE, 'allowWeakCipher', 'on'),
+                                                    (ldap.MOD_REPLACE, 'nsSSL3Ciphers', '+all')])
+
+    topo.standalone.modify_s('cn=config', [(ldap.MOD_REPLACE, 'nsslapd-security', 'on'),
+                                                (ldap.MOD_REPLACE, 'nsslapd-ssl-check-hostname', 'off'),
+                                                (ldap.MOD_REPLACE, 'nsslapd-secureport', '636')])
+
+    topo.standalone.add_s(Entry((RSA_DN, {'objectclass': "top nsEncryptionModule".split(),
+                                                 'cn': RSA,
+                                                 'nsSSLPersonalitySSL': SERVERCERT,
+                                                 'nsSSLToken': 'internal (software)',
+                                                 'nsSSLActivation': 'on'})))
+    if restart:
+        log.info("\n######################### SSL setup complete, restarting server... ######################\n")
+        topo.standalone.restart(30)
+    else:
+        log.info("\n######################### SSL setup complete ######################\n")
+
+
 def test_ticket49039(topo):
     """Test "password must change" verses "password min age".  Min age should not
     block password update if the password was reset.
     """
 
+    # Setup SSL (for ldappasswd test)
+    ssl_init(topo)
+
     # Configure password policy
     try:
         topo.standalone.modify_s("cn=config", [(ldap.MOD_REPLACE, 'nsslapd-pwpolicy-local', 'on'),
@@ -68,6 +171,39 @@ def test_ticket49039(topo):
         log.fatal('Failed to change password: error ' + e.message['desc'])
         assert False
 
+    #
+    # Make sure ldappasswd also works
+    #
+
+    # Reset password as RootDN
+    try:
+        topo.standalone.simple_bind_s(DN_DM, PASSWORD)
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as rootdn: error ' + e.message['desc'])
+        assert False
+
+    try:
+        topo.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'userpassword', PASSWORD)])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind: error ' + e.message['desc'])
+        assert False
+
+    time.sleep(1)
+
+    # Run ldappasswd as the User
+    cmd = ('LDAPTLS_CACERTDIR=' + topo.standalone.confdir + ' ldappasswd' +
+           ' -h localhost.localdomain -Z -p 38901 -D ' + USER_DN +
+           ' -w password -a password -s password2 ' + USER_DN)
+
+    os.system(cmd)
+    time.sleep(1)
+
+    try:
+        topo.standalone.simple_bind_s(USER_DN, "password2")
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind: error ' + e.message['desc'])
+        assert False
+
     log.info('Test Passed')
 
 
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
index dae9692..fa0d599 100644
--- a/ldap/servers/slapd/modify.c
+++ b/ldap/servers/slapd/modify.c
@@ -1326,7 +1326,7 @@ static int op_shared_allow_pw_change (Slapi_PBlock *pb, LDAPMod *mod, char **old
 	       
 	/* check if password is within password minimum age;
 	   error result is sent directly from check_pw_minage */	
-	if (!pb->pb_conn->c_needpw &&
+	if (pb->pb_conn && !pb->pb_conn->c_needpw &&
 	    check_pw_minage(pb, &sdn, mod->mod_bvalues) == 1)
 	{
 		if (operation_is_flag_set(operation,OP_FLAG_ACTION_LOG_ACCESS))
-- 
2.9.3

