static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type,
                            const char *p1, const char *p2, const char *p3)
{
    char *my_error_msg = NULL;
    //msre_rule *rule = NULL, *tmp_rule = NULL;
    char *rid = NULL;
    msre_rule *rule = NULL;
    extern msc_engine *modsecurity;
    int offset = 0;

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
        "Rule: type=%d p1='%s' p2='%s' p3='%s'", type, p1, p2, p3);
    #endif

    /* Create a ruleset if one does not exist. */
    if ((dcfg->ruleset == NULL)||(dcfg->ruleset == NOT_SET_P)) {
        dcfg->ruleset = msre_ruleset_create(modsecurity->msre, cmd->pool);
        if (dcfg->ruleset == NULL) return FATAL_ERROR;
    }

    /* Create the rule now. */
    switch(type) {
        #if defined(WITH_LUA)
        case RULE_TYPE_LUA :
            rule = msre_rule_lua_create(dcfg->ruleset, cmd->directive->filename,
                cmd->directive->line_num, p1, p2, &my_error_msg);
            break;
        #endif
        default :
            rule = msre_rule_create(dcfg->ruleset, type, cmd->directive->filename,
                cmd->directive->line_num, p1, p2, p3, &my_error_msg);
            break;
    }

    if (rule == NULL) {
        return my_error_msg;
    }

    /* Rules must have uniq ID */
    if (
#if defined(WITH_LUA)
            type != RULE_TYPE_LUA &&
#endif
            (dcfg->tmp_chain_starter == NULL))
                if(rule->actionset == NULL)
                    return "ModSecurity: Rules must have at least id action";

    if(rule->actionset != NULL && (dcfg->tmp_chain_starter == NULL))    {
        if(rule->actionset->id == NOT_SET_P
#if defined(WITH_LUA)
            && (type != RULE_TYPE_LUA)
#endif
          )
            return "ModSecurity: No action id present within the rule";
#if defined(WITH_LUA)
        if(type != RULE_TYPE_LUA)
#endif
        {
            rid = apr_hash_get(dcfg->rule_id_htab, rule->actionset->id, APR_HASH_KEY_STRING);
            if(rid != NULL) {
                return "ModSecurity: Found another rule with the same id";
            } else    {
                apr_hash_set(dcfg->rule_id_htab, apr_pstrdup(dcfg->mp, rule->actionset->id), APR_HASH_KEY_STRING, apr_pstrdup(dcfg->mp, "1"));
            }

            //tmp_rule = msre_ruleset_fetch_rule(dcfg->ruleset, rule->actionset->id, offset);
            //if(tmp_rule != NULL)
            //    return "ModSecurity: Found another rule with the same id";
        }
    }

    /* Create default actionset if one does not already exist. */
    if (dcfg->tmp_default_actionset == NULL) {
        dcfg->tmp_default_actionset = msre_actionset_create_default(modsecurity->msre);
        if (dcfg->tmp_default_actionset == NULL) return FATAL_ERROR;
    }

    /* Check some cases prior to merging so we know where it came from */

    /* Check syntax for chained rules */
    if ((rule->actionset != NULL) && (dcfg->tmp_chain_starter != NULL)) {
        /* Must NOT specify a disruptive action. */
        if (rule->actionset->intercept_action != NOT_SET) {
            return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions can only "
                "be specified by chain starter rules.");
        }

        /* Must NOT specify a skipafter action. */
        if (rule->actionset->skip_after != NOT_SET_P) {
            return apr_psprintf(cmd->pool, "ModSecurity: SkipAfter actions can only "
                "be specified by chain starter rules.");
        }

        /* Must NOT specify a phase. */
        if (rule->actionset->phase != NOT_SET) {
            return apr_psprintf(cmd->pool, "ModSecurity: Execution phases can only be "
                "specified by chain starter rules.");
        }

        /* Must NOT use metadata actions. */
        /* ENH: loop through to check for tags */
        if ((rule->actionset->id != NOT_SET_P)
            ||(rule->actionset->rev != NOT_SET_P)
            ||(rule->actionset->msg != NOT_SET_P)
            ||(rule->actionset->severity != NOT_SET)
            ||(rule->actionset->version != NOT_SET_P)
            ||(rule->actionset->accuracy != NOT_SET)
            ||(rule->actionset->maturity != NOT_SET)
            ||(rule->actionset->logdata != NOT_SET_P))
        {
            return apr_psprintf(cmd->pool, "ModSecurity: Metadata actions (id, rev, msg, tag, severity, ver, accuracy, maturity, logdata) "
                " can only be specified by chain starter rules.");
        }

        /* Must NOT use skip. */
        if (rule->actionset->skip_count != NOT_SET) {
            return apr_psprintf(cmd->pool, "ModSecurity: The skip action can only be used "
                " by chain starter rules. ");
        }
    }

    /* Merge actions with the parent.
     *
     * ENH Probably do not want this done fully for chained rules.
     */
    rule->actionset = msre_actionset_merge(modsecurity->msre, dcfg->tmp_default_actionset,
        rule->actionset, 1);

    /* Keep track of the parent action for "block" */
    rule->actionset->parent_intercept_action_rec = dcfg->tmp_default_actionset->intercept_action_rec;
    rule->actionset->parent_intercept_action = dcfg->tmp_default_actionset->intercept_action;

    /* Must NOT specify a disruptive action in logging phase. */
    if ((rule->actionset != NULL)
        && (rule->actionset->phase == PHASE_LOGGING)
        && (rule->actionset->intercept_action != ACTION_ALLOW)
        && (rule->actionset->intercept_action != ACTION_ALLOW_REQUEST)
        && (rule->actionset->intercept_action != ACTION_NONE)
    ) {
        return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions "
            "cannot be specified in the logging phase.");
    }

    if (dcfg->tmp_chain_starter != NULL) {
        rule->chain_starter = dcfg->tmp_chain_starter;
        rule->actionset->phase = rule->chain_starter->actionset->phase;
    }

    if (rule->actionset->is_chained != 1) {
        /* If this rule is part of the chain but does
         * not want more rules to follow in the chain
         * then cut it (the chain).
         */
        dcfg->tmp_chain_starter = NULL;
    } else {
        /* On the other hand, if this rule wants other
         * rules to follow it, then start a new chain
         * if there isn't one already.
         */
        if (dcfg->tmp_chain_starter == NULL) {
            dcfg->tmp_chain_starter = rule;
        }
    }

    /* Optimisation */
    if ((rule->op_name != NULL)&&(strcasecmp(rule->op_name, "inspectFile") == 0)) {
        dcfg->upload_validates_files = 1;
    }

    /* Create skip table if one does not already exist. */
    if (dcfg->tmp_rule_placeholders == NULL) {
        dcfg->tmp_rule_placeholders = apr_table_make(cmd->pool, 10);
        if (dcfg->tmp_rule_placeholders == NULL) return FATAL_ERROR;
    }

    /* Keep track of any rule IDs we need to skip after */
    if (rule->actionset->skip_after != NOT_SET_P) {
        char *tmp_id = apr_pstrdup(cmd->pool, rule->actionset->skip_after);
        apr_table_setn(dcfg->tmp_rule_placeholders, tmp_id, tmp_id);

        #ifdef DEBUG_CONF
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Watching for skipafter target rule id=\"%s\".", tmp_id);
        #endif

    }

    #ifdef DEBUG_CONF
    ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
        "Adding rule %pp phase=%d id=\"%s\".", rule, rule->actionset->phase, (rule->actionset->id == NOT_SET_P
        ? "(none)" : rule->actionset->id));
    #endif

    /* Add rule to the recipe. */
    if (msre_ruleset_rule_add(dcfg->ruleset, rule, rule->actionset->phase) < 0) {
        return "Internal Error: Failed to add rule to the ruleset.";
    }

    /* Add an additional placeholder if this rule ID is on the list */
    if ((rule->actionset->id != NULL) && apr_table_get(dcfg->tmp_rule_placeholders, rule->actionset->id)) {
        msre_rule *phrule = apr_palloc(rule->ruleset->mp, sizeof(msre_rule));
        if (phrule == NULL) {
            return FATAL_ERROR;
        }

        #ifdef DEBUG_CONF
        ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
            "Adding placeholder %pp for rule %pp id=\"%s\".", phrule, rule, rule->actionset->id);
        #endif

        /* shallow copy of original rule with placeholder marked as target */
        memcpy(phrule, rule, sizeof(msre_rule));
        phrule->placeholder = RULE_PH_SKIPAFTER;

        /* Add placeholder. */
        if (msre_ruleset_rule_add(dcfg->ruleset, phrule, phrule->actionset->phase) < 0) {
            return "Internal Error: Failed to add placeholder to the ruleset.";
        }

        /* No longer need to search for the ID */
        apr_table_unset(dcfg->tmp_rule_placeholders, rule->actionset->id);
    }

    /* Update the unparsed rule */
    rule->unparsed = msre_rule_generate_unparsed(dcfg->ruleset->mp, rule, NULL, NULL, NULL);

    return NULL;
}