/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/*
 * 7bit.c
 *
 * Implements a directory server pre-operation plugin to test
 * attributes for 7 bit clean within a defined subtree in the
 * directory.
 *
 */
#include <stdio.h>
#include <slapi-plugin.h>
#include <slapi-private.h>
#include <string.h>

/* DBDB this should be pulled from a common header file */
#if defined(LDAP_ERROR_LOGGING) && !defined(DEBUG)
#define DEBUG
#endif

/*
 * ISSUES:
 *   How should this plugin handle ACL issues?  It seems wrong to reject
 *   adds and modifies because there is already a conflicting UID, when
 *   the request would have failed because of an ACL check anyway.
 *
 *   This code currently defines a maximum filter string size of 512.  Is
 *   this large enough?
 *
 *   This code currently does not quote the value portion of the filter as
 *   it is created.  This is a bug.
 */

/* */
#define BEGIN do {
#define END   \
    }         \
    while (0) \
        ;

/*
 * Slapi plugin descriptor
 */
static char *plugin_name = "NS7bitAttr";
static Slapi_PluginDesc
    pluginDesc = {"NS7bitAttr", VENDOR, DS_PACKAGE_VERSION,
                  "Enforce  7-bit clean attribute values"};


/*
 * More information about constraint failure
 */
static char *moreInfo =
    "The value is not 7-bit clean: ";

/* ------------------------------------------------------------ */
/*
 * op_error - Record (and report) an operational error.
 */
static int
op_error(int internal_error)
{
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                  "op_error - %d\n", internal_error);

    return LDAP_OPERATIONS_ERROR;
}

static void
issue_error(Slapi_PBlock *pb, int result, char *type, char *value)
{
    char *moreinfop;

    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                  "issue_error - %s result %d\n", type, result);

    if (value == NULL) {
        value = "unknown";
    }
    moreinfop = slapi_ch_smprintf("%s%s", moreInfo, value);

    /* Send failure to the client */
    slapi_send_ldap_result(pb, result, 0, moreinfop, 0, 0);
    slapi_ch_free((void **)&moreinfop);

    return;
}


/*
 * Check 'value' for 7-bit cleanliness.
 */
static int
bit_check_one_berval(const struct berval *value, char **violated)
{
    int result;
    char *ch;
    int i;

#ifdef DEBUG
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "bit_check_one_berval - 7-bit checking begin\n");
#endif

    result = LDAP_SUCCESS;
    /* If no value, can't possibly be a conflict */
    if ((struct berval *)NULL == value)
        return result;

    for (i = 0, ch = value->bv_val; ch && i < (int)(value->bv_len);
         ch++, i++) {

        if ((0x80 & *ch) != 0) {
            result = LDAP_CONSTRAINT_VIOLATION;
            *violated = value->bv_val;
            break;
        }
    }

    return result;
}


/*
 * Check a set of values for 7-bit cleanliness.
 *
 * If 'attr' is NULL, the values are taken from 'values'.
 * If 'attr' is non-NULL, the values are taken from 'attr'.
 */
static int
bit_check(Slapi_Attr *attr, struct berval **values, char **violated)
{
    int result = LDAP_SUCCESS;
    *violated = NULL;

    /* If no values, can't possibly be a conflict */
    if ((Slapi_Attr *)NULL == attr && (struct berval **)NULL == values)
        return result;

    if ((Slapi_Attr *)NULL != attr) {
        Slapi_Value *v = NULL;
        int vhint = -1;

        for (vhint = slapi_attr_first_value(attr, &v);
             vhint != -1 && LDAP_SUCCESS == result;
             vhint = slapi_attr_next_value(attr, vhint, &v)) {
            result = bit_check_one_berval(slapi_value_get_berval(v), violated);
        }
    } else {
        for (; *values != NULL && LDAP_SUCCESS == result; values++) {
            result = bit_check_one_berval(*values, violated);
        }
    }

#ifdef DEBUG
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                  "bit_check - 7 bit check result = %d\n", result);
#endif

    return result;
}


/* ------------------------------------------------------------ */
/*
 * preop_add - pre-operation plug-in for add
 */
static int
preop_add(Slapi_PBlock *pb)
{
    int result;
    char *violated = NULL;
    char *pwd = NULL;
    char *origpwd = NULL;
#ifdef DEBUG
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "preop_add - ADD begin\n");
#endif

    result = LDAP_SUCCESS;

    /*
   * Do constraint check on the added entry.  Set result.
   */
    BEGIN
    int err;
    int argc;
    char **argv;
    char **attrName;
    const char *dn;
    Slapi_DN *sdn = NULL;
    Slapi_Entry *e;
    char **firstSubtree;
    char **subtreeDN;
    int subtreeCnt;
    int is_replicated_operation;
    struct berval *vals[2];
    struct berval val;
    vals[0] = &val;
    vals[1] = NULL;
    /*
     * Get the arguments
     */
    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
    if (err) {
        result = op_error(53);
        break;
    }

    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
    if (err) {
        result = op_error(54);
        break;
    }

    /*
     * If this is a replication update, just be a noop.
     */
    err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
    if (err) {
        result = op_error(56);
        break;
    }
    if (is_replicated_operation) {
        break;
    }

    /*
     * Get the target DN for this add operation
     */
    err = slapi_pblock_get(pb, SLAPI_ADD_TARGET_SDN, &sdn);
    if (err) {
        result = op_error(50);
        break;
    }

    dn = slapi_sdn_get_dn(sdn);

#ifdef DEBUG
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "preop_add - ADD target=%s\n", dn);
#endif

    /*
     * Get the entry data for this add. Check whether it
     * contains a value for the unique attribute
     */
    err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
    if (err) {
        result = op_error(51);
        break;
    }

    for (firstSubtree = argv; strcmp(*firstSubtree, ",") != 0;
         firstSubtree++, argc--) {
    }
    firstSubtree++;
    argc--;

    for (attrName = argv; attrName && *attrName && strcmp(*attrName, ","); attrName++) {
        /*
       * if the attribute is userpassword, check unhashed user password
       * instead.  "userpassword" is encoded; it will always pass the 7bit
       * check.
       */
        char *attr_name = NULL;
        Slapi_Attr *attr = NULL;
        if (strcasecmp(*attrName, "userpassword") == 0) {
            origpwd = pwd = slapi_get_first_clear_text_pw(e);
            if (pwd == NULL) {
                continue;
            }
            val.bv_val = pwd;
            val.bv_len = strlen(val.bv_val);
        } else {
            attr_name = *attrName;
            err = slapi_entry_attr_find(e, attr_name, &attr);
            if (err)
                continue; /* break;*/ /* no 7-bit attribute */
        }

        /*
       * For each DN in the managed list, do 7-bit checking if
       * the target DN is a subnode in the tree.
       */
        for (subtreeDN = firstSubtree, subtreeCnt = argc; subtreeCnt > 0;
             subtreeCnt--, subtreeDN++) {
            /*
         * issuffix determines whether the target is under the
         * subtree *subtreeDN
         */
            if (slapi_dn_issuffix(dn, *subtreeDN)) {
#ifdef DEBUG
                slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                              "preop_add - ADD subtree=%s\n", *subtreeDN);
#endif

                /*
           * Check if the value is 7-bit clean
           */
                if (pwd) {
                    result = bit_check(attr, vals, &violated);
                    if (!result)
                        pwd = NULL;
                } else
                    result = bit_check(attr, NULL, &violated);
                if (result)
                    break;
            }
        }
        /* don't have to go on if there is a value not 7-bit clean */
        if (result)
            break;
    }
    END

        if (result)
    {
        issue_error(pb, result, "ADD", violated);
    }
    slapi_ch_free_string(&origpwd);
    return (result == LDAP_SUCCESS) ? 0 : -1;
}

static void
addMod(LDAPMod ***modary, int *capacity, int *nmods, LDAPMod *toadd)
{
    if (*nmods == *capacity) {
        *capacity += 4;
        if (*modary) {
            *modary = (LDAPMod **)slapi_ch_realloc((char *)*modary, *capacity * sizeof(LDAPMod *));
        } else {
            *modary = (LDAPMod **)slapi_ch_malloc(*capacity * sizeof(LDAPMod *));
        }
    }
    (*modary)[*nmods] = toadd;
    (*nmods)++;
}

/* ------------------------------------------------------------ */
/*
 * preop_modify - pre-operation plug-in for modify
 */
static int
preop_modify(Slapi_PBlock *pb)
{
    int result;
    char *violated = NULL;
    LDAPMod **checkmods = NULL; /* holds mods to check */
    int checkmodsCapacity = 0;  /* max capacity of checkmods */

#ifdef DEBUG
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                  "preop_modify - MODIFY begin\n");
#endif

    result = LDAP_SUCCESS;

    BEGIN
    int err;
    int argc;
    char **argv;
    char **attrName;
    LDAPMod **mods;
    LDAPMod **firstMods;
    LDAPMod *mod;
    const char *target;
    Slapi_DN *target_sdn = NULL;
    char **firstSubtree;
    char **subtreeDN;
    int subtreeCnt;
    int is_replicated_operation;

    /*
     * Get the arguments
     */
    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
    if (err) {
        result = op_error(13);
        break;
    }

    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
    if (err) {
        result = op_error(14);
        break;
    }

    /*
     * If this is a replication update, just be a noop.
     */
    err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
    if (err) {
        result = op_error(16);
        break;
    }
    if (is_replicated_operation) {
        break;
    }

    err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &firstMods);
    if (err) {
        result = op_error(10);
        break;
    }

    /* Get the target DN */
    err = slapi_pblock_get(pb, SLAPI_MODIFY_TARGET_SDN, &target_sdn);
    if (err) {
        result = op_error(11);
        break;
    }

    target = slapi_sdn_get_dn(target_sdn);
    /*
     * Look for managed trees that include the target
     * Arguments before "," are the 7-bit clean attribute names.  Arguemnts
     * after "," are subtreeDN's.
     */
    for (firstSubtree = argv; strcmp(*firstSubtree, ",") != 0;
         firstSubtree++, argc--) {
    }
    firstSubtree++;
    argc--;

    for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++) {
        int modcount = 0;
        int ii = 0;

        /*
       * if the attribute is userpassword, check unhashed#user#password
       * instead.  "userpassword" is encoded; it will always pass the 7bit
       * check.
       */
        char *attr_name;
        if (strcasecmp(*attrName, "userpassword") == 0) {
            attr_name = "unhashed#user#password";
        } else {
            attr_name = *attrName;
        }

        /* There may be more than one mod that matches e.g.
     changetype: modify
     delete: uid
     uid: balster1950
     -
     add: uid
     uid: scottg

     So, we need to first find all mods that contain the attribute
     which are add or replace ops and are bvalue encoded
      */
        /* find out how many mods meet this criteria */
        for (mods = firstMods; mods && *mods; mods++) {
            mod = *mods;
            if ((slapi_attr_type_cmp(mod->mod_type, attr_name, 1) == 0) && /* mod contains target attr */
                (mod->mod_op & LDAP_MOD_BVALUES) &&                        /* mod is bval encoded (not string val) */
                (mod->mod_bvalues && mod->mod_bvalues[0]) &&               /* mod actually contains some values */
                (SLAPI_IS_MOD_ADD(mod->mod_op) ||                          /* mod is add */
                 SLAPI_IS_MOD_REPLACE(mod->mod_op)))                       /* mod is replace */
            {
                addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
            }
        }
        if (modcount == 0) {
            continue; /* no mods to check, go to next attr */
        }

        /*
       * stop checking at first mod that fails the check
       */
        for (ii = 0; (result == 0) && (ii < modcount); ++ii) {
            mod = checkmods[ii];
            /*
     * For each DN in the managed list, do 7-bit checking if
     * the target DN is a subnode in the tree.
     */
            for (subtreeDN = firstSubtree, subtreeCnt = argc; subtreeCnt > 0;
                 subtreeCnt--, subtreeDN++) {
                /*
       * issuffix determines whether the target is under the
       * subtree *subtreeDN
       */
                if (slapi_dn_issuffix(target, *subtreeDN)) {
#ifdef DEBUG
                    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                                  "preop_modify - MODIFY subtree=%s\n", *subtreeDN);
#endif
                    /*
         * Check if the value is 7-bit clean
         */
                    result = bit_check(NULL, mod->mod_bvalues, &violated);
                    if (result)
                        break;
                }
            }
        }
        /* don't have to go on if there is a value not 7-bit clean */
        if (result)
            break;
    }
    END

    slapi_ch_free((void **)&checkmods);
    if (result) {
        issue_error(pb, result, "MODIFY", violated);
    }

    return (result == LDAP_SUCCESS) ? 0 : -1;
}

/* ------------------------------------------------------------ */
/*
 * preop_modrdn - Pre-operation call for modify RDN
 *
 * Check that the new RDN does not include attributes that
 * cause a constraint violation
 */
static int
preop_modrdn(Slapi_PBlock *pb)
{
    int result;
    Slapi_Entry *e;
    char *violated = NULL;

#ifdef DEBUG
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                  "preop_modrdn - MODRDN begin\n");
#endif

    /* Init */
    result = LDAP_SUCCESS;
    e = 0;

    BEGIN
    int err;
    int argc;
    char **argv;
    char **attrName;
    Slapi_DN *target_sdn = NULL;
    Slapi_DN *superior = NULL;
    char *rdn;
    Slapi_Attr *attr;
    char **firstSubtree;
    char **subtreeDN;
    int subtreeCnt;
    int is_replicated_operation;

    /*
     * Get the arguments
     */
    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
    if (err) {
        result = op_error(30);
        break;
    }

    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
    if (err) {
        result = op_error(31);
        break;
    }

    /*
     * If this is a replication update, just be a noop.
     */
    err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
    if (err) {
        result = op_error(16);
        break;
    }
    if (is_replicated_operation) {
        break;
    }

    /* Get the DN of the entry being renamed */
    err = slapi_pblock_get(pb, SLAPI_MODRDN_TARGET_SDN, &target_sdn);
    if (err) {
        result = op_error(22);
        break;
    }

    /* Get superior value - unimplemented in 3.0 DS */
    err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR_SDN, &superior);
    if (err) {
        result = op_error(20);
        break;
    }

    /*
     * No superior means the entry is just renamed at
     * its current level in the tree.  Use the target DN for
     * determining which managed tree this belongs to
     */
    if (!slapi_sdn_get_dn(superior))
        superior = target_sdn;

    /* Get the new RDN - this has the attribute values */
    err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn);
    if (err) {
        result = op_error(33);
        break;
    }

#ifdef DEBUG
    slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                  "preop_modrdn - MODRDN newrdn=%s\n", rdn);
#endif

    /*
     * Parse the RDN into attributes by creating a "dummy" entry
     * and setting the attributes from the RDN.
     *
     * The new entry must be freed.
     */
    e = slapi_entry_alloc();
    if (!e) {
        result = op_error(32);
        break;
    }

    /* NOTE: strdup on the rdn, since it will be freed when
     * the entry is freed */
    /* slapi_entry_set_normdn expects rdn normalized, but not decapitalized */
    slapi_entry_set_normdn(e, slapi_ch_strdup(rdn));

    err = slapi_entry_add_rdn_values(e);
    if (err) {
        slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                      "preop_modrdn - MODRDN bad rdn value=%s\n", rdn);
        break; /* Bad DN */
    }

    /*
     * arguments before "," are the 7-bit clean attribute names.  Arguments
     * after "," are subtreeDN's.
     */
    for (firstSubtree = argv; strcmp(*firstSubtree, ",") != 0;
         firstSubtree++, argc--) {
    }
    firstSubtree++;
    argc--;

    /*
     * Find out if the node is being moved into one of
     * the managed subtrees
     */
    for (attrName = argv; strcmp(*attrName, ",") != 0; attrName++) {
        /*
       * If the attribute type is userpassword, do not replace it by
       * unhashed#user#password because unhashed#user#password does not exist
       * in this case.
       */
        /*
       * Find any 7-bit attribute data in the new RDN
       */
        err = slapi_entry_attr_find(e, *attrName, &attr);
        if (err)
            continue; /* break;*/ /* no 7-bit attribute */

        /*
       * For each DN in the managed list, do 7-bit checking if
       * the target DN is a subnode in the tree.
       */
        for (subtreeDN = firstSubtree, subtreeCnt = argc; subtreeCnt > 0;
             subtreeCnt--, subtreeDN++) {
            /*
         * issuffix determines whether the target is under the
         * subtree *subtreeDN
         */
            if (slapi_dn_issuffix(slapi_sdn_get_dn(superior), *subtreeDN)) {
#ifdef DEBUG
                slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name,
                              "preop_modrdn - MODRDN subtree=%s\n", *subtreeDN);
#endif

                /*
           * Check if the value is 7-bit clean
           */
                result = bit_check(attr, NULL, &violated);
                if (result)
                    break;
            }
        }
        /* don't have to go on if there is a value not 7-bit clean */
        if (result) {
            /* WB we need to issue the error before we free slapi_entry, else we
         * are triggering a use after free because we free violated.
         */
            issue_error(pb, result, "MODRDN", violated);
            break;
        }
    }
    END

        /* Clean-up */
        if (e) slapi_entry_free(e);

    return (result == LDAP_SUCCESS) ? 0 : -1;
}

/* ------------------------------------------------------------ */
/*
 * Initialize the plugin
 *
 */
int
NS7bitAttr_Init(Slapi_PBlock *pb)
{
    int32_t err = 0;
    Slapi_Entry *plugin_entry = NULL;
    const char *plugin_type = NULL;
    int32_t preadd = SLAPI_PLUGIN_PRE_ADD_FN;
    int32_t premod = SLAPI_PLUGIN_PRE_MODIFY_FN;
    int32_t premdn = SLAPI_PLUGIN_PRE_MODRDN_FN;

    BEGIN
    int32_t attr_count = 0;
    int32_t argc = 0;
    char **argv = NULL;
    int32_t valid_suffix = 0;

    /* Declare plugin version */
    err = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
                           SLAPI_PLUGIN_VERSION_01);
    if (err)
        break;

    if ((slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &plugin_entry) == 0) &&
        plugin_entry &&
        (plugin_type = slapi_entry_attr_get_ref(plugin_entry, "nsslapd-plugintype")) &&
        plugin_type && strstr(plugin_type, "betxn")) {
        preadd = SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN;
        premod = SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN;
        premdn = SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN;
    }

    /*
     * Get and normalize arguments
     */
    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
    if (err)
        break;

    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
    if (err || argv == NULL)
        break;

    for (attr_count = 0; argv && argv[attr_count]; attr_count++) {
        slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "NS7bitAttr_Init - %d: %s\n",
                      attr_count, argv[attr_count]);
    }
    /*
     * Arguments before "," are the 7-bit attribute names. Arguments after
     * "," are the subtree DN's.
     */
    if (argc < 1) {
        err = -2;
        break;
    } /* missing arguments */
    attr_count = 0;
    for (; *argv && strcmp(*argv, ",") != 0 && argc > 0; attr_count++, argc--, argv++)
        ;
    if (argc == 0) {
        err = -3;
        break;
    } /* no comma separator */
    if (attr_count == 0) {
        err = -4;
        break;
    } /* no attributes */
    argv++;
    argc--;
    if (argc == 0) {
        err = -5;
        break;
    } /* no suffix */
    for (; argc > 0; argc--, argv++) {
        err = slapi_dn_syntax_check(pb, *argv, 1);
        if (err) {
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "NS7bitAttr_Init - "
                                                      "Invalid suffix: %s\n",
                          *argv);
            continue;
        }
        if (!valid_suffix)
            valid_suffix = 1;
        char *normdn = slapi_create_dn_string_case("%s", *argv);
        slapi_ch_free_string(argv);
        *argv = normdn;
    }

    if (!valid_suffix) {
        err = -6;
        break;
    } /* Invalid suffix list */
    /* Provide descriptive information */
    err = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
                           (void *)&pluginDesc);
    if (err)
        break;

    /* Register functions */
    err = slapi_pblock_set(pb, preadd, (void *)preop_add);
    if (err)
        break;

    err = slapi_pblock_set(pb, premod, (void *)preop_modify);
    if (err)
        break;

    err = slapi_pblock_set(pb, premdn, (void *)preop_modrdn);
    if (err)
        break;

    END

        if (err)
    {
        if (err == -1) {
            slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "NS7bitAttr_Init - Error: %d\n", err);
        } else if (err == -2) {
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "NS7bitAttr_Init - "
                                                      "Invalid plugin arguments - missing arguments\n");
        } else if (err == -3) {
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "NS7bitAttr_Init - "
                                                      "Invalid plugin arguments - missing \",\" separator argument\n");
        } else if (err == -4) {
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "NS7bitAttr_Init - "
                                                      "Invalid plugin arguments - missing attributes\n");
        } else if (err == -5) {
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "NS7bitAttr_Init - "
                                                      "Invalid plugin arguments - missing suffix\n");
        } else if (err == -6) {
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "NS7bitAttr_Init - "
                                                      "Invalid plugin arguments - Invalid suffix list\n");
        }

        err = -1;
    }
    else slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "NS7bitAttr_Init - plugin loaded\n");

    return err;
}
