Diff for /servers/slapd/overlays/accesslog.c between versions 1.2.2.2 and 1.2.2.15

version 1.2.2.2, 2005/07/11 05:55:32 version 1.2.2.15, 2006/07/28 13:01:37
Line 1 Line 1
 /* accesslog.c - log operations for audit/history purposes */  /* accesslog.c - log operations for audit/history purposes */
 /* $OpenLDAP: pkg/ldap/servers/slapd/overlays/accesslog.c,v 1.4 2005/07/06 03:53:02 hallvard Exp $ */  /* $OpenLDAP: pkg/ldap/servers/slapd/overlays/accesslog.c,v 1.2.2.14 2006/05/09 17:43:12 kurt Exp $ */
 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.  /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
  *   *
  * Copyright 2005 The OpenLDAP Foundation.   * Copyright 2005-2006 The OpenLDAP Foundation.
  * Portions copyright 2004-2005 Symas Corporation.   * Portions copyright 2004-2005 Symas Corporation.
  * All rights reserved.   * All rights reserved.
  *   *
Line 43 Line 43
 #define LOG_OP_UNBIND   0x080  #define LOG_OP_UNBIND   0x080
 #define LOG_OP_ABANDON  0x100  #define LOG_OP_ABANDON  0x100
 #define LOG_OP_EXTENDED 0x200  #define LOG_OP_EXTENDED 0x200
   #define LOG_OP_UNKNOWN  0x400
   
 #define LOG_OP_WRITES   (LOG_OP_ADD|LOG_OP_DELETE|LOG_OP_MODIFY|LOG_OP_MODRDN)  #define LOG_OP_WRITES   (LOG_OP_ADD|LOG_OP_DELETE|LOG_OP_MODIFY|LOG_OP_MODRDN)
 #define LOG_OP_READS    (LOG_OP_COMPARE|LOG_OP_SEARCH)  #define LOG_OP_READS    (LOG_OP_COMPARE|LOG_OP_SEARCH)
 #define LOG_OP_SESSION  (LOG_OP_BIND|LOG_OP_UNBIND|LOG_OP_ABANDON)  #define LOG_OP_SESSION  (LOG_OP_BIND|LOG_OP_UNBIND|LOG_OP_ABANDON)
 #define LOG_OP_ALL              (LOG_OP_READS|LOG_OP_WRITES|LOG_OP_SESSION|LOG_OP_EXTENDED)  #define LOG_OP_ALL              (LOG_OP_READS|LOG_OP_WRITES|LOG_OP_SESSION| \
           LOG_OP_EXTENDED|LOG_OP_UNKNOWN)
   
 typedef struct log_info {  typedef struct log_info {
         BackendDB *li_db;          BackendDB *li_db;
Line 55  typedef struct log_info { Line 57  typedef struct log_info {
         int li_age;          int li_age;
         int li_cycle;          int li_cycle;
         struct re_s *li_task;          struct re_s *li_task;
           Filter *li_oldf;
           Entry *li_old;
           int li_success;
           ldap_pvt_thread_mutex_t li_op_mutex;
           ldap_pvt_thread_mutex_t li_log_mutex;
 } log_info;  } log_info;
   
 static ConfigDriver log_cf_gen;  static ConfigDriver log_cf_gen;
Line 62  static ConfigDriver log_cf_gen; Line 69  static ConfigDriver log_cf_gen;
 enum {  enum {
         LOG_DB = 1,          LOG_DB = 1,
         LOG_OPS,          LOG_OPS,
         LOG_PURGE          LOG_PURGE,
           LOG_SUCCESS,
           LOG_OLD
 };  };
   
 static ConfigTable log_cfats[] = {  static ConfigTable log_cfats[] = {
Line 80  static ConfigTable log_cfats[] = { Line 89  static ConfigTable log_cfats[] = {
                 log_cf_gen, "( OLcfgOvAt:4.3 NAME 'olcAccessLogPurge' "                  log_cf_gen, "( OLcfgOvAt:4.3 NAME 'olcAccessLogPurge' "
                         "DESC 'Log cleanup parameters' "                          "DESC 'Log cleanup parameters' "
                         "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },                          "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
           { "logsuccess", NULL, 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|LOG_SUCCESS,
                   log_cf_gen, "( OLcfgOvAt:4.4 NAME 'olcAccessLogSuccess' "
                           "DESC 'Log successful ops only' "
                           "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
           { "logold", "filter", 2, 2, 0, ARG_MAGIC|LOG_OLD,
                   log_cf_gen, "( OLcfgOvAt:4.5 NAME 'olcAccessLogOld' "
                           "DESC 'Log old values when modifying entries matching the filter' "
                           "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
         { NULL }          { NULL }
 };  };
   
Line 89  static ConfigOCs log_cfocs[] = { Line 106  static ConfigOCs log_cfocs[] = {
                 "DESC 'Access log configuration' "                  "DESC 'Access log configuration' "
                 "SUP olcOverlayConfig "                  "SUP olcOverlayConfig "
                 "MUST olcAccessLogDB "                  "MUST olcAccessLogDB "
                 "MAY ( olcAccessLogOps $ olcAccessLogPurge ) )",                  "MAY ( olcAccessLogOps $ olcAccessLogPurge $ olcAccessLogSuccess $ "
                           "olcAccessLogOld ) )",
                         Cft_Overlay, log_cfats },                          Cft_Overlay, log_cfats },
         { NULL }          { NULL }
 };  };
Line 109  static slap_verbmasks logops[] = { Line 127  static slap_verbmasks logops[] = {
         { BER_BVC("unbind"),    LOG_OP_UNBIND },          { BER_BVC("unbind"),    LOG_OP_UNBIND },
         { BER_BVC("abandon"),   LOG_OP_ABANDON },          { BER_BVC("abandon"),   LOG_OP_ABANDON },
         { BER_BVC("extended"),  LOG_OP_EXTENDED },          { BER_BVC("extended"),  LOG_OP_EXTENDED },
           { BER_BVC("unknown"),   LOG_OP_UNKNOWN },
         { BER_BVNULL, 0 }          { BER_BVNULL, 0 }
 };  };
   
Line 126  enum { Line 145  enum {
         LOG_EN_UNBIND,          LOG_EN_UNBIND,
         LOG_EN_ABANDON,          LOG_EN_ABANDON,
         LOG_EN_EXTENDED,          LOG_EN_EXTENDED,
           LOG_EN_UNKNOWN,
         LOG_EN__COUNT          LOG_EN__COUNT
 };  };
   
 static ObjectClass *log_ocs[LOG_EN__COUNT];  static ObjectClass *log_ocs[LOG_EN__COUNT], *log_container;
   
 #define LOG_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.5"  #define LOG_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.5"
   
Line 142  static AttributeDescription *ad_reqDN, * Line 162  static AttributeDescription *ad_reqDN, *
         *ad_reqNewSuperior, *ad_reqDeleteOldRDN, *ad_reqMod,          *ad_reqNewSuperior, *ad_reqDeleteOldRDN, *ad_reqMod,
         *ad_reqScope, *ad_reqFilter, *ad_reqAttr, *ad_reqEntries,          *ad_reqScope, *ad_reqFilter, *ad_reqAttr, *ad_reqEntries,
         *ad_reqSizeLimit, *ad_reqTimeLimit, *ad_reqAttrsOnly, *ad_reqData,          *ad_reqSizeLimit, *ad_reqTimeLimit, *ad_reqAttrsOnly, *ad_reqData,
         *ad_reqId, *ad_reqMessage, *ad_oldest;          *ad_reqId, *ad_reqMessage, *ad_reqVersion, *ad_reqDerefAliases,
           *ad_reqReferral, *ad_reqOld;
   
 static struct {  static struct {
         char *at;          char *at;
Line 175  static struct { Line 196  static struct {
                 "EQUALITY caseIgnoreMatch "                  "EQUALITY caseIgnoreMatch "
                 "SYNTAX OMsDirectoryString "                  "SYNTAX OMsDirectoryString "
                 "SINGLE-VALUE )", &ad_reqSession },                  "SINGLE-VALUE )", &ad_reqSession },
         { "( " LOG_SCHEMA_AT ".6 NAME 'reqResult' "          { "( " LOG_SCHEMA_AT ".6 NAME 'reqAuthzID' "
                   "DESC 'Authorization ID of requestor' "
                   "EQUALITY distinguishedNameMatch "
                   "SYNTAX OMsDN "
                   "SINGLE-VALUE )", &ad_reqAuthzID },
           { "( " LOG_SCHEMA_AT ".7 NAME 'reqResult' "
                 "DESC 'Result code of request' "                  "DESC 'Result code of request' "
                 "EQUALITY integerMatch "                  "EQUALITY integerMatch "
                   "ORDERING integerOrderingMatch "
                 "SYNTAX OMsInteger "                  "SYNTAX OMsInteger "
                 "SINGLE-VALUE )", &ad_reqResult },                  "SINGLE-VALUE )", &ad_reqResult },
         { "( " LOG_SCHEMA_AT ".7 NAME 'reqAuthzID' "          { "( " LOG_SCHEMA_AT ".8 NAME 'reqMessage' "
                 "DESC 'AUthorization ID of requestor' "                  "DESC 'Error text of request' "
                 "EQUALITY distinguishedNameMatch "                  "EQUALITY caseIgnoreMatch "
                 "SYNTAX OMsDN "                  "SUBSTR caseIgnoreSubstringsMatch "
                 "SINGLE-VALUE )", &ad_reqAuthzID },                  "SYNTAX OMsDirectoryString "
         { "( " LOG_SCHEMA_AT ".8 NAME 'reqControls' "                  "SINGLE-VALUE )", &ad_reqMessage },
           { "( " LOG_SCHEMA_AT ".9 NAME 'reqReferral' "
                   "DESC 'Referrals returned for request' "
                   "SUP labeledURI )", &ad_reqReferral },
           { "( " LOG_SCHEMA_AT ".10 NAME 'reqControls' "
                 "DESC 'Request controls' "                  "DESC 'Request controls' "
                 "SYNTAX OMsOctetString )", &ad_reqControls },                  "SYNTAX OMsOctetString )", &ad_reqControls },
         { "( " LOG_SCHEMA_AT ".9 NAME 'reqRespControls' "          { "( " LOG_SCHEMA_AT ".11 NAME 'reqRespControls' "
                 "DESC 'Response controls of request' "                  "DESC 'Response controls of request' "
                 "SYNTAX OMsOctetString )", &ad_reqRespControls },                  "SYNTAX OMsOctetString )", &ad_reqRespControls },
         { "( " LOG_SCHEMA_AT ".10 NAME 'reqMethod' "          { "( " LOG_SCHEMA_AT ".12 NAME 'reqId' "
                   "DESC 'ID of Request to Abandon' "
                   "EQUALITY integerMatch "
                   "ORDERING integerOrderingMatch "
                   "SYNTAX OMsInteger "
                   "SINGLE-VALUE )", &ad_reqId },
           { "( " LOG_SCHEMA_AT ".13 NAME 'reqVersion' "
                   "DESC 'Protocol version of Bind request' "
                   "EQUALITY integerMatch "
                   "ORDERING integerOrderingMatch "
                   "SYNTAX OMsInteger "
                   "SINGLE-VALUE )", &ad_reqVersion },
           { "( " LOG_SCHEMA_AT ".14 NAME 'reqMethod' "
                 "DESC 'Bind method of request' "                  "DESC 'Bind method of request' "
                 "EQUALITY caseIgnoreMatch "                  "EQUALITY caseIgnoreMatch "
                 "SYNTAX OMsDirectoryString "                  "SYNTAX OMsDirectoryString "
                 "SINGLE-VALUE )", &ad_reqMethod },                  "SINGLE-VALUE )", &ad_reqMethod },
         { "( " LOG_SCHEMA_AT ".11 NAME 'reqAssertion' "          { "( " LOG_SCHEMA_AT ".15 NAME 'reqAssertion' "
                 "DESC 'Compare Assertion of request' "                  "DESC 'Compare Assertion of request' "
                 "SYNTAX OMsDirectoryString "                  "SYNTAX OMsDirectoryString "
                 "SINGLE-VALUE )", &ad_reqAssertion },                  "SINGLE-VALUE )", &ad_reqAssertion },
         { "( " LOG_SCHEMA_AT ".12 NAME 'reqNewRDN' "          { "( " LOG_SCHEMA_AT ".16 NAME 'reqMod' "
                   "DESC 'Modifications of request' "
                   "EQUALITY octetStringMatch "
                   "SUBSTR octetStringSubstringsMatch "
                   "SYNTAX OMsOctetString )", &ad_reqMod },
           { "( " LOG_SCHEMA_AT ".17 NAME 'reqOld' "
                   "DESC 'Old values of entry before request completed' "
                   "EQUALITY octetStringMatch "
                   "SUBSTR octetStringSubstringsMatch "
                   "SYNTAX OMsOctetString )", &ad_reqOld },
           { "( " LOG_SCHEMA_AT ".18 NAME 'reqNewRDN' "
                 "DESC 'New RDN of request' "                  "DESC 'New RDN of request' "
                 "EQUALITY distinguishedNameMatch "                  "EQUALITY distinguishedNameMatch "
                 "SYNTAX OMsDN "                  "SYNTAX OMsDN "
                 "SINGLE-VALUE )", &ad_reqNewRDN },                  "SINGLE-VALUE )", &ad_reqNewRDN },
         { "( " LOG_SCHEMA_AT ".13 NAME 'reqNewSuperior' "          { "( " LOG_SCHEMA_AT ".19 NAME 'reqDeleteOldRDN' "
                   "DESC 'Delete old RDN' "
                   "EQUALITY booleanMatch "
                   "SYNTAX OMsBoolean "
                   "SINGLE-VALUE )", &ad_reqDeleteOldRDN },
           { "( " LOG_SCHEMA_AT ".20 NAME 'reqNewSuperior' "
                 "DESC 'New superior DN of request' "                  "DESC 'New superior DN of request' "
                 "EQUALITY distinguishedNameMatch "                  "EQUALITY distinguishedNameMatch "
                 "SYNTAX OMsDN "                  "SYNTAX OMsDN "
                 "SINGLE-VALUE )", &ad_reqNewSuperior },                  "SINGLE-VALUE )", &ad_reqNewSuperior },
         { "( " LOG_SCHEMA_AT ".14 NAME 'reqDeleteOldRDN' "          { "( " LOG_SCHEMA_AT ".21 NAME 'reqScope' "
                 "DESC 'Delete old RDN' "  
                 "SYNTAX OMsBoolean "  
                 "SINGLE-VALUE )", &ad_reqDeleteOldRDN },  
         { "( " LOG_SCHEMA_AT ".15 NAME 'reqMod' "  
                 "DESC 'Modifications of request' "  
                 "SYNTAX OMsDirectoryString "  
                 "EQUALITY caseIgnoreMatch "  
                 "SUBSTR caseIgnoreSubstringsMatch )", &ad_reqMod },  
         { "( " LOG_SCHEMA_AT ".16 NAME 'reqScope' "  
                 "DESC 'Scope of request' "                  "DESC 'Scope of request' "
                   "EQUALITY caseIgnoreMatch "
                 "SYNTAX OMsDirectoryString "                  "SYNTAX OMsDirectoryString "
                 "SINGLE-VALUE )", &ad_reqScope },                  "SINGLE-VALUE )", &ad_reqScope },
         { "( " LOG_SCHEMA_AT ".17 NAME 'reqFilter' "          { "( " LOG_SCHEMA_AT ".22 NAME 'reqDerefAliases' "
                   "DESC 'Disposition of Aliases in request' "
                   "EQUALITY caseIgnoreMatch "
                   "SYNTAX OMsDirectoryString "
                   "SINGLE-VALUE )", &ad_reqDerefAliases },
           { "( " LOG_SCHEMA_AT ".23 NAME 'reqAttrsOnly' "
                   "DESC 'Attributes and values of request' "
                   "EQUALITY booleanMatch "
                   "SYNTAX OMsBoolean "
                   "SINGLE-VALUE )", &ad_reqAttrsOnly },
           { "( " LOG_SCHEMA_AT ".24 NAME 'reqFilter' "
                 "DESC 'Filter of request' "                  "DESC 'Filter of request' "
                   "EQUALITY caseIgnoreMatch "
                   "SUBSTR caseIgnoreSubstringsMatch "
                 "SYNTAX OMsDirectoryString "                  "SYNTAX OMsDirectoryString "
                 "SINGLE-VALUE )", &ad_reqFilter },                  "SINGLE-VALUE )", &ad_reqFilter },
         { "( " LOG_SCHEMA_AT ".18 NAME 'reqAttr' "          { "( " LOG_SCHEMA_AT ".25 NAME 'reqAttr' "
                 "DESC 'Attributes of request' "                  "DESC 'Attributes of request' "
                   "EQUALITY caseIgnoreMatch "
                 "SYNTAX OMsDirectoryString )", &ad_reqAttr },                  "SYNTAX OMsDirectoryString )", &ad_reqAttr },
         { "( " LOG_SCHEMA_AT ".19 NAME 'reqEntries' "          { "( " LOG_SCHEMA_AT ".26 NAME 'reqSizeLimit' "
                 "DESC 'Number of entries returned' "  
                 "SYNTAX OMsInteger "  
                 "SINGLE-VALUE )", &ad_reqEntries },  
         { "( " LOG_SCHEMA_AT ".20 NAME 'reqSizeLimit' "  
                 "DESC 'Size limit of request' "                  "DESC 'Size limit of request' "
                   "EQUALITY integerMatch "
                   "ORDERING integerOrderingMatch "
                 "SYNTAX OMsInteger "                  "SYNTAX OMsInteger "
                 "SINGLE-VALUE )", &ad_reqSizeLimit },                  "SINGLE-VALUE )", &ad_reqSizeLimit },
         { "( " LOG_SCHEMA_AT ".21 NAME 'reqTimeLimit' "          { "( " LOG_SCHEMA_AT ".27 NAME 'reqTimeLimit' "
                 "DESC 'Time limit of request' "                  "DESC 'Time limit of request' "
                   "EQUALITY integerMatch "
                   "ORDERING integerOrderingMatch "
                 "SYNTAX OMsInteger "                  "SYNTAX OMsInteger "
                 "SINGLE-VALUE )", &ad_reqTimeLimit },                  "SINGLE-VALUE )", &ad_reqTimeLimit },
         { "( " LOG_SCHEMA_AT ".22 NAME 'reqAttrsOnly' "          { "( " LOG_SCHEMA_AT ".28 NAME 'reqEntries' "
                 "DESC 'Attributes and values of request' "                  "DESC 'Number of entries returned' "
                 "SYNTAX OMsBoolean "                  "EQUALITY integerMatch "
                 "SINGLE-VALUE )", &ad_reqAttrsOnly },                  "ORDERING integerOrderingMatch "
         { "( " LOG_SCHEMA_AT ".23 NAME 'reqData' "                  "SYNTAX OMsInteger "
                   "SINGLE-VALUE )", &ad_reqEntries },
           { "( " LOG_SCHEMA_AT ".29 NAME 'reqData' "
                 "DESC 'Data of extended request' "                  "DESC 'Data of extended request' "
                   "EQUALITY octetStringMatch "
                   "SUBSTR octetStringSubstringsMatch "
                 "SYNTAX OMsOctetString "                  "SYNTAX OMsOctetString "
                 "SINGLE-VALUE )", &ad_reqData },                  "SINGLE-VALUE )", &ad_reqData },
         { "( " LOG_SCHEMA_AT ".24 NAME 'reqId' "  
                 "DESC 'ID of Request to Abandon' "  
                 "SYNTAX OMsInteger "  
                 "SINGLE-VALUE )", &ad_reqId },  
         { "( " LOG_SCHEMA_AT ".25 NAME 'reqMessage' "  
                 "DESC 'Error text of request' "  
                 "SYNTAX OMsDirectoryString "  
                 "SINGLE-VALUE )", &ad_reqMessage },  
 #if 0  
         { "( " LOG_SCHEMA_AT ".26 NAME 'auditOldest' "  
                 "DESC 'Oldest record in this branch' "  
                 "EQUALITY generalizedTimeMatch "  
                 "ORDERING generalizedTimeOrderingMatch "  
                 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "  
                 "SINGLE-VALUE )", &ad_oldest },  
 #endif  
         { NULL, NULL }          { NULL, NULL }
 };  };
   
Line 273  static struct { Line 324  static struct {
         char *ot;          char *ot;
         ObjectClass **oc;          ObjectClass **oc;
 } locs[] = {  } locs[] = {
 #if 0  
         { "( " LOG_SCHEMA_OC ".0 NAME 'auditContainer' "          { "( " LOG_SCHEMA_OC ".0 NAME 'auditContainer' "
                   "DESC 'AuditLog container' "
                 "SUP top STRUCTURAL "                  "SUP top STRUCTURAL "
                 "MUST auditOldest "                  "MAY ( cn $ reqStart $ reqEnd ) )", &log_container },
                 "MAY cn )", &oc_container },  
 #endif  
         { "( " LOG_SCHEMA_OC ".1 NAME 'auditObject' "          { "( " LOG_SCHEMA_OC ".1 NAME 'auditObject' "
                 "DESC 'OpenLDAP request auditing' "                  "DESC 'OpenLDAP request auditing' "
                 "SUP top STRUCTURAL "                  "SUP top STRUCTURAL "
                 "MUST ( reqStart $ reqType $ reqSession ) "                  "MUST ( reqStart $ reqType $ reqSession ) "
                 "MAY ( reqDN $ reqAuthzID $ reqControls $ reqRespControls $ reqEnd $ "                  "MAY ( reqDN $ reqAuthzID $ reqControls $ reqRespControls $ reqEnd $ "
                         "reqResult $ reqMessage ) )", &log_ocs[LOG_EN_UNBIND] },                          "reqResult $ reqMessage $ reqReferral ) )",
                                   &log_ocs[LOG_EN_UNBIND] },
         { "( " LOG_SCHEMA_OC ".2 NAME 'auditReadObject' "          { "( " LOG_SCHEMA_OC ".2 NAME 'auditReadObject' "
                 "DESC 'OpenLDAP read request record' "                  "DESC 'OpenLDAP read request record' "
                 "SUP auditObject STRUCTURAL )", NULL },                  "SUP auditObject STRUCTURAL )", NULL },
         { "( " LOG_SCHEMA_OC ".3 NAME 'auditWriteObject' "          { "( " LOG_SCHEMA_OC ".3 NAME 'auditWriteObject' "
                 "DESC 'OpenLDAP write request record' "                  "DESC 'OpenLDAP write request record' "
                 "SUP auditObject STRUCTURAL )", &log_ocs[LOG_EN_DELETE] },                  "SUP auditObject STRUCTURAL )", NULL },
         { "( " LOG_SCHEMA_OC ".4 NAME 'auditAbandon' "          { "( " LOG_SCHEMA_OC ".4 NAME 'auditAbandon' "
                 "DESC 'Abandon operation' "                  "DESC 'Abandon operation' "
                 "SUP auditObject STRUCTURAL "                  "SUP auditObject STRUCTURAL "
Line 302  static struct { Line 352  static struct {
         { "( " LOG_SCHEMA_OC ".6 NAME 'auditBind' "          { "( " LOG_SCHEMA_OC ".6 NAME 'auditBind' "
                 "DESC 'Bind operation' "                  "DESC 'Bind operation' "
                 "SUP auditObject STRUCTURAL "                  "SUP auditObject STRUCTURAL "
                 "MUST reqMethod )", &log_ocs[LOG_EN_BIND] },                  "MUST ( reqVersion $ reqMethod ) )", &log_ocs[LOG_EN_BIND] },
         { "( " LOG_SCHEMA_OC ".7 NAME 'auditCompare' "          { "( " LOG_SCHEMA_OC ".7 NAME 'auditCompare' "
                 "DESC 'Compare operation' "                  "DESC 'Compare operation' "
                 "SUP auditReadObject STRUCTURAL "                  "SUP auditReadObject STRUCTURAL "
                 "MUST reqAssertion )", &log_ocs[LOG_EN_COMPARE] },                  "MUST reqAssertion )", &log_ocs[LOG_EN_COMPARE] },
         { "( " LOG_SCHEMA_OC ".8 NAME 'auditModify' "          { "( " LOG_SCHEMA_OC ".8 NAME 'auditDelete' "
                   "DESC 'Delete operation' "
                   "SUP auditWriteObject STRUCTURAL "
                   "MAY reqOld )", &log_ocs[LOG_EN_DELETE] },
           { "( " LOG_SCHEMA_OC ".9 NAME 'auditModify' "
                 "DESC 'Modify operation' "                  "DESC 'Modify operation' "
                 "SUP auditWriteObject STRUCTURAL "                  "SUP auditWriteObject STRUCTURAL "
                 "MUST reqMod )", &log_ocs[LOG_EN_MODIFY] },                  "MAY reqOld MUST reqMod )", &log_ocs[LOG_EN_MODIFY] },
         { "( " LOG_SCHEMA_OC ".9 NAME 'auditModRDN' "          { "( " LOG_SCHEMA_OC ".10 NAME 'auditModRDN' "
                 "DESC 'ModRDN operation' "                  "DESC 'ModRDN operation' "
                 "SUP auditWriteObject STRUCTURAL "                  "SUP auditWriteObject STRUCTURAL "
                 "MUST ( reqNewRDN $ reqDeleteOldRDN ) "                  "MUST ( reqNewRDN $ reqDeleteOldRDN ) "
                 "MAY reqNewSuperior )", &log_ocs[LOG_EN_MODRDN] },                  "MAY reqNewSuperior )", &log_ocs[LOG_EN_MODRDN] },
         { "( " LOG_SCHEMA_OC ".10 NAME 'auditSearch' "          { "( " LOG_SCHEMA_OC ".11 NAME 'auditSearch' "
                 "DESC 'Search operation' "                  "DESC 'Search operation' "
                 "SUP auditReadObject STRUCTURAL "                  "SUP auditReadObject STRUCTURAL "
                 "MUST ( reqScope $ reqAttrsonly ) "                  "MUST ( reqScope $ reqDerefAliases $ reqAttrsonly ) "
                 "MAY ( reqFilter $ reqAttr $ reqEntries $ reqSizeLimit $ "                  "MAY ( reqFilter $ reqAttr $ reqEntries $ reqSizeLimit $ "
                         "reqTimeLimit ) )", &log_ocs[LOG_EN_SEARCH] },                          "reqTimeLimit ) )", &log_ocs[LOG_EN_SEARCH] },
         { "( " LOG_SCHEMA_OC ".11 NAME 'auditExtended' "          { "( " LOG_SCHEMA_OC ".12 NAME 'auditExtended' "
                 "DESC 'Extended operation' "                  "DESC 'Extended operation' "
                 "SUP auditObject STRUCTURAL "                  "SUP auditObject STRUCTURAL "
                 "MAY reqData )", &log_ocs[LOG_EN_EXTENDED] },                  "MAY reqData )", &log_ocs[LOG_EN_EXTENDED] },
Line 331  static struct { Line 385  static struct {
   
 #define RDNEQ   "reqStart="  #define RDNEQ   "reqStart="
   
 /* Our time intervals are of the form [dd+]hh:mm[:ss]  /* Our time intervals are of the form [ddd+]hh:mm[:ss]
  * If a field is present, it must be two digits. We assume no one   * If a field is present, it must be two digits. (Except for
  * will want to keep log records for longer than 99 days.   * days, which can be arbitrary width.)
  */   */
 static int  static int
 log_age_parse(char *agestr)  log_age_parse(char *agestr)
 {  {
         char *ptr;  
         int t1, t2;          int t1, t2;
         int gotdays = 0;          int gotdays = 0;
           char *endptr;
   
         t1 = atoi( agestr );          t1 = strtol( agestr, &endptr, 10 );
         /* Is there a days delimiter? */          /* Is there a days delimiter? */
         if ( agestr[2] == '+' ) {          if ( *endptr == '+' ) {
                   /* 32 bit time only covers about 68 years */
                   if ( t1 < 0 || t1 > 25000 )
                           return -1;
                 t1 *= 24;                  t1 *= 24;
                 gotdays = 1;                  gotdays = 1;
         } else if ( agestr[2] != ':' ) {                  agestr = endptr + 1;
         /* No valid delimiter found, fail */          } else {
                 return -1;                  if ( agestr[2] != ':' ) {
                           /* No valid delimiter found, fail */
                           return -1;
                   }
                   t1 *= 60;
                   agestr += 3;
         }          }
   
         agestr += 3;  
         t2 = atoi( agestr );          t2 = atoi( agestr );
   
         /* if there's a delimiter, it can only be a colon */  
         if ( agestr[2] && agestr[2] != ':' )  
                 return -1;  
   
         /* If we're at the end of the string, and we started with days,  
          * fail because we expected to find minutes too.  
          */  
         if ( gotdays && !agestr[2] )  
                 return -1;  
   
         t1 *= 60;  
         t1 += t2;          t1 += t2;
   
         if ( !agestr[2] )          if ( agestr[2] ) {
                 return t1 * 60;                  /* if there's a delimiter, it can only be a colon */
                   if ( agestr[2] != ':' )
                           return -1;
           } else {
                   /* If we're at the end of the string, and we started with days,
                    * fail because we expected to find minutes too.
                    */
                   return gotdays ? -1 : t1 * 60;
           }
   
         agestr += 3;          agestr += 3;
         t2 = atoi( agestr );          t2 = atoi( agestr );
Line 380  log_age_parse(char *agestr) Line 437  log_age_parse(char *agestr)
         t1 *= 60;          t1 *= 60;
         t1 += t2;          t1 += t2;
   
         t1 *= 60;  
         if ( agestr[2] ) {          if ( agestr[2] ) {
                 agestr += 3;                  agestr += 3;
                 if ( agestr[2] )                  if ( agestr[2] )
                         return -1;                          return -1;
                   t1 *= 60;
                 t1 += atoi( agestr );                  t1 += atoi( agestr );
           } else if ( gotdays ) {
                   /* only got days+hh:mm */
                   t1 *= 60;
         }          }
         return t1;          return t1;
 }  }
Line 400  log_age_unparse( int age, struct berval Line 460  log_age_unparse( int age, struct berval
         age /= 60;          age /= 60;
         mm = age % 60;          mm = age % 60;
         age /= 60;          age /= 60;
         hh = age % 60;          hh = age % 24;
         age /= 24;          age /= 24;
         dd = age;          dd = age;
   
         ptr = agebv->bv_val;          ptr = agebv->bv_val;
   
         if ( dd )           if ( dd ) 
                 ptr += sprintf( ptr, "%02d+", dd );                  ptr += sprintf( ptr, "%d+", dd );
         ptr += sprintf( ptr, "%02d:%02d", hh, mm );          ptr += sprintf( ptr, "%02d:%02d", hh, mm );
         if ( ss )          if ( ss )
                 ptr += sprintf( ptr, ":%02d", ss );                  ptr += sprintf( ptr, ":%02d", ss );
Line 415  log_age_unparse( int age, struct berval Line 475  log_age_unparse( int age, struct berval
         agebv->bv_len = ptr - agebv->bv_val;          agebv->bv_len = ptr - agebv->bv_val;
 }  }
   
 static slap_callback nullsc = { NULL, slap_null_cb, NULL, NULL };  static slap_callback nullsc = { NULL, NULL, NULL, NULL };
   
 #define PURGE_INCREMENT 100  #define PURGE_INCREMENT 100
   
Line 452  accesslog_purge( void *ctx, void *arg ) Line 512  accesslog_purge( void *ctx, void *arg )
         struct log_info *li = rtask->arg;          struct log_info *li = rtask->arg;
   
         Connection conn = {0};          Connection conn = {0};
         char opbuf[OPERATION_BUFFER_SIZE];          OperationBuffer opbuf;
         Operation *op = (Operation *)opbuf;          Operation *op = (Operation *) &opbuf;
         SlapReply rs = {REP_RESULT};          SlapReply rs = {REP_RESULT};
         slap_callback cb = { NULL, log_old_lookup, NULL, NULL };          slap_callback cb = { NULL, log_old_lookup, NULL, NULL };
         Filter f;          Filter f;
Line 527  log_cf_gen(ConfigArgs *c) Line 587  log_cf_gen(ConfigArgs *c)
         struct log_info *li = on->on_bi.bi_private;          struct log_info *li = on->on_bi.bi_private;
         int rc = 0;          int rc = 0;
         slap_mask_t tmask = 0;          slap_mask_t tmask = 0;
         char agebuf[2*STRLENOF("dd+hh:mm:ss  ")];          char agebuf[2*STRLENOF("ddddd+hh:mm:ss  ")];
         struct berval agebv, cyclebv;          struct berval agebv, cyclebv;
   
         switch( c->op ) {          switch( c->op ) {
Line 541  log_cf_gen(ConfigArgs *c) Line 601  log_cf_gen(ConfigArgs *c)
                         rc = mask_to_verbs( logops, li->li_ops, &c->rvalue_vals );                          rc = mask_to_verbs( logops, li->li_ops, &c->rvalue_vals );
                         break;                          break;
                 case LOG_PURGE:                  case LOG_PURGE:
                           if ( !li->li_age ) {
                                   rc = 1;
                                   break;
                           }
                         agebv.bv_val = agebuf;                          agebv.bv_val = agebuf;
                         log_age_unparse( li->li_age, &agebv );                          log_age_unparse( li->li_age, &agebv );
                         agebv.bv_val[agebv.bv_len] = ' ';                          agebv.bv_val[agebv.bv_len] = ' ';
Line 550  log_cf_gen(ConfigArgs *c) Line 614  log_cf_gen(ConfigArgs *c)
                         agebv.bv_len += cyclebv.bv_len;                          agebv.bv_len += cyclebv.bv_len;
                         value_add_one( &c->rvalue_vals, &agebv );                          value_add_one( &c->rvalue_vals, &agebv );
                         break;                          break;
                   case LOG_SUCCESS:
                           if ( li->li_success )
                                   c->value_int = li->li_success;
                           else
                                   rc = 1;
                           break;
                   case LOG_OLD:
                           if ( li->li_oldf ) {
                                   filter2bv( li->li_oldf, &agebv );
                                   value_add_one( &c->rvalue_vals, &agebv );
                           }
                           else
                                   rc = 1;
                           break;
                 }                  }
                 break;                  break;
         case LDAP_MOD_DELETE:          case LDAP_MOD_DELETE:
Line 577  log_cf_gen(ConfigArgs *c) Line 655  log_cf_gen(ConfigArgs *c)
                         li->li_age = 0;                          li->li_age = 0;
                         li->li_cycle = 0;                          li->li_cycle = 0;
                         break;                          break;
                   case LOG_SUCCESS:
                           li->li_success = 0;
                           break;
                   case LOG_OLD:
                           if ( li->li_oldf ) {
                                   filter_free( li->li_oldf );
                                   li->li_oldf = NULL;
                           }
                           break;
                 }                  }
                 break;                  break;
         default:          default:
Line 589  log_cf_gen(ConfigArgs *c) Line 676  log_cf_gen(ConfigArgs *c)
                                 Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",                                  Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
                                         c->log, c->msg, c->value_dn.bv_val );                                          c->log, c->msg, c->value_dn.bv_val );
                                 rc = 1;                                  rc = 1;
                           } else if ( BER_BVISEMPTY( &li->li_db->be_rootdn )) {
                                   snprintf( c->msg, sizeof( c->msg ),
                                                   "<%s> no rootDN was configured for suffix",
                                                   c->argv[0] );
                                   Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
                                                   c->log, c->msg, c->value_dn.bv_val );
                                   rc = 1;
                         }                          }
                         ch_free( c->value_dn.bv_val );                          ch_free( c->value_dn.bv_val );
                         ch_free( c->value_ndn.bv_val );                          ch_free( c->value_ndn.bv_val );
Line 600  log_cf_gen(ConfigArgs *c) Line 694  log_cf_gen(ConfigArgs *c)
                         break;                          break;
                 case LOG_PURGE:                  case LOG_PURGE:
                         li->li_age = log_age_parse( c->argv[1] );                          li->li_age = log_age_parse( c->argv[1] );
                         if ( li->li_age == -1 ) {                          if ( li->li_age < 1 ) {
                                 rc = 1;                                  rc = 1;
                         } else {                          } else {
                                 li->li_cycle = log_age_parse( c->argv[2] );                                  li->li_cycle = log_age_parse( c->argv[2] );
                                 if ( li->li_cycle == -1 ) {                                  if ( li->li_cycle < 1 ) {
                                         rc = 1;                                          rc = 1;
                                 } else if ( slapMode & SLAP_SERVER_MODE ) {                                  } else if ( slapMode & SLAP_SERVER_MODE ) {
                                         struct re_s *re = li->li_task;                                          struct re_s *re = li->li_task;
Line 619  log_cf_gen(ConfigArgs *c) Line 713  log_cf_gen(ConfigArgs *c)
                                 }                                  }
                         }                          }
                         break;                          break;
                   case LOG_SUCCESS:
                           li->li_success = c->value_int;
                           break;
                   case LOG_OLD:
                           li->li_oldf = str2filter( c->argv[1] );
                           if ( !li->li_oldf ) {
                                   sprintf( c->msg, "bad filter!" );
                                   rc = 1;
                           }
                 }                  }
                 break;                  break;
         }          }
         return rc;          return rc;
 }  }
   
 static Entry *accesslog_entry( Operation *op, int logop ) {  static Entry *accesslog_entry( Operation *op, int logop,
           Operation *op2 ) {
         slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;          slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
         log_info *li = on->on_bi.bi_private;          log_info *li = on->on_bi.bi_private;
   
         char rdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8];          char rdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8];
         struct berval rdn, timestamp, bv;          char nrdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8];
   
           struct berval rdn, nrdn, timestamp, ntimestamp, bv;
         slap_verbmasks *lo = logops+logop+EN_OFFSET;          slap_verbmasks *lo = logops+logop+EN_OFFSET;
   
         Entry *e = ch_calloc( 1, sizeof(Entry) );          Entry *e = ch_calloc( 1, sizeof(Entry) );
   
         strcpy( rdnbuf, RDNEQ );          strcpy( rdnbuf, RDNEQ );
         rdn.bv_val = rdnbuf;          rdn.bv_val = rdnbuf;
           strcpy( nrdnbuf, RDNEQ );
           nrdn.bv_val = nrdnbuf;
   
         timestamp.bv_val = rdnbuf+STRLENOF(RDNEQ);          timestamp.bv_val = rdnbuf+STRLENOF(RDNEQ);
         timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ);          timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ);
         slap_timestamp( &op->o_time, &timestamp );          slap_timestamp( &op->o_time, &timestamp );
         if ( op->o_tincr ) {          sprintf( timestamp.bv_val + timestamp.bv_len-1, ".%06dZ", op->o_tincr );
                 sprintf( timestamp.bv_val + timestamp.bv_len-1, ".%06dZ", op->o_tincr );          timestamp.bv_len += 7;
                 timestamp.bv_len += 7;  
         }  
         rdn.bv_len = STRLENOF(RDNEQ)+timestamp.bv_len;          rdn.bv_len = STRLENOF(RDNEQ)+timestamp.bv_len;
           ad_reqStart->ad_type->sat_equality->smr_normalize(
                   SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, ad_reqStart->ad_type->sat_syntax,
                   ad_reqStart->ad_type->sat_equality, &timestamp, &ntimestamp,
                   op->o_tmpmemctx );
   
           strcpy( nrdn.bv_val + STRLENOF(RDNEQ), ntimestamp.bv_val );
           nrdn.bv_len = STRLENOF(RDNEQ)+ntimestamp.bv_len;
         build_new_dn( &e->e_name, li->li_db->be_suffix, &rdn, NULL );          build_new_dn( &e->e_name, li->li_db->be_suffix, &rdn, NULL );
         build_new_dn( &e->e_nname, li->li_db->be_nsuffix, &rdn, NULL );          build_new_dn( &e->e_nname, li->li_db->be_nsuffix, &nrdn, NULL );
   
         attr_merge_one( e, slap_schema.si_ad_objectClass,          attr_merge_one( e, slap_schema.si_ad_objectClass,
                 &log_ocs[logop]->soc_cname, NULL );                  &log_ocs[logop]->soc_cname, NULL );
         attr_merge_one( e, slap_schema.si_ad_structuralObjectClass,          attr_merge_one( e, slap_schema.si_ad_structuralObjectClass,
                 &log_ocs[logop]->soc_cname, NULL );                  &log_ocs[logop]->soc_cname, NULL );
         attr_merge_one( e, ad_reqStart, &timestamp, NULL );          attr_merge_one( e, ad_reqStart, &timestamp, &ntimestamp );
           op->o_tmpfree( ntimestamp.bv_val, op->o_tmpmemctx );
   
           slap_op_time( &op2->o_time, &op2->o_tincr );
   
           timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ);
           slap_timestamp( &op2->o_time, &timestamp );
           sprintf( timestamp.bv_val + timestamp.bv_len-1, ".%06dZ", op2->o_tincr );
           timestamp.bv_len += 7;
   
           attr_merge_normalize_one( e, ad_reqEnd, &timestamp, op->o_tmpmemctx );
   
         /* Exops have OID appended */          /* Exops have OID appended */
         if ( logop == LOG_EN_EXTENDED ) {          if ( logop == LOG_EN_EXTENDED ) {
Line 686  static Entry *accesslog_entry( Operation Line 810  static Entry *accesslog_entry( Operation
   
 static struct berval scopes[] = {  static struct berval scopes[] = {
         BER_BVC("base"),          BER_BVC("base"),
         BER_BVC("onelevel"),          BER_BVC("one"),
         BER_BVC("subtree"),          BER_BVC("sub"),
         BER_BVC("subordinate")          BER_BVC("subord")
   };
   
   static struct berval derefs[] = {
           BER_BVC("never"),
           BER_BVC("searching"),
           BER_BVC("finding"),
           BER_BVC("always")
 };  };
   
 static struct berval simple = BER_BVC("SIMPLE");  static struct berval simple = BER_BVC("SIMPLE");
   
   static void accesslog_val2val(AttributeDescription *ad, struct berval *val,
           char c_op, struct berval *dst) {
           char *ptr;
   
           dst->bv_len = ad->ad_cname.bv_len + val->bv_len + 2;
           if ( c_op ) dst->bv_len++;
   
           dst->bv_val = ch_malloc( dst->bv_len+1 );
   
           ptr = lutil_strcopy( dst->bv_val, ad->ad_cname.bv_val );
           *ptr++ = ':';
           if ( c_op )
                   *ptr++ = c_op;
           *ptr++ = ' ';
           AC_MEMCPY( ptr, val->bv_val, val->bv_len );
           dst->bv_val[dst->bv_len] = '\0';
   }
   
 static int accesslog_response(Operation *op, SlapReply *rs) {  static int accesslog_response(Operation *op, SlapReply *rs) {
         slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;          slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
         log_info *li = on->on_bi.bi_private;          log_info *li = on->on_bi.bi_private;
         Attribute *a, *last_attr;          Attribute *a, *last_attr;
         Modifications *m;          Modifications *m;
         struct berval *b;          struct berval *b;
         time_t endtime;  
         int i;          int i;
         int logop;          int logop;
         slap_verbmasks *lo;          slap_verbmasks *lo;
         Entry *e;          Entry *e = NULL, *old = NULL;
         char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE];          char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE+8];
         struct berval bv;          struct berval bv;
         char *ptr;          char *ptr;
         BerVarray vals;          BerVarray vals;
         Operation op2;          Operation op2 = {0};
         SlapReply rs2 = {REP_RESULT};          SlapReply rs2 = {REP_RESULT};
   
         if ( rs->sr_type != REP_RESULT && rs->sr_type != REP_EXTENDED )          if ( rs->sr_type != REP_RESULT && rs->sr_type != REP_EXTENDED )
Line 723  static int accesslog_response(Operation Line 871  static int accesslog_response(Operation
         case LDAP_REQ_SEARCH:   logop = LOG_EN_SEARCH; break;          case LDAP_REQ_SEARCH:   logop = LOG_EN_SEARCH; break;
         case LDAP_REQ_BIND:             logop = LOG_EN_BIND; break;          case LDAP_REQ_BIND:             logop = LOG_EN_BIND; break;
         case LDAP_REQ_EXTENDED: logop = LOG_EN_EXTENDED; break;          case LDAP_REQ_EXTENDED: logop = LOG_EN_EXTENDED; break;
           default:        /* unknown operation type */
                   logop = LOG_EN_UNKNOWN; break;
         }       /* Unbind and Abandon never reach here */          }       /* Unbind and Abandon never reach here */
   
         lo = logops+logop+EN_OFFSET;          lo = logops+logop+EN_OFFSET;
         if ( !( li->li_ops & lo->mask ))          if ( !( li->li_ops & lo->mask ))
                 return SLAP_CB_CONTINUE;                  return SLAP_CB_CONTINUE;
   
         endtime = slap_get_time();          if ( lo->mask & LOG_OP_WRITES ) {
                   ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
                   old = li->li_old;
                   li->li_old = NULL;
                   ldap_pvt_thread_mutex_unlock( &li->li_op_mutex );
           }
   
         e = accesslog_entry( op, logop );          if ( li->li_success && rs->sr_err != LDAP_SUCCESS )
                   goto done;
   
         bv.bv_val = timebuf;          e = accesslog_entry( op, logop, &op2 );
         bv.bv_len = sizeof(timebuf);  
         slap_timestamp( &endtime, &bv );  
   
         attr_merge_one( e, ad_reqEnd, &bv, NULL );  
   
         attr_merge_one( e, ad_reqDN, &op->o_req_dn, &op->o_req_ndn );          attr_merge_one( e, ad_reqDN, &op->o_req_dn, &op->o_req_ndn );
   
Line 754  static int accesslog_response(Operation Line 906  static int accesslog_response(Operation
   
         switch( logop ) {          switch( logop ) {
         case LOG_EN_ADD:          case LOG_EN_ADD:
           case LOG_EN_DELETE: {
                   char c_op;
                   Entry *e2;
   
                   if ( logop == LOG_EN_ADD ) {
                           e2 = op->ora_e;
                           c_op = '+';
                   } else {
                           if ( !old )
                                   break;
                           e2 = old;
                           c_op = 0;
                   }
                 /* count all the vals */                  /* count all the vals */
                 i = 0;                  i = 0;
                 for ( a=op->ora_e->e_attrs; a; a=a->a_next ) {                  for ( a=e2->e_attrs; a; a=a->a_next ) {
                         if ( a->a_vals ) {                          if ( a->a_vals ) {
                                 for (b=a->a_vals; !BER_BVISNULL( b ); b++) {                                  for (b=a->a_vals; !BER_BVISNULL( b ); b++) {
                                         i++;                                          i++;
Line 765  static int accesslog_response(Operation Line 930  static int accesslog_response(Operation
                 }                  }
                 vals = ch_malloc( (i+1) * sizeof( struct berval ));                  vals = ch_malloc( (i+1) * sizeof( struct berval ));
                 i = 0;                  i = 0;
                 for ( a=op->ora_e->e_attrs; a; a=a->a_next ) {                  for ( a=e2->e_attrs; a; a=a->a_next ) {
                         if ( a->a_vals ) {                          if ( a->a_vals ) {
                                 for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) {                                  for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) {
                                         vals[i].bv_len = a->a_desc->ad_cname.bv_len + b->bv_len +3;                                          accesslog_val2val( a->a_desc, b, c_op, &vals[i] );
                                         vals[i].bv_val = ch_malloc( vals[i].bv_len+1 );  
                                         ptr = lutil_strcopy( vals[i].bv_val,  
                                                 a->a_desc->ad_cname.bv_val );  
                                         *ptr++ = ':';  
                                         *ptr++ = '+';  
                                         *ptr++ = ' ';  
                                         AC_MEMCPY( ptr, b->bv_val, b->bv_len );  
                                         vals[i].bv_val[vals[i].bv_len] = '\0';  
                                 }                                  }
                         }                          }
                 }                  }
                 vals[i].bv_val = NULL;                  vals[i].bv_val = NULL;
                 vals[i].bv_len = 0;                  vals[i].bv_len = 0;
                 a = attr_alloc( ad_reqMod );                  a = attr_alloc( logop == LOG_EN_ADD ? ad_reqMod : ad_reqOld );
                 a->a_vals = vals;                  a->a_vals = vals;
                 a->a_nvals = vals;                  a->a_nvals = vals;
                 last_attr->a_next = a;                  last_attr->a_next = a;
                 break;                  break;
           }
         case LOG_EN_DELETE:  
                 /* needs nothing else */  
                 break;  
   
         case LOG_EN_MODIFY:          case LOG_EN_MODIFY:
                 /* count all the mods */                  /* count all the mods */
Line 806  static int accesslog_response(Operation Line 960  static int accesslog_response(Operation
                 }                  }
                 vals = ch_malloc( (i+1) * sizeof( struct berval ));                  vals = ch_malloc( (i+1) * sizeof( struct berval ));
                 i = 0;                  i = 0;
   
                   /* Zero flags on old entry */
                   if ( old ) {
                           for ( a=old->e_attrs; a; a=a->a_next )
                                   a->a_flags = 0;
                   }
   
                 for ( m=op->orm_modlist; m; m=m->sml_next ) {                  for ( m=op->orm_modlist; m; m=m->sml_next ) {
                           /* Mark this attribute as modified */
                           if ( old ) {
                                   a = attr_find( old->e_attrs, m->sml_desc );
                                   if ( a )
                                           a->a_flags = 1;
                           }
                         if ( m->sml_values ) {                          if ( m->sml_values ) {
                                 for (b=m->sml_values; !BER_BVISNULL( b ); b++,i++) {                                  for (b=m->sml_values; !BER_BVISNULL( b ); b++,i++) {
                                         char c_op;                                          char c_op;
                                         vals[i].bv_len = a->a_desc->ad_cname.bv_len + b->bv_len +3;  
                                         vals[i].bv_val = ch_malloc( vals[i].bv_len+1 );  
                                         ptr = lutil_strcopy( vals[i].bv_val,  
                                                 a->a_desc->ad_cname.bv_val );  
                                         *ptr++ = ':';  
                                         switch( m->sml_op ) {                                          switch( m->sml_op ) {
                                         case LDAP_MOD_ADD: c_op = '+'; break;                                          case LDAP_MOD_ADD: c_op = '+'; break;
                                         case LDAP_MOD_DELETE:   c_op = '-'; break;                                          case LDAP_MOD_DELETE:   c_op = '-'; break;
                                         case LDAP_MOD_REPLACE:  c_op = '='; break;                                          case LDAP_MOD_REPLACE:  c_op = '='; break;
                                         case LDAP_MOD_INCREMENT:        c_op = '#'; break;                                          case LDAP_MOD_INCREMENT:        c_op = '#'; break;
   
                                           /* unknown op. there shouldn't be any of these. we
                                            * don't know what to do with it, but we shouldn't just
                                            * ignore it.
                                            */
                                           default: c_op = '?'; break;
                                         }                                          }
                                         *ptr++ = c_op;                                          accesslog_val2val( m->sml_desc, b, c_op, &vals[i] );
                                         *ptr++ = ' ';  
                                         AC_MEMCPY( ptr, b->bv_val, b->bv_len );  
                                         vals[i].bv_val[vals[i].bv_len] = '\0';  
                                 }                                  }
                         } else if ( m->sml_op == LDAP_MOD_DELETE ) {                          } else if ( m->sml_op == LDAP_MOD_DELETE ) {
                                 vals[i].bv_len = a->a_desc->ad_cname.bv_len + 2;                                  vals[i].bv_len = m->sml_desc->ad_cname.bv_len + 2;
                                 vals[i].bv_val = ch_malloc( vals[i].bv_len+1 );                                  vals[i].bv_val = ch_malloc( vals[i].bv_len+1 );
                                 ptr = lutil_strcopy( vals[i].bv_val,                                  ptr = lutil_strcopy( vals[i].bv_val,
                                         a->a_desc->ad_cname.bv_val );                                          m->sml_desc->ad_cname.bv_val );
                                 *ptr++ = ':';                                  *ptr++ = ':';
                                 *ptr++ = '-';                                  *ptr++ = '-';
                                 *ptr = '\0';                                  *ptr = '\0';
Line 843  static int accesslog_response(Operation Line 1009  static int accesslog_response(Operation
                 a->a_vals = vals;                  a->a_vals = vals;
                 a->a_nvals = vals;                  a->a_nvals = vals;
                 last_attr->a_next = a;                  last_attr->a_next = a;
   
                   if ( old ) {
                           last_attr = a;
                           /* count all the vals */
                           i = 0;
                           for ( a=old->e_attrs; a; a=a->a_next ) {
                                   if ( a->a_vals && a->a_flags ) {
                                           for (b=a->a_vals; !BER_BVISNULL( b ); b++) {
                                           i++;
                                           }
                                   }
                           }
                           vals = ch_malloc( (i+1) * sizeof( struct berval ));
                           i = 0;
                           for ( a=old->e_attrs; a; a=a->a_next ) {
                                   if ( a->a_vals && a->a_flags ) {
                                           for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) {
                                                   accesslog_val2val( a->a_desc, b, 0, &vals[i] );
                                           }
                                   }
                           }
                           vals[i].bv_val = NULL;
                           vals[i].bv_len = 0;
                           a = attr_alloc( ad_reqOld );
                           a->a_vals = vals;
                           a->a_nvals = vals;
                           last_attr->a_next = a;
                   }
                 break;                  break;
   
         case LOG_EN_MODRDN:          case LOG_EN_MODRDN:
Line 869  static int accesslog_response(Operation Line 1063  static int accesslog_response(Operation
   
         case LOG_EN_SEARCH:          case LOG_EN_SEARCH:
                 attr_merge_one( e, ad_reqScope, &scopes[op->ors_scope], NULL );                  attr_merge_one( e, ad_reqScope, &scopes[op->ors_scope], NULL );
                   attr_merge_one( e, ad_reqDerefAliases, &derefs[op->ors_deref], NULL );
                 attr_merge_one( e, ad_reqAttrsOnly, op->ors_attrsonly ?                  attr_merge_one( e, ad_reqAttrsOnly, op->ors_attrsonly ?
                         (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv,                          (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv,
                         NULL );                          NULL );
Line 893  static int accesslog_response(Operation Line 1088  static int accesslog_response(Operation
   
                 bv.bv_len = sprintf( bv.bv_val, "%d", op->ors_tlimit );                  bv.bv_len = sprintf( bv.bv_val, "%d", op->ors_tlimit );
                 attr_merge_one( e, ad_reqTimeLimit, &bv, NULL );                  attr_merge_one( e, ad_reqTimeLimit, &bv, NULL );
                 /* FIXME: slimit was zeroed by the backends */  
                   bv.bv_len = sprintf( bv.bv_val, "%d", op->ors_slimit );
                   attr_merge_one( e, ad_reqSizeLimit, &bv, NULL );
                 break;                  break;
   
         case LOG_EN_BIND:          case LOG_EN_BIND:
                   bv.bv_val = timebuf;
                   bv.bv_len = sprintf( bv.bv_val, "%d", op->o_protocol );
                   attr_merge_one( e, ad_reqVersion, &bv, NULL );
                 if ( op->orb_method == LDAP_AUTH_SIMPLE ) {                  if ( op->orb_method == LDAP_AUTH_SIMPLE ) {
                         attr_merge_one( e, ad_reqMethod, &simple, NULL );                          attr_merge_one( e, ad_reqMethod, &simple, NULL );
                 } else {                  } else {
Line 909  static int accesslog_response(Operation Line 1109  static int accesslog_response(Operation
                         attr_merge_one( e, ad_reqMethod, &bv, NULL );                          attr_merge_one( e, ad_reqMethod, &bv, NULL );
                         op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );                          op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
                 }                  }
   
                 break;                  break;
   
         case LOG_EN_EXTENDED:          case LOG_EN_EXTENDED:
Line 916  static int accesslog_response(Operation Line 1117  static int accesslog_response(Operation
                         attr_merge_one( e, ad_reqData, op->ore_reqdata, NULL );                          attr_merge_one( e, ad_reqData, op->ore_reqdata, NULL );
                 }                  }
                 break;                  break;
   
           case LOG_EN_UNKNOWN:
                   /* we don't know its parameters, don't add any */
                   break;
         }          }
   
         op2.o_hdr = op->o_hdr;          op2.o_hdr = op->o_hdr;
         op2.o_tag = LDAP_REQ_ADD;          op2.o_tag = LDAP_REQ_ADD;
         op2.o_time = endtime;  
         op2.o_tincr = 0;  
         op2.o_bd = li->li_db;          op2.o_bd = li->li_db;
         op2.o_dn = li->li_db->be_rootdn;          op2.o_dn = li->li_db->be_rootdn;
         op2.o_ndn = li->li_db->be_rootndn;          op2.o_ndn = li->li_db->be_rootndn;
Line 930  static int accesslog_response(Operation Line 1133  static int accesslog_response(Operation
         op2.ora_e = e;          op2.ora_e = e;
         op2.o_callback = &nullsc;          op2.o_callback = &nullsc;
   
           if (( lo->mask & LOG_OP_WRITES ) && !BER_BVISEMPTY( &op->o_csn )) {
                   slap_queue_csn( &op2, &op->o_csn );
           }
   
         op2.o_bd->be_add( &op2, &rs2 );          op2.o_bd->be_add( &op2, &rs2 );
         entry_free( e );  
   
   done:
           if ( lo->mask & LOG_OP_WRITES )
                   ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
           if ( e ) entry_free( e );
           if ( old ) entry_free( old );
           return SLAP_CB_CONTINUE;
   }
   
   /* Since Bind success is sent by the frontend, it won't normally enter
    * the overlay response callback. Add another callback to make sure it
    * gets here.
    */
   static int
   accesslog_bind_resp( Operation *op, SlapReply *rs )
   {
           BackendDB *be, db;
           int rc;
           slap_callback *sc;
   
           be = op->o_bd;
           db = *be;
           op->o_bd = &db;
           db.bd_info = op->o_callback->sc_private;
           rc = accesslog_response( op, rs );
           op->o_bd = be;
           sc = op->o_callback;
           op->o_callback = sc->sc_next;
           op->o_tmpfree( sc, op->o_tmpmemctx );
           return rc;
   }
   
   static int
   accesslog_op_bind( Operation *op, SlapReply *rs )
   {
           slap_callback *sc;
   
           sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
           sc->sc_response = accesslog_bind_resp;
           sc->sc_private = op->o_bd->bd_info;
   
           if ( op->o_callback ) {
                   sc->sc_next = op->o_callback->sc_next;
                   op->o_callback->sc_next = sc;
           } else {
                   op->o_callback = sc;
           }
           return SLAP_CB_CONTINUE;
   }
   
   static int
   accesslog_op_mod( Operation *op, SlapReply *rs )
   {
           slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
           log_info *li = on->on_bi.bi_private;
   
           if ( li->li_ops & LOG_OP_WRITES ) {
                   /* FIXME: this needs to be a recursive mutex to allow
                    * overlays like refint to keep working.
                    */
                   ldap_pvt_thread_mutex_lock( &li->li_op_mutex );
                   if ( li->li_oldf && ( op->o_tag == LDAP_REQ_DELETE ||
                           op->o_tag == LDAP_REQ_MODIFY )) {
                           int rc;
                           Entry *e;
   
                           op->o_bd->bd_info = on->on_info->oi_orig;
                           rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
                           if ( e ) {
                                   if ( test_filter( op, e, li->li_oldf ) == LDAP_COMPARE_TRUE )
                                           li->li_old = entry_dup( e );
                                   be_entry_release_rw( op, e, 0 );
                           }
                           op->o_bd->bd_info = (BackendInfo *)on;
                   }
           }
         return SLAP_CB_CONTINUE;          return SLAP_CB_CONTINUE;
 }  }
   
Line 942  static int accesslog_response(Operation Line 1223  static int accesslog_response(Operation
 static int  static int
 accesslog_unbind( Operation *op, SlapReply *rs )  accesslog_unbind( Operation *op, SlapReply *rs )
 {  {
         if ( op->o_conn->c_authz_backend == op->o_bd ) {          slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
                 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;          if ( op->o_conn->c_authz_backend == on->on_info->oi_origdb ) {
                 log_info *li = on->on_bi.bi_private;                  log_info *li = on->on_bi.bi_private;
                 Operation op2;                  Operation op2 = {0};
                   void *cids[SLAP_MAX_CIDS];
                 SlapReply rs2 = {REP_RESULT};                  SlapReply rs2 = {REP_RESULT};
                 Entry *e;                  Entry *e;
   
                 e = accesslog_entry( op, LOG_EN_UNBIND );                  if ( !( li->li_ops & LOG_OP_UNBIND ))
                           return SLAP_CB_CONTINUE;
   
                   e = accesslog_entry( op, LOG_EN_UNBIND, &op2 );
                 op2.o_hdr = op->o_hdr;                  op2.o_hdr = op->o_hdr;
                 op2.o_tag = LDAP_REQ_ADD;                  op2.o_tag = LDAP_REQ_ADD;
                 op2.o_time = op->o_time;  
                 op2.o_tincr = 0;  
                 op2.o_bd = li->li_db;                  op2.o_bd = li->li_db;
                 op2.o_dn = li->li_db->be_rootdn;                  op2.o_dn = li->li_db->be_rootdn;
                 op2.o_ndn = li->li_db->be_rootndn;                  op2.o_ndn = li->li_db->be_rootndn;
Line 961  accesslog_unbind( Operation *op, SlapRep Line 1244  accesslog_unbind( Operation *op, SlapRep
                 op2.o_req_ndn = e->e_nname;                  op2.o_req_ndn = e->e_nname;
                 op2.ora_e = e;                  op2.ora_e = e;
                 op2.o_callback = &nullsc;                  op2.o_callback = &nullsc;
                   op2.o_controls = cids;
                   memset(cids, 0, sizeof( cids ));
   
                 op2.o_bd->be_add( &op2, &rs2 );                  op2.o_bd->be_add( &op2, &rs2 );
                 entry_free( e );                  entry_free( e );
Line 973  accesslog_abandon( Operation *op, SlapRe Line 1258  accesslog_abandon( Operation *op, SlapRe
 {  {
         slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;          slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
         log_info *li = on->on_bi.bi_private;          log_info *li = on->on_bi.bi_private;
         Operation op2;          Operation op2 = {0};
           void *cids[SLAP_MAX_CIDS];
         SlapReply rs2 = {REP_RESULT};          SlapReply rs2 = {REP_RESULT};
         Entry *e;          Entry *e;
         char buf[64];          char buf[64];
         struct berval bv;          struct berval bv;
   
         if ( !op->o_time )          if ( !op->o_time || !( li->li_ops & LOG_OP_ABANDON ))
                 return SLAP_CB_CONTINUE;                  return SLAP_CB_CONTINUE;
   
         e = accesslog_entry( op, LOG_EN_ABANDON );          e = accesslog_entry( op, LOG_EN_ABANDON, &op2 );
         bv.bv_val = buf;          bv.bv_val = buf;
         bv.bv_len = sprintf( buf, "%d", op->orn_msgid );          bv.bv_len = sprintf( buf, "%d", op->orn_msgid );
         attr_merge_one( e, ad_reqId, &bv, NULL );          attr_merge_one( e, ad_reqId, &bv, NULL );
   
         op2.o_hdr = op->o_hdr;          op2.o_hdr = op->o_hdr;
         op2.o_tag = LDAP_REQ_ADD;          op2.o_tag = LDAP_REQ_ADD;
         op2.o_time = op->o_time;  
         op2.o_tincr = 0;  
         op2.o_bd = li->li_db;          op2.o_bd = li->li_db;
         op2.o_dn = li->li_db->be_rootdn;          op2.o_dn = li->li_db->be_rootdn;
         op2.o_ndn = li->li_db->be_rootndn;          op2.o_ndn = li->li_db->be_rootndn;
Line 998  accesslog_abandon( Operation *op, SlapRe Line 1282  accesslog_abandon( Operation *op, SlapRe
         op2.o_req_ndn = e->e_nname;          op2.o_req_ndn = e->e_nname;
         op2.ora_e = e;          op2.ora_e = e;
         op2.o_callback = &nullsc;          op2.o_callback = &nullsc;
           op2.o_controls = cids;
           memset(cids, 0, sizeof( cids ));
   
         op2.o_bd->be_add( &op2, &rs2 );          op2.o_bd->be_add( &op2, &rs2 );
         entry_free( e );          entry_free( e );
Line 1016  accesslog_db_init( Line 1302  accesslog_db_init(
         log_info *li = ch_calloc(1, sizeof(log_info));          log_info *li = ch_calloc(1, sizeof(log_info));
   
         on->on_bi.bi_private = li;          on->on_bi.bi_private = li;
           ldap_pvt_thread_mutex_init( &li->li_op_mutex );
           ldap_pvt_thread_mutex_init( &li->li_log_mutex );
         return 0;          return 0;
 }  }
   
Line 1026  accesslog_db_destroy( Line 1314  accesslog_db_destroy(
 {  {
         slap_overinst *on = (slap_overinst *)be->bd_info;          slap_overinst *on = (slap_overinst *)be->bd_info;
         log_info *li = on->on_bi.bi_private;          log_info *li = on->on_bi.bi_private;
           
           ldap_pvt_thread_mutex_destroy( &li->li_log_mutex );
           ldap_pvt_thread_mutex_destroy( &li->li_op_mutex );
         free( li );          free( li );
         return LDAP_SUCCESS;          return LDAP_SUCCESS;
 }  }
   
 int accesslog_init()  static int
   accesslog_db_open(
           BackendDB *be
   )
   {
           slap_overinst *on = (slap_overinst *)be->bd_info;
           log_info *li = on->on_bi.bi_private;
   
           Connection conn;
           OperationBuffer opbuf;
           Operation *op = (Operation *) &opbuf;
           Entry *e;
           int rc;
           void *thrctx;
   
           if ( slapMode & SLAP_TOOL_MODE )
                   return 0;
   
           thrctx = ldap_pvt_thread_pool_context();
           connection_fake_init( &conn, op, thrctx );
           op->o_bd = li->li_db;
           op->o_dn = li->li_db->be_rootdn;
           op->o_ndn = li->li_db->be_rootndn;
   
           rc = be_entry_get_rw( op, li->li_db->be_nsuffix, NULL, NULL, 0, &e );
   
           if ( e ) {
                   be_entry_release_rw( op, e, 0 );
           } else {
                   SlapReply rs = {REP_RESULT};
                   struct berval rdn, nrdn, attr;
                   char *ptr;
                   AttributeDescription *ad = NULL;
                   const char *text = NULL;
                   Entry *e_ctx;
   
                   e = ch_calloc( 1, sizeof( Entry ));
                   e->e_name = *li->li_db->be_suffix;
                   e->e_nname = *li->li_db->be_nsuffix;
   
                   attr_merge_one( e, slap_schema.si_ad_objectClass,
                           &log_container->soc_cname, NULL );
   
                   dnRdn( &e->e_name, &rdn );
                   dnRdn( &e->e_nname, &nrdn );
                   ptr = ber_bvchr( &rdn, '=' );
   
                   assert( ptr != NULL );
   
                   attr.bv_val = rdn.bv_val;
                   attr.bv_len = ptr - rdn.bv_val;
   
                   slap_bv2ad( &attr, &ad, &text );
   
                   rdn.bv_val = ptr+1;
                   rdn.bv_len -= attr.bv_len + 1;
                   ptr = ber_bvchr( &nrdn, '=' );
                   nrdn.bv_len -= ptr - nrdn.bv_val + 1;
                   nrdn.bv_val = ptr+1;
                   attr_merge_one( e, ad, &rdn, &nrdn );
   
                   /* Get contextCSN from main DB */
                   op->o_bd = be;
                   op->o_bd->bd_info = on->on_info->oi_orig;
                   rc = be_entry_get_rw( op, be->be_nsuffix, NULL,
                           slap_schema.si_ad_contextCSN, 0, &e_ctx );
   
                   if ( e_ctx ) {
                           Attribute *a;
   
                           a = attr_find( e_ctx->e_attrs, slap_schema.si_ad_contextCSN );
                           if ( a ) {
                                   attr_merge( e, slap_schema.si_ad_entryCSN, a->a_vals, NULL );
                                   attr_merge( e, a->a_desc, a->a_vals, NULL );
                           }
                           be_entry_release_rw( op, e_ctx, 0 );
                   }
                   op->o_bd->bd_info = (BackendInfo *)on;
                   op->o_bd = li->li_db;
   
                   op->ora_e = e;
                   op->o_req_dn = e->e_name;
                   op->o_req_ndn = e->e_nname;
                   op->o_callback = &nullsc;
                   SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD;
                   rc = op->o_bd->be_add( op, &rs );
                   SLAP_DBFLAGS( op->o_bd ) ^= SLAP_DBFLAG_NOLASTMOD;
                   attrs_free( e->e_attrs );
                   ch_free( e );
           }
           ldap_pvt_thread_pool_context_reset( thrctx );
           return rc;
   }
   
   int accesslog_initialize()
 {  {
         int i, rc;          int i, rc;
   
         accesslog.on_bi.bi_type = "accesslog";          accesslog.on_bi.bi_type = "accesslog";
         accesslog.on_bi.bi_db_init = accesslog_db_init;          accesslog.on_bi.bi_db_init = accesslog_db_init;
         accesslog.on_bi.bi_db_destroy = accesslog_db_destroy;          accesslog.on_bi.bi_db_destroy = accesslog_db_destroy;
           accesslog.on_bi.bi_db_open = accesslog_db_open;
   
           accesslog.on_bi.bi_op_add = accesslog_op_mod;
           accesslog.on_bi.bi_op_bind = accesslog_op_bind;
           accesslog.on_bi.bi_op_delete = accesslog_op_mod;
           accesslog.on_bi.bi_op_modify = accesslog_op_mod;
           accesslog.on_bi.bi_op_modrdn = accesslog_op_mod;
         accesslog.on_bi.bi_op_unbind = accesslog_unbind;          accesslog.on_bi.bi_op_unbind = accesslog_unbind;
         accesslog.on_bi.bi_op_abandon = accesslog_abandon;          accesslog.on_bi.bi_op_abandon = accesslog_abandon;
         accesslog.on_response = accesslog_response;          accesslog.on_response = accesslog_response;
   
         accesslog.on_bi.bi_cf_ocs = log_cfocs;          accesslog.on_bi.bi_cf_ocs = log_cfocs;
   
           nullsc.sc_response = slap_null_cb;
   
         rc = config_register_schema( log_cfats, log_cfocs );          rc = config_register_schema( log_cfats, log_cfocs );
         if ( rc ) return rc;          if ( rc ) return rc;
   
Line 1109  int accesslog_init() Line 1501  int accesslog_init()
 }  }
   
 #if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC  #if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC
 int init_module( int argc, char *argv[]) {  int
         return accesslog_init();  init_module( int argc, char *argv[] )
   {
           return accesslog_initialize();
 }  }
 #endif  #endif
   

Removed from v.1.2.2.2  
changed lines
  Added in v.1.2.2.15


______________
© Copyright 1998-2020, OpenLDAP Foundation, info@OpenLDAP.org