]> git.sur5r.net Git - openldap/blobdiff - contrib/slapd-modules/nssov/pam.c
Merge remote-tracking branch 'origin/mdb.master' into OPENLDAP_REL_ENG_2_4
[openldap] / contrib / slapd-modules / nssov / pam.c
index 2f3969dd2d93249f013e9cf7e4eb0d5de5be51b5..e1d5edaa3b181ca492a7e221f6137eaaf100e36d 100644 (file)
@@ -2,8 +2,9 @@
 /* $OpenLDAP$ */
 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 
  *
- * Copyright 2008-2009 The OpenLDAP Foundation.
+ * Copyright 2008-2014 The OpenLDAP Foundation.
  * Portions Copyright 2008 by Howard Chu, Symas Corp.
+ * Portions Copyright 2013 by Ted C. Cheng, Symas Corp.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 static int ppolicy_cid;
 static AttributeDescription *ad_loginStatus;
 
-const char *at_loginStatus =
-       "( 1.3.6.1.4.1.4745.1.20.1 "
-       "NAME ( 'loginStatus' ) "
-       "DESC 'Currently logged in sessions for a user' "
-       "EQUALITY caseIgnoreMatch "
-       "SUBSTR caseIgnoreSubstringsMatch "
-       "ORDERING caseIgnoreOrderingMatch "
-       "SYNTAX OMsDirectoryString "
-       "USAGE directoryOperation )";
-
 struct paminfo {
        struct berval uid;
        struct berval dn;
@@ -38,6 +29,7 @@ struct paminfo {
        struct berval pwd;
        int authz;
        struct berval msg;
+       int ispwdmgr;
 };
 
 static int pam_bindcb(
@@ -110,24 +102,17 @@ static int pam_bindcb(
        return LDAP_SUCCESS;
 }
 
-int pam_do_bind(nssov_info *ni,TFILE *fp,Operation *op,
+static int pam_uid2dn(nssov_info *ni, Operation *op,
        struct paminfo *pi)
 {
-       int rc;
-       slap_callback cb = {0};
-       SlapReply rs = {REP_RESULT};
        struct berval sdn;
 
-       pi->msg.bv_val = pi->pwd.bv_val;
-       pi->msg.bv_len = 0;
-       pi->authz = NSLCD_PAM_SUCCESS;
        BER_BVZERO(&pi->dn);
 
        if (!isvalidusername(&pi->uid)) {
-               Debug(LDAP_DEBUG_ANY,"nssov_pam_do_bind(%s): invalid user name\n",
-                       pi->uid.bv_val,0,0);
-               rc = NSLCD_PAM_USER_UNKNOWN;
-               goto finish;
+               Debug(LDAP_DEBUG_ANY,"nssov_pam_uid2dn(%s): invalid user name\n",
+                       pi->uid.bv_val ? pi->uid.bv_val : "NULL",0,0);
+               return NSLCD_PAM_USER_UNKNOWN;
        }
 
        if (ni->ni_pam_opts & NI_PAM_SASL2DN) {
@@ -151,28 +136,47 @@ int pam_do_bind(nssov_info *ni,TFILE *fp,Operation *op,
                        dnNormalize( 0, NULL, NULL, &sdn, &pi->dn, op->o_tmpmemctx );
                }
        }
-       BER_BVZERO(&sdn);
        if (BER_BVISEMPTY(&pi->dn)) {
-               rc = NSLCD_PAM_USER_UNKNOWN;
-               goto finish;
+               return NSLCD_PAM_USER_UNKNOWN;
        }
+       return 0;
+}
 
-       if (BER_BVISEMPTY(&pi->pwd)) {
-               rc = NSLCD_PAM_IGNORE;
-               goto finish;
-       }
+int pam_do_bind(nssov_info *ni,TFILE *fp,Operation *op,
+       struct paminfo *pi)
+{
+       int rc;
+       slap_callback cb = {0};
+       SlapReply rs = {REP_RESULT};
 
-       /* Should only need to do this once at open time, but there's always
-        * the possibility that ppolicy will get loaded later.
-        */
-       if (!ppolicy_cid) {
-               rc = slap_find_control_id(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
-                       &ppolicy_cid);
-       }
-       /* of course, 0 is a valid cid, but it won't be ppolicy... */
-       if (ppolicy_cid) {
-               op->o_ctrlflag[ppolicy_cid] = SLAP_CONTROL_NONCRITICAL;
+       pi->msg.bv_val = pi->pwd.bv_val;
+       pi->msg.bv_len = 0;
+       pi->authz = NSLCD_PAM_SUCCESS;
+
+       if (!pi->ispwdmgr) {
+
+               BER_BVZERO(&pi->dn);
+               rc = pam_uid2dn(ni, op, pi);
+               if (rc) goto finish;
+
+               if (BER_BVISEMPTY(&pi->pwd)) {
+                       rc = NSLCD_PAM_PERM_DENIED;
+                       goto finish;
+               }
+
+               /* Should only need to do this once at open time, but there's always
+                * the possibility that ppolicy will get loaded later.
+                */
+               if (!ppolicy_cid) {
+                       rc = slap_find_control_id(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+                               &ppolicy_cid);
+               }
+               /* of course, 0 is a valid cid, but it won't be ppolicy... */
+               if (ppolicy_cid) {
+                       op->o_ctrlflag[ppolicy_cid] = SLAP_CONTROL_NONCRITICAL;
+               }
        }
+
        cb.sc_response = pam_bindcb;
        cb.sc_private = pi;
        op->o_callback = &cb;
@@ -200,6 +204,8 @@ int pam_do_bind(nssov_info *ni,TFILE *fp,Operation *op,
        default: rc = NSLCD_PAM_AUTH_ERR; break;
        }
 finish:
+       Debug(LDAP_DEBUG_ANY,"pam_do_bind (%s): rc (%d)\n",
+               pi->dn.bv_val ? pi->dn.bv_val : "NULL", rc, 0);
        return rc;
 }
 
@@ -208,7 +214,6 @@ int pam_authc(nssov_info *ni,TFILE *fp,Operation *op)
        int32_t tmpint32;
        int rc;
        slap_callback cb = {0};
-       SlapReply rs = {REP_RESULT};
        char dnc[1024];
        char uidc[32];
        char svcc[256];
@@ -217,27 +222,80 @@ int pam_authc(nssov_info *ni,TFILE *fp,Operation *op)
        struct paminfo pi;
 
 
-       READ_STRING_BUF2(fp,uidc,sizeof(uidc));
+       READ_STRING(fp,uidc);
        pi.uid.bv_val = uidc;
        pi.uid.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,dnc,sizeof(dnc));
+       READ_STRING(fp,dnc);
        pi.dn.bv_val = dnc;
        pi.dn.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,svcc,sizeof(svcc));
+       READ_STRING(fp,svcc);
        pi.svc.bv_val = svcc;
        pi.svc.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,pwdc,sizeof(pwdc));
+       READ_STRING(fp,pwdc);
        pi.pwd.bv_val = pwdc;
        pi.pwd.bv_len = tmpint32;
 
-       Debug(LDAP_DEBUG_TRACE,"nssov_pam_authc(%s)\n",pi.uid.bv_val,0,0);
+       Debug(LDAP_DEBUG_TRACE,"nssov_pam_authc(%s)\n",
+                       pi.uid.bv_val ? pi.uid.bv_val : "NULL",0,0);
+
+       pi.ispwdmgr = 0;
+
+       /* if service is "passwd" and "nssov-pam-password-prohibit-message */
+       /* is set, deny the auth request */
+       if (!strcmp(svcc, "passwd") &&
+               !BER_BVISEMPTY(&ni->ni_pam_password_prohibit_message)) {
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_authc(): %s (%s)\n",
+                       "password_prohibit_message for passwd",
+                       ni->ni_pam_password_prohibit_message.bv_val,0);
+               ber_str2bv(ni->ni_pam_password_prohibit_message.bv_val, 0, 0, &pi.msg);
+               pi.authz = NSLCD_PAM_PERM_DENIED;
+               rc = NSLCD_PAM_PERM_DENIED;
+               goto finish;
+       }
+
+       /* if username is null, pwdmgr password preliminary check */
+       if (BER_BVISEMPTY(&pi.uid)) {
+               if (BER_BVISEMPTY(&ni->ni_pam_pwdmgr_dn)) {
+                       /* pwdmgr dn not configured */
+                       Debug(LDAP_DEBUG_TRACE,"nssov_pam_authc(prelim check): %s\n",
+                               "pwdmgr dn not configured", 0, 0);
+                       ber_str2bv("pwdmgr dn not configured", 0, 0, &pi.msg);
+                       pi.authz = NSLCD_PAM_PERM_DENIED;
+                       rc = NSLCD_PAM_PERM_DENIED;
+                       goto finish;
+               } else {
+                       /* use pwdmgr dn */
+                       ber_str2bv(ni->ni_pam_pwdmgr_dn.bv_val, 0, 0, &pi.dn);
+               }
+
+               /* use pwdmgr pwd if configured */
+               if (BER_BVISEMPTY(&pi.pwd)) {
+                       if (BER_BVISEMPTY(&ni->ni_pam_pwdmgr_pwd)) {
+                               Debug(LDAP_DEBUG_TRACE,"nssov_pam_authc(prelim check): %s\n",
+                                       "no pwdmgr pwd", 0, 0);
+                               ber_str2bv("pwdmgr pwd not configured", 0, 0, &pi.msg);
+                               pi.authz = NSLCD_PAM_PERM_DENIED;
+                               rc = NSLCD_PAM_PERM_DENIED;
+                               goto finish;
+                       }
+                       /* use configured pwdmgr pwd */
+                       memset((void *) pwdc, 0, 256);
+                       strncpy(pi.pwd.bv_val, ni->ni_pam_pwdmgr_pwd.bv_val,
+                                       ni->ni_pam_pwdmgr_pwd.bv_len);
+                       pi.pwd.bv_len = ni->ni_pam_pwdmgr_pwd.bv_len;
+               }
+               pi.ispwdmgr = 1;
+       }
+
 
        rc = pam_do_bind(ni, fp, op, &pi);
 
 finish:
+       Debug(LDAP_DEBUG_TRACE,"nssov_pam_authc(%s): rc (%d)\n",
+               pi.dn.bv_val ? pi.dn.bv_val : "NULL",rc,0);
        WRITE_INT32(fp,NSLCD_VERSION);
        WRITE_INT32(fp,NSLCD_ACTION_PAM_AUTHC);
-       WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
+       WRITE_INT32(fp,NSLCD_RESULT_BEGIN);
        WRITE_BERVAL(fp,&pi.uid);
        WRITE_BERVAL(fp,&pi.dn);
        WRITE_INT32(fp,rc);
@@ -255,6 +313,13 @@ static struct berval svcmsg =
 static struct berval uidmsg =
        BER_BVC("Access denied by UID check");
 
+static int pam_compare_cb(Operation *op, SlapReply *rs)
+{
+       if (rs->sr_err == LDAP_COMPARE_TRUE)
+               op->o_callback->sc_private = (void *)1;
+       return LDAP_SUCCESS;
+}
+
 int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
 {
        struct berval dn, uid, svc, ruser, rhost, tty;
@@ -266,37 +331,42 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
        char ruserc[32];
        char rhostc[256];
        char ttyc[256];
-       int rc = NSLCD_PAM_SUCCESS;
+       int rc;
        Entry *e = NULL;
        Attribute *a;
-       SlapReply rs = {REP_RESULT};
        slap_callback cb = {0};
 
-       READ_STRING_BUF2(fp,uidc,sizeof(uidc));
+       READ_STRING(fp,uidc);
        uid.bv_val = uidc;
        uid.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,dnc,sizeof(dnc));
+       READ_STRING(fp,dnc);
        dn.bv_val = dnc;
        dn.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,svcc,sizeof(svcc));
+       READ_STRING(fp,svcc);
        svc.bv_val = svcc;
        svc.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,svcc,sizeof(ruserc));
+       READ_STRING(fp,ruserc);
        ruser.bv_val = ruserc;
        ruser.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,svcc,sizeof(rhostc));
+       READ_STRING(fp,rhostc);
        rhost.bv_val = rhostc;
        rhost.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,svcc,sizeof(ttyc));
+       READ_STRING(fp,ttyc);
        tty.bv_val = ttyc;
        tty.bv_len = tmpint32;
 
-       Debug(LDAP_DEBUG_TRACE,"nssov_pam_authz(%s)\n",dn.bv_val,0,0);
+       Debug(LDAP_DEBUG_TRACE,"nssov_pam_authz(%s)\n",
+                       dn.bv_val ? dn.bv_val : "NULL",0,0);
 
-       /* We don't do authorization if they weren't authenticated by us */
+       /* If we didn't do authc, we don't have a DN yet */
        if (BER_BVISEMPTY(&dn)) {
-               rc = NSLCD_PAM_USER_UNKNOWN;
-               goto finish;
+               struct paminfo pi;
+               pi.uid = uid;
+               pi.svc = svc;
+
+               rc = pam_uid2dn(ni, op, &pi);
+               if (rc) goto finish;
+               dn = pi.dn;
        }
 
        /* See if they have access to the host and service */
@@ -304,6 +374,7 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
                AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
                struct berval hostdn = BER_BVNULL;
                struct berval odn = op->o_ndn;
+               SlapReply rs = {REP_RESULT};
                op->o_dn = dn;
                op->o_ndn = dn;
                {
@@ -332,8 +403,7 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
                                !BER_BVISEMPTY(&ni->ni_pam_defhost)) {
                                filter.bv_len = sizeof(fbuf);
                                filter.bv_val = fbuf;
-                               memset(&rs2, 0, sizeof(rs2));
-                               rs2.sr_type = REP_RESULT;
+                               rs_reinit(&rs2, REP_RESULT);
                                nssov_filter_byname(mi,0,&ni->ni_pam_defhost,&filter);
                                op->ors_filterstr = filter;
                                op->ors_filter = str2filter_x(op, filter.bv_val);
@@ -349,7 +419,7 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
                        }
                }
 
-               cb.sc_response = slap_null_cb;
+               cb.sc_response = pam_compare_cb;
                cb.sc_private = NULL;
                op->o_tag = LDAP_REQ_COMPARE;
                op->o_req_dn = hostdn;
@@ -358,7 +428,7 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
                ava.aa_value = svc;
                op->orc_ava = &ava;
                rc = op->o_bd->be_compare( op, &rs );
-               if ( rs.sr_err != LDAP_COMPARE_TRUE ) {
+               if ( cb.sc_private == NULL ) {
                        authzmsg = svcmsg;
                        rc = NSLCD_PAM_PERM_DENIED;
                        goto finish;
@@ -372,6 +442,7 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
                !BER_BVISEMPTY(&ni->ni_pam_group_dn) &&
                ni->ni_pam_group_ad) {
                AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+               SlapReply rs = {REP_RESULT};
                op->o_callback = &cb;
                cb.sc_response = slap_null_cb;
                op->o_tag = LDAP_REQ_COMPARE;
@@ -400,9 +471,10 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
        }
        if ((ni->ni_pam_opts & NI_PAM_USERHOST) && nssov_pam_host_ad) {
                a = attr_find(e->e_attrs, nssov_pam_host_ad);
-               if (!a || value_find_ex( nssov_pam_host_ad,
-                       SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
-                       a->a_vals, &global_host_bv, op->o_tmpmemctx )) {
+               if (!a || attr_valfind( a,
+                       SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+                       SLAP_MR_VALUE_OF_SYNTAX,
+                       &global_host_bv, NULL, op->o_tmpmemctx )) {
                        rc = NSLCD_PAM_PERM_DENIED;
                        authzmsg = hostmsg;
                        goto finish;
@@ -410,9 +482,10 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
        }
        if ((ni->ni_pam_opts & NI_PAM_USERSVC) && nssov_pam_svc_ad) {
                a = attr_find(e->e_attrs, nssov_pam_svc_ad);
-               if (!a || value_find_ex( nssov_pam_svc_ad,
-                       SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
-                       a->a_vals, &svc, op->o_tmpmemctx )) {
+               if (!a || attr_valfind( a,
+                       SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+                       SLAP_MR_VALUE_OF_SYNTAX,
+                       &svc, NULL, op->o_tmpmemctx )) {
                        rc = NSLCD_PAM_PERM_DENIED;
                        authzmsg = svcmsg;
                        goto finish;
@@ -425,7 +498,7 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
        if (ni->ni_pam_min_uid || ni->ni_pam_max_uid) {
                int id;
                char *tmp;
-               nssov_mapinfo *mi = &ni->ni_maps[NM_host];
+               nssov_mapinfo *mi = &ni->ni_maps[NM_passwd];
                a = attr_find(e->e_attrs, mi->mi_attrs[UIDN_KEY].an_desc);
                if (!a) {
                        rc = NSLCD_PAM_PERM_DENIED;
@@ -453,11 +526,12 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
                else if (!BER_BVISEMPTY(&ni->ni_pam_template))
                        uid = ni->ni_pam_template;
        }
+       rc = NSLCD_PAM_SUCCESS;
 
 finish:
        WRITE_INT32(fp,NSLCD_VERSION);
        WRITE_INT32(fp,NSLCD_ACTION_PAM_AUTHZ);
-       WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
+       WRITE_INT32(fp,NSLCD_RESULT_BEGIN);
        WRITE_BERVAL(fp,&uid);
        WRITE_BERVAL(fp,&dn);
        WRITE_INT32(fp,rc);
@@ -465,6 +539,19 @@ finish:
        if (e) {
                be_entry_release_r(op, e);
        }
+       switch (rc) {
+       case NSLCD_PAM_SUCCESS:
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_authz(): success\n", 0,0,0);
+               break;
+       case NSLCD_PAM_PERM_DENIED:
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_authz(): %s\n",
+                       authzmsg.bv_val ? authzmsg.bv_val : "NULL",0,0);
+               break;
+       default:
+               Debug(LDAP_DEBUG_TRACE,
+                       "nssov_pam_authz(): permission denied, rc (%d)\n",
+                       rc, 0, 0);
+       }
        return 0;
 }
 
@@ -484,23 +571,25 @@ static int pam_sess(nssov_info *ni,TFILE *fp,Operation *op,int action)
        struct berval timestamp, bv[2], *nbv;
        time_t stamp;
        Modifications mod;
+       int rc = 0;
+       int sessionID = -1;
 
-       READ_STRING_BUF2(fp,uidc,sizeof(uidc));
+       READ_STRING(fp,uidc);
        uid.bv_val = uidc;
        uid.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,dnc,sizeof(dnc));
+       READ_STRING(fp,dnc);
        dn.bv_val = dnc;
        dn.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,svcc,sizeof(svcc));
+       READ_STRING(fp,svcc);
        svc.bv_val = svcc;
        svc.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,ttyc,sizeof(ttyc));
+       READ_STRING(fp,ttyc);
        tty.bv_val = ttyc;
        tty.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,rhostc,sizeof(rhostc));
+       READ_STRING(fp,rhostc);
        rhost.bv_val = rhostc;
        rhost.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,ruserc,sizeof(ruserc));
+       READ_STRING(fp,ruserc);
        ruser.bv_val = ruserc;
        ruser.bv_len = tmpint32;
        READ_INT32(fp,stamp);
@@ -508,7 +597,20 @@ static int pam_sess(nssov_info *ni,TFILE *fp,Operation *op,int action)
        Debug(LDAP_DEBUG_TRACE,"nssov_pam_sess_%c(%s)\n",
                action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c', dn.bv_val,0);
 
-       if (!dn.bv_len || !ni->ni_pam_sessions) return 0;
+       if (!dn.bv_len) {
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_sess_%c(): %s\n",
+                       action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c', "null DN",0);
+               rc = -1;
+               goto done;
+       }
+
+       if (!ni->ni_pam_sessions) {
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_sess_%c(): %s\n",
+                       action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c',
+                       "pam session(s) not configured, ignored",0);
+               rc = -1;
+               goto done;
+       }
 
        {
                int i, found=0;
@@ -520,7 +622,14 @@ static int pam_sess(nssov_info *ni,TFILE *fp,Operation *op,int action)
                                break;
                        }
                }
-               if (!found) return 0;
+               if (!found) {
+                       Debug(LDAP_DEBUG_TRACE,
+                               "nssov_pam_sess_%c(): service(%s) not configured, ignored\n",
+                               action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c',
+                               svc.bv_val,0);
+                       rc = -1;
+                       goto done;
+               }
        }
 
        slap_op_time( &op->o_time, &op->o_tincr );
@@ -535,6 +644,9 @@ static int pam_sess(nssov_info *ni,TFILE *fp,Operation *op,int action)
        sprintf(bv[0].bv_val, "%s %s %s %s (%s@%s)",
                timestamp.bv_val, global_host_bv.bv_val, svc.bv_val, tty.bv_val,
                ruser.bv_val, rhost.bv_val);
+
+       Debug(LDAP_DEBUG_TRACE, "nssov_pam_sess_%c(): loginStatus (%s) \n",
+                       action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c', bv[0].bv_val,0);
        
        mod.sml_numvals = 1;
        mod.sml_values = bv;
@@ -556,16 +668,32 @@ static int pam_sess(nssov_info *ni,TFILE *fp,Operation *op,int action)
        op->orm_no_opattrs = 1;
        op->o_req_dn = dn;
        op->o_req_ndn = dn;
-       op->o_bd->be_modify( op, &rs );
+       if (op->o_bd->be_modify( op, &rs ) != LDAP_SUCCESS) {
+               Debug(LDAP_DEBUG_TRACE,
+                       "nssov_pam_sess_%c(): modify op failed\n",
+                       action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c',
+                       0,0);
+               rc = -1;
+       }
+
        if ( mod.sml_next ) {
                slap_mods_free( mod.sml_next, 1 );
        }
        ber_bvarray_free_x( nbv, op->o_tmpmemctx );
 
+done:;
+
+       if (rc == 0) {
+               Debug(LDAP_DEBUG_TRACE,
+                       "nssov_pam_sess_%c(): success\n",
+                       action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c',
+                       0,0);
+               sessionID = op->o_time;
+       }
        WRITE_INT32(fp,NSLCD_VERSION);
        WRITE_INT32(fp,action);
-       WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
-       WRITE_INT32(fp,op->o_time);
+       WRITE_INT32(fp,NSLCD_RESULT_BEGIN);
+       WRITE_INT32(fp,sessionID);
        return 0;
 }
 
@@ -591,69 +719,116 @@ int pam_pwmod(nssov_info *ni,TFILE *fp,Operation *op)
        struct paminfo pi;
        int rc;
 
-       READ_STRING_BUF2(fp,uidc,sizeof(uidc));
+       READ_STRING(fp,uidc);
        pi.uid.bv_val = uidc;
        pi.uid.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,dnc,sizeof(dnc));
+       READ_STRING(fp,dnc);
        pi.dn.bv_val = dnc;
        pi.dn.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,svcc,sizeof(svcc));
+       READ_STRING(fp,svcc);
        pi.svc.bv_val = svcc;
        pi.svc.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,opwc,sizeof(opwc));
+       READ_STRING(fp,opwc);
        pi.pwd.bv_val = opwc;
        pi.pwd.bv_len = tmpint32;
-       READ_STRING_BUF2(fp,npwc,sizeof(npwc));
+       READ_STRING(fp,npwc);
        npw.bv_val = npwc;
        npw.bv_len = tmpint32;
 
        Debug(LDAP_DEBUG_TRACE,"nssov_pam_pwmod(%s), %s\n",
-               pi.dn.bv_val,pi.uid.bv_val,0);
+               pi.dn.bv_val ? pi.dn.bv_val : "NULL",
+               pi.uid.bv_val ? pi.uid.bv_val : "NULL" ,0);
 
        BER_BVZERO(&pi.msg);
+       pi.ispwdmgr = 0;
+
+       /* nssov_pam prohibits password mod */
+       if (!BER_BVISEMPTY(&ni->ni_pam_password_prohibit_message)) {
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_pwmod(): %s (%s)\n",
+                       "password_prohibit_message",
+                       ni->ni_pam_password_prohibit_message.bv_val,0);
+               ber_str2bv(ni->ni_pam_password_prohibit_message.bv_val, 0, 0, &pi.msg);
+               rc = NSLCD_PAM_PERM_DENIED;
+               goto done;
+       }
 
-       /* This is a prelim check */
        if (BER_BVISEMPTY(&pi.dn)) {
-               rc = pam_do_bind(ni,fp,op,&pi);
-               if (rc == NSLCD_PAM_IGNORE)
-                       rc = NSLCD_PAM_SUCCESS;
+               /* should not be here at all, pam_authc() should have returned */
+               /* error */
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_pwmod(), %s\n",
+                       "prelim checking failed", 0, 0);
+               ber_str2bv("no pwmod requesting dn", 0, 0, &pi.msg);
+               rc = NSLCD_PAM_PERM_DENIED;
+               goto done;
+       }
+
+       if (BER_BVISEMPTY(&ni->ni_pam_pwdmgr_dn)) {
+               Debug(LDAP_DEBUG_TRACE,"nssov_pam_pwmod(), %s\n",
+                       "pwdmgr not configured", 0, 0);
+               ber_str2bv("pwdmgr not configured", 0, 0, &pi.msg);
+               rc = NSLCD_PAM_PERM_DENIED;
+               goto done;
+       } else if (!ber_bvcmp(&pi.dn, &ni->ni_pam_pwdmgr_dn)) {
+               /* root user requesting pwmod, convert uid to dn */
+               pi.ispwdmgr = 1;
+               rc = pam_uid2dn(ni, op, &pi);
+               if (rc) {
+                       ber_str2bv("unable to convert uid to dn", 0, 0, &pi.msg);
+                       rc = NSLCD_PAM_PERM_DENIED;
+                       goto done;
+               }
+       }
+
+       BerElementBuffer berbuf;
+       BerElement *ber = (BerElement *)&berbuf;
+       struct berval bv;
+       SlapReply rs = {REP_RESULT};
+       slap_callback cb = {0};
+
+       ber_init_w_nullc(ber, LBER_USE_DER);
+       ber_printf(ber, "{");
+       if (!BER_BVISEMPTY(&pi.dn))
+               ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_ID,
+                       &pi.dn);
+       /* supply old pwd only when end-user changing pwd */
+       if (!BER_BVISEMPTY(&pi.pwd) && pi.ispwdmgr == 0)
+               ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD,
+                       &pi.pwd);
+       if (!BER_BVISEMPTY(&npw))
+               ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW,
+                       &npw);
+       ber_printf(ber, "N}");
+       ber_flatten2(ber, &bv, 0);
+       op->o_tag = LDAP_REQ_EXTENDED;
+       op->ore_reqoid = slap_EXOP_MODIFY_PASSWD;
+       op->ore_reqdata = &bv;
+
+       if (pi.ispwdmgr) {
+               /* root user changing end-user passwords */
+               op->o_dn = ni->ni_pam_pwdmgr_dn;
+               op->o_ndn = ni->ni_pam_pwdmgr_dn;
        } else {
-               BerElementBuffer berbuf;
-               BerElement *ber = (BerElement *)&berbuf;
-               struct berval bv;
-               SlapReply rs = {REP_RESULT};
-               slap_callback cb = {0};
-
-               ber_init_w_nullc(ber, LBER_USE_DER);
-               ber_printf(ber, "{");
-               if (!BER_BVISEMPTY(&pi.pwd))
-                       ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD,
-                               &pi.pwd);
-               if (!BER_BVISEMPTY(&npw))
-                       ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW,
-                               &npw);
-               ber_printf(ber, "N}");
-               ber_flatten2(ber, &bv, 0);
-               op->o_tag = LDAP_REQ_EXTENDED;
-               op->ore_reqoid = slap_EXOP_MODIFY_PASSWD;
-               op->ore_reqdata = &bv;
+               /* end-user self-pwd-mod */
                op->o_dn = pi.dn;
                op->o_ndn = pi.dn;
-               op->o_callback = &cb;
-               op->o_conn->c_authz_backend = op->o_bd;
-               cb.sc_response = slap_null_cb;
-               op->o_bd = frontendDB;
-               rc = op->o_bd->be_extended(op, &rs);
-               if (rs.sr_text)
-                       ber_str2bv(rs.sr_text, 0, 0, &pi.msg);
-               if (rc == LDAP_SUCCESS)
-                       rc = NSLCD_PAM_SUCCESS;
-               else
-                       rc = NSLCD_PAM_PERM_DENIED;
        }
+       op->o_callback = &cb;
+       op->o_conn->c_authz_backend = op->o_bd;
+       cb.sc_response = slap_null_cb;
+       op->o_bd = frontendDB;
+       rc = op->o_bd->be_extended(op, &rs);
+       if (rs.sr_text)
+               ber_str2bv(rs.sr_text, 0, 0, &pi.msg);
+       if (rc == LDAP_SUCCESS)
+               rc = NSLCD_PAM_SUCCESS;
+       else
+               rc = NSLCD_PAM_PERM_DENIED;
+
+done:;
+       Debug(LDAP_DEBUG_TRACE,"nssov_pam_pwmod(), rc (%d)\n", rc, 0, 0);
        WRITE_INT32(fp,NSLCD_VERSION);
        WRITE_INT32(fp,NSLCD_ACTION_PAM_PWMOD);
-       WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
+       WRITE_INT32(fp,NSLCD_RESULT_BEGIN);
        WRITE_BERVAL(fp,&pi.uid);
        WRITE_BERVAL(fp,&pi.dn);
        WRITE_INT32(fp,rc);
@@ -664,7 +839,9 @@ int pam_pwmod(nssov_info *ni,TFILE *fp,Operation *op)
 int nssov_pam_init()
 {
        int code = 0;
+       const char *text;
        if (!ad_loginStatus)
-               code = register_at( at_loginStatus, &ad_loginStatus, 0 );
+               code = slap_str2ad("loginStatus", &ad_loginStatus, &text);
+
        return code;
 }