diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:35:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:35:32 +0000 |
commit | 5ea77a75dd2d2158401331879f3c8f47940a732c (patch) | |
tree | d89dc06e9f4850a900f161e25f84e922c4f86cc8 /servers/slapd/back-sql | |
parent | Initial commit. (diff) | |
download | openldap-upstream.tar.xz openldap-upstream.zip |
Adding upstream version 2.5.13+dfsg.upstream/2.5.13+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servers/slapd/back-sql')
74 files changed, 15371 insertions, 0 deletions
diff --git a/servers/slapd/back-sql/Makefile.in b/servers/slapd/back-sql/Makefile.in new file mode 100644 index 0000000..b90bb31 --- /dev/null +++ b/servers/slapd/back-sql/Makefile.in @@ -0,0 +1,45 @@ +# Makefile.in for back-sql +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2022 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c config.c search.c bind.c compare.c operational.c \ + entry-id.c schema-map.c sql-wrap.c modify.c util.c \ + add.c delete.c modrdn.c api.c +OBJS = init.lo config.lo search.lo bind.lo compare.lo operational.lo \ + entry-id.lo schema-map.lo sql-wrap.lo modify.lo util.lo \ + add.lo delete.lo modrdn.lo api.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-sql" +BUILD_MOD = @BUILD_SQL@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_SQL@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(SLAPD_SQL_LIBS) + +LIBBASE = back_sql + +XINCPATH = -I.. -I$(srcdir)/.. $(SLAPD_SQL_INCLUDES) +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-sql/add.c b/servers/slapd/back-sql/add.c new file mode 100644 index 0000000..3098521 --- /dev/null +++ b/servers/slapd/back-sql/add.c @@ -0,0 +1,1560 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +#ifdef BACKSQL_SYNCPROV +#include <lutil.h> +#endif /* BACKSQL_SYNCPROV */ + +const char * processable_op_attrs[] = { + "pwdAccountLockedTime", + "pwdChangedTime", + "pwdFailureTime", + "pwdGraceUseTime", + "pwdHistory", + "pwdPolicySubentry", + "pwdReset", + "entryUUID" +}; + +#define processable_op_attrs_length (sizeof (processable_op_attrs) / sizeof (const char *)) + +static int indexOf(const char *array[], int array_size, const char * value) { + for (int i = 0; i < array_size; ++i) { + if(strcmp(array[i], value) == 0) { + return i; + } + } + return -1; +} + +static int is_processable_opattr(const char * attr) { + return indexOf(processable_op_attrs, processable_op_attrs_length, attr) >= 0; +} + +#define backsql_opattr_skip(ad) \ + (is_at_operational( (ad)->ad_type ) && (ad) != slap_schema.si_ad_ref ) + +/* + * Skip: + * - null values (e.g. delete modification) + * - single occurrence of objectClass, because it is already used + * to determine how to build the SQL entry + * - operational attributes (except those in processable_op_attrs) + * - empty attributes + */ +#define backsql_attr_skip(ad, vals) \ + ( \ + ( ( (ad) == slap_schema.si_ad_objectClass \ + && (vals) && BER_BVISNULL( &((vals)[ 1 ]) ) ) \ + || backsql_opattr_skip( (ad) ) \ + || ( (vals) && BER_BVISNULL( &((vals)[ 0 ]) ) ) \ + ) && !is_processable_opattr( ad->ad_cname.bv_val ) ) + +int +backsql_modify_delete_all_values( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_entryID *e_id, + backsql_at_map_rec *at ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + RETCODE rc; + SQLHSTMT asth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row; + + assert( at != NULL ); + if ( at->bam_delete_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "missing attribute value delete procedure " + "for attr \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + return LDAP_SUCCESS; + } + + rc = backsql_Prepare( dbh, &asth, at->bam_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error preparing attribute value select query " + "\"%s\"\n", + at->bam_query ); + backsql_PrintErrors( bi->sql_db_env, dbh, + asth, rc ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + rc = backsql_BindParamID( asth, 1, SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding key value parameter " + "to attribute value select query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, + asth, rc ); + SQLFreeStmt( asth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + rc = SQLExecute( asth ); + if ( !BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error executing attribute value select query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, + asth, rc ); + SQLFreeStmt( asth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + backsql_BindRowAsStrings_x( asth, &row, op->o_tmpmemctx ); + for ( rc = SQLFetch( asth ); + BACKSQL_SUCCESS( rc ); + rc = SQLFetch( asth ) ) + { + int i; + /* first parameter no, parameter order */ + SQLUSMALLINT pno = 0, + po = 0; + /* procedure return code */ + int prc = LDAP_SUCCESS; + + for ( i = 0; i < row.ncols; i++ ) { + SQLHSTMT sth = SQL_NULL_HSTMT; + ber_len_t col_len; + + rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error preparing attribute value " + "delete procedure " + "\"%s\"\n", + at->bam_delete_proc ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, + SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding output parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + } + po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0; + rc = backsql_BindParamID( sth, pno + 1 + po, + SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding keyval parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values() " + "arg(%d)=" BACKSQL_IDFMT "\n", + pno + 1 + po, + BACKSQL_IDARG(e_id->eid_keyval) ); + + /* + * check for syntax needed here + * maybe need binary bind? + */ + col_len = strlen( row.cols[ i ] ); + rc = backsql_BindParamStr( sth, pno + 2 - po, + SQL_PARAM_INPUT, row.cols[ i ], col_len ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "error binding value parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "arg(%d)=%s; executing \"%s\"\n", + pno + 2 - po, row.cols[ i ], + at->bam_delete_proc ); + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_delete_all_values(): " + "delete_proc " + "execution failed (rc=%d, prc=%d)\n", + rc, prc ); + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + rs->sr_err = LDAP_OTHER; + } + rs->sr_text = op->o_req_dn.bv_val; + SQLFreeStmt( sth, SQL_DROP ); + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + } + } + + rs->sr_err = LDAP_SUCCESS; + +done:; + backsql_FreeRow_x( &row, op->o_tmpmemctx ); + SQLFreeStmt( asth, SQL_DROP ); + + return rs->sr_err; +} + +int +backsql_modify_internal( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_oc_map_rec *oc, + backsql_entryID *e_id, + Modifications *modlist ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + RETCODE rc; + Modifications *ml; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_modify_internal(): " + "traversing modifications list\n" ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + AttributeDescription *ad; + int sm_op; + static char *sm_ops[] = { "add", "delete", "replace", "increment", NULL }; + + BerVarray sm_values; +#if 0 + /* NOTE: some day we'll have to pass + * the normalized values as well */ + BerVarray sm_nvalues; +#endif + backsql_at_map_rec *at = NULL; + struct berval *at_val; + int i; + + ad = ml->sml_mod.sm_desc; + sm_op = ( ml->sml_mod.sm_op & LDAP_MOD_OP ); + sm_values = ml->sml_mod.sm_values; +#if 0 + sm_nvalues = ml->sml_mod.sm_nvalues; +#endif + + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "modifying attribute \"%s\" (%s) according to " + "mappings for objectClass \"%s\"\n", + ad->ad_cname.bv_val, sm_ops[ sm_op ], BACKSQL_OC_NAME( oc ) ); + + if ( backsql_attr_skip( ad, sm_values ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "skipping attribute \"%s\"\n", + ad->ad_cname.bv_val, 0, 0 ); + + continue; + } + + at = backsql_ad2at( oc, ad ); + if ( at == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "attribute \"%s\" is not registered " + "in objectClass \"%s\"\n", + ad->ad_cname.bv_val, BACKSQL_OC_NAME( oc ) ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + continue; + } + + switch ( sm_op ) { + case LDAP_MOD_REPLACE: { + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "replacing values for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + + if ( at->bam_add_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "add procedure is not defined " + "for attribute \"%s\" " + "- unable to perform replacements\n", + at->bam_ad->ad_cname.bv_val ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + break; + } + + if ( at->bam_delete_proc == NULL ) { + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete procedure is not defined " + "for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete procedure is not defined " + "for attribute \"%s\" " + "- adding only\n", + at->bam_ad->ad_cname.bv_val ); + + goto add_only; + } + +del_all: + rs->sr_err = backsql_modify_delete_all_values( op, rs, dbh, e_id, at ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + /* LDAP_MOD_DELETE gets here if all values must be deleted */ + if ( sm_op == LDAP_MOD_DELETE ) { + break; + } + } + + /* + * PASSTHROUGH - to add new attributes -- do NOT add break + */ + case LDAP_MOD_ADD: + /* case SLAP_MOD_SOFTADD: */ + /* case SLAP_MOD_ADD_IF_NOT_PRESENT: */ +add_only:; + if ( at->bam_add_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "add procedure is not defined " + "for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + break; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "adding new values for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + + /* can't add a NULL val array */ + assert( sm_values != NULL ); + + for ( i = 0, at_val = sm_values; + !BER_BVISNULL( at_val ); + i++, at_val++ ) + { + SQLHSTMT sth = SQL_NULL_HSTMT; + /* first parameter position, parameter order */ + SQLUSMALLINT pno = 0, + po; + /* procedure return code */ + int prc = LDAP_SUCCESS; + + rc = backsql_Prepare( dbh, &sth, at->bam_add_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error preparing add query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + + if ( BACKSQL_IS_ADD( at->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, + SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding output parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + } + po = ( BACKSQL_IS_ADD( at->bam_param_order ) ) > 0; + rc = backsql_BindParamID( sth, pno + 1 + po, + SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding keyval parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "arg(%d)=" BACKSQL_IDFMT "\n", + pno + 1 + po, + BACKSQL_IDARG(e_id->eid_keyval) ); + + /* + * check for syntax needed here + * maybe need binary bind? + */ + rc = backsql_BindParamBerVal( sth, pno + 2 - po, + SQL_PARAM_INPUT, at_val ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding value parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "arg(%d)=\"%s\"; executing \"%s\"\n", + pno + 2 - po, at_val->bv_val, + at->bam_add_proc ); + + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "add_proc execution failed " + "(rc=%d, prc=%d)\n", + rc, prc ); + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + rs->sr_text = at->bam_ad->ad_cname.bv_val; + return rs->sr_err; + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) + { + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + } + } + SQLFreeStmt( sth, SQL_DROP ); + } + break; + + case LDAP_MOD_DELETE: + /* case SLAP_MOD_SOFTDEL: */ + if ( at->bam_delete_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete procedure is not defined " + "for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted " + "within namingContext"; + goto done; + } + + break; + } + + if ( sm_values == NULL ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "no values given to delete " + "for attribute \"%s\" " + "-- deleting all values\n", + at->bam_ad->ad_cname.bv_val ); + goto del_all; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "deleting values for attribute \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + + for ( i = 0, at_val = sm_values; + !BER_BVISNULL( at_val ); + i++, at_val++ ) + { + SQLHSTMT sth = SQL_NULL_HSTMT; + /* first parameter position, parameter order */ + SQLUSMALLINT pno = 0, + po; + /* procedure return code */ + int prc = LDAP_SUCCESS; + + rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error preparing delete query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + + if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, + SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding output parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + } + po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0; + rc = backsql_BindParamID( sth, pno + 1 + po, + SQL_PARAM_INPUT, &e_id->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding keyval parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "arg(%d)=" BACKSQL_IDFMT "\n", + pno + 1 + po, + BACKSQL_IDARG(e_id->eid_keyval) ); + + /* + * check for syntax needed here + * maybe need binary bind? + */ + rc = backsql_BindParamBerVal( sth, pno + 2 - po, + SQL_PARAM_INPUT, at_val ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "error binding value parameter for %s[%d]\n", + at->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "executing \"%s\"\n", + at->bam_delete_proc ); + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) + { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_modify_internal(): " + "delete_proc execution " + "failed (rc=%d, prc=%d)\n", + rc, prc ); + + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + rs->sr_text = at->bam_ad->ad_cname.bv_val; + goto done; + + } else { + backsql_PrintErrors( bi->sql_db_env, + dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = at->bam_ad->ad_cname.bv_val; + goto done; + } + } + SQLFreeStmt( sth, SQL_DROP ); + } + break; + + case LDAP_MOD_INCREMENT: + Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): " + "increment not supported yet\n" ); + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + goto done; + } + break; + } + } + +done:; + Debug( LDAP_DEBUG_TRACE, "<==backsql_modify_internal(): %d%s%s\n", + rs->sr_err, + rs->sr_text ? ": " : "", + rs->sr_text ? rs->sr_text : "" ); + + /* + * FIXME: should fail in case one change fails? + */ + return rs->sr_err; +} + +static int +backsql_add_attr( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_oc_map_rec *oc, + Attribute *at, + backsql_key_t new_keyval ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + backsql_at_map_rec *at_rec = NULL; + struct berval *at_val; + unsigned long i; + RETCODE rc; + SQLUSMALLINT currpos; + SQLHSTMT sth = SQL_NULL_HSTMT; + + at_rec = backsql_ad2at( oc, at->a_desc ); + + if ( at_rec == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): " + "attribute \"%s\" is not registered " + "in objectclass \"%s\"\n", + op->ora_e->e_name.bv_val, + at->a_desc->ad_cname.bv_val, + BACKSQL_OC_NAME( oc ) ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_text = "operation not permitted " + "within namingContext"; + return rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + return LDAP_SUCCESS; + } + + if ( at_rec->bam_add_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): " + "add procedure is not defined " + "for attribute \"%s\" " + "of structuralObjectClass \"%s\"\n", + op->ora_e->e_name.bv_val, + at->a_desc->ad_cname.bv_val, + BACKSQL_OC_NAME( oc ) ); + + if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) { + rs->sr_text = "operation not permitted " + "within namingContext"; + return rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + return LDAP_SUCCESS; + } + + for ( i = 0, at_val = &at->a_vals[ i ]; + !BER_BVISNULL( at_val ); + i++, at_val = &at->a_vals[ i ] ) + { + /* procedure return code */ + int prc = LDAP_SUCCESS; + /* first parameter #, parameter order */ + SQLUSMALLINT pno, po; + + /* + * Do not deal with the objectClass that is used + * to build the entry + */ + if ( at->a_desc == slap_schema.si_ad_objectClass ) { + if ( dn_match( at_val, &oc->bom_oc->soc_cname ) ) + { + continue; + } + } + + rc = backsql_Prepare( dbh, &sth, at_rec->bam_add_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + if ( BACKSQL_IS_ADD( at_rec->bam_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(): " + "error binding output parameter for %s[%lu]\n", + at_rec->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + } else { + pno = 0; + } + + po = ( BACKSQL_IS_ADD( at_rec->bam_param_order ) ) > 0; + currpos = pno + 1 + po; + rc = backsql_BindParamNumID( sth, currpos, + SQL_PARAM_INPUT, &new_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(): " + "error binding keyval parameter for %s[%lu]\n", + at_rec->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + + currpos = pno + 2 - po; + + /* + * check for syntax needed here + * maybe need binary bind? + */ + + rc = backsql_BindParamBerVal( sth, currpos, SQL_PARAM_INPUT, at_val ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(): " + "error binding value parameter for %s[%lu]\n", + at_rec->bam_ad->ad_cname.bv_val, i ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + return rs->sr_err = LDAP_OTHER; + } + +#ifdef LDAP_DEBUG + Debug(LDAP_DEBUG_TRACE, + " backsql_add_attr(\"%s\"): " "executing \"%s\" val[%lu], id=" BACKSQL_IDNUMFMT "\n", + op->ora_e->e_name.bv_val, at_rec->bam_add_proc, + i, new_keyval ); +#endif + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, + " backsql_add_attr(\"%s\"): " + "add_proc execution failed (rc=%d, prc=%d)\n", + op->ora_e->e_name.bv_val, rc, prc ); + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + rs->sr_text = op->ora_e->e_name.bv_val; + SQLFreeStmt( sth, SQL_DROP ); + return rs->sr_err; + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = op->ora_e->e_name.bv_val; + SQLFreeStmt( sth, SQL_DROP ); + return rs->sr_err; + } + } + SQLFreeStmt( sth, SQL_DROP ); + } + + return LDAP_SUCCESS; +} + +int +backsql_add( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + SQLHSTMT sth = SQL_NULL_HSTMT; + backsql_key_t new_keyval = 0; + RETCODE rc; + backsql_oc_map_rec *oc = NULL; + backsql_srch_info bsi = { 0 }; + Entry p = { 0 }, *e = NULL; + Attribute *at, + *at_objectClass = NULL; + ObjectClass *soc = NULL; + struct berval scname = BER_BVNULL; + struct berval pdn; + struct berval realdn = BER_BVNULL; + int colnum; + slap_mask_t mask; + + char textbuf[ SLAP_TEXT_BUFLEN ]; + size_t textlen = sizeof( textbuf ); + +#ifdef BACKSQL_SYNCPROV + /* + * NOTE: fake successful result to force contextCSN to be bumped up + */ + if ( op->o_sync ) { + char buf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + struct berval csn; + + csn.bv_val = buf; + csn.bv_len = sizeof( buf ); + slap_get_csn( op, &csn, 1 ); + + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + + slap_graduate_commit_csn( op ); + + return 0; + } +#endif /* BACKSQL_SYNCPROV */ + + Debug( LDAP_DEBUG_TRACE, "==>backsql_add(\"%s\")\n", + op->ora_e->e_name.bv_val ); + + /* check schema */ + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + rs->sr_err = entry_schema_check( op, op->ora_e, NULL, 0, 1, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "entry failed schema check -- aborting\n", + op->ora_e->e_name.bv_val ); + e = NULL; + goto done; + } + } + + slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 ); + + if ( get_assert( op ) && + ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "assertion control failed -- aborting\n", + op->ora_e->e_name.bv_val ); + e = NULL; + rs->sr_err = LDAP_ASSERTION_FAILED; + goto done; + } + + /* search structuralObjectClass */ + for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) { + if ( at->a_desc == slap_schema.si_ad_structuralObjectClass ) { + break; + } + } + + /* there must exist */ + if ( at == NULL ) { + char buf[ SLAP_TEXT_BUFLEN ]; + const char *text; + + /* search structuralObjectClass */ + for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) { + if ( at->a_desc == slap_schema.si_ad_objectClass ) { + break; + } + } + + if ( at == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "no objectClass\n", + op->ora_e->e_name.bv_val ); + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + e = NULL; + goto done; + } + + rs->sr_err = structural_class( at->a_vals, &soc, NULL, + &text, buf, sizeof( buf ), op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "%s (%d)\n", + op->ora_e->e_name.bv_val, text, rs->sr_err ); + e = NULL; + goto done; + } + scname = soc->soc_cname; + + } else { + scname = at->a_vals[0]; + } + + /* I guess we should play with sub/supertypes to find a suitable oc */ + oc = backsql_name2oc( bi, &scname ); + + if ( oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "cannot map structuralObjectClass \"%s\" -- aborting\n", + op->ora_e->e_name.bv_val, + scname.bv_val ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + } + + if ( oc->bom_create_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create procedure is not defined " + "for structuralObjectClass \"%s\" - aborting\n", + op->ora_e->e_name.bv_val, + scname.bv_val ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + + } else if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) + && oc->bom_create_keyval == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create procedure needs select procedure, " + "but none is defined for structuralObjectClass \"%s\" " + "- aborting\n", + op->ora_e->e_name.bv_val, + scname.bv_val ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + } + + /* check write access */ + if ( !access_allowed_mask( op, op->ora_e, + slap_schema.si_ad_entry, + NULL, ACL_WADD, NULL, &mask ) ) + { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = op->ora_e; + goto done; + } + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "could not get connection handle - exiting\n", + op->ora_e->e_name.bv_val ); + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + e = NULL; + goto done; + } + + /* + * Check if entry exists + * + * NOTE: backsql_api_dn2odbc() is called explicitly because + * we need the mucked DN to pass it to the create procedure. + */ + realdn = op->ora_e->e_name; + if ( backsql_api_dn2odbc( op, rs, &realdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "backsql_api_dn2odbc(\"%s\") failed\n", + op->ora_e->e_name.bv_val, realdn.bv_val ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + rs->sr_err = backsql_dn2id( op, rs, dbh, &realdn, NULL, 0, 0 ); + if ( rs->sr_err == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "entry exists\n", + op->ora_e->e_name.bv_val ); + rs->sr_err = LDAP_ALREADY_EXISTS; + e = op->ora_e; + goto done; + } + + /* + * Get the parent dn and see if the corresponding entry exists. + */ + if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) { + pdn = slap_empty_bv; + + } else { + dnParent( &op->ora_e->e_nname, &pdn ); + + /* + * Get the parent + */ + bsi.bsi_e = &p; + rs->sr_err = backsql_init_search( &bsi, &pdn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_add(): " + "could not retrieve addDN parent " + "\"%s\" ID - %s matched=\"%s\"\n", + pdn.bv_val, + rs->sr_err == LDAP_REFERRAL ? "referral" : "no such entry", + rs->sr_matched ? rs->sr_matched : "(null)" ); + e = &p; + goto done; + } + + /* check "children" pseudo-attribute access to parent */ + if ( !access_allowed( op, &p, slap_schema.si_ad_children, + NULL, ACL_WADD, NULL ) ) + { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &p; + goto done; + } + } + + /* + * create_proc is executed; if expect_return is set, then + * an output parameter is bound, which should contain + * the id of the added row; otherwise the procedure + * is expected to return the id as the first column of a select + */ + rc = backsql_Prepare( dbh, &sth, oc->bom_create_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + colnum = 1; + if ( BACKSQL_IS_ADD( oc->bom_expect_return ) ) { + rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_OUTPUT, &new_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding keyval parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + colnum++; + } + + if ( oc->bom_create_hint ) { + at = attr_find( op->ora_e->e_attrs, oc->bom_create_hint ); + if ( at && at->a_vals ) { + backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT, + at->a_vals[0].bv_val, + at->a_vals[0].bv_len ); + Debug( LDAP_DEBUG_TRACE, "backsql_add(): " + "create_proc hint: param = '%s'\n", + at->a_vals[0].bv_val ); + + } else { + backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT, + "", 0 ); + Debug( LDAP_DEBUG_TRACE, "backsql_add(): " + "create_proc hint (%s) not available\n", + oc->bom_create_hint->ad_cname.bv_val ); + } + colnum++; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): executing \"%s\"\n", + op->ora_e->e_name.bv_val, oc->bom_create_proc ); + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc execution failed\n", + op->ora_e->e_name.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + /* FIXME: after SQLExecute(), the row is already inserted + * (at least with PostgreSQL and unixODBC); needs investigation */ + + if ( !BACKSQL_IS_ADD( oc->bom_expect_return ) ) { + SWORD ncols; + SQLLEN value_len; + + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + SQLFreeStmt( sth, SQL_DROP ); + + rc = backsql_Prepare( dbh, &sth, oc->bom_create_keyval, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + } + + /* + * the query to know the id of the inserted entry + * must be embedded in the create procedure + */ + rc = SQLNumResultCols( sth, &ncols ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc result evaluation failed\n", + op->ora_e->e_name.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + + } else if ( ncols != 1 ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc result is bogus (ncols=%d)\n", + op->ora_e->e_name.bv_val, ncols ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + +#if 0 + { + SQLCHAR colname[ 64 ]; + SQLSMALLINT name_len, col_type, col_scale, col_null; + UDWORD col_prec; + + /* + * FIXME: check whether col_type is compatible, + * if it can be null and so on ... + */ + rc = SQLDescribeCol( sth, (SQLUSMALLINT)1, + &colname[ 0 ], + (SQLUINTEGER)( sizeof( colname ) - 1 ), + &name_len, &col_type, + &col_prec, &col_scale, &col_null ); + } +#endif + + rc = SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_ULONG, + (SQLPOINTER)&new_keyval, + (SQLINTEGER)sizeof( new_keyval ), + &value_len ); + + rc = SQLFetch( sth ); + + if ( value_len <= 0 ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc result is empty?\n", + op->ora_e->e_name.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + } + + SQLFreeStmt( sth, SQL_DROP ); + + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "create_proc returned keyval=" BACKSQL_IDNUMFMT "\n", + op->ora_e->e_name.bv_val, new_keyval ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_insentry_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realdn ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding DN parameter for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamNumID( sth, 2, SQL_PARAM_INPUT, &oc->bom_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding objectClass ID parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &bsi.bsi_base_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding parent ID parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamNumID( sth, 4, SQL_PARAM_INPUT, &new_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "error binding entry ID parameter " + "for objectClass %s\n", + op->ora_e->e_name.bv_val, + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + Debug(LDAP_DEBUG_TRACE, + " backsql_add(): executing \"%s\" for dn=\"%s\" oc_map_id=" BACKSQL_IDNUMFMT " p_id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDNUMFMT "\n", + bi->sql_insentry_stmt, op->ora_e->e_name.bv_val, + oc->bom_id, BACKSQL_IDARG(bsi.bsi_base_id.eid_id), + new_keyval ); + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): " + "could not insert ldap_entries record\n", + op->ora_e->e_name.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + /* + * execute delete_proc to delete data added !!! + */ + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + + SQLFreeStmt( sth, SQL_DROP ); + + for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) { + Debug( LDAP_DEBUG_TRACE, " backsql_add(): " + "adding attribute \"%s\"\n", + at->a_desc->ad_cname.bv_val ); + + /* + * Skip: + * - the first occurrence of objectClass, which is used + * to determine how to build the SQL entry (FIXME ?!?) + * - operational attributes + * - empty attributes (FIXME ?!?) + */ + if ( backsql_attr_skip( at->a_desc, at->a_vals ) ) { + continue; + } + + if ( at->a_desc == slap_schema.si_ad_objectClass ) { + at_objectClass = at; + continue; + } + + rs->sr_err = backsql_add_attr( op, rs, dbh, oc, at, new_keyval ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = op->ora_e; + goto done; + } + } + + if ( at_objectClass ) { + rs->sr_err = backsql_add_attr( op, rs, dbh, oc, + at_objectClass, new_keyval ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = op->ora_e; + goto done; + } + } + +done:; + /* + * Commit only if all operations succeed + */ + if ( sth != SQL_NULL_HSTMT ) { + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + assert( e == NULL ); + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + } + + /* + * FIXME: NOOP does not work for add -- it works for all + * the other operations, and I don't get the reason :( + * + * hint: there might be some autocommit in Postgres + * so that when the unique id of the key table is + * automatically increased, there's no rollback. + * We might implement a "rollback" procedure consisting + * in deleting that row. + */ + + if ( e != NULL ) { + int disclose = 1; + + if ( e == op->ora_e && !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + /* mask already collected */ + disclose = 0; + + } else if ( e == &p && !access_allowed( op, &p, + slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + disclose = 0; + } + + if ( disclose == 0 ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if ( !BER_BVISNULL( &realdn ) + && realdn.bv_val != op->ora_e->e_name.bv_val ) + { + ch_free( realdn.bv_val ); + } + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &p.e_nname ) ) { + backsql_entry_clean( op, &p ); + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_add(\"%s\"): %d \"%s\"\n", + op->ora_e->e_name.bv_val, + rs->sr_err, + rs->sr_text ? rs->sr_text : "" ); + + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/api.c b/servers/slapd/back-sql/api.c new file mode 100644 index 0000000..7ef1d9f --- /dev/null +++ b/servers/slapd/back-sql/api.c @@ -0,0 +1,211 @@ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2004 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +static backsql_api *backsqlapi; + +int +backsql_api_config( backsql_info *bi, const char *name, int argc, char *argv[] ) +{ + backsql_api *ba; + + assert( bi != NULL ); + assert( name != NULL ); + + for ( ba = backsqlapi; ba; ba = ba->ba_next ) { + if ( strcasecmp( name, ba->ba_name ) == 0 ) { + backsql_api *ba2; + + ba2 = ch_malloc( sizeof( backsql_api ) ); + *ba2 = *ba; + + if ( ba2->ba_config ) { + if ( ( *ba2->ba_config )( ba2, argc, argv ) ) { + ch_free( ba2 ); + return 1; + } + ba2->ba_argc = argc; + if ( argc ) { + int i; + ba2->ba_argv = ch_malloc( argc * sizeof(char *)); + for ( i=0; i<argc; i++ ) + ba2->ba_argv[i] = ch_strdup( argv[i] ); + } + } + + ba2->ba_next = bi->sql_api; + bi->sql_api = ba2; + return 0; + } + } + + return 1; +} + +int +backsql_api_destroy( backsql_info *bi ) +{ + backsql_api *ba; + + assert( bi != NULL ); + + ba = bi->sql_api; + + if ( ba == NULL ) { + return 0; + } + + for ( ; ba; ba = ba->ba_next ) { + if ( ba->ba_destroy ) { + (void)( *ba->ba_destroy )( ba ); + } + } + + return 0; +} + +int +backsql_api_register( backsql_api *ba ) +{ + backsql_api *ba2; + + assert( ba != NULL ); + assert( ba->ba_private == NULL ); + + if ( ba->ba_name == NULL ) { + fprintf( stderr, "API module has no name\n" ); + exit(EXIT_FAILURE); + } + + for ( ba2 = backsqlapi; ba2; ba2 = ba2->ba_next ) { + if ( strcasecmp( ba->ba_name, ba2->ba_name ) == 0 ) { + fprintf( stderr, "API module \"%s\" already defined\n", ba->ba_name ); + exit( EXIT_FAILURE ); + } + } + + ba->ba_next = backsqlapi; + backsqlapi = ba; + + return 0; +} + +int +backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + backsql_api *ba; + int rc; + struct berval bv; + + ba = bi->sql_api; + + if ( ba == NULL ) { + return 0; + } + + ber_dupbv( &bv, dn ); + + for ( ; ba; ba = ba->ba_next ) { + if ( ba->ba_dn2odbc ) { + /* + * The dn2odbc() helper is supposed to rewrite + * the contents of bv, freeing the original value + * with ch_free() if required and replacing it + * with a newly allocated one using ch_malloc() + * or companion functions. + * + * NOTE: it is supposed to __always__ free + * the value of bv in case of error, and reset + * it with BER_BVZERO() . + */ + rc = ( *ba->ba_dn2odbc )( op, rs, &bv ); + + if ( rc ) { + /* in case of error, dn2odbc() must cleanup */ + assert( BER_BVISNULL( &bv ) ); + + return rc; + } + } + } + + assert( !BER_BVISNULL( &bv ) ); + + *dn = bv; + + return 0; +} + +int +backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + backsql_api *ba; + int rc; + struct berval bv; + + ba = bi->sql_api; + + if ( ba == NULL ) { + return 0; + } + + ber_dupbv( &bv, dn ); + + for ( ; ba; ba = ba->ba_next ) { + if ( ba->ba_dn2odbc ) { + rc = ( *ba->ba_odbc2dn )( op, rs, &bv ); + /* + * The odbc2dn() helper is supposed to rewrite + * the contents of bv, freeing the original value + * with ch_free() if required and replacing it + * with a newly allocated one using ch_malloc() + * or companion functions. + * + * NOTE: it is supposed to __always__ free + * the value of bv in case of error, and reset + * it with BER_BVZERO() . + */ + if ( rc ) { + /* in case of error, odbc2dn() must cleanup */ + assert( BER_BVISNULL( &bv ) ); + + return rc; + } + } + } + + assert( !BER_BVISNULL( &bv ) ); + + *dn = bv; + + return 0; +} + diff --git a/servers/slapd/back-sql/back-sql.h b/servers/slapd/back-sql/back-sql.h new file mode 100644 index 0000000..556ea6f --- /dev/null +++ b/servers/slapd/back-sql/back-sql.h @@ -0,0 +1,631 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Mararati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ +/* + * The following changes have been addressed: + * + * Enhancements: + * - re-styled code for better readability + * - upgraded backend API to reflect recent changes + * - LDAP schema is checked when loading SQL/LDAP mapping + * - AttributeDescription/ObjectClass pointers used for more efficient + * mapping lookup + * - bervals used where string length is required often + * - atomized write operations by committing at the end of each operation + * and defaulting connection closure to rollback + * - added LDAP access control to write operations + * - fully implemented modrdn (with rdn attrs change, deleteoldrdn, + * access check, parent/children check and more) + * - added parent access control, children control to delete operation + * - added structuralObjectClass operational attribute check and + * value return on search + * - added hasSubordinate operational attribute on demand + * - search limits are appropriately enforced + * - function backsql_strcat() has been made more efficient + * - concat function has been made configurable by means of a pattern + * - added config switches: + * - fail_if_no_mapping write operations fail if there is no mapping + * - has_ldapinfo_dn_ru overrides autodetect + * - concat_pattern a string containing two '?' is used + * (note that "?||?" should be more portable + * than builtin function "CONCAT(?,?)") + * - strcast_func cast of string constants in "SELECT DISTINCT + * statements (needed by PostgreSQL) + * - upper_needs_cast cast the argument of upper when required + * (basically when building dn substring queries) + * - added noop control + * - added values return filter control + * - hasSubordinate can be used in search filters (with limitations) + * - eliminated oc->name; use oc->oc->soc_cname instead + * + * Todo: + * - add security checks for SQL statements that can be injected (?) + * - re-test with previously supported RDBMs + * - replace dn_ru and so with normalized dn (no need for upper() and so + * in dn match) + * - implement a backsql_normalize() function to replace the upper() + * conversion routines + * - note that subtree deletion, subtree renaming and so could be easily + * implemented (rollback and consistency checks are available :) + * - implement "lastmod" and other operational stuff (ldap_entries table ?) + * - check how to allow multiple operations with one statement, to remove + * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?) + */ +/* + * Improvements submitted by (ITS#3432) + * + * 1. id_query.patch applied (with changes) + * 2. shortcut.patch applied (reworked) + * 3. create_hint.patch applied + * 4. count_query.patch applied (reworked) + * 5. returncodes.patch applied (with sanity checks) + * 6. connpool.patch under evaluation + * 7. modoc.patch under evaluation (requires + * manageDSAit and "manage" + * access privileges) + * 8. miscfixes.patch applied (reworked; other + * operations need to load the + * entire entry for ACL purposes; + * see ITS#3480, now fixed) + * + * original description: + + Changes that were made to the SQL backend. + +The patches were made against 2.2.18 and can be applied individually, +but would best be applied in the numerical order of the file names. +A synopsis of each patch is given here: + + +1. Added an option to set SQL query for the "id_query" operation. + +2. Added an option to the SQL backend called "use_subtree_shortcut". +When a search is performed, the SQL query includes a WHERE clause +which says the DN must be "LIKE %<searchbase>". The LIKE operation +can be slow in an RDBM. This shortcut option says that if the +searchbase of the LDAP search is the root DN of the SQL backend, +and thus all objects will match the LIKE operator, do not include +the "LIKE %<searchbase>" clause in the SQL query (it is replaced +instead by the always true "1=1" clause to keep the "AND"'s +working correctly). This option is off by default, and should be +turned on only if all objects to be found in the RDBM are under the +same root DN. Multiple backends working within the same RDBM table +space would encounter problems. LDAP searches whose searchbase are +not at the root DN will bypass this shortcut and employ the LIKE +clause. + +3. Added a "create_hint" column to ldap_oc_mappings table. Allows +taking the value of an attr named in "create_hint" and passing it to +the create_proc procedure. This is necessary for when an objectClass's +table is partition indexed by some indexing column and thus the value +in that indexing column cannot change after the row is created. The +value for the indexed column is passed into the create_proc, which +uses it to fill in the indexed column as the new row is created. + +4. When loading the values of an attribute, the count(*) of the number +of values is fetched first and memory is allocated for the array of +values and normalized values. The old system of loading the values one +by one and running realloc() on the array of values and normalized +values each time was badly fragmenting memory. The array of values and +normalized values would be side by side in memory, and realloc()'ing +them over and over would force them to leapfrog each other through all +of available memory. Attrs with a large number of values could not be +loaded without crashing the slapd daemon. + +5. Added code to interpret the value returned by stored procedures +which have expect_return set. Returned value is interpreted as an LDAP +return code. This allows the distinction between the SQL failing to +execute and the SQL running to completion and returning an error code +which can indicate a policy violation. + +6. Added RDBM connection pooling. Once an operation is finished the +connection to the RDBM is returned to a pool rather than closing. +Allows the next operation to skip the initialization and authentication +phases of contacting the RDBM. Also, if licensing with ODBC places +a limit on the number of connections, an LDAP thread can block waiting +for another thread to finish, so that no LDAP errors are returned +for having more LDAP connections than allowed RDBM connections. An +RDBM connection which receives an SQL error is marked as "tainted" +so that it will be closed rather than returned to the pool. + Also, RDBM connections must be bound to a given LDAP connection AND +operation number, and NOT just the connection number. Asynchronous +LDAP clients can have multiple simultaneous LDAP operations which +should not share the same RDBM connection. A given LDAP operation can +even make multiple SQL operations (e.g. a BIND operation which +requires SASL to perform an LDAP search to convert the SASL ID to an +LDAP DN), so each RDBM connection now has a refcount that must reach +zero before the connection is returned to the free pool. + +7. Added ability to change the objectClass of an object. Required +considerable work to copy all attributes out of old object and into +new object. Does a schema check before proceeding. Creates a new +object, fills it in, deletes the old object, then changes the +oc_map_id and keyval of the entry in the "ldap_entries" table. + +8. Generic fixes. Includes initializing pointers before they +get used in error branch cases, pointer checks before dereferencing, +resetting a return code to success after a COMPARE op, sealing +memory leaks, and in search.c, changing some of the "1=1" tests to +"2=2", "3=3", etc so that when reading slapd trace output, the +location in the source code where the x=x test was added to the SQL +can be easily distinguished. + */ + +#ifndef __BACKSQL_H__ +#define __BACKSQL_H__ + +/* former sql-types.h */ +#include <sql.h> +#include <sqlext.h> + +typedef struct { + SWORD ncols; + BerVarray col_names; + UDWORD *col_prec; + SQLSMALLINT *col_type; + char **cols; + SQLLEN *value_len; +} BACKSQL_ROW_NTS; + +/* + * Better use the standard length of 8192 (as of slap.h)? + * + * NOTE: must be consistent with definition in ldap_entries table + */ +/* #define BACKSQL_MAX_DN_LEN SLAP_LDAPDN_MAXLEN */ +#define BACKSQL_MAX_DN_LEN 255 + +/* + * define to enable very extensive trace logging (debug only) + */ +#undef BACKSQL_TRACE + +/* + * define if using MS SQL and workaround needed (see sql-wrap.c) + */ +#undef BACKSQL_MSSQL_WORKAROUND + +/* + * define to enable values counting for attributes + */ +#define BACKSQL_COUNTQUERY + +/* + * define to enable prettification/validation of values + */ +#define BACKSQL_PRETTY_VALIDATE + +/* + * define to enable varchars as unique keys in user tables + * + * by default integers are used (and recommended) + * for performances. Integers are used anyway in back-sql + * related tables. + */ +#undef BACKSQL_ARBITRARY_KEY + +/* + * type used for keys + */ +#if defined(HAVE_LONG_LONG) && defined(SQL_C_UBIGINT) && \ + ( defined(HAVE_STRTOULL) || defined(HAVE_STRTOUQ) ) +typedef unsigned long long backsql_key_t; +#define BACKSQL_C_NUMID SQL_C_UBIGINT +#define BACKSQL_IDNUMFMT "%llu" +#define BACKSQL_STR2ID lutil_atoullx +#else /* ! HAVE_LONG_LONG || ! SQL_C_UBIGINT */ +typedef unsigned long backsql_key_t; +#define BACKSQL_C_NUMID SQL_C_ULONG +#define BACKSQL_IDNUMFMT "%lu" +#define BACKSQL_STR2ID lutil_atoulx +#endif /* ! HAVE_LONG_LONG */ + +/* + * define to enable support for syncprov overlay + */ +#define BACKSQL_SYNCPROV + +/* + * define to the appropriate aliasing string + * + * some RDBMSes tolerate (or require) that " AS " is not used + * when aliasing tables/columns + */ +#define BACKSQL_ALIASING "AS " +/* #define BACKSQL_ALIASING "" */ + +/* + * define to the appropriate quoting char + * + * some RDBMSes tolerate/require that the aliases be enclosed + * in quotes. This is especially true for those that do not + * allow keywords used as aliases. + */ +#define BACKSQL_ALIASING_QUOTE "" +/* #define BACKSQL_ALIASING_QUOTE "\"" */ +/* #define BACKSQL_ALIASING_QUOTE "'" */ + +/* + * API + * + * a simple mechanism to allow DN mucking between the LDAP + * and the stored string representation. + */ +typedef struct backsql_api { + char *ba_name; + int (*ba_config)( struct backsql_api *self, int argc, char *argv[] ); + int (*ba_destroy)( struct backsql_api *self ); + + int (*ba_dn2odbc)( Operation *op, SlapReply *rs, struct berval *dn ); + int (*ba_odbc2dn)( Operation *op, SlapReply *rs, struct berval *dn ); + + void *ba_private; + struct backsql_api *ba_next; + char **ba_argv; + int ba_argc; +} backsql_api; + +/* + * "structural" objectClass mapping structure + */ +typedef struct backsql_oc_map_rec { + /* + * Structure of corresponding LDAP objectClass definition + */ + ObjectClass *bom_oc; +#define BACKSQL_OC_NAME(ocmap) ((ocmap)->bom_oc->soc_cname.bv_val) + + struct berval bom_keytbl; + struct berval bom_keycol; + /* expected to return keyval of newly created entry */ + char *bom_create_proc; + /* in case create_proc does not return the keyval of the newly + * created row */ + char *bom_create_keyval; + /* supposed to expect keyval as parameter and delete + * all the attributes as well */ + char *bom_delete_proc; + /* flags whether delete_proc is a function (whether back-sql + * should bind first parameter as output for return code) */ + int bom_expect_return; + backsql_key_t bom_id; + Avlnode *bom_attrs; + AttributeDescription *bom_create_hint; +} backsql_oc_map_rec; + +/* + * attributeType mapping structure + */ +typedef struct backsql_at_map_rec { + /* Description of corresponding LDAP attribute type */ + AttributeDescription *bam_ad; + AttributeDescription *bam_true_ad; + /* ObjectClass if bam_ad is objectClass */ + ObjectClass *bam_oc; + + struct berval bam_from_tbls; + struct berval bam_join_where; + struct berval bam_sel_expr; + + /* TimesTen, or, if a uppercase function is defined, + * an uppercased version of bam_sel_expr */ + struct berval bam_sel_expr_u; + + /* supposed to expect 2 binded values: entry keyval + * and attr. value to add, like "add_name(?,?,?)" */ + char *bam_add_proc; + /* supposed to expect 2 binded values: entry keyval + * and attr. value to delete */ + char *bam_delete_proc; + /* for optimization purposes attribute load query + * is preconstructed from parts on schemamap load time */ + char *bam_query; +#ifdef BACKSQL_COUNTQUERY + char *bam_countquery; +#endif /* BACKSQL_COUNTQUERY */ + /* following flags are bitmasks (first bit used for add_proc, + * second - for delete_proc) */ + /* order of parameters for procedures above; + * 1 means "data then keyval", 0 means "keyval then data" */ + int bam_param_order; + /* flags whether one or more of procedures is a function + * (whether back-sql should bind first parameter as output + * for return code) */ + int bam_expect_return; + + /* next mapping for attribute */ + struct backsql_at_map_rec *bam_next; +} backsql_at_map_rec; + +#define BACKSQL_AT_MAP_REC_INIT { NULL, NULL, BER_BVC(""), BER_BVC(""), BER_BVNULL, BER_BVNULL, NULL, NULL, NULL, 0, 0, NULL } + +/* define to uppercase filters only if the matching rule requires it + * (currently broken) */ +/* #define BACKSQL_UPPERCASE_FILTER */ + +#define BACKSQL_AT_CANUPPERCASE(at) ( !BER_BVISNULL( &(at)->bam_sel_expr_u ) ) + +/* defines to support bitmasks above */ +#define BACKSQL_ADD 0x1 +#define BACKSQL_DEL 0x2 + +#define BACKSQL_IS_ADD(x) ( ( BACKSQL_ADD & (x) ) == BACKSQL_ADD ) +#define BACKSQL_IS_DEL(x) ( ( BACKSQL_DEL & (x) ) == BACKSQL_DEL ) + +#define BACKSQL_NCMP(v1,v2) ber_bvcmp((v1),(v2)) + +#define BACKSQL_CONCAT +/* + * berbuf structure: a berval with a buffer size associated + */ +typedef struct berbuf { + struct berval bb_val; + ber_len_t bb_len; +} BerBuffer; + +#define BB_NULL { BER_BVNULL, 0 } + +/* + * Entry ID structure + */ +typedef struct backsql_entryID { + /* #define BACKSQL_ARBITRARY_KEY to allow a non-numeric key. + * It is required by some special applications that use + * strings as keys for the main table. + * In this case, #define BACKSQL_MAX_KEY_LEN consistently + * with the key size definition */ +#ifdef BACKSQL_ARBITRARY_KEY + struct berval eid_id; + struct berval eid_keyval; +#define BACKSQL_MAX_KEY_LEN 64 +#else /* ! BACKSQL_ARBITRARY_KEY */ + /* The original numeric key is maintained as default. */ + backsql_key_t eid_id; + backsql_key_t eid_keyval; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + backsql_key_t eid_oc_id; + backsql_oc_map_rec *eid_oc; + struct berval eid_dn; + struct berval eid_ndn; + struct backsql_entryID *eid_next; +} backsql_entryID; + +#ifdef BACKSQL_ARBITRARY_KEY +#define BACKSQL_ENTRYID_INIT { BER_BVNULL, BER_BVNULL, 0, NULL, BER_BVNULL, BER_BVNULL, NULL } +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_ENTRYID_INIT { 0, 0, 0, NULL, BER_BVNULL, BER_BVNULL, NULL } +#endif /* BACKSQL_ARBITRARY_KEY */ + +/* the function must collect the entry associated to nbase */ +#define BACKSQL_ISF_GET_ID 0x1U +#define BACKSQL_ISF_GET_ENTRY ( 0x2U | BACKSQL_ISF_GET_ID ) +#define BACKSQL_ISF_GET_OC ( 0x4U | BACKSQL_ISF_GET_ID ) +#define BACKSQL_ISF_MATCHED 0x8U +#define BACKSQL_IS_GET_ID(f) \ + ( ( (f) & BACKSQL_ISF_GET_ID ) == BACKSQL_ISF_GET_ID ) +#define BACKSQL_IS_GET_ENTRY(f) \ + ( ( (f) & BACKSQL_ISF_GET_ENTRY ) == BACKSQL_ISF_GET_ENTRY ) +#define BACKSQL_IS_GET_OC(f) \ + ( ( (f) & BACKSQL_ISF_GET_OC ) == BACKSQL_ISF_GET_OC ) +#define BACKSQL_IS_MATCHED(f) \ + ( ( (f) & BACKSQL_ISF_MATCHED ) == BACKSQL_ISF_MATCHED ) +typedef struct backsql_srch_info { + Operation *bsi_op; + SlapReply *bsi_rs; + + unsigned bsi_flags; +#define BSQL_SF_NONE 0x0000U +#define BSQL_SF_ALL_USER 0x0001U +#define BSQL_SF_ALL_OPER 0x0002U +#define BSQL_SF_ALL_ATTRS (BSQL_SF_ALL_USER|BSQL_SF_ALL_OPER) +#define BSQL_SF_FILTER_HASSUBORDINATE 0x0010U +#define BSQL_SF_FILTER_ENTRYUUID 0x0020U +#define BSQL_SF_FILTER_ENTRYCSN 0x0040U +#define BSQL_SF_RETURN_ENTRYUUID (BSQL_SF_FILTER_ENTRYUUID << 8) +#define BSQL_ISF(bsi, f) ( ( (bsi)->bsi_flags & f ) == f ) +#define BSQL_ISF_ALL_USER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_USER) +#define BSQL_ISF_ALL_OPER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_OPER) +#define BSQL_ISF_ALL_ATTRS(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_ATTRS) + + struct berval *bsi_base_ndn; + int bsi_use_subtree_shortcut; + backsql_entryID bsi_base_id; + int bsi_scope; +/* BACKSQL_SCOPE_BASE_LIKE can be set by API in ors_scope + * whenever the search base DN contains chars that cannot + * be mapped into the charset used in the RDBMS; so they're + * turned into '%' and an approximate ('LIKE') condition + * is used */ +#define BACKSQL_SCOPE_BASE_LIKE ( LDAP_SCOPE_BASE | 0x1000 ) + Filter *bsi_filter; + time_t bsi_stoptime; + + backsql_entryID *bsi_id_list, + **bsi_id_listtail, + *bsi_c_eid; + int bsi_n_candidates; + int bsi_status; + + backsql_oc_map_rec *bsi_oc; + struct berbuf bsi_sel, + bsi_from, + bsi_join_where, + bsi_flt_where; + ObjectClass *bsi_filter_oc; + SQLHDBC bsi_dbh; + AttributeName *bsi_attrs; + + Entry *bsi_e; +} backsql_srch_info; + +/* + * Backend private data structure + */ +typedef struct backsql_info { + char *sql_dbhost; + int sql_dbport; + char *sql_dbuser; + char *sql_dbpasswd; + char *sql_dbname; + + /* + * SQL condition for subtree searches differs in syntax: + * "LIKE CONCAT('%',?)" or "LIKE '%'+?" or "LIKE '%'||?" + * or smtg else + */ + struct berval sql_subtree_cond; + struct berval sql_children_cond; + struct berval sql_dn_match_cond; + char *sql_oc_query; + char *sql_at_query; + char *sql_insentry_stmt; + char *sql_delentry_stmt; + char *sql_renentry_stmt; + char *sql_delobjclasses_stmt; + char *sql_id_query; + char *sql_has_children_query; + char *sql_list_children_query; + + MatchingRule *sql_caseIgnoreMatch; + MatchingRule *sql_telephoneNumberMatch; + + struct berval sql_upper_func; + struct berval sql_upper_func_open; + struct berval sql_upper_func_close; + struct berval sql_strcast_func; + BerVarray sql_concat_func; + char *sql_concat_patt; + + struct berval sql_aliasing; + struct berval sql_aliasing_quote; + struct berval sql_dn_oc_aliasing; + + AttributeName *sql_anlist; + + unsigned int sql_flags; +#define BSQLF_SCHEMA_LOADED 0x0001 +#define BSQLF_UPPER_NEEDS_CAST 0x0002 +#define BSQLF_CREATE_NEEDS_SELECT 0x0004 +#define BSQLF_FAIL_IF_NO_MAPPING 0x0008 +#define BSQLF_HAS_LDAPINFO_DN_RU 0x0010 +#define BSQLF_DONTCHECK_LDAPINFO_DN_RU 0x0020 +#define BSQLF_USE_REVERSE_DN 0x0040 +#define BSQLF_ALLOW_ORPHANS 0x0080 +#define BSQLF_USE_SUBTREE_SHORTCUT 0x0100 +#define BSQLF_FETCH_ALL_USERATTRS 0x0200 +#define BSQLF_FETCH_ALL_OPATTRS 0x0400 +#define BSQLF_FETCH_ALL_ATTRS (BSQLF_FETCH_ALL_USERATTRS|BSQLF_FETCH_ALL_OPATTRS) +#define BSQLF_CHECK_SCHEMA 0x0800 +#define BSQLF_AUTOCOMMIT_ON 0x1000 + +#define BACKSQL_ISF(si, f) \ + (((si)->sql_flags & f) == f) + +#define BACKSQL_SCHEMA_LOADED(si) \ + BACKSQL_ISF(si, BSQLF_SCHEMA_LOADED) +#define BACKSQL_UPPER_NEEDS_CAST(si) \ + BACKSQL_ISF(si, BSQLF_UPPER_NEEDS_CAST) +#define BACKSQL_CREATE_NEEDS_SELECT(si) \ + BACKSQL_ISF(si, BSQLF_CREATE_NEEDS_SELECT) +#define BACKSQL_FAIL_IF_NO_MAPPING(si) \ + BACKSQL_ISF(si, BSQLF_FAIL_IF_NO_MAPPING) +#define BACKSQL_HAS_LDAPINFO_DN_RU(si) \ + BACKSQL_ISF(si, BSQLF_HAS_LDAPINFO_DN_RU) +#define BACKSQL_DONTCHECK_LDAPINFO_DN_RU(si) \ + BACKSQL_ISF(si, BSQLF_DONTCHECK_LDAPINFO_DN_RU) +#define BACKSQL_USE_REVERSE_DN(si) \ + BACKSQL_ISF(si, BSQLF_USE_REVERSE_DN) +#define BACKSQL_CANUPPERCASE(si) \ + (!BER_BVISNULL( &(si)->sql_upper_func )) +#define BACKSQL_ALLOW_ORPHANS(si) \ + BACKSQL_ISF(si, BSQLF_ALLOW_ORPHANS) +#define BACKSQL_USE_SUBTREE_SHORTCUT(si) \ + BACKSQL_ISF(si, BSQLF_USE_SUBTREE_SHORTCUT) +#define BACKSQL_FETCH_ALL_USERATTRS(si) \ + BACKSQL_ISF(si, BSQLF_FETCH_ALL_USERATTRS) +#define BACKSQL_FETCH_ALL_OPATTRS(si) \ + BACKSQL_ISF(si, BSQLF_FETCH_ALL_OPATTRS) +#define BACKSQL_FETCH_ALL_ATTRS(si) \ + BACKSQL_ISF(si, BSQLF_FETCH_ALL_ATTRS) +#define BACKSQL_CHECK_SCHEMA(si) \ + BACKSQL_ISF(si, BSQLF_CHECK_SCHEMA) +#define BACKSQL_AUTOCOMMIT_ON(si) \ + BACKSQL_ISF(si, BSQLF_AUTOCOMMIT_ON) + + Entry *sql_baseObject; + char *sql_base_ob_file; +#ifdef BACKSQL_ARBITRARY_KEY +#define BACKSQL_BASEOBJECT_IDSTR "baseObject" +#define BACKSQL_BASEOBJECT_KEYVAL BACKSQL_BASEOBJECT_IDSTR +#define BACKSQL_IS_BASEOBJECT_ID(id) (bvmatch((id), &backsql_baseObject_bv)) +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_BASEOBJECT_ID 0 +#define BACKSQL_BASEOBJECT_IDSTR LDAP_XSTRING(BACKSQL_BASEOBJECT_ID) +#define BACKSQL_BASEOBJECT_KEYVAL 0 +#define BACKSQL_IS_BASEOBJECT_ID(id) (*(id) == BACKSQL_BASEOBJECT_ID) +#endif /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_BASEOBJECT_OC 0 + + Avlnode *sql_db_conns; + SQLHDBC sql_dbh; + ldap_pvt_thread_mutex_t sql_dbconn_mutex; + Avlnode *sql_oc_by_oc; + Avlnode *sql_oc_by_id; + ldap_pvt_thread_mutex_t sql_schema_mutex; + SQLHENV sql_db_env; + + backsql_api *sql_api; +} backsql_info; + +#define BACKSQL_SUCCESS( rc ) \ + ( (rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO ) + +#define BACKSQL_AVL_STOP 0 +#define BACKSQL_AVL_CONTINUE 1 + +/* see ldap.h for the meaning of the macros and of the values */ +#define BACKSQL_LEGAL_ERROR( rc ) \ + ( LDAP_RANGE( (rc), 0x00, 0x0e ) \ + || LDAP_ATTR_ERROR( (rc) ) \ + || LDAP_NAME_ERROR( (rc) ) \ + || LDAP_SECURITY_ERROR( (rc) ) \ + || LDAP_SERVICE_ERROR( (rc) ) \ + || LDAP_UPDATE_ERROR( (rc) ) ) +#define BACKSQL_SANITIZE_ERROR( rc ) \ + ( BACKSQL_LEGAL_ERROR( (rc) ) ? (rc) : LDAP_OTHER ) + +#define BACKSQL_IS_BINARY(ct) \ + ( (ct) == SQL_BINARY \ + || (ct) == SQL_VARBINARY \ + || (ct) == SQL_LONGVARBINARY) + +#ifdef BACKSQL_ARBITRARY_KEY +#define BACKSQL_IDFMT "%s" +#define BACKSQL_IDARG(arg) ((arg).bv_val) +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define BACKSQL_IDFMT BACKSQL_IDNUMFMT +#define BACKSQL_IDARG(arg) (arg) +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +#endif /* __BACKSQL_H__ */ + diff --git a/servers/slapd/back-sql/bind.c b/servers/slapd/back-sql/bind.c new file mode 100644 index 0000000..ac78fc8 --- /dev/null +++ b/servers/slapd/back-sql/bind.c @@ -0,0 +1,115 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_bind( Operation *op, SlapReply *rs ) +{ + SQLHDBC dbh = SQL_NULL_HDBC; + Entry e = { 0 }; + Attribute *a; + backsql_srch_info bsi = { 0 }; + AttributeName anlist[2]; + int rc; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_bind()\n" ); + + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + /* in case of success, front end will send result; + * otherwise, be_rootdn_bind() did */ + Debug( LDAP_DEBUG_TRACE, "<==backsql_bind(%d)\n", + rs->sr_err ); + return rs->sr_err; + } + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_bind(): " + "could not get connection handle - exiting\n" ); + + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + goto error_return; + } + + anlist[0].an_name = slap_schema.si_ad_userPassword->ad_cname; + anlist[0].an_desc = slap_schema.si_ad_userPassword; + anlist[1].an_name.bv_val = NULL; + + bsi.bsi_e = &e; + rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, anlist, + BACKSQL_ISF_GET_ENTRY ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_bind(): " + "could not retrieve bindDN ID - no such entry\n" ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto error_return; + } + + a = attr_find( e.e_attrs, slap_schema.si_ad_userPassword ); + if ( a == NULL ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto error_return; + } + + if ( slap_passwd_check( op, &e, a, &op->oq_bind.rb_cred, + &rs->sr_text ) != 0 ) + { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto error_return; + } + +error_return:; + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &e.e_nname ) ) { + backsql_entry_clean( op, &e ); + } + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + } + + SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK ); + Debug( LDAP_DEBUG_TRACE,"<==backsql_bind()\n" ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/compare.c b/servers/slapd/back-sql/compare.c new file mode 100644 index 0000000..d457085 --- /dev/null +++ b/servers/slapd/back-sql/compare.c @@ -0,0 +1,194 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_compare( Operation *op, SlapReply *rs ) +{ + SQLHDBC dbh = SQL_NULL_HDBC; + Entry e = { 0 }; + Attribute *a = NULL; + backsql_srch_info bsi = { 0 }; + int rc; + int manageDSAit = get_manageDSAit( op ); + AttributeName anlist[2]; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_compare()\n" ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_compare(): " + "could not get connection handle - exiting\n" ); + + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + goto return_results; + } + + anlist[ 0 ].an_name = op->oq_compare.rs_ava->aa_desc->ad_cname; + anlist[ 0 ].an_desc = op->oq_compare.rs_ava->aa_desc; + BER_BVZERO( &anlist[ 1 ].an_name ); + + /* + * Get the entry + */ + bsi.bsi_e = &e; + rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, anlist, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + switch ( rc ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_compare(): " + "could not retrieve compareDN ID - no such entry\n" ); + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + if ( is_at_operational( op->oq_compare.rs_ava->aa_desc->ad_type ) ) { + SlapReply nrs = { REP_SEARCH }; + Attribute **ap; + + for ( ap = &e.e_attrs; *ap; ap = &(*ap)->a_next ) + ; + + nrs.sr_attrs = anlist; + nrs.sr_entry = &e; + nrs.sr_attr_flags = SLAP_OPATTRS_NO; + nrs.sr_operational_attrs = NULL; + + rs->sr_err = backsql_operational( op, &nrs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto return_results; + } + + *ap = nrs.sr_operational_attrs; + } + + if ( ! access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc, + &op->oq_compare.rs_ava->aa_value, + ACL_COMPARE, NULL ) ) + { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + for ( a = attrs_find( e.e_attrs, op->oq_compare.rs_ava->aa_desc ); + a != NULL; + a = attrs_find( a->a_next, op->oq_compare.rs_ava->aa_desc ) ) + { + rs->sr_err = LDAP_COMPARE_FALSE; + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->oq_compare.rs_ava->aa_value, NULL, + op->o_tmpmemctx ) == 0 ) + { + rs->sr_err = LDAP_COMPARE_TRUE; + break; + } + } + +return_results:; + switch ( rs->sr_err ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + break; + + default: + if ( !BER_BVISNULL( &e.e_nname ) && + ! access_allowed( op, &e, + slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + } + break; + } + + send_ldap_result( op, rs ); + + if ( rs->sr_matched ) { + rs->sr_matched = NULL; + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &e.e_nname ) ) { + backsql_entry_clean( op, &e ); + } + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + Debug(LDAP_DEBUG_TRACE,"<==backsql_compare()\n" ); + switch ( rs->sr_err ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + return LDAP_SUCCESS; + + default: + return rs->sr_err; + } +} + diff --git a/servers/slapd/back-sql/config.c b/servers/slapd/back-sql/config.c new file mode 100644 index 0000000..51a6be3 --- /dev/null +++ b/servers/slapd/back-sql/config.c @@ -0,0 +1,778 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include "ac/string.h" +#include <sys/types.h> + +#include "slap.h" +#include "slap-config.h" +#include "ldif.h" +#include "lutil.h" +#include "proto-sql.h" + +static int +create_baseObject( + BackendDB *be, + const char *fname, + int lineno ); + +static int +read_baseObject( + BackendDB *be, + const char *fname ); + +static ConfigDriver sql_cf_gen; + +enum { + BSQL_CONCAT_PATT = 1, + BSQL_CREATE_NEEDS_SEL, + BSQL_UPPER_NEEDS_CAST, + BSQL_HAS_LDAPINFO_DN_RU, + BSQL_FAIL_IF_NO_MAPPING, + BSQL_ALLOW_ORPHANS, + BSQL_BASE_OBJECT, + BSQL_LAYER, + BSQL_SUBTREE_SHORTCUT, + BSQL_FETCH_ALL_ATTRS, + BSQL_FETCH_ATTRS, + BSQL_CHECK_SCHEMA, + BSQL_ALIASING_KEYWORD, + BSQL_AUTOCOMMIT +}; + +static ConfigTable sqlcfg[] = { + { "dbhost", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbhost), + "( OLcfgDbAt:6.1 NAME 'olcDbHost' " + "DESC 'Hostname of SQL server' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbname", "name", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbname), + "( OLcfgDbAt:6.2 NAME 'olcDbName' " + "DESC 'Name of SQL database' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbuser", "username", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbuser), + "( OLcfgDbAt:6.3 NAME 'olcDbUser' " + "DESC 'Username for SQL session' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbpasswd", "password", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dbpasswd), + "( OLcfgDbAt:6.4 NAME 'olcDbPass' " + "DESC 'Password for SQL session' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "concat_pattern", "pattern", 2, 2, 0, + ARG_STRING|ARG_MAGIC|BSQL_CONCAT_PATT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.20 NAME 'olcSqlConcatPattern' " + "DESC 'Pattern used to concatenate strings' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "subtree_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_subtree_cond), + "( OLcfgDbAt:6.21 NAME 'olcSqlSubtreeCond' " + "DESC 'Where-clause template for a subtree search condition' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "children_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_children_cond), + "( OLcfgDbAt:6.22 NAME 'olcSqlChildrenCond' " + "DESC 'Where-clause template for a children search condition' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dn_match_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_dn_match_cond), + "( OLcfgDbAt:6.23 NAME 'olcSqlDnMatchCond' " + "DESC 'Where-clause template for a DN match search condition' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "oc_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_oc_query), + "( OLcfgDbAt:6.24 NAME 'olcSqlOcQuery' " + "DESC 'Query used to collect objectClass mapping data' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "at_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_at_query), + "( OLcfgDbAt:6.25 NAME 'olcSqlAtQuery' " + "DESC 'Query used to collect attributeType mapping data' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "insentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_insentry_stmt), + "( OLcfgDbAt:6.26 NAME 'olcSqlInsEntryStmt' " + "DESC 'Statement used to insert a new entry' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "create_needs_select", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_CREATE_NEEDS_SEL, (void *)sql_cf_gen, + "( OLcfgDbAt:6.27 NAME 'olcSqlCreateNeedsSelect' " + "DESC 'Whether entry creation needs a subsequent select' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "upper_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_upper_func), + "( OLcfgDbAt:6.28 NAME 'olcSqlUpperFunc' " + "DESC 'Function that converts a value to uppercase' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "upper_needs_cast", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_UPPER_NEEDS_CAST, (void *)sql_cf_gen, + "( OLcfgDbAt:6.29 NAME 'olcSqlUpperNeedsCast' " + "DESC 'Whether olcSqlUpperFunc needs an explicit cast' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "strcast_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_strcast_func), + "( OLcfgDbAt:6.30 NAME 'olcSqlStrcastFunc' " + "DESC 'Function that converts a value to a string' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "delentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_delentry_stmt), + "( OLcfgDbAt:6.31 NAME 'olcSqlDelEntryStmt' " + "DESC 'Statement used to delete an existing entry' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "renentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_renentry_stmt), + "( OLcfgDbAt:6.32 NAME 'olcSqlRenEntryStmt' " + "DESC 'Statement used to rename an entry' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "delobjclasses_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_delobjclasses_stmt), + "( OLcfgDbAt:6.33 NAME 'olcSqlDelObjclassesStmt' " + "DESC 'Statement used to delete the ID of an entry' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "has_ldapinfo_dn_ru", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_HAS_LDAPINFO_DN_RU, (void *)sql_cf_gen, + "( OLcfgDbAt:6.34 NAME 'olcSqlHasLDAPinfoDnRu' " + "DESC 'Whether the dn_ru column is present' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "fail_if_no_mapping", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_FAIL_IF_NO_MAPPING, (void *)sql_cf_gen, + "( OLcfgDbAt:6.35 NAME 'olcSqlFailIfNoMapping' " + "DESC 'Whether to fail on unknown attribute mappings' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "allow_orphans", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_ALLOW_ORPHANS, (void *)sql_cf_gen, + "( OLcfgDbAt:6.36 NAME 'olcSqlAllowOrphans' " + "DESC 'Whether to allow adding entries with no parent' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "baseobject", "[file]", 1, 2, 0, + ARG_STRING|ARG_MAGIC|BSQL_BASE_OBJECT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.37 NAME 'olcSqlBaseObject' " + "DESC 'Manage an in-memory baseObject entry' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "sqllayer", "name", 2, 0, 0, + ARG_MAGIC|BSQL_LAYER, (void *)sql_cf_gen, + "( OLcfgDbAt:6.38 NAME 'olcSqlLayer' " + "DESC 'Helper used to map DNs between LDAP and SQL' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "use_subtree_shortcut", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_SUBTREE_SHORTCUT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.39 NAME 'olcSqlUseSubtreeShortcut' " + "DESC 'Collect all entries when searchBase is DB suffix' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "fetch_all_attrs", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_FETCH_ALL_ATTRS, (void *)sql_cf_gen, + "( OLcfgDbAt:6.40 NAME 'olcSqlFetchAllAttrs' " + "DESC 'Require all attributes to always be loaded' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "fetch_attrs", "attrlist", 2, 0, 0, + ARG_MAGIC|BSQL_FETCH_ATTRS, (void *)sql_cf_gen, + "( OLcfgDbAt:6.41 NAME 'olcSqlFetchAttrs' " + "DESC 'Set of attributes to always fetch' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "check_schema", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_CHECK_SCHEMA, (void *)sql_cf_gen, + "( OLcfgDbAt:6.42 NAME 'olcSqlCheckSchema' " + "DESC 'Check schema after modifications' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "aliasing_keyword", "string", 2, 2, 0, + ARG_STRING|ARG_MAGIC|BSQL_ALIASING_KEYWORD, (void *)sql_cf_gen, + "( OLcfgDbAt:6.43 NAME 'olcSqlAliasingKeyword' " + "DESC 'The aliasing keyword' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "aliasing_quote", "string", 2, 2, 0, ARG_BERVAL|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_aliasing_quote), + "( OLcfgDbAt:6.44 NAME 'olcSqlAliasingQuote' " + "DESC 'Quoting char of the aliasing keyword' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "autocommit", "yes|no", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|BSQL_AUTOCOMMIT, (void *)sql_cf_gen, + "( OLcfgDbAt:6.45 NAME 'olcSqlAutocommit' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "id_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET, + (void *)offsetof(struct backsql_info, sql_id_query), + "( OLcfgDbAt:6.46 NAME 'olcSqlIdQuery' " + "DESC 'Query used to collect entryID mapping data' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs sqlocs[] = { + { + "( OLcfgDbOc:6.1 " + "NAME 'olcSqlConfig' " + "DESC 'SQL backend configuration' " + "SUP olcDatabaseConfig " + "MUST olcDbName " + "MAY ( olcDbHost $ olcDbUser $ olcDbPass $ olcSqlConcatPattern $ " + "olcSqlSubtreeCond $ olcsqlChildrenCond $ olcSqlDnMatchCond $ " + "olcSqlOcQuery $ olcSqlAtQuery $ olcSqlInsEntryStmt $ " + "olcSqlCreateNeedsSelect $ olcSqlUpperFunc $ olcSqlUpperNeedsCast $ " + "olcSqlStrCastFunc $ olcSqlDelEntryStmt $ olcSqlRenEntryStmt $ " + "olcSqlDelObjClassesStmt $ olcSqlHasLDAPInfoDnRu $ " + "olcSqlFailIfNoMapping $ olcSqlAllowOrphans $ olcSqlBaseObject $ " + "olcSqlLayer $ olcSqlUseSubtreeShortcut $ olcSqlFetchAllAttrs $ " + "olcSqlFetchAttrs $ olcSqlCheckSchema $ olcSqlAliasingKeyword $ " + "olcSqlAliasingQuote $ olcSqlAutocommit $ olcSqlIdQuery ) )", + Cft_Database, sqlcfg }, + { NULL, Cft_Abstract, NULL } +}; + +static int +sql_cf_gen( ConfigArgs *c ) +{ + backsql_info *bi = (backsql_info *)c->be->be_private; + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case BSQL_CONCAT_PATT: + if ( bi->sql_concat_patt ) { + c->value_string = ch_strdup( bi->sql_concat_patt ); + } else { + rc = 1; + } + break; + case BSQL_CREATE_NEEDS_SEL: + if ( bi->sql_flags & BSQLF_CREATE_NEEDS_SELECT ) + c->value_int = 1; + break; + case BSQL_UPPER_NEEDS_CAST: + if ( bi->sql_flags & BSQLF_UPPER_NEEDS_CAST ) + c->value_int = 1; + break; + case BSQL_HAS_LDAPINFO_DN_RU: + if ( !(bi->sql_flags & BSQLF_DONTCHECK_LDAPINFO_DN_RU) ) + return 1; + if ( bi->sql_flags & BSQLF_HAS_LDAPINFO_DN_RU ) + c->value_int = 1; + break; + case BSQL_FAIL_IF_NO_MAPPING: + if ( bi->sql_flags & BSQLF_FAIL_IF_NO_MAPPING ) + c->value_int = 1; + break; + case BSQL_ALLOW_ORPHANS: + if ( bi->sql_flags & BSQLF_ALLOW_ORPHANS ) + c->value_int = 1; + break; + case BSQL_SUBTREE_SHORTCUT: + if ( bi->sql_flags & BSQLF_USE_SUBTREE_SHORTCUT ) + c->value_int = 1; + break; + case BSQL_FETCH_ALL_ATTRS: + if ( bi->sql_flags & BSQLF_FETCH_ALL_ATTRS ) + c->value_int = 1; + break; + case BSQL_CHECK_SCHEMA: + if ( bi->sql_flags & BSQLF_CHECK_SCHEMA ) + c->value_int = 1; + break; + case BSQL_AUTOCOMMIT: + if ( bi->sql_flags & BSQLF_AUTOCOMMIT_ON ) + c->value_int = 1; + break; + case BSQL_BASE_OBJECT: + if ( bi->sql_base_ob_file ) { + c->value_string = ch_strdup( bi->sql_base_ob_file ); + } else if ( bi->sql_baseObject ) { + c->value_string = ch_strdup( "TRUE" ); + } else { + rc = 1; + } + break; + case BSQL_LAYER: + if ( bi->sql_api ) { + backsql_api *ba; + struct berval bv; + char *ptr; + int i; + for ( ba = bi->sql_api; ba; ba = ba->ba_next ) { + bv.bv_len = strlen( ba->ba_name ); + if ( ba->ba_argc ) { + for ( i = 0; i<ba->ba_argc; i++ ) + bv.bv_len += strlen( ba->ba_argv[i] ) + 3; + } + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( bv.bv_val, ba->ba_name ); + if ( ba->ba_argc ) { + for ( i = 0; i<ba->ba_argc; i++ ) { + *ptr++ = ' '; + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, ba->ba_argv[i] ); + *ptr++ = '"'; + } + } + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + case BSQL_ALIASING_KEYWORD: + if ( !BER_BVISNULL( &bi->sql_aliasing )) { + struct berval bv; + bv = bi->sql_aliasing; + bv.bv_len--; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + break; + case BSQL_FETCH_ATTRS: + if ( bi->sql_anlist || + ( bi->sql_flags & (BSQLF_FETCH_ALL_USERATTRS| + BSQLF_FETCH_ALL_OPATTRS))) + { + char buf[BUFSIZ*2], *ptr; + struct berval bv; +# define WHATSLEFT ((ber_len_t) (&buf[sizeof( buf )] - ptr)) + ptr = buf; + if ( bi->sql_anlist ) { + ptr = anlist_unparse( bi->sql_anlist, ptr, WHATSLEFT ); + if ( ptr == NULL ) + return 1; + } + if ( bi->sql_flags & BSQLF_FETCH_ALL_USERATTRS ) { + if ( WHATSLEFT <= STRLENOF( ",*" )) return 1; + if ( ptr != buf ) *ptr++ = ','; + *ptr++ = '*'; + } + if ( bi->sql_flags & BSQLF_FETCH_ALL_OPATTRS ) { + if ( WHATSLEFT <= STRLENOF( ",+" )) return 1; + if ( ptr != buf ) *ptr++ = ','; + *ptr++ = '+'; + } + bv.bv_val = buf; + bv.bv_len = ptr - buf; + value_add_one( &c->rvalue_vals, &bv ); + } + break; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { /* FIXME */ + return -1; + } + + switch( c->type ) { + case BSQL_CONCAT_PATT: + if ( backsql_split_pattern( c->argv[ 1 ], &bi->sql_concat_func, 2 ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: unable to parse pattern \"%s\"", + c->log, c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg ); + return -1; + } + bi->sql_concat_patt = c->value_string; + break; + case BSQL_CREATE_NEEDS_SEL: + if ( c->value_int ) + bi->sql_flags |= BSQLF_CREATE_NEEDS_SELECT; + else + bi->sql_flags &= ~BSQLF_CREATE_NEEDS_SELECT; + break; + case BSQL_UPPER_NEEDS_CAST: + if ( c->value_int ) + bi->sql_flags |= BSQLF_UPPER_NEEDS_CAST; + else + bi->sql_flags &= ~BSQLF_UPPER_NEEDS_CAST; + break; + case BSQL_HAS_LDAPINFO_DN_RU: + bi->sql_flags |= BSQLF_DONTCHECK_LDAPINFO_DN_RU; + if ( c->value_int ) + bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU; + else + bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU; + break; + case BSQL_FAIL_IF_NO_MAPPING: + if ( c->value_int ) + bi->sql_flags |= BSQLF_FAIL_IF_NO_MAPPING; + else + bi->sql_flags &= ~BSQLF_FAIL_IF_NO_MAPPING; + break; + case BSQL_ALLOW_ORPHANS: + if ( c->value_int ) + bi->sql_flags |= BSQLF_ALLOW_ORPHANS; + else + bi->sql_flags &= ~BSQLF_ALLOW_ORPHANS; + break; + case BSQL_SUBTREE_SHORTCUT: + if ( c->value_int ) + bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT; + else + bi->sql_flags &= ~BSQLF_USE_SUBTREE_SHORTCUT; + break; + case BSQL_FETCH_ALL_ATTRS: + if ( c->value_int ) + bi->sql_flags |= BSQLF_FETCH_ALL_ATTRS; + else + bi->sql_flags &= ~BSQLF_FETCH_ALL_ATTRS; + break; + case BSQL_CHECK_SCHEMA: + if ( c->value_int ) + bi->sql_flags |= BSQLF_CHECK_SCHEMA; + else + bi->sql_flags &= ~BSQLF_CHECK_SCHEMA; + break; + case BSQL_AUTOCOMMIT: + if ( c->value_int ) + bi->sql_flags |= BSQLF_AUTOCOMMIT_ON; + else + bi->sql_flags &= ~BSQLF_AUTOCOMMIT_ON; + break; + case BSQL_BASE_OBJECT: + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: suffix must be set", c->log ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( bi->sql_baseObject ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: " + "\"baseObject\" already provided (will be overwritten)\n", + c->log ); + entry_free( bi->sql_baseObject ); + } + if ( c->argc == 2 && !strcmp( c->argv[1], "TRUE" )) + c->argc = 1; + switch( c->argc ) { + case 1: + return create_baseObject( c->be, c->fname, c->lineno ); + + case 2: + rc = read_baseObject( c->be, c->argv[ 1 ] ); + if ( rc == 0 ) { + ch_free( bi->sql_base_ob_file ); + bi->sql_base_ob_file = c->value_string; + } + return rc; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: trailing values in directive", c->log ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg ); + return 1; + } + break; + case BSQL_LAYER: + if ( backsql_api_config( bi, c->argv[ 1 ], c->argc - 2, &c->argv[ 2 ] ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: unable to load sql layer", c->log ); + Debug( LDAP_DEBUG_ANY, "%s \"%s\"\n", + c->cr_msg, c->argv[1] ); + return 1; + } + break; + case BSQL_ALIASING_KEYWORD: + if ( ! BER_BVISNULL( &bi->sql_aliasing ) ) { + ch_free( bi->sql_aliasing.bv_val ); + } + + ber_str2bv( c->argv[ 1 ], strlen( c->argv[ 1 ] ) + 1, 1, + &bi->sql_aliasing ); + /* add a trailing space... */ + bi->sql_aliasing.bv_val[ bi->sql_aliasing.bv_len - 1] = ' '; + break; + case BSQL_FETCH_ATTRS: { + char *str, *s, *next; + const char *delimstr = ","; + + str = ch_strdup( c->argv[ 1 ] ); + for ( s = ldap_pvt_strtok( str, delimstr, &next ); + s != NULL; + s = ldap_pvt_strtok( NULL, delimstr, &next ) ) + { + if ( strlen( s ) == 1 ) { + if ( *s == '*' ) { + bi->sql_flags |= BSQLF_FETCH_ALL_USERATTRS; + c->argv[ 1 ][ s - str ] = ','; + + } else if ( *s == '+' ) { + bi->sql_flags |= BSQLF_FETCH_ALL_OPATTRS; + c->argv[ 1 ][ s - str ] = ','; + } + } + } + ch_free( str ); + bi->sql_anlist = str2anlist( bi->sql_anlist, c->argv[ 1 ], delimstr ); + if ( bi->sql_anlist == NULL ) { + return -1; + } + } + break; + } + return rc; +} + +/* + * Read the entries specified in fname and merge the attributes + * to the user defined baseObject entry. Note that if we find any errors + * what so ever, we will discard the entire entries, print an + * error message and return. + */ +static int +read_baseObject( + BackendDB *be, + const char *fname ) +{ + backsql_info *bi = (backsql_info *)be->be_private; + LDIFFP *fp; + int rc = 0, lmax = 0, ldifrc; + unsigned long lineno = 0; + char *buf = NULL; + + assert( fname != NULL ); + + fp = ldif_open( fname, "r" ); + if ( fp == NULL ) { + Debug( LDAP_DEBUG_ANY, + "could not open back-sql baseObject " + "attr file \"%s\" - absolute path?\n", + fname ); + perror( fname ); + return LDAP_OTHER; + } + + bi->sql_baseObject = entry_alloc(); + if ( bi->sql_baseObject == NULL ) { + Debug( LDAP_DEBUG_ANY, + "read_baseObject_file: entry_alloc failed" ); + ldif_close( fp ); + return LDAP_NO_MEMORY; + } + bi->sql_baseObject->e_name = be->be_suffix[0]; + bi->sql_baseObject->e_nname = be->be_nsuffix[0]; + bi->sql_baseObject->e_attrs = NULL; + + while (( ldifrc = ldif_read_record( fp, &lineno, &buf, &lmax )) > 0 ) { + Entry *e = str2entry( buf ); + Attribute *a; + + if( e == NULL ) { + fprintf( stderr, "back-sql baseObject: " + "could not parse entry (line=%lu)\n", + lineno ); + rc = LDAP_OTHER; + break; + } + + /* make sure the DN is the database's suffix */ + if ( !be_issuffix( be, &e->e_nname ) ) { + fprintf( stderr, + "back-sql: invalid baseObject - " + "dn=\"%s\" (line=%lu)\n", + e->e_name.bv_val, lineno ); + entry_free( e ); + rc = LDAP_OTHER; + break; + } + + /* + * we found a valid entry, so walk thru all the attributes in the + * entry, and add each attribute type and description to baseObject + */ + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + if ( attr_merge( bi->sql_baseObject, a->a_desc, + a->a_vals, + ( a->a_nvals == a->a_vals ) ? + NULL : a->a_nvals ) ) + { + rc = LDAP_OTHER; + break; + } + } + + entry_free( e ); + if ( rc ) { + break; + } + } + + if ( ldifrc < 0 ) + rc = LDAP_OTHER; + + if ( rc ) { + entry_free( bi->sql_baseObject ); + bi->sql_baseObject = NULL; + } + + ch_free( buf ); + + ldif_close( fp ); + + Debug( LDAP_DEBUG_CONFIG, "back-sql baseObject file \"%s\" read.\n", + fname ); + + return rc; +} + +static int +create_baseObject( + BackendDB *be, + const char *fname, + int lineno ) +{ + backsql_info *bi = (backsql_info *)be->be_private; + LDAPRDN rdn; + char *p; + int rc, iAVA; + char buf[1024]; + + snprintf( buf, sizeof(buf), + "dn: %s\n" + "objectClass: extensibleObject\n" + "description: builtin baseObject for back-sql\n" + "description: all entries mapped " + "in table \"ldap_entries\" " + "must have " + "\"" BACKSQL_BASEOBJECT_IDSTR "\" " + "in the \"parent\" column", + be->be_suffix[0].bv_val ); + + bi->sql_baseObject = str2entry( buf ); + if ( bi->sql_baseObject == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): " + "unable to parse baseObject entry\n", + fname, lineno ); + return 1; + } + + if ( BER_BVISEMPTY( &be->be_suffix[ 0 ] ) ) { + return 0; + } + + rc = ldap_bv2rdn( &be->be_suffix[ 0 ], &rdn, (char **)&p, + LDAP_DN_FORMAT_LDAP ); + if ( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): unable to extract RDN " "from baseObject DN \"%s\" (%d: %s)\n", + fname, lineno, be->be_suffix[0].bv_val, rc, + ldap_err2string(rc) ); + return 1; + } + + for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) { + LDAPAVA *ava = rdn[ iAVA ]; + AttributeDescription *ad = NULL; + slap_syntax_transform_func *transf = NULL; + struct berval bv = BER_BVNULL; + const char *text = NULL; + + assert( ava != NULL ); + + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): AttributeDescription of naming " "attribute #%d from baseObject " "DN \"%s\": %d: %s\n", + fname, lineno, iAVA, be->be_suffix[0].bv_val, + rc, ldap_err2string(rc) ); + return 1; + } + + transf = ad->ad_type->sat_syntax->ssyn_pretty; + if ( transf ) { + /* + * transform value by pretty function + * if value is empty, use empty_bv + */ + rc = ( *transf )( ad->ad_type->sat_syntax, + ava->la_value.bv_len + ? &ava->la_value + : (struct berval *) &slap_empty_bv, + &bv, NULL ); + + if ( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<==backsql_db_config (%s line %d): " "prettying of attribute #%d " "from baseObject " "DN \"%s\" failed: %d: %s\n", + fname, lineno, iAVA, + be->be_suffix[0].bv_val, rc, + ldap_err2string(rc) ); + return 1; + } + } + + if ( !BER_BVISNULL( &bv ) ) { + if ( ava->la_flags & LDAP_AVA_FREE_VALUE ) { + ber_memfree( ava->la_value.bv_val ); + } + ava->la_value = bv; + ava->la_flags |= LDAP_AVA_FREE_VALUE; + } + + attr_merge_normalize_one( bi->sql_baseObject, + ad, &ava->la_value, NULL ); + } + + ldap_rdnfree( rdn ); + + return 0; +} + +int backsql_init_cf( BackendInfo *bi ) +{ + int rc; + + bi->bi_cf_ocs = sqlocs; + rc = config_register_schema( sqlcfg, sqlocs ); + if ( rc ) return rc; + return 0; +} diff --git a/servers/slapd/back-sql/delete.c b/servers/slapd/back-sql/delete.c new file mode 100644 index 0000000..489d4d3 --- /dev/null +++ b/servers/slapd/back-sql/delete.c @@ -0,0 +1,627 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +typedef struct backsql_delete_attr_t { + Operation *op; + SlapReply *rs; + SQLHDBC dbh; + backsql_entryID *e_id; +} backsql_delete_attr_t; + +static int +backsql_delete_attr_f( void *v_at, void *v_bda ) +{ + backsql_at_map_rec *at = (backsql_at_map_rec *)v_at; + backsql_delete_attr_t *bda = (backsql_delete_attr_t *)v_bda; + int rc; + + rc = backsql_modify_delete_all_values( bda->op, + bda->rs, bda->dbh, bda->e_id, at ); + + if ( rc != LDAP_SUCCESS ) { + return BACKSQL_AVL_STOP; + } + + return BACKSQL_AVL_CONTINUE; +} + +static int +backsql_delete_all_attrs( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_entryID *eid ) +{ + backsql_delete_attr_t bda; + int rc; + + bda.op = op; + bda.rs = rs; + bda.dbh = dbh; + bda.e_id = eid; + + rc = ldap_avl_apply( eid->eid_oc->bom_attrs, backsql_delete_attr_f, &bda, + BACKSQL_AVL_STOP, AVL_INORDER ); + if ( rc == BACKSQL_AVL_STOP ) { + return rs->sr_err; + } + + return LDAP_SUCCESS; +} + +static int +backsql_delete_int( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + SQLHSTMT *sthp, + backsql_entryID *eid, + Entry **ep ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + int prc = LDAP_SUCCESS; + /* first parameter no */ + SQLUSMALLINT pno = 0; + + sth = *sthp; + + /* ldap_avl_apply ... */ + rs->sr_err = backsql_delete_all_attrs( op, rs, dbh, eid ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + rc = backsql_Prepare( dbh, &sth, eid->eid_oc->bom_delete_proc, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error preparing delete query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + + if ( BACKSQL_IS_DEL( eid->eid_oc->bom_expect_return ) ) { + pno = 1; + rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding output parameter for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + } + + rc = backsql_BindParamID( sth, pno + 1, SQL_PARAM_INPUT, &eid->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding keyval parameter for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) { + rs->sr_err = LDAP_SUCCESS; + + } else { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "delete_proc execution failed (rc=%d, prc=%d)\n", + rc, prc ); + + + if ( prc != LDAP_SUCCESS ) { + /* SQL procedure executed fine + * but returned an error */ + rs->sr_err = BACKSQL_SANITIZE_ERROR( prc ); + + } else { + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + rs->sr_err = LDAP_OTHER; + } + SQLFreeStmt( sth, SQL_DROP ); + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + /* delete "auxiliary" objectClasses, if any... */ + rc = backsql_Prepare( dbh, &sth, bi->sql_delobjclasses_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error preparing ldap_entry_objclasses delete query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding auxiliary objectClasses " + "entry ID parameter for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + + rc = SQLExecute( sth ); + switch ( rc ) { + case SQL_NO_DATA: + /* apparently there were no "auxiliary" objectClasses + * for this entry... */ + case SQL_SUCCESS: + break; + + default: + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "failed to delete record from ldap_entry_objclasses\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + /* delete entry... */ + rc = backsql_Prepare( dbh, &sth, bi->sql_delentry_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error preparing ldap_entries delete query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_delete(): " + "error binding entry ID parameter " + "for objectClass %s\n", + eid->eid_oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + *ep = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "failed to delete record from ldap_entries\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + *ep = NULL; + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_err = LDAP_SUCCESS; + *ep = NULL; + +done:; + *sthp = sth; + + return rs->sr_err; +} + +typedef struct backsql_tree_delete_t { + Operation *btd_op; + int btd_rc; + backsql_entryID *btd_eid; +} backsql_tree_delete_t; + +static int +backsql_tree_delete_search_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + backsql_tree_delete_t *btd; + backsql_entryID *eid; + + btd = (backsql_tree_delete_t *)op->o_callback->sc_private; + + if ( !access_allowed( btd->btd_op, rs->sr_entry, + slap_schema.si_ad_entry, NULL, ACL_WDEL, NULL ) + || !access_allowed( btd->btd_op, rs->sr_entry, + slap_schema.si_ad_children, NULL, ACL_WDEL, NULL ) ) + { + btd->btd_rc = LDAP_INSUFFICIENT_ACCESS; + return rs->sr_err = LDAP_UNAVAILABLE; + } + + assert( rs->sr_entry != NULL ); + assert( rs->sr_entry->e_private != NULL ); + + eid = (backsql_entryID *)rs->sr_entry->e_private; + assert( eid->eid_oc != NULL ); + if ( eid->eid_oc == NULL || eid->eid_oc->bom_delete_proc == NULL ) { + btd->btd_rc = LDAP_UNWILLING_TO_PERFORM; + return rs->sr_err = LDAP_UNAVAILABLE; + } + + eid = backsql_entryID_dup( eid, op->o_tmpmemctx ); + eid->eid_next = btd->btd_eid; + btd->btd_eid = eid; + } + + return 0; +} + +static int +backsql_tree_delete( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + SQLHSTMT *sthp ) +{ + Operation op2 = *op; + slap_callback sc = { 0 }; + SlapReply rs2 = { REP_RESULT }; + backsql_tree_delete_t btd = { 0 }; + + int rc; + + /* + * - perform an internal subtree search as the rootdn + * - for each entry + * - check access + * - check objectClass and delete method(s) + * - for each entry + * - delete + * - if successful, commit + */ + + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_protocol = LDAP_VERSION3; + + btd.btd_op = op; + sc.sc_private = &btd; + sc.sc_response = backsql_tree_delete_search_cb; + op2.o_callback = ≻ + + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + op2.o_managedsait = SLAP_CONTROL_CRITICAL; + + op2.ors_scope = LDAP_SCOPE_SUBTREE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_slimit = SLAP_NO_LIMIT; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_filter = (Filter *)slap_filter_objectClass_pres; + op2.ors_filterstr = *slap_filterstr_objectClass_pres; + op2.ors_attrs = slap_anlist_all_attributes; + op2.ors_attrsonly = 0; + + rc = op->o_bd->be_search( &op2, &rs2 ); + if ( rc != LDAP_SUCCESS ) { + rc = rs->sr_err = btd.btd_rc; + rs->sr_text = "subtree delete not possible"; + send_ldap_result( op, rs ); + goto clean; + } + + for ( ; btd.btd_eid != NULL; + btd.btd_eid = backsql_free_entryID( btd.btd_eid, + 1, op->o_tmpmemctx ) ) + { + Entry *e = (void *)0xbad; + rc = backsql_delete_int( op, rs, dbh, sthp, btd.btd_eid, &e ); + if ( rc != LDAP_SUCCESS ) { + break; + } + } + +clean:; + for ( ; btd.btd_eid != NULL; + btd.btd_eid = backsql_free_entryID( btd.btd_eid, + 1, op->o_tmpmemctx ) ) + ; + + return rc; +} + +int +backsql_delete( Operation *op, SlapReply *rs ) +{ + SQLHDBC dbh = SQL_NULL_HDBC; + SQLHSTMT sth = SQL_NULL_HSTMT; + backsql_oc_map_rec *oc = NULL; + backsql_srch_info bsi = { 0 }; + backsql_entryID e_id = { 0 }; + Entry d = { 0 }, p = { 0 }, *e = NULL; + struct berval pdn = BER_BVNULL; + int manageDSAit = get_manageDSAit( op ); + + Debug( LDAP_DEBUG_TRACE, "==>backsql_delete(): deleting entry \"%s\"\n", + op->o_req_ndn.bv_val ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "could not get connection handle - exiting\n" ); + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + e = NULL; + goto done; + } + + /* + * Get the entry + */ + bsi.bsi_e = &d; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &d; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_delete(): " + "could not retrieve deleteDN ID - no such entry\n" ); + if ( !BER_BVISNULL( &d.e_nname ) ) { + /* FIXME: should always be true! */ + e = &d; + + } else { + e = NULL; + } + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, &d, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + e = &d; + goto done; + } + + if ( !access_allowed( op, &d, slap_schema.si_ad_entry, + NULL, ACL_WDEL, NULL ) ) + { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "no write access to entry\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &d; + goto done; + } + + rs->sr_err = backsql_has_children( op, dbh, &op->o_req_ndn ); + switch ( rs->sr_err ) { + case LDAP_COMPARE_FALSE: + rs->sr_err = LDAP_SUCCESS; + break; + + case LDAP_COMPARE_TRUE: +#ifdef SLAP_CONTROL_X_TREE_DELETE + if ( get_treeDelete( op ) ) { + rs->sr_err = LDAP_SUCCESS; + break; + } +#endif /* SLAP_CONTROL_X_TREE_DELETE */ + + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "entry \"%s\" has children\n", + op->o_req_dn.bv_val ); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subordinate objects must be deleted first"; + /* fallthru */ + + default: + e = &d; + goto done; + } + + assert( bsi.bsi_base_id.eid_oc != NULL ); + oc = bsi.bsi_base_id.eid_oc; + if ( oc->bom_delete_proc == NULL ) { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "delete procedure is not defined " + "for this objectclass - aborting\n" ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "operation not permitted within namingContext"; + e = NULL; + goto done; + } + + /* + * Get the parent + */ + e_id = bsi.bsi_base_id; + memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) ); + if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_ndn, &pdn ); + bsi.bsi_e = &p; + rs->sr_err = backsql_init_search( &bsi, &pdn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_no_attrs, + BACKSQL_ISF_GET_ENTRY ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_delete(): " + "could not retrieve deleteDN ID " + "- no such entry\n" ); + e = &p; + goto done; + } + + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + /* check parent for "children" acl */ + if ( !access_allowed( op, &p, slap_schema.si_ad_children, + NULL, ACL_WDEL, NULL ) ) + { + Debug( LDAP_DEBUG_TRACE, " backsql_delete(): " + "no write access to parent\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &p; + goto done; + + } + } + + e = &d; +#ifdef SLAP_CONTROL_X_TREE_DELETE + if ( get_treeDelete( op ) ) { + backsql_tree_delete( op, rs, dbh, &sth ); + if ( rs->sr_err == LDAP_OTHER || rs->sr_err == LDAP_SUCCESS ) + { + e = NULL; + } + + } else +#endif /* SLAP_CONTROL_X_TREE_DELETE */ + { + backsql_delete_int( op, rs, dbh, &sth, &e_id, &e ); + } + + /* + * Commit only if all operations succeed + */ + if ( sth != SQL_NULL_HSTMT ) { + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + assert( e == NULL ); + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + } + +done:; + if ( e != NULL ) { + if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_delete()\n" ); + + if ( !BER_BVISNULL( &e_id.eid_ndn ) ) { + (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &d.e_nname ) ) { + backsql_entry_clean( op, &d ); + } + + if ( !BER_BVISNULL( &p.e_nname ) ) { + backsql_entry_clean( op, &p ); + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/docs/bugs b/servers/slapd/back-sql/docs/bugs new file mode 100644 index 0000000..4b2f3c7 --- /dev/null +++ b/servers/slapd/back-sql/docs/bugs @@ -0,0 +1,16 @@ +1) driver name comparison for MS SQL Server workaround is really kinda dirty + hack, but for now i don't know how to code it more carefully +2) another dirty hack: length of LONGVARCHAR and LONGVARBINARY fields is + currently set to MAX_ATTR_LEN. Maybe such fields must be handled with + SQLGetData() instead of SQLBindCol(), but it is said in documentation, + that it is guaranteed to work only when such column goes after last bound + column. Or should we get ALL columns with SQLGetData (then something like + _SQLFetchAsStrings() wrapper would do SQLGetData() for all columns)... +3) in some cases (particularly, when using OpenLink Generic ODBC driver with + MS SQL Server), it returns "Function sequence error" after all records are + fetched. I really don't know what it means, and after all + - it works with any other driver I tried +4) ldapsearch sometimes refuses to show some attributes ("NOT PRINTABLE" diags) + on Win32 (on linux everything's fine) +5) back-sql crashes on invalid filters (to be fixed ASAP) +
\ No newline at end of file diff --git a/servers/slapd/back-sql/docs/concept b/servers/slapd/back-sql/docs/concept new file mode 100644 index 0000000..ed29047 --- /dev/null +++ b/servers/slapd/back-sql/docs/concept @@ -0,0 +1 @@ +The SQL backend is described in the slapd-sql(5) manual page. diff --git a/servers/slapd/back-sql/docs/install b/servers/slapd/back-sql/docs/install new file mode 100644 index 0000000..230bf0a --- /dev/null +++ b/servers/slapd/back-sql/docs/install @@ -0,0 +1,86 @@ +PLEASE READ THIS WHOLE FILE AND CONCEPT, BECAUSE THEY COVER SEVERAL STICKY +ISSUES THAT YOU WILL PROBABLY STUMBLE ACROSS ANYWAY + +1. Build +To build slapd with back-sql under Unix you need to build and install +iODBC 2.50.3 (later versions should probably work, but not earlier), +or unixODBC (you will have to change -liodbc to -lodbc then). +Then, at top of OpenLDAP source tree, run +"configure <other options you need> --enable-sql", then "make" - +this should build back-sql-enabled slapd, provided that you have iODBC/unixODBC +libraries and include files in include/library paths, "make install"... +In other words, follow installation procedure described in OpenLDAP +Administrators Guide, adding --enable-sql option to configure, and +having iODBC/unixODBC libraries installed an accessible by compiler. + +Under Win32/MSVC++, I modified the workspace so that back-sql is built into +slapd automatically, since MS ODBC manager, odbc32.dll, is included in +standard library pack, and it does no bad even if you don't plan to use it. +I also could provide precompiled executables for those who don't have MSVC. +Note that Win32 port of OpenLDAP itself is experimental, and thus doesn't +provide very convenient build environment (yet). + +2. Tune datasources and slapd.conf +Next, you need to define ODBC datasource with data you want to publish +with help of back-sql. Assuming that you have your data in some SQL-compliant +RDBMS, and have installed proper ODBC driver for this RDBMS, this is as simple +as adding a record into odbc.ini (for iODBC/unixODBC), or using ODBC wizard in +Control Panel (for odbc32). +Next, you need to add appropriate "database" record to your slapd.conf. +See samples provided in "back-sql/RDBMS_DEPENDENT/" subdirectory. + +Several things worth noting about ODBC: +- "dbname" directive stands for ODBC datasource name (DSN), + not the name of your database in RDBMS context +- ODBC under Unix is not so common as under Windows, so you could have + problems with Unix drivers for your RDBMS. Visit http://www.openlinksw.com, + they provide a multitier solution which allows connecting to DBMSes on + different platforms, proxying and other connectivity and integration issues. + They also support iODBC, and have good free customer service through + newsserver (at news.openlinksw.com). + Also worth noting are: ODBC-ODBC bridge by EasySoft (which was claimed + by several people to be far more effective and stable than OpenLink), + OpenRDA package etc. +- be careful defining RDBMS connection parameters, you'll probably need only + "dbname" directive - all the rest can be defined in datasource. Every other + directive is used to override value stored in datasource definition. + Maybe you will want to use dbuser/dbpasswd to override credentials defined in datasource +- full list of configuration directives supported is available in file "guide", + you may also analyze output of 'slapd -d 5' to find out some useful + directives for redefining default queries + +3. Creating and using back-sql metatables +Read the file "concept" to understand, what metainformation you need to add, +and what for... ;) +See SQL scripts and slapd.conf files in samples directory. +Find subdirectory in "rdbms_depend/" corresponding to your RDBMS (Oracle, +MS SQL Server and mySQL are listed there currently), or copy and edit +any of these to conform to SQL dialect of your RDBMS (please be sure to send +me scripts and notes for new RDBMSes ;). + +Execute "backsql_create.sql" from that subdirectory (or edited one), +so that the tables it creates appear in the same +context with the data you want to export through LDAP (under same DB/user, +or whatever is needed in RDBMS you use). You can use something like +"mysql < xxx.sql" for mySQL, Query Analyzer+Open query file for MS SQL, +sqlplus and "@xxx.sql" for Oracle. + +You may well want to try it with test data first, and see how metatables +are used. Create test data and metadata by running testdb_create.sql, +testdb_data.sql, and testdb_metadata.sql scripts (again, adopted for your +RDBMS, and in the same context as metatables you created before), and +tune slapd.conf to use your test DB. + +4. Testing +To diagnose back-sql, run slapd with debug level TRACE ("slapd -d 5" will go). +Then, use some LDAP client to query corresponding subtree (for test database, +you could for instance search one level from "o=sql,c=RU"). I personally used +saucer, which is included in OpenLDAP package (it builds automatically under +Unix/GNU configure and for MSVC I added appropriate project to workspace). +And also Java LDAP browser-editor (see link somewhere on OpenLDAP site) to +test ADD/DELETE/MODIFY operations on Oracle and MS SQL. + +See file "platforms" if you encounter connection problems - you may find +a hint for your RDBMS or OS there. If you are stuck - please contact me at +mit@openldap.org, or (better) post an issue through OpenLDAP's Issue Tracking +System (see http:/www.openldap.org/its). diff --git a/servers/slapd/back-sql/docs/platforms b/servers/slapd/back-sql/docs/platforms new file mode 100644 index 0000000..65e326a --- /dev/null +++ b/servers/slapd/back-sql/docs/platforms @@ -0,0 +1,8 @@ +Platforms and configurations it has been tested on: + +General: + - ODBC managers: iODBC,unixODBC under unixes, odbc32.dll under Win32 family + - OSes: Linux/glibc, FreeBSD, OpenBSD, Solaris 2.6, Win98, WinNT, Win2000 server + - RDBMSes: Oracle 7/8/8i, MS SQL Server 6.5/7, mySQL + - access suites: OpenLink DAS, EasySoft OOB, various win32 drivers + diff --git a/servers/slapd/back-sql/docs/todo b/servers/slapd/back-sql/docs/todo new file mode 100644 index 0000000..9d8736f --- /dev/null +++ b/servers/slapd/back-sql/docs/todo @@ -0,0 +1,12 @@ +1) must add alias handling +2) [sizelimit moved to frontend] + must set time limit when preparing all queries, and check size limit +3) there was expressed a need to have access to IP in while constructing + queries, to have response alter in accordance to client IP. Will add + preprocessor for values in metatables, which would substitute things + like "$IP$". +4) must handle NOT filters (see ITS#2652) +5) must map attribute types and syntaxes between LDAP and SQL types (e.g. + use BLOBs for octet streams) +6) must define another mech to add auxiliary objectClass to all entries + according to ldap_at_mappings (ldap_entry_objclasses has limitations) diff --git a/servers/slapd/back-sql/entry-id.c b/servers/slapd/back-sql/entry-id.c new file mode 100644 index 0000000..c2d78a7 --- /dev/null +++ b/servers/slapd/back-sql/entry-id.c @@ -0,0 +1,1092 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "lutil.h" +#include "slap.h" +#include "proto-sql.h" + +#ifdef BACKSQL_ARBITRARY_KEY +struct berval backsql_baseObject_bv = BER_BVC( BACKSQL_BASEOBJECT_IDSTR ); +#endif /* BACKSQL_ARBITRARY_KEY */ + +backsql_entryID * +backsql_entryID_dup( backsql_entryID *src, void *ctx ) +{ + backsql_entryID *dst; + + if ( src == NULL ) return NULL; + + dst = slap_sl_calloc( 1, sizeof( backsql_entryID ), ctx ); + ber_dupbv_x( &dst->eid_ndn, &src->eid_ndn, ctx ); + if ( src->eid_dn.bv_val == src->eid_ndn.bv_val ) { + dst->eid_dn = dst->eid_ndn; + } else { + ber_dupbv_x( &dst->eid_dn, &src->eid_dn, ctx ); + } + +#ifdef BACKSQL_ARBITRARY_KEY + ber_dupbv_x( &dst->eid_id, &src->eid_id, ctx ); + ber_dupbv_x( &dst->eid_keyval, &src->eid_keyval, ctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + dst->eid_id = src->eid_id; + dst->eid_keyval = src->eid_keyval; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + dst->eid_oc = src->eid_oc; + dst->eid_oc_id = src->eid_oc_id; + + return dst; +} + +backsql_entryID * +backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx ) +{ + backsql_entryID *next; + + assert( id != NULL ); + + next = id->eid_next; + + if ( !BER_BVISNULL( &id->eid_ndn ) ) { + if ( !BER_BVISNULL( &id->eid_dn ) + && id->eid_dn.bv_val != id->eid_ndn.bv_val ) + { + slap_sl_free( id->eid_dn.bv_val, ctx ); + BER_BVZERO( &id->eid_dn ); + } + + slap_sl_free( id->eid_ndn.bv_val, ctx ); + BER_BVZERO( &id->eid_ndn ); + } + +#ifdef BACKSQL_ARBITRARY_KEY + if ( !BER_BVISNULL( &id->eid_id ) ) { + slap_sl_free( id->eid_id.bv_val, ctx ); + BER_BVZERO( &id->eid_id ); + } + + if ( !BER_BVISNULL( &id->eid_keyval ) ) { + slap_sl_free( id->eid_keyval.bv_val, ctx ); + BER_BVZERO( &id->eid_keyval ); + } +#endif /* BACKSQL_ARBITRARY_KEY */ + + if ( freeit ) { + slap_sl_free( id, ctx ); + } + + return next; +} + +/* + * NOTE: the dn must be normalized + */ +int +backsql_dn2id( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + struct berval *ndn, + backsql_entryID *id, + int matched, + int muck ) +{ + backsql_info *bi = op->o_bd->be_private; + SQLHSTMT sth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row = { 0 }; + RETCODE rc; + int res; + struct berval realndn = BER_BVNULL; + + /* TimesTen */ + char upperdn[ BACKSQL_MAX_DN_LEN + 1 ]; + struct berval tbbDN; + int i, j; + + /* + * NOTE: id can be NULL; in this case, the function + * simply checks whether the DN can be successfully + * turned into an ID, returning LDAP_SUCCESS for + * positive cases, or the most appropriate error + */ + + Debug( LDAP_DEBUG_TRACE, "==>backsql_dn2id(\"%s\")%s%s\n", + ndn->bv_val, id == NULL ? " (no ID expected)" : "", + matched ? " matched expected" : "" ); + + if ( id ) { + /* NOTE: trap inconsistencies */ + assert( BER_BVISNULL( &id->eid_ndn ) ); + } + + if ( ndn->bv_len > BACKSQL_MAX_DN_LEN ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): DN length=%ld " + "exceeds max DN length %d:\n", + ndn->bv_val, ndn->bv_len, BACKSQL_MAX_DN_LEN ); + return LDAP_OTHER; + } + + /* return baseObject if available and matches */ + /* FIXME: if ndn is already mucked, we cannot check this */ + if ( bi->sql_baseObject != NULL && + dn_match( ndn, &bi->sql_baseObject->e_nname ) ) + { + if ( id != NULL ) { +#ifdef BACKSQL_ARBITRARY_KEY + ber_dupbv_x( &id->eid_id, &backsql_baseObject_bv, + op->o_tmpmemctx ); + ber_dupbv_x( &id->eid_keyval, &backsql_baseObject_bv, + op->o_tmpmemctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + id->eid_id = BACKSQL_BASEOBJECT_ID; + id->eid_keyval = BACKSQL_BASEOBJECT_KEYVAL; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + id->eid_oc_id = BACKSQL_BASEOBJECT_OC; + + ber_dupbv_x( &id->eid_ndn, &bi->sql_baseObject->e_nname, + op->o_tmpmemctx ); + ber_dupbv_x( &id->eid_dn, &bi->sql_baseObject->e_name, + op->o_tmpmemctx ); + + id->eid_next = NULL; + } + + return LDAP_SUCCESS; + } + + /* begin TimesTen */ + assert( bi->sql_id_query != NULL ); + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): id_query \"%s\"\n", + ndn->bv_val, bi->sql_id_query ); + rc = backsql_Prepare( dbh, &sth, bi->sql_id_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): " + "error preparing SQL:\n %s", + ndn->bv_val, bi->sql_id_query ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + res = LDAP_OTHER; + goto done; + } + + realndn = *ndn; + if ( muck ) { + if ( backsql_api_dn2odbc( op, rs, &realndn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "backsql_api_dn2odbc(\"%s\") failed\n", + ndn->bv_val, realndn.bv_val ); + res = LDAP_OTHER; + goto done; + } + } + + if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) { + /* + * Prepare an upper cased, byte reversed version + * that can be searched using indexes + */ + + for ( i = 0, j = realndn.bv_len - 1; realndn.bv_val[ i ]; i++, j--) + { + upperdn[ i ] = realndn.bv_val[ j ]; + } + upperdn[ i ] = '\0'; + ldap_pvt_str2upper( upperdn ); + + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "upperdn=\"%s\"\n", + ndn->bv_val, upperdn ); + ber_str2bv( upperdn, 0, 0, &tbbDN ); + + } else { + if ( BACKSQL_USE_REVERSE_DN( bi ) ) { + AC_MEMCPY( upperdn, realndn.bv_val, realndn.bv_len + 1 ); + ldap_pvt_str2upper( upperdn ); + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): " + "upperdn=\"%s\"\n", + ndn->bv_val, upperdn ); + ber_str2bv( upperdn, 0, 0, &tbbDN ); + + } else { + tbbDN = realndn; + } + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &tbbDN ); + if ( rc != SQL_SUCCESS) { + /* end TimesTen */ + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "error binding dn=\"%s\" parameter:\n", + ndn->bv_val, tbbDN.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + res = LDAP_OTHER; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): " + "error executing query (\"%s\", \"%s\"):\n", + ndn->bv_val, bi->sql_id_query, tbbDN.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + res = LDAP_OTHER; + goto done; + } + + backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx ); + rc = SQLFetch( sth ); + if ( BACKSQL_SUCCESS( rc ) ) { +#ifdef LDAP_DEBUG + Debug(LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): id=%s keyval=%s oc_id=%s dn=%s\n", + ndn->bv_val, row.cols[0], row.cols[1], row.cols[2], + row.cols[3] ); +#endif /* LDAP_DEBUG */ + + res = LDAP_SUCCESS; + if ( id != NULL ) { + struct berval dn; + + id->eid_next = NULL; + +#ifdef BACKSQL_ARBITRARY_KEY + ber_str2bv_x( row.cols[ 0 ], 0, 1, &id->eid_id, + op->o_tmpmemctx ); + ber_str2bv_x( row.cols[ 1 ], 0, 1, &id->eid_keyval, + op->o_tmpmemctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + if ( BACKSQL_STR2ID( &id->eid_id, row.cols[ 0 ], 0 ) != 0 ) { + res = LDAP_OTHER; + goto done; + } + if ( BACKSQL_STR2ID( &id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) { + res = LDAP_OTHER; + goto done; + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + if ( BACKSQL_STR2ID( &id->eid_oc_id, row.cols[ 2 ], 0 ) != 0 ) { + res = LDAP_OTHER; + goto done; + } + + ber_str2bv( row.cols[ 3 ], 0, 0, &dn ); + + if ( backsql_api_odbc2dn( op, rs, &dn ) ) { + res = LDAP_OTHER; + goto done; + } + + res = dnPrettyNormal( NULL, &dn, + &id->eid_dn, &id->eid_ndn, + op->o_tmpmemctx ); + if ( res != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_dn2id(\"%s\"): " + "dnPrettyNormal failed (%d: %s)\n", + realndn.bv_val, res, + ldap_err2string( res ) ); + + /* cleanup... */ + (void)backsql_free_entryID( id, 0, op->o_tmpmemctx ); + } + + if ( dn.bv_val != row.cols[ 3 ] ) { + free( dn.bv_val ); + } + } + + } else { + res = LDAP_NO_SUCH_OBJECT; + if ( matched ) { + struct berval pdn = *ndn; + + /* + * Look for matched + */ + rs->sr_matched = NULL; + while ( !be_issuffix( op->o_bd, &pdn ) ) { + char *matchedDN = NULL; + + dnParent( &pdn, &pdn ); + + /* + * Empty DN ("") defaults to LDAP_SUCCESS + */ + rs->sr_err = backsql_dn2id( op, rs, dbh, &pdn, id, 0, 1 ); + switch ( rs->sr_err ) { + case LDAP_NO_SUCH_OBJECT: + /* try another one */ + break; + + case LDAP_SUCCESS: + matchedDN = pdn.bv_val; + /* fail over to next case */ + + default: + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_matched = matchedDN; + goto done; + } + } + } + } + +done:; + backsql_FreeRow_x( &row, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<==backsql_dn2id(\"%s\"): err=%d\n", + ndn->bv_val, res ); + if ( sth != SQL_NULL_HSTMT ) { + SQLFreeStmt( sth, SQL_DROP ); + } + + if ( !BER_BVISNULL( &realndn ) && realndn.bv_val != ndn->bv_val ) { + ch_free( realndn.bv_val ); + } + + return res; +} + +int +backsql_count_children( + Operation *op, + SQLHDBC dbh, + struct berval *dn, + unsigned long *nchildren ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + SQLHSTMT sth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row; + RETCODE rc; + int res = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_count_children(): dn=\"%s\"\n", + dn->bv_val ); + + if ( dn->bv_len > BACKSQL_MAX_DN_LEN ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_count_children(): DN \"%s\" (%ld bytes) " + "exceeds max DN length (%d):\n", + dn->bv_val, dn->bv_len, BACKSQL_MAX_DN_LEN ); + return LDAP_OTHER; + } + + /* begin TimesTen */ + assert( bi->sql_has_children_query != NULL ); + Debug(LDAP_DEBUG_TRACE, "children id query \"%s\"\n", + bi->sql_has_children_query ); + rc = backsql_Prepare( dbh, &sth, bi->sql_has_children_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_count_children(): error preparing SQL:\n%s", + bi->sql_has_children_query ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, dn ); + if ( rc != SQL_SUCCESS) { + /* end TimesTen */ + Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): " + "error binding dn=\"%s\" parameter:\n", + dn->bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): " + "error executing query (\"%s\", \"%s\"):\n", + bi->sql_has_children_query, dn->bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx ); + + rc = SQLFetch( sth ); + if ( BACKSQL_SUCCESS( rc ) ) { + char *end; + + *nchildren = strtol( row.cols[ 0 ], &end, 0 ); + if ( end == row.cols[ 0 ] ) { + res = LDAP_OTHER; + + } else { + switch ( end[ 0 ] ) { + case '\0': + break; + + case '.': { + unsigned long ul; + + /* FIXME: braindead RDBMSes return + * a fractional number from COUNT! + */ + if ( lutil_atoul( &ul, end + 1 ) != 0 || ul != 0 ) { + res = LDAP_OTHER; + } + } break; + + default: + res = LDAP_OTHER; + } + } + + } else { + res = LDAP_OTHER; + } + backsql_FreeRow_x( &row, op->o_tmpmemctx ); + + SQLFreeStmt( sth, SQL_DROP ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_count_children(): %lu\n", + *nchildren ); + + return res; +} + +int +backsql_has_children( + Operation *op, + SQLHDBC dbh, + struct berval *dn ) +{ + unsigned long nchildren; + int rc; + + rc = backsql_count_children( op, dbh, dn, &nchildren ); + + if ( rc == LDAP_SUCCESS ) { + return nchildren > 0 ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE; + } + + return rc; +} + +static int +backsql_get_attr_vals( void *v_at, void *v_bsi ) +{ + backsql_at_map_rec *at = v_at; + backsql_srch_info *bsi = v_bsi; + backsql_info *bi; + RETCODE rc; + SQLHSTMT sth = SQL_NULL_HSTMT; + BACKSQL_ROW_NTS row; + unsigned long i, + k = 0, + oldcount = 0, + res = 0; +#ifdef BACKSQL_COUNTQUERY + unsigned count, + j, + append = 0; + SQLLEN countsize = sizeof( count ); + Attribute *attr = NULL; + + slap_mr_normalize_func *normfunc = NULL; +#endif /* BACKSQL_COUNTQUERY */ +#ifdef BACKSQL_PRETTY_VALIDATE + slap_syntax_validate_func *validate = NULL; + slap_syntax_transform_func *pretty = NULL; +#endif /* BACKSQL_PRETTY_VALIDATE */ + + assert( at != NULL ); + assert( bsi != NULL ); + Debug( LDAP_DEBUG_TRACE, "==>backsql_get_attr_vals(): " + "oc=\"%s\" attr=\"%s\" keyval=" BACKSQL_IDFMT "\n", + BACKSQL_OC_NAME( bsi->bsi_oc ), at->bam_ad->ad_cname.bv_val, + BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval) ); + + bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + +#ifdef BACKSQL_PRETTY_VALIDATE + validate = at->bam_true_ad->ad_type->sat_syntax->ssyn_validate; + pretty = at->bam_true_ad->ad_type->sat_syntax->ssyn_pretty; + + if ( validate == NULL && pretty == NULL ) { + return 1; + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + +#ifdef BACKSQL_COUNTQUERY + if ( at->bam_true_ad->ad_type->sat_equality ) { + normfunc = at->bam_true_ad->ad_type->sat_equality->smr_normalize; + } + + /* Count how many rows will be returned. This avoids memory + * fragmentation that can result from loading the values in + * one by one and using realloc() + */ + rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_countquery, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error preparing count query: %s\n", + at->bam_countquery ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + return 1; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, + &bsi->bsi_c_eid->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error binding key value parameter\n" ); + SQLFreeStmt( sth, SQL_DROP ); + return 1; + } + + rc = SQLExecute( sth ); + if ( ! BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error executing attribute count query '%s'\n", + at->bam_countquery ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return 1; + } + + SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_LONG, + (SQLPOINTER)&count, + (SQLINTEGER)sizeof( count ), + &countsize ); + + rc = SQLFetch( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error fetch results of count query: %s\n", + at->bam_countquery ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return 1; + } + + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "number of values in query: %u\n", count ); + SQLFreeStmt( sth, SQL_DROP ); + if ( count == 0 ) { + return 1; + } + + attr = attr_find( bsi->bsi_e->e_attrs, at->bam_true_ad ); + if ( attr != NULL ) { + BerVarray tmp; + + if ( attr->a_vals != NULL ) { + oldcount = attr->a_numvals; + } + + tmp = ch_realloc( attr->a_vals, ( oldcount + count + 1 ) * sizeof( struct berval ) ); + if ( tmp == NULL ) { + return 1; + } + attr->a_vals = tmp; + memset( &attr->a_vals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) ); + + if ( normfunc ) { + tmp = ch_realloc( attr->a_nvals, ( oldcount + count + 1 ) * sizeof( struct berval ) ); + if ( tmp == NULL ) { + return 1; + } + attr->a_nvals = tmp; + memset( &attr->a_nvals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) ); + + } else { + attr->a_nvals = attr->a_vals; + } + attr->a_numvals += count; + + } else { + append = 1; + + /* Make space for the array of values */ + attr = attr_alloc( at->bam_true_ad ); + attr->a_numvals = count; + attr->a_vals = ch_calloc( count + 1, sizeof( struct berval ) ); + if ( attr->a_vals == NULL ) { + Debug( LDAP_DEBUG_TRACE, "Out of memory!\n" ); + ch_free( attr ); + return 1; + } + if ( normfunc ) { + attr->a_nvals = ch_calloc( count + 1, sizeof( struct berval ) ); + if ( attr->a_nvals == NULL ) { + ch_free( attr->a_vals ); + ch_free( attr ); + return 1; + + } + + } else { + attr->a_nvals = attr->a_vals; + } + } +#endif /* BACKSQL_COUNTQUERY */ + + rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error preparing query: %s\n", at->bam_query ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); +#ifdef BACKSQL_COUNTQUERY + if ( append ) { + attr_free( attr ); + } +#endif /* BACKSQL_COUNTQUERY */ + return 1; + } + + rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, + &bsi->bsi_c_eid->eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error binding key value parameter\n" ); +#ifdef BACKSQL_COUNTQUERY + if ( append ) { + attr_free( attr ); + } +#endif /* BACKSQL_COUNTQUERY */ + return 1; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "query=\"%s\" keyval=" BACKSQL_IDFMT "\n", at->bam_query, + BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval) ); +#endif /* BACKSQL_TRACE */ + + rc = SQLExecute( sth ); + if ( ! BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): " + "error executing attribute query \"%s\"\n", + at->bam_query ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); +#ifdef BACKSQL_COUNTQUERY + if ( append ) { + attr_free( attr ); + } +#endif /* BACKSQL_COUNTQUERY */ + return 1; + } + + backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx ); +#ifdef BACKSQL_COUNTQUERY + j = oldcount; +#endif /* BACKSQL_COUNTQUERY */ + for ( rc = SQLFetch( sth ), k = 0; + BACKSQL_SUCCESS( rc ); + rc = SQLFetch( sth ), k++ ) + { + for ( i = 0; i < (unsigned long)row.ncols; i++ ) { + + if ( row.value_len[ i ] > 0 ) { + struct berval bv; + int retval; +#ifdef BACKSQL_TRACE + AttributeDescription *ad = NULL; + const char *text; + + retval = slap_bv2ad( &row.col_names[ i ], &ad, &text ); + if ( retval != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "==>backsql_get_attr_vals(\"%s\"): " + "unable to find AttributeDescription %s " + "in schema (%d)\n", + bsi->bsi_e->e_name.bv_val, + row.col_names[ i ].bv_val, retval ); + res = 1; + goto done; + } + + if ( ad != at->bam_ad ) { + Debug( LDAP_DEBUG_ANY, + "==>backsql_get_attr_vals(\"%s\"): " + "column name %s differs from " + "AttributeDescription %s\n", + bsi->bsi_e->e_name.bv_val, + ad->ad_cname.bv_val, + at->bam_ad->ad_cname.bv_val ); + res = 1; + goto done; + } +#endif /* BACKSQL_TRACE */ + + /* ITS#3386, ITS#3113 - 20070308 + * If a binary is fetched? + * must use the actual size read + * from the database. + */ + if ( BACKSQL_IS_BINARY( row.col_type[ i ] ) ) { +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_ANY, + "==>backsql_get_attr_vals(\"%s\"): " + "column name %s: data is binary; " + "using database size %ld\n", + bsi->bsi_e->e_name.bv_val, + ad->ad_cname.bv_val, + row.value_len[ i ] ); +#endif /* BACKSQL_TRACE */ + bv.bv_val = row.cols[ i ]; + bv.bv_len = row.value_len[ i ]; + + } else { + ber_str2bv( row.cols[ i ], 0, 0, &bv ); + } + +#ifdef BACKSQL_PRETTY_VALIDATE + if ( pretty ) { + struct berval pbv; + + retval = pretty( at->bam_true_ad->ad_type->sat_syntax, + &bv, &pbv, bsi->bsi_op->o_tmpmemctx ); + bv = pbv; + + } else { + retval = validate( at->bam_true_ad->ad_type->sat_syntax, + &bv ); + } + + if ( retval != LDAP_SUCCESS ) { + /* FIXME: we're ignoring invalid values, + * but we're accepting the attributes; + * should we fail at all? */ + Debug(LDAP_DEBUG_TRACE, + "==>backsql_get_attr_vals(\"%s\"): " "unable to %s value #%lu " "of AttributeDescription %s (%d)\n", + bsi->bsi_e->e_name.bv_val, + pretty ? "prettify" : "validate", + k - oldcount, + at->bam_ad->ad_cname.bv_val, + retval ); + continue; + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + +#ifndef BACKSQL_COUNTQUERY + (void)backsql_entry_addattr( bsi->bsi_e, + at->bam_true_ad, &bv, + bsi->bsi_op->o_tmpmemctx ); + +#else /* BACKSQL_COUNTQUERY */ + if ( normfunc ) { + struct berval nbv; + + retval = (*normfunc)( SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + at->bam_true_ad->ad_type->sat_syntax, + at->bam_true_ad->ad_type->sat_equality, + &bv, &nbv, + bsi->bsi_op->o_tmpmemctx ); + + if ( retval != LDAP_SUCCESS ) { + /* FIXME: we're ignoring invalid values, + * but we're accepting the attributes; + * should we fail at all? */ + Debug(LDAP_DEBUG_TRACE, + "==>backsql_get_attr_vals(\"%s\"): " "unable to normalize value #%lu " "of AttributeDescription %s (%d)\n", + bsi->bsi_e->e_name.bv_val, + k - oldcount, + at->bam_ad->ad_cname.bv_val, + retval ); + +#ifdef BACKSQL_PRETTY_VALIDATE + if ( pretty ) { + bsi->bsi_op->o_tmpfree( bv.bv_val, + bsi->bsi_op->o_tmpmemctx ); + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + + continue; + } + ber_dupbv( &attr->a_nvals[ j ], &nbv ); + bsi->bsi_op->o_tmpfree( nbv.bv_val, + bsi->bsi_op->o_tmpmemctx ); + } + + ber_dupbv( &attr->a_vals[ j ], &bv ); + + assert( j < oldcount + count ); + j++; +#endif /* BACKSQL_COUNTQUERY */ + +#ifdef BACKSQL_PRETTY_VALIDATE + if ( pretty ) { + bsi->bsi_op->o_tmpfree( bv.bv_val, + bsi->bsi_op->o_tmpmemctx ); + } +#endif /* BACKSQL_PRETTY_VALIDATE */ + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "prec=%d\n", + (int)row.col_prec[ i ] ); + + } else { + Debug( LDAP_DEBUG_TRACE, "NULL value " + "in this row for attribute \"%s\"\n", + row.col_names[ i ].bv_val ); +#endif /* BACKSQL_TRACE */ + } + } + } + +#ifdef BACKSQL_COUNTQUERY + if ( BER_BVISNULL( &attr->a_vals[ 0 ] ) ) { + /* don't leave around attributes with no values */ + attr_free( attr ); + + } else if ( append ) { + Attribute **ap; + + for ( ap = &bsi->bsi_e->e_attrs; (*ap) != NULL; ap = &(*ap)->a_next ) + /* goto last */ ; + *ap = attr; + } +#endif /* BACKSQL_COUNTQUERY */ + + SQLFreeStmt( sth, SQL_DROP ); + Debug( LDAP_DEBUG_TRACE, "<==backsql_get_attr_vals()\n" ); + + if ( at->bam_next ) { + res = backsql_get_attr_vals( at->bam_next, v_bsi ); + } else { + res = 1; + } + +#ifdef BACKSQL_TRACE +done:; +#endif /* BACKSQL_TRACE */ + backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx ); + + return res; +} + +int +backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *eid ) +{ + Operation *op = bsi->bsi_op; + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + int i; + int rc; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_id2entry()\n" ); + + assert( bsi->bsi_e != NULL ); + + memset( bsi->bsi_e, 0, sizeof( Entry ) ); + + if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) { + (void)entry_dup2( bsi->bsi_e, bi->sql_baseObject ); + goto done; + } + + bsi->bsi_e->e_attrs = NULL; + bsi->bsi_e->e_private = NULL; + + if ( eid->eid_oc == NULL ) { + eid->eid_oc = backsql_id2oc( bsi->bsi_op->o_bd->be_private, + eid->eid_oc_id ); + if ( eid->eid_oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_id2entry(): unable to fetch objectClass with id=" BACKSQL_IDNUMFMT " for entry id=" BACKSQL_IDFMT " dn=\"%s\"\n", + eid->eid_oc_id, BACKSQL_IDARG(eid->eid_id), + eid->eid_dn.bv_val ); + return LDAP_OTHER; + } + } + bsi->bsi_oc = eid->eid_oc; + bsi->bsi_c_eid = eid; + + ber_dupbv_x( &bsi->bsi_e->e_name, &eid->eid_dn, op->o_tmpmemctx ); + ber_dupbv_x( &bsi->bsi_e->e_nname, &eid->eid_ndn, op->o_tmpmemctx ); + +#ifndef BACKSQL_ARBITRARY_KEY + /* FIXME: unused */ + bsi->bsi_e->e_id = eid->eid_id; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + rc = attr_merge_normalize_one( bsi->bsi_e, + slap_schema.si_ad_objectClass, + &bsi->bsi_oc->bom_oc->soc_cname, + bsi->bsi_op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + + if ( bsi->bsi_attrs == NULL || ( bsi->bsi_flags & BSQL_SF_ALL_USER ) ) + { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): " + "retrieving all attributes\n" ); + ldap_avl_apply( bsi->bsi_oc->bom_attrs, backsql_get_attr_vals, + bsi, 0, AVL_INORDER ); + + } else { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): " + "custom attribute list\n" ); + for ( i = 0; !BER_BVISNULL( &bsi->bsi_attrs[ i ].an_name ); i++ ) { + backsql_at_map_rec **vat; + AttributeName *an = &bsi->bsi_attrs[ i ]; + int j; + + /* if one of the attributes listed here is + * a subtype of another, it must be ignored, + * because subtypes are already dealt with + * by backsql_supad2at() + */ + for ( j = 0; !BER_BVISNULL( &bsi->bsi_attrs[ j ].an_name ); j++ ) { + /* skip self */ + if ( j == i ) { + continue; + } + + /* skip subtypes */ + if ( is_at_subtype( an->an_desc->ad_type, + bsi->bsi_attrs[ j ].an_desc->ad_type ) ) + { + goto next; + } + } + + rc = backsql_supad2at( bsi->bsi_oc, an->an_desc, &vat ); + if ( rc != 0 || vat == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): " + "attribute \"%s\" is not defined " + "for objectclass \"%s\"\n", + an->an_name.bv_val, + BACKSQL_OC_NAME( bsi->bsi_oc ) ); + continue; + } + + for ( j = 0; vat[j]; j++ ) { + backsql_get_attr_vals( vat[j], bsi ); + } + + ch_free( vat ); + +next:; + } + } + + if ( bsi->bsi_flags & BSQL_SF_RETURN_ENTRYUUID ) { + Attribute *a_entryUUID, + **ap; + + a_entryUUID = backsql_operational_entryUUID( bi, eid ); + if ( a_entryUUID != NULL ) { + for ( ap = &bsi->bsi_e->e_attrs; + *ap; + ap = &(*ap)->a_next ); + + *ap = a_entryUUID; + } + } + + if ( ( bsi->bsi_flags & BSQL_SF_ALL_OPER ) + || an_find( bsi->bsi_attrs, slap_bv_all_operational_attrs ) + || an_find( bsi->bsi_attrs, &slap_schema.si_ad_structuralObjectClass->ad_cname ) ) + { + ObjectClass *soc = NULL; + + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + Attribute *a; + const char *text = NULL; + char textbuf[ 1024 ]; + size_t textlen = sizeof( textbuf ); + struct berval bv[ 2 ], + *nvals; + int rc = LDAP_SUCCESS; + + a = attr_find( bsi->bsi_e->e_attrs, + slap_schema.si_ad_objectClass ); + if ( a != NULL ) { + nvals = a->a_nvals; + + } else { + bv[ 0 ] = bsi->bsi_oc->bom_oc->soc_cname; + BER_BVZERO( &bv[ 1 ] ); + nvals = bv; + } + + rc = structural_class( nvals, &soc, NULL, + &text, textbuf, textlen, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): " + "structural_class() failed %d (%s)\n", + bsi->bsi_e->e_name.bv_val, + rc, text ? text : "" ); + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + + if ( !bvmatch( &soc->soc_cname, &bsi->bsi_oc->bom_oc->soc_cname ) ) { + if ( !is_object_subclass( bsi->bsi_oc->bom_oc, soc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): " + "computed structuralObjectClass %s " + "does not match objectClass %s associated " + "to entry\n", + bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val, + bsi->bsi_oc->bom_oc->soc_cname.bv_val ); + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + + Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): " + "computed structuralObjectClass %s " + "is subclass of objectClass %s associated " + "to entry\n", + bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val, + bsi->bsi_oc->bom_oc->soc_cname.bv_val ); + } + + } else { + soc = bsi->bsi_oc->bom_oc; + } + + rc = attr_merge_normalize_one( bsi->bsi_e, + slap_schema.si_ad_structuralObjectClass, + &soc->soc_cname, + bsi->bsi_op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + backsql_entry_clean( op, bsi->bsi_e ); + return rc; + } + } + +done:; + Debug( LDAP_DEBUG_TRACE, "<==backsql_id2entry()\n" ); + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-sql/init.c b/servers/slapd/back-sql/init.c new file mode 100644 index 0000000..1b45f8f --- /dev/null +++ b/servers/slapd/back-sql/init.c @@ -0,0 +1,661 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "slap-config.h" +#include "proto-sql.h" + +int +sql_back_initialize( + BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, +#ifdef SLAP_CONTROL_X_TREE_DELETE + SLAP_CONTROL_X_TREE_DELETE, +#endif /* SLAP_CONTROL_X_TREE_DELETE */ +#ifndef BACKSQL_ARBITRARY_KEY + LDAP_CONTROL_PAGEDRESULTS, +#endif /* ! BACKSQL_ARBITRARY_KEY */ + NULL + }; + int rc; + + bi->bi_controls = controls; + + bi->bi_flags |= +#if 0 + SLAP_BFLAG_INCREMENT | +#endif + SLAP_BFLAG_REFERRALS; + + Debug( LDAP_DEBUG_TRACE,"==>sql_back_initialize()\n" ); + + bi->bi_db_init = backsql_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = backsql_db_open; + bi->bi_db_close = backsql_db_close; + bi->bi_db_destroy = backsql_db_destroy; + + bi->bi_op_abandon = 0; + bi->bi_op_compare = backsql_compare; + bi->bi_op_bind = backsql_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = backsql_search; + bi->bi_op_modify = backsql_modify; + bi->bi_op_modrdn = backsql_modrdn; + bi->bi_op_add = backsql_add; + bi->bi_op_delete = backsql_delete; + + bi->bi_chk_referrals = 0; + bi->bi_operational = backsql_operational; + bi->bi_entry_get_rw = backsql_entry_get; + bi->bi_entry_release_rw = backsql_entry_release; + + bi->bi_connection_init = 0; + + rc = backsql_init_cf( bi ); + Debug( LDAP_DEBUG_TRACE,"<==sql_back_initialize()\n" ); + return rc; +} + +int +backsql_destroy( + BackendInfo *bi ) +{ + Debug( LDAP_DEBUG_TRACE, "==>backsql_destroy()\n" ); + Debug( LDAP_DEBUG_TRACE, "<==backsql_destroy()\n" ); + return 0; +} + +int +backsql_db_init( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_init()\n" ); + + bi = (backsql_info *)ch_calloc( 1, sizeof( backsql_info ) ); + ldap_pvt_thread_mutex_init( &bi->sql_dbconn_mutex ); + ldap_pvt_thread_mutex_init( &bi->sql_schema_mutex ); + + if ( backsql_init_db_env( bi ) != SQL_SUCCESS ) { + rc = -1; + } + + bd->be_private = bi; + bd->be_cf_ocs = bd->bd_info->bi_cf_ocs; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_init()\n" ); + + return rc; +} + +int +backsql_db_destroy( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi = (backsql_info*)bd->be_private; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_destroy()\n" ); + + backsql_free_db_env( bi ); + ldap_pvt_thread_mutex_destroy( &bi->sql_dbconn_mutex ); + backsql_destroy_schema_map( bi ); + ldap_pvt_thread_mutex_destroy( &bi->sql_schema_mutex ); + + if ( bi->sql_dbname ) { + ch_free( bi->sql_dbname ); + } + if ( bi->sql_dbuser ) { + ch_free( bi->sql_dbuser ); + } + if ( bi->sql_dbpasswd ) { + ch_free( bi->sql_dbpasswd ); + } + if ( bi->sql_dbhost ) { + ch_free( bi->sql_dbhost ); + } + if ( bi->sql_upper_func.bv_val ) { + ch_free( bi->sql_upper_func.bv_val ); + ch_free( bi->sql_upper_func_open.bv_val ); + ch_free( bi->sql_upper_func_close.bv_val ); + } + if ( bi->sql_concat_func ) { + ber_bvarray_free( bi->sql_concat_func ); + } + if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) { + ch_free( bi->sql_strcast_func.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_children_cond ) ) { + ch_free( bi->sql_children_cond.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_dn_match_cond ) ) { + ch_free( bi->sql_dn_match_cond.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) { + ch_free( bi->sql_subtree_cond.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_dn_oc_aliasing ) ) { + ch_free( bi->sql_dn_oc_aliasing.bv_val ); + } + if ( bi->sql_oc_query ) { + ch_free( bi->sql_oc_query ); + } + if ( bi->sql_at_query ) { + ch_free( bi->sql_at_query ); + } + if ( bi->sql_id_query ) { + ch_free( bi->sql_id_query ); + } + if ( bi->sql_has_children_query ) { + ch_free( bi->sql_has_children_query ); + } + if ( bi->sql_insentry_stmt ) { + ch_free( bi->sql_insentry_stmt ); + } + if ( bi->sql_delentry_stmt ) { + ch_free( bi->sql_delentry_stmt ); + } + if ( bi->sql_renentry_stmt ) { + ch_free( bi->sql_renentry_stmt ); + } + if ( bi->sql_delobjclasses_stmt ) { + ch_free( bi->sql_delobjclasses_stmt ); + } + if ( !BER_BVISNULL( &bi->sql_aliasing ) ) { + ch_free( bi->sql_aliasing.bv_val ); + } + if ( !BER_BVISNULL( &bi->sql_aliasing_quote ) ) { + ch_free( bi->sql_aliasing_quote.bv_val ); + } + + if ( bi->sql_anlist ) { + int i; + + for ( i = 0; !BER_BVISNULL( &bi->sql_anlist[ i ].an_name ); i++ ) + { + ch_free( bi->sql_anlist[ i ].an_name.bv_val ); + } + ch_free( bi->sql_anlist ); + } + + if ( bi->sql_baseObject ) { + entry_free( bi->sql_baseObject ); + } + + ch_free( bi ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_destroy()\n" ); + return 0; +} + +int +backsql_db_open( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi = (backsql_info*)bd->be_private; + struct berbuf bb = BB_NULL; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation* op; + SQLHDBC dbh = SQL_NULL_HDBC; + void *thrctx = ldap_pvt_thread_pool_context(); + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_open(): " + "testing RDBMS connection\n" ); + if ( bi->sql_dbname == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "datasource name not specified " + "(use \"dbname\" directive in slapd.conf)\n" ); + return 1; + } + + if ( bi->sql_concat_func == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "concat func not specified (use \"concat_pattern\" " + "directive in slapd.conf)\n" ); + + if ( backsql_split_pattern( backsql_def_concat_func, + &bi->sql_concat_func, 2 ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "unable to parse pattern \"%s\"", + backsql_def_concat_func ); + return 1; + } + } + + /* + * see back-sql.h for default values + */ + if ( BER_BVISNULL( &bi->sql_aliasing ) ) { + ber_str2bv( BACKSQL_ALIASING, + STRLENOF( BACKSQL_ALIASING ), + 1, &bi->sql_aliasing ); + } + + if ( BER_BVISNULL( &bi->sql_aliasing_quote ) ) { + ber_str2bv( BACKSQL_ALIASING_QUOTE, + STRLENOF( BACKSQL_ALIASING_QUOTE ), + 1, &bi->sql_aliasing_quote ); + } + + /* + * Prepare cast string as required + */ + if ( bi->sql_upper_func.bv_val ) { + char buf[1024]; + + if ( BACKSQL_UPPER_NEEDS_CAST( bi ) ) { + snprintf( buf, sizeof( buf ), + "%s(cast (" /* ? as varchar(%d))) */ , + bi->sql_upper_func.bv_val ); + ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open ); + + snprintf( buf, sizeof( buf ), + /* (cast(? */ " as varchar(%d)))", + BACKSQL_MAX_DN_LEN ); + ber_str2bv( buf, 0, 1, &bi->sql_upper_func_close ); + + } else { + snprintf( buf, sizeof( buf ), "%s(" /* ?) */ , + bi->sql_upper_func.bv_val ); + ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open ); + + ber_str2bv( /* (? */ ")", 0, 1, &bi->sql_upper_func_close ); + } + } + + /* normalize filter values only if necessary */ + bi->sql_caseIgnoreMatch = mr_find( "caseIgnoreMatch" ); + assert( bi->sql_caseIgnoreMatch != NULL ); + + bi->sql_telephoneNumberMatch = mr_find( "telephoneNumberMatch" ); + assert( bi->sql_telephoneNumberMatch != NULL ); + + if ( bi->sql_dbuser == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "user name not specified " + "(use \"dbuser\" directive in slapd.conf)\n" ); + return 1; + } + + if ( BER_BVISNULL( &bi->sql_subtree_cond ) ) { + /* + * Prepare concat function for subtree search condition + */ + struct berval concat; + struct berval values[] = { + BER_BVC( "'%'" ), + BER_BVC( "?" ), + BER_BVNULL + }; + struct berbuf bb = BB_NULL; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "subtree search SQL condition not specified " + "(use \"subtree_cond\" directive in slapd.conf); " + "preparing default\n" ); + + if ( backsql_prepare_pattern( bi->sql_concat_func, values, + &concat ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "unable to prepare CONCAT pattern for subtree search" ); + return 1; + } + + if ( bi->sql_upper_func.bv_val ) { + + /* + * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%',?)) + */ + + backsql_strfcat_x( &bb, NULL, "blbbb", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ), + "(ldap_entries.dn) LIKE ", + &bi->sql_upper_func_open, + &concat, + &bi->sql_upper_func_close ); + + } else { + + /* + * ldap_entries.dn LIKE CONCAT('%',?) + */ + + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ), + "ldap_entries.dn LIKE ", + &concat ); + } + + ch_free( concat.bv_val ); + + bi->sql_subtree_cond = bb.bb_val; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" as default \"subtree_cond\"\n", + bi->sql_subtree_cond.bv_val ); + } + + if ( bi->sql_children_cond.bv_val == NULL ) { + /* + * Prepare concat function for children search condition + */ + struct berval concat; + struct berval values[] = { + BER_BVC( "'%,'" ), + BER_BVC( "?" ), + BER_BVNULL + }; + struct berbuf bb = BB_NULL; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "children search SQL condition not specified " + "(use \"children_cond\" directive in slapd.conf); " + "preparing default\n" ); + + if ( backsql_prepare_pattern( bi->sql_concat_func, values, + &concat ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "unable to prepare CONCAT pattern for children search" ); + return 1; + } + + if ( bi->sql_upper_func.bv_val ) { + + /* + * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%,',?)) + */ + + backsql_strfcat_x( &bb, NULL, "blbbb", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ), + "(ldap_entries.dn) LIKE ", + &bi->sql_upper_func_open, + &concat, + &bi->sql_upper_func_close ); + + } else { + + /* + * ldap_entries.dn LIKE CONCAT('%,',?) + */ + + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ), + "ldap_entries.dn LIKE ", + &concat ); + } + + ch_free( concat.bv_val ); + + bi->sql_children_cond = bb.bb_val; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" as default \"children_cond\"\n", + bi->sql_children_cond.bv_val ); + } + + if ( bi->sql_dn_match_cond.bv_val == NULL ) { + /* + * Prepare concat function for dn match search condition + */ + struct berbuf bb = BB_NULL; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "DN match search SQL condition not specified " + "(use \"dn_match_cond\" directive in slapd.conf); " + "preparing default\n" ); + + if ( bi->sql_upper_func.bv_val ) { + + /* + * UPPER(ldap_entries.dn)=? + */ + + backsql_strfcat_x( &bb, NULL, "blbcb", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn)=" ), + "(ldap_entries.dn)=", + &bi->sql_upper_func_open, + '?', + &bi->sql_upper_func_close ); + + } else { + + /* + * ldap_entries.dn=? + */ + + backsql_strfcat_x( &bb, NULL, "l", + (ber_len_t)STRLENOF( "ldap_entries.dn=?" ), + "ldap_entries.dn=?" ); + } + + bi->sql_dn_match_cond = bb.bb_val; + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" as default \"dn_match_cond\"\n", + bi->sql_dn_match_cond.bv_val ); + } + + if ( bi->sql_oc_query == NULL ) { + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + bi->sql_oc_query = + ch_strdup( backsql_def_needs_select_oc_query ); + + } else { + bi->sql_oc_query = ch_strdup( backsql_def_oc_query ); + } + + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "objectclass mapping SQL statement not specified " + "(use \"oc_query\" directive in slapd.conf)\n" ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", bi->sql_oc_query ); + } + + if ( bi->sql_at_query == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "attribute mapping SQL statement not specified " + "(use \"at_query\" directive in slapd.conf)\n" ); + Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_at_query ); + bi->sql_at_query = ch_strdup( backsql_def_at_query ); + } + + if ( bi->sql_insentry_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "entry insertion SQL statement not specified " + "(use \"insentry_stmt\" directive in slapd.conf)\n" ); + Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_insentry_stmt ); + bi->sql_insentry_stmt = ch_strdup( backsql_def_insentry_stmt ); + } + + if ( bi->sql_delentry_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "entry deletion SQL statement not specified " + "(use \"delentry_stmt\" directive in slapd.conf)\n" ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_delentry_stmt ); + bi->sql_delentry_stmt = ch_strdup( backsql_def_delentry_stmt ); + } + + if ( bi->sql_renentry_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "entry deletion SQL statement not specified " + "(use \"renentry_stmt\" directive in slapd.conf)\n" ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_renentry_stmt ); + bi->sql_renentry_stmt = ch_strdup( backsql_def_renentry_stmt ); + } + + if ( bi->sql_delobjclasses_stmt == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "objclasses deletion SQL statement not specified " + "(use \"delobjclasses_stmt\" directive in slapd.conf)\n" ); + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "setting \"%s\" by default\n", + backsql_def_delobjclasses_stmt ); + bi->sql_delobjclasses_stmt = ch_strdup( backsql_def_delobjclasses_stmt ); + } + + /* This should just be to force schema loading */ + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = bd; + if ( backsql_get_db_conn( op, &dbh ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "connection failed, exiting\n" ); + return 1; + } + if ( backsql_load_schema_map( bi, dbh ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "schema mapping failed, exiting\n" ); + return 1; + } + if ( backsql_free_db_conn( op, dbh ) != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "connection free failed\n" ); + } + if ( !BACKSQL_SCHEMA_LOADED( bi ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): " + "test failed, schema map not loaded - exiting\n" ); + return 1; + } + + /* + * Prepare ID selection query + */ + if ( bi->sql_id_query == NULL ) { + /* no custom id_query provided */ + if ( bi->sql_upper_func.bv_val == NULL ) { + backsql_strcat_x( &bb, NULL, backsql_id_query, "dn=?", NULL ); + + } else { + if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) { + backsql_strcat_x( &bb, NULL, backsql_id_query, + "dn_ru=?", NULL ); + } else { + if ( BACKSQL_USE_REVERSE_DN( bi ) ) { + backsql_strfcat_x( &bb, NULL, "sbl", + backsql_id_query, + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(dn)=?" ), "(dn)=?" ); + } else { + backsql_strfcat_x( &bb, NULL, "sblbcb", + backsql_id_query, + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(dn)=" ), "(dn)=", + &bi->sql_upper_func_open, + '?', + &bi->sql_upper_func_close ); + } + } + } + bi->sql_id_query = bb.bb_val.bv_val; + } + + /* + * Prepare children count query + */ + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "sbsb", + "SELECT COUNT(distinct subordinates.id) " + "FROM ldap_entries,ldap_entries ", + &bi->sql_aliasing, "subordinates " + "WHERE subordinates.parent=ldap_entries.id AND ", + &bi->sql_dn_match_cond ); + bi->sql_has_children_query = bb.bb_val.bv_val; + + /* + * Prepare DN and objectClass aliasing bit of query + */ + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "sbbsbsbbsb", + " ", &bi->sql_aliasing, &bi->sql_aliasing_quote, + "objectClass", &bi->sql_aliasing_quote, + ",ldap_entries.dn ", &bi->sql_aliasing, + &bi->sql_aliasing_quote, "dn", &bi->sql_aliasing_quote ); + bi->sql_dn_oc_aliasing = bb.bb_val; + + /* should never happen! */ + assert( bd->be_nsuffix != NULL ); + + if ( BER_BVISNULL( &bd->be_nsuffix[ 1 ] ) ) { + /* enable if only one suffix is defined */ + bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT; + } + + bi->sql_flags |= BSQLF_CHECK_SCHEMA; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_open(): " + "test succeeded, schema map loaded\n" ); + return 0; +} + +int +backsql_db_close( + BackendDB *bd, + ConfigReply *cr ) +{ + backsql_info *bi = (backsql_info*)bd->be_private; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_db_close()\n" ); + + backsql_conn_destroy( bi ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_db_close()\n" ); + + return 0; +} + +#if SLAPD_SQL == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( sql ) + +#endif /* SLAPD_SQL == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-sql/modify.c b/servers/slapd/back-sql/modify.c new file mode 100644 index 0000000..83afec3 --- /dev/null +++ b/servers/slapd/back-sql/modify.c @@ -0,0 +1,212 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_modify( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + backsql_oc_map_rec *oc = NULL; + backsql_srch_info bsi = { 0 }; + Entry m = { 0 }, *e = NULL; + int manageDSAit = get_manageDSAit( op ); + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + /* + * FIXME: in case part of the operation cannot be performed + * (missing mapping, SQL write fails or so) the entire operation + * should be rolled-back + */ + Debug( LDAP_DEBUG_TRACE, "==>backsql_modify(): modifying entry \"%s\"\n", + op->o_req_ndn.bv_val ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modify(): " + "could not get connection handle - exiting\n" ); + /* + * FIXME: we don't want to send back + * excessively detailed messages + */ + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + goto done; + } + + bsi.bsi_e = &m; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &m; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_modify(): " + "could not retrieve modifyDN ID - no such entry\n" ); + if ( !BER_BVISNULL( &m.e_nname ) ) { + /* FIXME: should always be true! */ + e = &m; + + } else { + e = NULL; + } + goto done; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modify(): " + "modifying entry \"%s\" (id=" BACKSQL_IDFMT ")\n", + bsi.bsi_base_id.eid_dn.bv_val, + BACKSQL_IDARG(bsi.bsi_base_id.eid_id) ); + + if ( get_assert( op ) && + ( test_filter( op, &m, get_assertion( op ) ) + != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + e = &m; + goto done; + } + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + + assert( bsi.bsi_base_id.eid_oc != NULL ); + oc = bsi.bsi_base_id.eid_oc; + + if ( !acl_check_modlist( op, &m, op->orm_modlist ) ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &m; + goto done; + } + + rs->sr_err = backsql_modify_internal( op, rs, dbh, oc, + &bsi.bsi_base_id, op->orm_modlist ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = &m; + goto do_transact; + } + + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + backsql_entry_clean( op, &m ); + + bsi.bsi_e = &m; + rs->sr_err = backsql_id2entry( &bsi, &bsi.bsi_base_id ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = &m; + goto do_transact; + } + + rs->sr_err = entry_schema_check( op, &m, NULL, 0, 0, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modify(\"%s\"): " + "entry failed schema check -- aborting\n", + m.e_name.bv_val ); + e = NULL; + goto do_transact; + } + } + +do_transact:; + /* + * Commit only if all operations succeed + */ + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + assert( e == NULL ); + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + +done:; + if ( e != NULL ) { + if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &m.e_nname ) ) { + backsql_entry_clean( op, &m ); + } + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_modify()\n" ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/modrdn.c b/servers/slapd/back-sql/modrdn.c new file mode 100644 index 0000000..1299923 --- /dev/null +++ b/servers/slapd/back-sql/modrdn.c @@ -0,0 +1,524 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "slap.h" +#include "proto-sql.h" + +int +backsql_modrdn( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + backsql_entryID e_id = BACKSQL_ENTRYID_INIT, + n_id = BACKSQL_ENTRYID_INIT; + backsql_srch_info bsi = { 0 }; + backsql_oc_map_rec *oc = NULL; + struct berval pdn = BER_BVNULL, pndn = BER_BVNULL, + *new_pdn = NULL, *new_npdn = NULL, + new_dn = BER_BVNULL, new_ndn = BER_BVNULL, + realnew_dn = BER_BVNULL; + Entry r = { 0 }, + p = { 0 }, + n = { 0 }, + *e = NULL; + int manageDSAit = get_manageDSAit( op ); + struct berval *newSuperior = op->oq_modrdn.rs_newSup; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_modrdn() renaming entry \"%s\", " + "newrdn=\"%s\", newSuperior=\"%s\"\n", + op->o_req_dn.bv_val, op->oq_modrdn.rs_newrdn.bv_val, + newSuperior ? newSuperior->bv_val : "(NULL)" ); + + rs->sr_err = backsql_get_db_conn( op, &dbh ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "could not get connection handle - exiting\n" ); + rs->sr_text = ( rs->sr_err == LDAP_OTHER ) + ? "SQL-backend error" : NULL; + e = NULL; + goto done; + } + + bsi.bsi_e = &r; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &r; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve modrdnDN ID - no such entry\n" ); + if ( !BER_BVISNULL( &r.e_nname ) ) { + /* FIXME: should always be true! */ + e = &r; + + } else { + e = NULL; + } + goto done; + } + + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): entry id=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(e_id.eid_id) ); + + if ( get_assert( op ) && + ( test_filter( op, &r, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + e = &r; + goto done; + } + + if ( backsql_has_children( op, dbh, &op->o_req_ndn ) == LDAP_COMPARE_TRUE ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "entry \"%s\" has children\n", + op->o_req_dn.bv_val ); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subtree rename not supported"; + e = &r; + goto done; + } + + /* + * Check for entry access to target + */ + if ( !access_allowed( op, &r, slap_schema.si_ad_entry, + NULL, ACL_WRITE, NULL ) ) { + Debug( LDAP_DEBUG_TRACE, " no access to entry\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto done; + } + + dnParent( &op->o_req_dn, &pdn ); + dnParent( &op->o_req_ndn, &pndn ); + + /* + * namingContext "" is not supported + */ + if ( BER_BVISEMPTY( &pdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "parent is \"\" - aborting\n" ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "not allowed within namingContext"; + e = NULL; + goto done; + } + + /* + * Check for children access to parent + */ + bsi.bsi_e = &p; + e_id = bsi.bsi_base_id; + memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) ); + rs->sr_err = backsql_init_search( &bsi, &pndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + BACKSQL_ISF_GET_ENTRY ); + + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): old parent entry id is " BACKSQL_IDFMT "\n", + BACKSQL_IDARG(bsi.bsi_base_id.eid_id) ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve renameDN ID - no such entry\n" ); + e = &p; + goto done; + } + + if ( !access_allowed( op, &p, slap_schema.si_ad_children, NULL, + newSuperior ? ACL_WDEL : ACL_WRITE, NULL ) ) + { + Debug( LDAP_DEBUG_TRACE, " no access to parent\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto done; + } + + if ( newSuperior ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + /* + * namingContext "" is not supported + */ + if ( BER_BVISEMPTY( newSuperior ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "newSuperior is \"\" - aborting\n" ); + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "not allowed within namingContext"; + e = NULL; + goto done; + } + + new_pdn = newSuperior; + new_npdn = op->oq_modrdn.rs_nnewSup; + + /* + * Check for children access to new parent + */ + bsi.bsi_e = &n; + rs->sr_err = backsql_init_search( &bsi, new_npdn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve renameDN ID - no such entry\n" ); + e = &n; + goto done; + } + + n_id = bsi.bsi_base_id; + + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): new parent entry id=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(n_id.eid_id) ); + + if ( !access_allowed( op, &n, slap_schema.si_ad_children, + NULL, ACL_WADD, NULL ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "no access to new parent \"%s\"\n", + new_pdn->bv_val ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + e = &n; + goto done; + } + + } else { + n_id = bsi.bsi_base_id; + new_pdn = &pdn; + new_npdn = &pndn; + } + + memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) ); + + if ( newSuperior && dn_match( &pndn, new_npdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "newSuperior is equal to old parent - ignored\n" ); + newSuperior = NULL; + } + + if ( newSuperior && dn_match( &op->o_req_ndn, new_npdn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "newSuperior is equal to entry being moved " + "- aborting\n" ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "newSuperior is equal to old DN"; + e = &r; + goto done; + } + + build_new_dn( &new_dn, new_pdn, &op->oq_modrdn.rs_newrdn, + op->o_tmpmemctx ); + build_new_dn( &new_ndn, new_npdn, &op->oq_modrdn.rs_nnewrdn, + op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): new entry dn is \"%s\"\n", + new_dn.bv_val ); + + realnew_dn = new_dn; + if ( backsql_api_dn2odbc( op, rs, &realnew_dn ) ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): " + "backsql_api_dn2odbc(\"%s\") failed\n", + op->o_req_dn.bv_val, realnew_dn.bv_val ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "executing renentry_stmt\n" ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_renentry_stmt, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error preparing renentry_stmt\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realnew_dn ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding DN parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT, &n_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding parent ID parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &e_id.eid_keyval ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding entry ID parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = backsql_BindParamID( sth, 4, SQL_PARAM_INPUT, &e_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + " backsql_modrdn(): " + "error binding ID parameter for objectClass %s\n", + oc->bom_oc->soc_cname.bv_val ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + + rs->sr_text = "SQL-backend error"; + rs->sr_err = LDAP_OTHER; + e = NULL; + goto done; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): " + "could not rename ldap_entries record\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "SQL-backend error"; + e = NULL; + goto done; + } + SQLFreeStmt( sth, SQL_DROP ); + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + assert( e_id.eid_oc != NULL ); + oc = e_id.eid_oc; + + if ( op->orr_modlist != NULL ) { + rs->sr_err = backsql_modify_internal( op, rs, dbh, oc, &e_id, op->orr_modlist ); + slap_graduate_commit_csn( op ); + if ( rs->sr_err != LDAP_SUCCESS ) { + e = &r; + goto done; + } + } + + if ( BACKSQL_CHECK_SCHEMA( bi ) ) { + char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + backsql_entry_clean( op, &r ); + (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx ); + + bsi.bsi_e = &r; + rs->sr_err = backsql_init_search( &bsi, &new_ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, + slap_anlist_all_attributes, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &new_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + e = &r; + /* fallthru */ + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): " + "could not retrieve modrdnDN ID - no such entry\n" ); + if ( !BER_BVISNULL( &r.e_nname ) ) { + /* FIXME: should always be true! */ + e = &r; + + } else { + e = NULL; + } + goto done; + } + + e_id = bsi.bsi_base_id; + + rs->sr_err = entry_schema_check( op, &r, NULL, 0, 0, NULL, + &rs->sr_text, textbuf, sizeof( textbuf ) ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): " + "entry failed schema check -- aborting\n", + r.e_name.bv_val ); + e = NULL; + goto done; + } + } + +done:; + if ( e != NULL ) { + if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + } + + /* + * Commit only if all operations succeed + */ + if ( sth != SQL_NULL_HSTMT ) { + SQLUSMALLINT CompletionType = SQL_ROLLBACK; + + if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) { + CompletionType = SQL_COMMIT; + } + + SQLTransact( SQL_NULL_HENV, dbh, CompletionType ); + } + + if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if ( !BER_BVISNULL( &realnew_dn ) && realnew_dn.bv_val != new_dn.bv_val ) { + ch_free( realnew_dn.bv_val ); + } + + if ( !BER_BVISNULL( &new_dn ) ) { + slap_sl_free( new_dn.bv_val, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &new_ndn ) ) { + slap_sl_free( new_ndn.bv_val, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &e_id.eid_ndn ) ) { + (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &n_id.eid_ndn ) ) { + (void)backsql_free_entryID( &n_id, 0, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &r.e_nname ) ) { + backsql_entry_clean( op, &r ); + } + + if ( !BER_BVISNULL( &p.e_nname ) ) { + backsql_entry_clean( op, &p ); + } + + if ( !BER_BVISNULL( &n.e_nname ) ) { + backsql_entry_clean( op, &n ); + } + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_modrdn()\n" ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-sql/operational.c b/servers/slapd/back-sql/operational.c new file mode 100644 index 0000000..3eb1ec2 --- /dev/null +++ b/servers/slapd/back-sql/operational.c @@ -0,0 +1,246 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" +#include "lutil.h" + +/* + * sets the supported operational attributes (if required) + */ + +Attribute * +backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id ) +{ + int rc; + struct berval val, nval; + AttributeDescription *desc = slap_schema.si_ad_entryUUID; + Attribute *a; + + backsql_entryUUID( bi, id, &val, NULL ); + + rc = (*desc->ad_type->sat_equality->smr_normalize)( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + desc->ad_type->sat_syntax, + desc->ad_type->sat_equality, + &val, &nval, NULL ); + if ( rc != LDAP_SUCCESS ) { + ber_memfree( val.bv_val ); + return NULL; + } + + a = attr_alloc( desc ); + + a->a_numvals = 1; + a->a_vals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + a->a_vals[ 0 ] = val; + BER_BVZERO( &a->a_vals[ 1 ] ); + + a->a_nvals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + a->a_nvals[ 0 ] = nval; + BER_BVZERO( &a->a_nvals[ 1 ] ); + + return a; +} + +Attribute * +backsql_operational_entryCSN( Operation *op ) +{ + char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + struct berval entryCSN; + Attribute *a; + + a = attr_alloc( slap_schema.si_ad_entryCSN ); + a->a_numvals = 1; + a->a_vals = ch_malloc( 2 * sizeof( struct berval ) ); + BER_BVZERO( &a->a_vals[ 1 ] ); + +#ifdef BACKSQL_SYNCPROV + if ( op->o_sync && op->o_tag == LDAP_REQ_SEARCH && op->o_private != NULL ) { + assert( op->o_private != NULL ); + + entryCSN = *((struct berval *)op->o_private); + + } else +#endif /* BACKSQL_SYNCPROV */ + { + entryCSN.bv_val = csnbuf; + entryCSN.bv_len = sizeof( csnbuf ); + slap_get_csn( op, &entryCSN, 0 ); + } + + ber_dupbv( &a->a_vals[ 0 ], &entryCSN ); + + a->a_nvals = a->a_vals; + + return a; +} + +int +backsql_operational( + Operation *op, + SlapReply *rs ) +{ + + backsql_info *bi = (backsql_info*)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + int rc = 0; + Attribute **ap; + enum { + BACKSQL_OP_HASSUBORDINATES = 0, + BACKSQL_OP_ENTRYUUID, + BACKSQL_OP_ENTRYCSN, + + BACKSQL_OP_LAST + }; + int get_conn = BACKSQL_OP_LAST, + got[ BACKSQL_OP_LAST ] = { 0 }; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_operational(): entry \"%s\"\n", + rs->sr_entry->e_nname.bv_val ); + + for ( ap = &rs->sr_entry->e_attrs; *ap; ap = &(*ap)->a_next ) { + if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) { + get_conn--; + got[ BACKSQL_OP_HASSUBORDINATES ] = 1; + + } else if ( (*ap)->a_desc == slap_schema.si_ad_entryUUID ) { + get_conn--; + got[ BACKSQL_OP_ENTRYUUID ] = 1; + + } else if ( (*ap)->a_desc == slap_schema.si_ad_entryCSN ) { + get_conn--; + got[ BACKSQL_OP_ENTRYCSN ] = 1; + } + } + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) { + if ( !got[ BACKSQL_OP_HASSUBORDINATES ] && + (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) + { + get_conn--; + got[ BACKSQL_OP_HASSUBORDINATES ] = 1; + + } else if ( !got[ BACKSQL_OP_ENTRYUUID ] && + (*ap)->a_desc == slap_schema.si_ad_entryUUID ) + { + get_conn--; + got[ BACKSQL_OP_ENTRYUUID ] = 1; + + } else if ( !got[ BACKSQL_OP_ENTRYCSN ] && + (*ap)->a_desc == slap_schema.si_ad_entryCSN ) + { + get_conn--; + got[ BACKSQL_OP_ENTRYCSN ] = 1; + } + } + + if ( !get_conn ) { + return 0; + } + + rc = backsql_get_db_conn( op, &dbh ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not get connection handle - exiting\n" ); + return 1; + } + + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) + && !got[ BACKSQL_OP_HASSUBORDINATES ] + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL ) + { + rc = backsql_has_children( op, dbh, &rs->sr_entry->e_nname ); + + switch( rc ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + *ap = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE ); + assert( *ap != NULL ); + ap = &(*ap)->a_next; + rc = 0; + break; + + default: + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "has_children failed( %d)\n", rc ); + return 1; + } + } + + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryUUID, rs->sr_attrs ) ) + && !got[ BACKSQL_OP_ENTRYUUID ] + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID ) == NULL ) + { + backsql_srch_info bsi = { 0 }; + + rc = backsql_init_search( &bsi, &rs->sr_entry->e_nname, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, dbh, op, rs, NULL, + BACKSQL_ISF_GET_ID ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not retrieve entry ID - no such entry\n" ); + return 1; + } + + *ap = backsql_operational_entryUUID( bi, &bsi.bsi_base_id ); + + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( *ap == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not retrieve entryUUID\n" ); + return 1; + } + + ap = &(*ap)->a_next; + } + + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryCSN, rs->sr_attrs ) ) + && !got[ BACKSQL_OP_ENTRYCSN ] + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN ) == NULL ) + { + *ap = backsql_operational_entryCSN( op ); + if ( *ap == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_operational(): " + "could not retrieve entryCSN\n" ); + return 1; + } + + ap = &(*ap)->a_next; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_operational(%d)\n", rc ); + + return rc; +} + diff --git a/servers/slapd/back-sql/proto-sql.h b/servers/slapd/back-sql/proto-sql.h new file mode 100644 index 0000000..169be76 --- /dev/null +++ b/servers/slapd/back-sql/proto-sql.h @@ -0,0 +1,313 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Mararati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati + */ + +/* + * The following changes have been addressed: + * + * Enhancements: + * - re-styled code for better readability + * - upgraded backend API to reflect recent changes + * - LDAP schema is checked when loading SQL/LDAP mapping + * - AttributeDescription/ObjectClass pointers used for more efficient + * mapping lookup + * - bervals used where string length is required often + * - atomized write operations by committing at the end of each operation + * and defaulting connection closure to rollback + * - added LDAP access control to write operations + * - fully implemented modrdn (with rdn attrs change, deleteoldrdn, + * access check, parent/children check and more) + * - added parent access control, children control to delete operation + * - added structuralObjectClass operational attribute check and + * value return on search + * - added hasSubordinate operational attribute on demand + * - search limits are appropriately enforced + * - function backsql_strcat() has been made more efficient + * - concat function has been made configurable by means of a pattern + * - added config switches: + * - fail_if_no_mapping write operations fail if there is no mapping + * - has_ldapinfo_dn_ru overrides autodetect + * - concat_pattern a string containing two '?' is used + * (note that "?||?" should be more portable + * than builtin function "CONCAT(?,?)") + * - strcast_func cast of string constants in "SELECT DISTINCT + * statements (needed by PostgreSQL) + * - upper_needs_cast cast the argument of upper when required + * (basically when building dn substring queries) + * - added noop control + * - added values return filter control + * - hasSubordinate can be used in search filters (with limitations) + * - eliminated oc->name; use oc->oc->soc_cname instead + * + * Todo: + * - add security checks for SQL statements that can be injected (?) + * - re-test with previously supported RDBMs + * - replace dn_ru and so with normalized dn (no need for upper() and so + * in dn match) + * - implement a backsql_normalize() function to replace the upper() + * conversion routines + * - note that subtree deletion, subtree renaming and so could be easily + * implemented (rollback and consistency checks are available :) + * - implement "lastmod" and other operational stuff (ldap_entries table ?) + * - check how to allow multiple operations with one statement, to remove + * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?) + */ + +#ifndef PROTO_SQL_H +#define PROTO_SQL_H + +#include "back-sql.h" + +/* + * add.c + */ +int backsql_modify_delete_all_values( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_entryID *e_id, + backsql_at_map_rec *at ); + +int backsql_modify_internal( + Operation *op, + SlapReply *rs, + SQLHDBC dbh, + backsql_oc_map_rec *oc, + backsql_entryID *e_id, + Modifications *modlist ); + +/* + * api.c + */ +int backsql_api_config( backsql_info *bi, const char *name, + int argc, char *argv[] ); +int backsql_api_destroy( backsql_info *bi ); +int backsql_api_register( backsql_api *ba ); +int backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn ); +int backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn ); + +/* + * entry-id.c + */ +#ifdef BACKSQL_ARBITRARY_KEY +extern struct berval backsql_baseObject_bv; +#endif /* BACKSQL_ARBITRARY_KEY */ + +/* stores in *id the ID in table ldap_entries corresponding to DN, if any */ +extern int +backsql_dn2id( Operation *op, SlapReply *rs, SQLHDBC dbh, + struct berval *ndn, backsql_entryID *id, + int matched, int muck ); + +/* stores in *nchildren the count of children for an entry */ +extern int +backsql_count_children( Operation *op, SQLHDBC dbh, + struct berval *dn, unsigned long *nchildren ); + +/* returns LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE if the entry corresponding + * to DN has/has not children */ +extern int +backsql_has_children( Operation *op, SQLHDBC dbh, struct berval *dn ); + +/* free *id and return next in list */ +extern backsql_entryID * +backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx ); + +/* turn an ID into an entry */ +extern int +backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *id ); + +/* duplicate an entryID */ +extern backsql_entryID * +backsql_entryID_dup( backsql_entryID *eid, void *ctx ); + +/* + * operational.c + */ + +Attribute *backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id ); + +Attribute *backsql_operational_entryCSN( Operation *op ); + +/* + * schema-map.c + */ + +int backsql_load_schema_map( backsql_info *si, SQLHDBC dbh ); + +backsql_oc_map_rec *backsql_oc2oc( backsql_info *si, ObjectClass *oc ); + +backsql_oc_map_rec *backsql_id2oc( backsql_info *si, unsigned long id ); + +backsql_oc_map_rec * backsql_name2oc( backsql_info *si, + struct berval *oc_name ); + +backsql_at_map_rec *backsql_ad2at( backsql_oc_map_rec *objclass, + AttributeDescription *ad ); + +int backsql_supad2at( backsql_oc_map_rec *objclass, + AttributeDescription *supad, backsql_at_map_rec ***pret ); + +int backsql_destroy_schema_map( backsql_info *si ); + +/* + * search.c + */ + +int backsql_init_search( backsql_srch_info *bsi, + struct berval *nbase, int scope, + time_t stoptime, Filter *filter, SQLHDBC dbh, + Operation *op, SlapReply *rs, AttributeName *attrs, + unsigned flags ); + +void backsql_entry_clean( Operation *op, Entry *e ); + +/* + * sql-wrap.h + */ + +RETCODE backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char* query, int timeout ); + +#define backsql_BindParamStr( sth, par_ind, io, str, maxlen ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), SQL_C_CHAR, SQL_VARCHAR, \ + (SQLULEN)(maxlen), 0, (SQLPOINTER)(str), \ + (SQLLEN)(maxlen), NULL ) + +#define backsql_BindParamBerVal( sth, par_ind, io, bv ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), SQL_C_CHAR, SQL_VARCHAR, \ + (SQLULEN)(bv)->bv_len, 0, \ + (SQLPOINTER)(bv)->bv_val, \ + (SQLLEN)(bv)->bv_len, NULL ) + +#define backsql_BindParamInt( sth, par_ind, io, val ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), SQL_C_ULONG, SQL_INTEGER, \ + 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL ) + +#define backsql_BindParamNumID( sth, par_ind, io, val ) \ + SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \ + (io), BACKSQL_C_NUMID, SQL_INTEGER, \ + 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL ) + +#ifdef BACKSQL_ARBITRARY_KEY +#define backsql_BindParamID( sth, par_ind, io, id ) \ + backsql_BindParamBerVal( (sth), (par_ind), (io), (id) ) +#else /* ! BACKSQL_ARBITRARY_KEY */ +#define backsql_BindParamID( sth, par_ind, io, id ) \ + backsql_BindParamNumID( (sth), (par_ind), (io), (id) ) +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +RETCODE backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx ); + +RETCODE backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row ); + +RETCODE backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx ); + +RETCODE backsql_FreeRow( BACKSQL_ROW_NTS *row ); + +void backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc ); + +int backsql_conn_destroy( backsql_info *bi ); + +int backsql_init_db_env( backsql_info *si ); + +int backsql_free_db_env( backsql_info *si ); + +int backsql_get_db_conn( Operation *op, SQLHDBC *dbh ); + +int backsql_free_db_conn( Operation *op, SQLHDBC dbh ); + +/* + * util.c + */ + +extern const char + backsql_def_oc_query[], + backsql_def_needs_select_oc_query[], + backsql_def_at_query[], + backsql_def_delentry_stmt[], + backsql_def_renentry_stmt[], + backsql_def_insentry_stmt[], + backsql_def_delobjclasses_stmt[], + backsql_def_subtree_cond[], + backsql_def_upper_subtree_cond[], + backsql_id_query[], + backsql_def_concat_func[], + backsql_check_dn_ru_query[]; + +struct berbuf * backsql_strcat_x( struct berbuf *dest, void *memctx, ... ); +struct berbuf * backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... ); + +int backsql_entry_addattr( Entry *e, AttributeDescription *ad, + struct berval *at_val, void *memctx ); + +int backsql_merge_from_clause( backsql_info *bi, struct berbuf *dest_from, + struct berval *src_from ); + +int backsql_split_pattern( const char *pattern, BerVarray *split_pattern, + int expected ); + +int backsql_prepare_pattern( BerVarray split_pattern, BerVarray values, + struct berval *res ); + +int backsql_entryUUID( backsql_info *bi, backsql_entryID *id, + struct berval *entryUUID, void *memctx ); +int backsql_entryUUID_decode( struct berval *entryUUID, unsigned long *oc_id, +#ifdef BACKSQL_ARBITRARY_KEY + struct berval *keyval +#else /* ! BACKSQL_ARBITRARY_KEY */ + unsigned long *keyval +#endif /* ! BACKSQL_ARBITRARY_KEY */ + ); + +/* + * former external.h + */ + +extern BI_init sql_back_initialize; + +extern BI_destroy backsql_destroy; + +extern BI_db_init backsql_db_init; +extern BI_db_open backsql_db_open; +extern BI_db_close backsql_db_close; +extern BI_db_destroy backsql_db_destroy; +extern BI_db_config backsql_db_config; + +extern BI_op_bind backsql_bind; +extern BI_op_search backsql_search; +extern BI_op_compare backsql_compare; +extern BI_op_modify backsql_modify; +extern BI_op_modrdn backsql_modrdn; +extern BI_op_add backsql_add; +extern BI_op_delete backsql_delete; + +extern BI_operational backsql_operational; +extern BI_entry_get_rw backsql_entry_get; +extern BI_entry_release_rw backsql_entry_release; + +extern BI_connection_destroy backsql_connection_destroy; + +int backsql_init_cf( BackendInfo * bi ); + +#endif /* PROTO_SQL_H */ diff --git a/servers/slapd/back-sql/rdbms_depend/README b/servers/slapd/back-sql/rdbms_depend/README new file mode 100644 index 0000000..2b281f6 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/README @@ -0,0 +1,189 @@ +Author: Pierangelo Masarati <ando@OpenLDAP.org> + +Back-sql can be tested with sql-test000-read; it requires a bit of work +to get everything up and running appropriately. + +This document briefly describes the steps that are required to prepare +a quick'n'dirty installation of back-sql and of the related RDBMS +and ODBC; Examples are provided, but by no means they pretent +to represent an exhaustive source of info about how to setup the ODBC; +refer to the docs for any problem or detail. + +Currently, the system has been tested with IBM db2, PostgreSQL and MySQL; +basic support and test data for other RDBMSes is in place, but as of +today (November 2004) it's totally untested. If you succeed in running +any of the other RDBMSes, please provide feedback about any required +change either in the code or in the test scripts by means of OpenLDAP's +Issue Tracking System (http://www.openldap.org/its/). + +1) slapd must be compiled with back-sql support, i.e. configure +with --enable-sql switch. This requires an implementation of the ODBC +to be installed. + +2) The ODBC must be set up appropriately, by editing the odbc.ini file +in /etc/ (or wherever your installation puts it) and, if appropriate, +the odbcinst.ini file. Note: you can also use custom odbc.ini and +odbcinst.ini files, provided you export in ODBCINI the full path to the +odbc.ini file, and in ODBCSYSINI the directory where the odbcinst.ini +file resides. +Relevant info for our test setup is highlighted with '<===' on the right. + +2.1) PostgreSQL + +2.1.1) Add to the odbc.ini file a block of the form + +[example] <=== +Description = Example for OpenLDAP's back-sql +Driver = PostgreSQL +Trace = No +Database = example <=== +Servername = localhost +UserName = manager <=== +Password = secret <=== +Port = 5432 +;Protocol = 6.4 +ReadOnly = No +RowVersioning = No +ShowSystemTables = No +ShowOidColumn = No +FakeOidIndex = No +ConnSettings = + +2.1.2) Add to the odbcinst.ini file a block of the form + +[PostgreSQL] +Description = ODBC for PostgreSQL +Driver = /usr/lib/libodbcpsql.so +Setup = /usr/lib/libodbcpsqlS.so +FileUsage = 1 + +2.2) MySQL + +2.2.1) Add to the odbc.ini file a block of the form + +[example] <=== +Description = Example for OpenLDAP's back-sql +Driver = MySQL +Trace = No +Database = example <=== +Servername = localhost +UserName = manager <=== +Password = secret <=== +ReadOnly = No +RowVersioning = No +ShowSystemTables = No +ShowOidColumn = No +FakeOidIndex = No +ConnSettings = +SOCKET = /var/lib/mysql/mysql.sock + +2.2.2) Add to the odbcinst.ini file a block of the form + +[MySQL] +Description = ODBC for MySQL +Driver = /usr/lib/libmyodbc.so +FileUsage = 1 + +2.3) IBM db2 +[n.a.] + +3) The RDBMS must be setup; examples are provided for my installations +of PostgreSQL and MySQL, but details may change; other RDBMSes should +be configured in a similar manner, you need to find out the details by +reading their documentation. + +3.1) PostgreSQL + +3.1.1) Start the server +on RedHat: +[root@localhost]# service postgresql start +on other systems: read the docs... + +3.1.2) Create the database: +[root@localhost]# su - postgres +[postgres@localhost]$ createdb example + +3.1.3) Create the user: +[root@localhost]# su - postgres +[postgres@localhost]$ psql example +example=> create user manager with password 'secret'; +example=> <control-D> + +3.1.4) Populate the database: +[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/pgsql/ +[root@localhost]# psql -U manager -W example +example=> <control-D> +[root@localhost]# psql -U manager example < backsql_create.sql +[root@localhost]# psql -U manager example < testdb_create.sql +[root@localhost]# psql -U manager example < testdb_data.sql +[root@localhost]# psql -U manager example < testdb_metadata.sql + +3.1.5) Run the test: +[root@localhost]# cd $SOURCES/tests +[root@localhost]# SLAPD_USE_SQL=pgsql ./run sql-test000 + +3.2) MySQL + +3.2.1) Start the server +on RedHat: +[root@localhost]# service mysqld start +on other systems: read the docs... + +3.2.2) Create the database: +[root@localhost]# mysqladmin -u root -p create example +(hit <return> for the empty password). + +3.2.3) Create the user: +[root@localhost]# mysql -u root -p example +(hit <return> for the empty password) +mysql> grant all privileges on *.* \ + to 'manager'@'localhost' identified by 'secret' with grant option; +mysql> exit; + +3.2.4) Populate the database: +[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/mysql/ +[root@localhost]# mysql -u manager -p example < backsql_create.sql +[root@localhost]# mysql -u manager -p example < testdb_create.sql +[root@localhost]# mysql -u manager -p example < testdb_data.sql +[root@localhost]# mysql -u manager -p example < testdb_metadata.sql + +3.2.5) Run the test: +[root@localhost]# cd $SOURCES/tests +[root@localhost]# SLAPD_USE_SQL=mysql ./run sql-test000 + +3.3) IBM db2 +[n.a.] + +3.3.1) Start the server: + +3.3.2) Create the database: + +3.3.3) Create the user: + +3.3.4) Populate the database: +connect to the database as user manager, and execute the test files +in auto-commit mode (-c) +[root@localhost]# su - manager +[manager@localhost]$ db2 "connect to example user manager using secret" +[manager@localhost]$ db2 -ctvf backsql_create.sql +[manager@localhost]$ db2 -ctvf testdb_create.sql +[manager@localhost]$ db2 -ctvf testdb_data.sql +[manager@localhost]$ db2 -ctvf testdb_metadata.sql +[manager@localhost]$ db2 "connect reset" + +3.3.5) Run the test: +[root@localhost]# cd $SOURCES/tests +[root@localhost]# SLAPD_USE_SQL=ibmdb2 ./run sql-test000 + +4) Cleanup: +The test is basically readonly; this can be performed by all RDBMSes +(listed above). + +There is another test, sql-test900-write, which is currently enabled +only for PostgreSQL and IBM db2. Note that after a successful run +of the write test, the database is no longer in the correct state +to restart either of the tests, and step 3.X.4 needs to be re-run first. + +More tests are to come; PostgreSQL is known to allow a full reload +of the test database starting from an empty database. + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql new file mode 100644 index 0000000..cb2856b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql @@ -0,0 +1,59 @@ +drop table ldap_oc_mappings; +create table ldap_oc_mappings + ( + id integer not null primary key, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + create_keyval varchar(255), + delete_proc varchar(255), + expect_return integer not null +); + +drop table ldap_attr_mappings; +create table ldap_attr_mappings + ( + id integer not null primary key, + oc_map_id integer not null references ldap_oc_mappings(id), + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order integer not null, + expect_return integer not null +); + +drop table ldap_entries; +create table ldap_entries + ( + id integer not null primary key, + dn varchar(255) not null, + oc_map_id integer not null references ldap_oc_mappings(id), + parent int NOT NULL , + keyval int NOT NULL +); + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ); + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ); + +drop table ldap_entry_objclasses; +create table ldap_entry_objclasses + ( + entry_id integer not null references ldap_entries(id), + oc_name varchar(64) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql new file mode 100644 index 0000000..49e7e3a --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE ldap_referrals; +DROP TABLE ldap_entry_objclasses; +DROP TABLE ldap_attr_mappings; +DROP TABLE ldap_entries; +DROP TABLE ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf new file mode 100644 index 0000000..f6c1613 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf @@ -0,0 +1,36 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_db2 +dbuser db2inst1 +dbpasswd ibmdb2 +subtree_cond "upper(ldap_entries.dn) LIKE CONCAT('%',?)" +insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)" +upper_func "upper" +upper_needs_cast "yes" +create_needs_select "yes" +has_ldapinfo_dn_ru "no" + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql new file mode 100644 index 0000000..b6e850c --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql @@ -0,0 +1,75 @@ +drop table persons; +CREATE TABLE persons ( + id int NOT NULL, + name varchar(255) NOT NULL, + surname varchar(255) NOT NULL, + password varchar(64) +); + +drop table institutes; +CREATE TABLE institutes ( + id int NOT NULL, + name varchar(255) +); + +drop table documents; +CREATE TABLE documents ( + id int NOT NULL, + title varchar(255) NOT NULL, + abstract varchar(255) +); + +drop table authors_docs; +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL +); + +drop table phones; +CREATE TABLE phones ( + id int NOT NULL , + phone varchar(255) NOT NULL , + pers_id int NOT NULL +); + +drop table referrals; +CREATE TABLE referrals ( + id int NOT NULL, + name varchar(255) NOT NULL, + url varchar(255) NOT NULL +); + + + +ALTER TABLE authors_docs ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ); + +ALTER TABLE documents ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ); + +ALTER TABLE institutes ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ); + + +ALTER TABLE persons ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ); + +ALTER TABLE phones ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql new file mode 100644 index 0000000..7bef374 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql @@ -0,0 +1,18 @@ +insert into institutes (id,name) values (1,'Example'); + +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit'); +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy'); +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); + +insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/'); diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql new file mode 100644 index 0000000..17b12af --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql new file mode 100644 index 0000000..0b0d1c2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql @@ -0,0 +1,123 @@ +--mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- create_keyval a query that returns the id of the last inserted entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id','INSERT INTO persons (id,name,surname) VALUES ((SELECT max(id)+1 FROM persons),'''','''')', + 'SELECT max(id) FROM persons','DELETE FROM persons WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (2,'document','documents','id','INSERT INTO documents (id,title,abstract) VALUES ((SELECT max(id)+1 FROM documents),'''','''')', + 'SELECT max(id) FROM documents','DELETE FROM documents WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (3,'organization','institutes','id','INSERT INTO institutes (id,name) VALUES ((SELECT max(id)+1 FROM institutes),'''')', + 'SELECT max(id) FROM institutes','DELETE FROM institutes WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return) +values (4,'referral','referrals','id','INSERT INTO referrals (id,name,url) VALUES ((SELECT max(id)+1 FROM referrals),'''','''')', + 'SELECT max(id) FROM referrals','DELETE FROM referrals WHERE id=?',0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id','INSERT INTO phones (id,phone,pers_id) VALUES ((SELECT max(id)+1 FROM phones),?,?)', + 'DELETE FROM phones WHERE phone=? AND pers_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?', + 'UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255)))=ucase(dn)),?)', + 'DELETE FROM authors_docs WHERE pers_id=(SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255))=ucase(dn)) AND doc_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','''document ''||rtrim(cast(documents.id AS CHAR(16)))','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lcase(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?',NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?',NULL,3,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (1,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,dc=example,dc=com',2,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (7,'ou=Referral,dc=example,dc=com',4,1,1); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject'); diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql new file mode 100644 index 0000000..1f1f6d2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql @@ -0,0 +1,100 @@ +create table ldap_oc_mappings ( + id int identity (1, 1) not null , + name varchar (64) not null , + keytbl varchar (64) not null , + keycol varchar (64) not null , + create_proc varchar (255) NULL , + delete_proc varchar (255) NULL, + expect_return int not null +) +GO + +alter table ldap_oc_mappings add + constraint pk_ldap_oc_mappings primary key + ( + id + ) +GO + + +alter table ldap_oc_mappings add + constraint unq1_ldap_oc_mappings unique + ( + name + ) +GO + + +create table ldap_attr_mappings ( + id int identity (1, 1) not null , + oc_map_id int not null references ldap_oc_mappings(id), + name varchar (255) not null , + sel_expr varchar (255) not null , + sel_expr_u varchar(255), + from_tbls varchar (255) not null , + join_where varchar (255) NULL , + add_proc varchar (255) NULL , + delete_proc varchar (255) NULL , + param_order int not null, + expect_return int not null +) +GO + +alter table ldap_attr_mappings add + constraint pk_ldap_attr_mappings primary key + ( + id + ) +GO + + +create table ldap_entries ( + id int identity (1, 1) not null , + dn varchar (255) not null , + oc_map_id int not null references ldap_oc_mappings(id), + parent int not null , + keyval int not null +) +GO + + +alter table ldap_entries add + constraint pk_ldap_entries primary key + ( + id + ) +GO + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ) +GO + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ) +GO + + +create table ldap_referrals + ( + entry_id int not null references ldap_entries(id), + url text not null +) +GO + +create index entry_idx on ldap_referrals(entry_id); + +create table ldap_entry_objclasses + ( + entry_id int not null references ldap_entries(id), + oc_name varchar(64) + ) +GO + +create index entry_idx on ldap_entry_objclasses(entry_id); diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql new file mode 100644 index 0000000..0e888b3 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql @@ -0,0 +1,14 @@ +drop table ldap_attr_mappings +GO + +drop table ldap_referrals +GO + +drop table ldap_entry_objclasses +GO + +drop table ldap_entries +GO + +drop table ldap_oc_mappings +GO diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf new file mode 100644 index 0000000..c3032f2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf @@ -0,0 +1,30 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include ./schema/core.schema +include ./schema/cosine.schema +include ./schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile ./slapd.pid +argsfile ./slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_mssql +dbuser ldap +dbpasswd ldap +subtree_cond "ldap_entries.dn LIKE '%'+?" diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql new file mode 100644 index 0000000..2034afd --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql @@ -0,0 +1,74 @@ + +CREATE TABLE authors_docs ( + pers_id int NOT NULL , + doc_id int NOT NULL +) +GO + +CREATE TABLE documents ( + id int IDENTITY (1, 1) NOT NULL , + abstract varchar (255) NULL , + title varchar (255) NULL , + body binary (255) NULL +) +GO + +CREATE TABLE institutes ( + id int IDENTITY (1, 1) NOT NULL , + name varchar (255) NOT NULL +) +GO + + +CREATE TABLE persons ( + id int IDENTITY (1, 1) NOT NULL , + name varchar (255) NULL , + surname varchar (255) NULL , + password varchar (64) NULL +) +GO + +CREATE TABLE phones ( + id int IDENTITY (1, 1) NOT NULL , + phone varchar (255) NOT NULL , + pers_id int NOT NULL +) +GO + +ALTER TABLE authors_docs WITH NOCHECK ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ) +GO + +ALTER TABLE documents WITH NOCHECK ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ) +GO + +ALTER TABLE institutes WITH NOCHECK ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ) +GO + + +ALTER TABLE persons WITH NOCHECK ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ) +GO + +ALTER TABLE phones WITH NOCHECK ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ) +GO + diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql new file mode 100644 index 0000000..21a51ef --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql @@ -0,0 +1,24 @@ +set IDENTITY_INSERT institutes ON +insert into institutes (id,name) values (1,'Example') +set IDENTITY_INSERT institutes OFF + +set IDENTITY_INSERT persons ON +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit') +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy') +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein') +set IDENTITY_INSERT persons OFF + +set IDENTITY_INSERT phones ON +insert into phones (id,phone,pers_id) values (1,'332-2334',1) +insert into phones (id,phone,pers_id) values (2,'222-3234',1) +insert into phones (id,phone,pers_id) values (3,'545-4563',2) +set IDENTITY_INSERT phones OFF + +set IDENTITY_INSERT documents ON +insert into documents (id,abstract,title) values (1,'abstract1','book1') +insert into documents (id,abstract,title) values (2,'abstract2','book2') +set IDENTITY_INSERT documents OFF + +insert into authors_docs (pers_id,doc_id) values (1,1) +insert into authors_docs (pers_id,doc_id) values (1,2) +insert into authors_docs (pers_id,doc_id) values (2,1) diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql new file mode 100644 index 0000000..4842ed8 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql @@ -0,0 +1,39 @@ +drop procedure create_person +drop procedure set_person_name +drop procedure delete_phone +drop procedure add_phone +drop procedure make_doc_link +drop procedure del_doc_link +drop procedure delete_person + +drop procedure create_org +drop procedure set_org_name +drop procedure delete_org + +drop procedure create_document +drop procedure set_doc_title +drop procedure set_doc_abstract +drop procedure make_author_link +drop procedure del_author_link +drop procedure delete_document + +if exists (select * from sysobjects where id = object_id(N'authors_docs') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table authors_docs +GO + +if exists (select * from sysobjects where id = object_id(N'documents') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table documents +GO + +if exists (select * from sysobjects where id = object_id(N'institutes') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table institutes +GO + +if exists (select * from sysobjects where id = object_id(N'persons') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table persons +GO + +if exists (select * from sysobjects where id = object_id(N'phones') and OBJECTPROPERTY(id, N'IsUserTable') = 1) +drop table phones +GO + diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql new file mode 100644 index 0000000..e087523 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql @@ -0,0 +1,198 @@ +-- mappings + + +SET IDENTITY_INSERT ldap_oc_mappings ON +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id','{call create_person(?)}','{call delete_person(?)}',0) + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (2,'document','documents','id','{call create_document(?)}','{call delete_document(?)}',0) + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (3,'organization','institutes','id','{call create_org(?)}','{call delete_org(?)}',0) +SET IDENTITY_INSERT ldap_oc_mappings OFF + + +SET IDENTITY_INSERT ldap_attr_mappings ON +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn','persons.name+'' ''+persons.surname','persons',NULL, + NULL,NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id','{call add_phone(?,?)}', + '{call delete_phone(?,?)}',0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'givenName','persons.name','persons',NULL, + '{call set_person_name(?,?)}',NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'sn','persons.surname','persons',NULL, + '{call set_person_surname(?,?)}',NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL', + '{call set_person_password(?,?)}','call del_person_password(?,?)',0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,'{call set_doc_abstract(?,?)}', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL, '{call set_doc_title(?,?)}', + NULL,0,0) + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)', + 'DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','''document ''+text(documents.id)','documents', + NULL,NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + '{call set_org_name(?,?)}',NULL,3,0); + +SET IDENTITY_INSERT ldap_attr_mappings OFF + +-- entries + +SET IDENTITY_INSERT ldap_entries ON +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (1,'dc=example,dc=com',3,0,1) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,dc=example,dc=com',2,1,1) + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,dc=example,dc=com',2,1,2) + +SET IDENTITY_INSERT ldap_entries OFF + +-- referrals +insert into ldap_entry_objclasses (entry_id,oc_name) +values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +insert into ldap_referrals (entry_id,url) +values (4,'ldap://localhost:9012/'); + +-- support procedures + +SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS ON +GO + + +CREATE PROCEDURE create_person @@keyval int OUTPUT AS +INSERT INTO example.persons (name) VALUES (''); +set @@keyval=(SELECT MAX(id) FROM example.persons) +GO + +CREATE PROCEDURE delete_person @keyval int AS +DELETE FROM example.phones WHERE pers_id=@keyval; +DELETE FROM example.authors_docs WHERE pers_id=@keyval; +DELETE FROM example.persons WHERE id=@keyval; +GO + +CREATE PROCEDURE create_org @@keyval int OUTPUT AS +INSERT INTO example.institutes (name) VALUES (''); +set @@keyval=(SELECT MAX(id) FROM example.institutes) +GO + +CREATE PROCEDURE delete_org @keyval int AS +DELETE FROM example.institutes WHERE id=@keyval; +GO + +CREATE PROCEDURE create_document @@keyval int OUTPUT AS +INSERT INTO example.documents (title) VALUES (''); +set @@keyval=(SELECT MAX(id) FROM example.documents) +GO + +CREATE PROCEDURE delete_document @keyval int AS +DELETE FROM example.authors_docs WHERE doc_id=@keyval; +DELETE FROM example.documents WHERE id=@keyval; +GO + +CREATE PROCEDURE add_phone @pers_id int, @phone varchar(255) AS +INSERT INTO example.phones (pers_id,phone) VALUES (@pers_id,@phone) +GO + +CREATE PROCEDURE delete_phone @keyval int,@phone varchar(64) AS +DELETE FROM example.phones WHERE pers_id=@keyval AND phone=@phone; +GO + +CREATE PROCEDURE set_person_name @keyval int, @new_name varchar(255) AS +UPDATE example.persons SET name=@new_name WHERE id=@keyval; +GO + +CREATE PROCEDURE set_person_surname @keyval int, @new_surname varchar(255) AS +UPDATE example.persons SET surname=@new_surname WHERE id=@keyval; +GO + +CREATE PROCEDURE set_org_name @keyval int, @new_name varchar(255) AS +UPDATE example.institutes SET name=@new_name WHERE id=@keyval; +GO + +CREATE PROCEDURE set_doc_title @keyval int, @new_title varchar(255) AS +UPDATE example.documents SET title=@new_title WHERE id=@keyval; +GO + +CREATE PROCEDURE set_doc_abstract @keyval int, @new_abstract varchar(255) AS +UPDATE example.documents SET abstract=@new_abstract WHERE id=@keyval; +GO + +CREATE PROCEDURE make_author_link @keyval int, @author_dn varchar(255) AS +DECLARE @per_id int; +SET @per_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=1 AND dn=@author_dn); +IF NOT (@per_id IS NULL) + INSERT INTO example.authors_docs (doc_id,pers_id) VALUES (@keyval,@per_id); +GO + +CREATE PROCEDURE make_doc_link @keyval int, @doc_dn varchar(255) AS +DECLARE @doc_id int; +SET @doc_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=2 AND dn=@doc_dn); +IF NOT (@doc_id IS NULL) + INSERT INTO example.authors_docs (pers_id,doc_id) VALUES (@keyval,@doc_id); +GO + +CREATE PROCEDURE del_doc_link @keyval int, @doc_dn varchar(255) AS +DECLARE @doc_id int; +SET @doc_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=2 AND dn=@doc_dn); +IF NOT (@doc_id IS NULL) +DELETE FROM example.authors_docs WHERE pers_id=@keyval AND doc_id=@doc_id; +GO + +CREATE PROCEDURE del_author_link @keyval int, @author_dn varchar(255) AS +DECLARE @per_id int; +SET @per_id=(SELECT keyval FROM example.ldap_entries + WHERE oc_map_id=1 AND dn=@author_dn); +IF NOT (@per_id IS NULL) + DELETE FROM example.authors_docs WHERE doc_id=@keyval AND pers_id=@per_id; +GO diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql new file mode 100644 index 0000000..b2029c4 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql @@ -0,0 +1,58 @@ +drop table if exists ldap_oc_mappings; +create table ldap_oc_mappings + ( + id integer unsigned not null primary key auto_increment, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + delete_proc varchar(255), + expect_return tinyint not null +); + +drop table if exists ldap_attr_mappings; +create table ldap_attr_mappings + ( + id integer unsigned not null primary key auto_increment, + oc_map_id integer unsigned not null references ldap_oc_mappings(id), + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order tinyint not null, + expect_return tinyint not null +); + +drop table if exists ldap_entries; +create table ldap_entries + ( + id integer unsigned not null primary key auto_increment, + dn varchar(255) not null, + oc_map_id integer unsigned not null references ldap_oc_mappings(id), + parent int NOT NULL , + keyval int NOT NULL +); + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ); + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ); + +drop table if exists ldap_entry_objclasses; +create table ldap_entry_objclasses + ( + entry_id integer unsigned not null references ldap_entries(id), + oc_name varchar(64) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql new file mode 100644 index 0000000..a81fa8b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS ldap_entry_objclasses; + +DROP TABLE IF EXISTS ldap_attr_mappings; + +DROP TABLE IF EXISTS ldap_entries; + +DROP TABLE IF EXISTS ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf new file mode 100644 index 0000000..8f6e4e1 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf @@ -0,0 +1,32 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_mysql +dbuser root +dbpasswd +subtree_cond "ldap_entries.dn LIKE CONCAT('%',?)" +insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)" +has_ldapinfo_dn_ru no diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql new file mode 100644 index 0000000..b35261b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql @@ -0,0 +1,86 @@ +drop table if exists persons; +CREATE TABLE persons ( + id int NOT NULL, + name varchar(255) NOT NULL, + surname varchar(255) NOT NULL, + password varchar(64) +); + +drop table if exists institutes; +CREATE TABLE institutes ( + id int NOT NULL, + name varchar(255) +); + +drop table if exists documents; +CREATE TABLE documents ( + id int NOT NULL, + title varchar(255) NOT NULL, + abstract varchar(255) +); + +drop table if exists authors_docs; +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL +); + +drop table if exists phones; +CREATE TABLE phones ( + id int NOT NULL , + phone varchar(255) NOT NULL , + pers_id int NOT NULL +); + +drop table if exists certs; +CREATE TABLE certs ( + id int NOT NULL , + cert LONGBLOB NOT NULL, + pers_id int NOT NULL +); + +ALTER TABLE authors_docs ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ); + +ALTER TABLE documents ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ); + +ALTER TABLE institutes ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ); + + +ALTER TABLE persons ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ); + +ALTER TABLE phones ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ); + +ALTER TABLE certs ADD + CONSTRAINT PK_certs PRIMARY KEY + ( + id + ); + +drop table if exists referrals; +CREATE TABLE referrals ( + id int NOT NULL, + name varchar(255) NOT NULL, + url varchar(255) NOT NULL +); + diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql new file mode 100644 index 0000000..0ccbfb7 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql @@ -0,0 +1,21 @@ +insert into institutes (id,name) values (1,'Example'); + +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit'); +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy'); +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); + +insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/'); + +insert into certs (id,cert,pers_id) values (1,UNHEX('3082036b308202d4a003020102020102300d06092a864886f70d01010405003077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d301e170d3033313031373136333331395a170d3034313031363136333331395a307e310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311830160603550403130f557273756c612048616d7073746572311f301d06092a864886f70d01090116107568616d406578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100eec60a7910b57d2e687158ca55eea738d36f10413dfecf31435e1aeeb9713b8e2da7dd2dde6bc6cec03b4987eaa7b037b9eb50e11c71e58088cc282883122cd8329c6f24f6045e6be9d21b9190c8292998267a5f7905292de936262747ab4b76a88a63872c41629a69d32e894d44c896a8d06fab0a1bc7de343c6c1458478f290203010001a381ff3081fc30090603551d1304023000302c06096086480186f842010d041f161d4f70656e53534c2047656e657261746564204365727469666963617465301d0603551d0e04160414a323de136c19ae0c479450e882dfb10ad147f45e3081a10603551d2304819930819680144b6f211a3624d290f943b053472d7de1c0e69823a17ba4793077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d820100300d06092a864886f70d010104050003818100881470045bdce95660d6e6af59e6a844aec4b9f5eaea88d4eb7a5a47080afa64750f81a3e47d00fd39c69a17a1c66d29d36f06edc537107f8c592239c2d4da55fb3f1d488e7b2387ad2a551cbd1ceb070ae9e020a9467275cb28798abb4cbfff98ddb3f1e7689b067072392511bb08125b5bec2bc207b7b6b275c47248f29acd'),3); + diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql new file mode 100644 index 0000000..7c5e9e7 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS persons; +DROP TABLE IF EXISTS institutes; +DROP TABLE IF EXISTS documents; +DROP TABLE IF EXISTS authors_docs; +DROP TABLE IF EXISTS phones; diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql new file mode 100644 index 0000000..d7e88e4 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql @@ -0,0 +1,125 @@ +-- mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id',NULL,NULL,0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (2,'document','documents','id',NULL,NULL,0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (3,'organization','institutes','id',NULL,NULL,0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (4,'referral','referrals','id',NULL,NULL,0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn',"concat(persons.name,' ',persons.surname)",'persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id',NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'givenName','persons.name','persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'sn','persons.surname','persons',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL',NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','concat(''document '',documents.id)','documents',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (13,4,'ou','referrals.name','referrals',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (14,4,'ref','referrals.url','referrals',NULL,NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (15,1,'userCertificate','certs.cert','persons,certs', + 'certs.pers_id=persons.id',NULL,NULL,3,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (1,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,dc=example,dc=com',2,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (7,'ou=Referral,dc=example,dc=com',4,1,1); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) +values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'pkiUser'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (7,'extensibleObject'); + diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql new file mode 100644 index 0000000..2e4e6ec --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql @@ -0,0 +1,90 @@ +create table ldap_oc_mappings ( + id number not null , + name varchar2(64) not null , + keytbl varchar2(64) not null , + keycol varchar2(64) not null , + create_proc varchar2(255), + delete_proc varchar2(255), + expect_return number not null +); + +alter table ldap_oc_mappings add + constraint PK_ldap_oc_mappings primary key + ( + id + ); + +alter table ldap_oc_mappings add + constraint unq_ldap_oc_mappings unique + ( + name + ); + +create table ldap_attr_mappings ( + id number not null, + oc_map_id number not null references ldap_oc_mappings(id), + name varchar2(255) not null, + sel_expr varchar2(255) not null, + sel_expr_u varchar2(255), + from_tbls varchar2(255) not null, + join_where varchar2(255), + add_proc varchar2(255), + delete_proc varchar2(255), + param_order number not null, + expect_return number not null +); + +alter table ldap_attr_mappings add + constraint pk_ldap_attr_mappings primary key + ( + id + ); + + +create table ldap_entries ( + id number not null , + dn varchar2(255) not null , + dn_ru varchar2(255), + oc_map_id number not null references ldap_oc_mappings(id), + parent number not null , + keyval number not null +); + +alter table ldap_entries add + constraint PK_ldap_entries primary key + ( + id + ); + +alter table ldap_entries add + constraint unq1_ldap_entries unique + ( + oc_map_id, + keyval + ); + +alter table ldap_entries add + constraint unq2_ldap_entries unique + ( + dn + ); + +create sequence ldap_objclass_ids start with 1 increment by 1; + +create sequence ldap_attr_ids start with 1 increment by 1; + +create sequence ldap_entry_ids start with 1 increment by 1; + +create table ldap_referrals + ( + entry_id number not null references ldap_entries(id), + url varchar(1023) not null +); + +create table ldap_entry_objclasses + ( + entry_id number not null references ldap_entries(id), + oc_name varchar(64) + ); + +quit diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql new file mode 100644 index 0000000..19bb8b6 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql @@ -0,0 +1,8 @@ +drop table ldap_attr_mappings; +drop table ldap_entry_objclasses; +drop table ldap_referrals; +drop sequence ldap_entry_ids; +drop sequence ldap_attr_ids; +drop sequence ldap_objclass_ids; +drop table ldap_entries; +drop table ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf new file mode 100644 index 0000000..cc195d9 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf @@ -0,0 +1,32 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_ora8 +dbuser ldap +dbpasswd ldap +subtree_cond "UPPER(ldap_entries.dn) LIKE CONCAT('%',UPPER(?))" +insentry_stmt "INSERT INTO ldap_entries (id,dn,oc_map_id,parent,keyval) VALUES (ldap_entry_ids.nextval,?,?,?,?)" +upper_func UPPER diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql new file mode 100644 index 0000000..710a5fa --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql @@ -0,0 +1,68 @@ +CREATE TABLE persons ( + id NUMBER NOT NULL, + name varchar2(255) NOT NULL, + surname varchar2(255) NOT NULL, + password varchar2(64) NOT NULL +); + +CREATE TABLE institutes ( + id NUMBER NOT NULL, + name varchar2(255) +); + +CREATE TABLE documents ( + id NUMBER NOT NULL, + title varchar2(255) NOT NULL, + abstract varchar2(255) +); + +CREATE TABLE authors_docs ( + pers_id NUMBER NOT NULL, + doc_id NUMBER NOT NULL +); + +CREATE TABLE phones ( + id NUMBER NOT NULL , + phone varchar2(255) NOT NULL , + pers_id NUMBER NOT NULL +); + + +ALTER TABLE authors_docs ADD + CONSTRAINT PK_authors_docs PRIMARY KEY + ( + pers_id, + doc_id + ); + +ALTER TABLE documents ADD + CONSTRAINT PK_documents PRIMARY KEY + ( + id + ); + +ALTER TABLE institutes ADD + CONSTRAINT PK_institutes PRIMARY KEY + ( + id + ); + +ALTER TABLE persons ADD + CONSTRAINT PK_persons PRIMARY KEY + ( + id + ); + +ALTER TABLE phones ADD + CONSTRAINT PK_phones PRIMARY KEY + ( + id + ); + +CREATE SEQUENCE person_ids START WITH 1 INCREMENT BY 1; + +CREATE SEQUENCE document_ids START WITH 1 INCREMENT BY 1; + +CREATE SEQUENCE institute_ids START WITH 1 INCREMENT BY 1; + +CREATE SEQUENCE phone_ids START WITH 1 INCREMENT BY 1; diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql new file mode 100644 index 0000000..4fc1977 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql @@ -0,0 +1,27 @@ +insert into institutes (id,name) values (institute_ids.nextval,'example'); + +insert into persons (id,name,surname,password) values (person_ids.nextval,'Mitya','Kovalev','mit'); + +insert into persons (id,name,surname) values (person_ids.nextval,'Torvlobnor','Puzdoy'); + +insert into persons (id,name,surname) values (person_ids.nextval,'Akakiy','Zinberstein'); + + +insert into phones (id,phone,pers_id) values (phone_ids.nextval,'332-2334',1); + +insert into phones (id,phone,pers_id) values (phone_ids.nextval,'222-3234',1); + +insert into phones (id,phone,pers_id) values (phone_ids.nextval,'545-4563',2); + + +insert into documents (id,abstract,title) values (document_ids.nextval,'abstract1','book1'); + +insert into documents (id,abstract,title) values (document_ids.nextval,'abstract2','book2'); + + +insert into authors_docs (pers_id,doc_id) values (1,1); + +insert into authors_docs (pers_id,doc_id) values (1,2); + +insert into authors_docs (pers_id,doc_id) values (2,1); + diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql new file mode 100644 index 0000000..0cf4463 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql @@ -0,0 +1,25 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; +DROP SEQUENCE person_ids; +DROP SEQUENCE institute_ids; +DROP SEQUENCE document_ids; +DROP SEQUENCE phone_ids; +DROP PROCEDURE create_person; +DROP PROCEDURE delete_person; +DROP PROCEDURE add_phone; +DROP PROCEDURE delete_phone; +DROP PROCEDURE set_person_name; +DROP PROCEDURE set_org_name; +DROP PROCEDURE set_doc_title; +DROP PROCEDURE set_doc_abstract; +DROP PROCEDURE create_document; +DROP PROCEDURE create_org; +DROP PROCEDURE delete_document; +DROP PROCEDURE delete_org; +DROP PROCEDURE make_doc_link; +DROP PROCEDURE del_doc_link; +DROP PROCEDURE make_author_link; +DROP PROCEDURE del_author_link; diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql new file mode 100644 index 0000000..354d7bd --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql @@ -0,0 +1,252 @@ +-- mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (1,'inetOrgPerson','persons','id','call create_person(?)','call delete_person(?)',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (2,'document','documents','id','call create_document(?)','call delete_document(?)',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) +values (3,'organization','institutes','id','call create_org(?)','call delete_org(?)',0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL, + NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (2,1,'telephoneNumber','phones.phone','persons,phones', + 'phones.pers_id=persons.id','call add_phone(?,?)', + 'call delete_phone(?,?)',0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (3,1,'givenName','persons.name','persons',NULL,'call set_person_name(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (4,1,'sn','persons.surname','persons',NULL,'call set_person_surname(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (5,1,'userPassword','persons.password','persons', + 'persons.password IS NOT NULL','call set_person_password(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (6,1,'seeAlso','seeAlso.dn','ldap_entries seeAlso,documents,authors_docs,persons', + 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (7,2,'description','documents.abstract','documents',NULL,'call set_doc_abstract(?,?)', + NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,2,'documentTitle','documents.title','documents',NULL,'call set_doc_title(?,?)',NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries documentAuthor,documents,authors_docs,persons', + 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + '?=call make_author_link(?,?)','?=call del_author_link(?,?)',0,3); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (11,3,'o','institutes.name','institutes',NULL,'call set_org_name(?,?)',NULL,0,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries dcObject,ldap_entry_objclasses auxObjectClass', + 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''', + NULL,NULL,0,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (ldap_entry_ids.nextval,'documentTitle=book2,dc=example,dc=com',2,1,2); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) +values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +-- referrals mapping: entries that should be treated as referrals are stored here +-- entry_id the "ldap_entries.id" of the entry that should be treated as a referral +-- url the URI of the referral +insert into ldap_referrals (entry_id,url) +values (4,'ldap://localhost:9012/'); + + +-- procedures +-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion +CREATE OR REPLACE PROCEDURE create_person(keyval OUT NUMBER) AS +BEGIN +INSERT INTO persons (id,name) VALUES (person_ids.nextval,' '); +SELECT person_ids.currval INTO keyval FROM DUAL; +END; +/ + +CREATE OR REPLACE PROCEDURE delete_person(keyval IN NUMBER) AS +BEGIN +DELETE FROM phones WHERE pers_id=keyval; +DELETE FROM authors_docs WHERE pers_id=keyval; +DELETE FROM persons WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE create_org(keyval OUT NUMBER) AS +BEGIN +INSERT INTO institutes (id,name) VALUES (institute_ids.nextval,' '); +SELECT institute_ids.currval INTO keyval FROM DUAL; +END; +/ + +CREATE OR REPLACE PROCEDURE delete_org(keyval IN NUMBER) AS +BEGIN +DELETE FROM institutes WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE create_document(keyval OUT NUMBER) AS +BEGIN +INSERT INTO documents (id,title) VALUES (document_ids.nextval,' '); +SELECT document_ids.currval INTO keyval FROM DUAL; +END; +/ + +CREATE OR REPLACE PROCEDURE delete_document (keyval IN NUMBER) AS +BEGIN +DELETE FROM authors_docs WHERE doc_id=keyval; +DELETE FROM documents WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE add_phone(pers_id IN NUMBER, phone IN varchar2) AS +BEGIN +INSERT INTO phones (id,pers_id,phone) VALUES (phone_ids.nextval,pers_id,phone); +END; +/ + +CREATE OR REPLACE PROCEDURE delete_phone(keyval IN NUMBER, phone IN varchar2) AS +BEGIN +DELETE FROM phones WHERE pers_id=keyval AND phone=phone; +END; +/ + +CREATE OR REPLACE PROCEDURE set_person_name(keyval IN NUMBER, new_name IN varchar2) AS +BEGIN +UPDATE persons SET name=new_name WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE set_org_name(keyval IN NUMBER, new_name IN varchar2) AS +BEGIN +UPDATE institutes SET name=new_name WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE set_doc_title (keyval IN NUMBER, new_title IN varchar2) AS +BEGIN +UPDATE documents SET title=new_title WHERE id=keyval; +END; +/ + +CREATE OR REPLACE PROCEDURE set_doc_abstract (keyval IN NUMBER, new_abstract IN varchar2) AS +BEGIN +UPDATE documents SET abstract=new_abstract WHERE id=keyval; +END; +/ + +CREATE OR REPLACE FUNCTION make_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS +per_id NUMBER; +BEGIN +SELECT keyval INTO per_id FROM ldap_entries + WHERE oc_map_id=1 AND dn=author_dn; +IF NOT (per_id IS NULL) THEN + INSERT INTO authors_docs (doc_id,pers_id) VALUES (keyval,per_id); + RETURN 1; +END IF; +RETURN 0; +END; +/ + +CREATE OR REPLACE FUNCTION make_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS +docid NUMBER; +BEGIN +SELECT keyval INTO docid FROM ldap_entries + WHERE oc_map_id=2 AND dn=doc_dn; +IF NOT (docid IS NULL) THEN + INSERT INTO authors_docs (pers_id,doc_id) VALUES (keyval,docid); + RETURN 1; +END IF; +RETURN 0; +END; +/ + +CREATE OR REPLACE FUNCTION del_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS +docid NUMBER; +BEGIN +SELECT keyval INTO docid FROM ldap_entries + WHERE oc_map_id=2 AND dn=doc_dn; +IF NOT (docid IS NULL) THEN + DELETE FROM authors_docs WHERE pers_id=keyval AND doc_id=docid; + RETURN 1; +END IF; +RETURN 0; +END; +/ + +CREATE OR REPLACE FUNCTION del_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS +per_id NUMBER; +BEGIN +SELECT keyval INTO per_id FROM ldap_entries + WHERE oc_map_id=1 AND dn=author_dn; + +IF NOT (per_id IS NULL) THEN + DELETE FROM authors_docs WHERE doc_id=keyval AND pers_id=per_id; + RETURN 1; +END IF; + RETURN 0; +END; +/ diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql new file mode 100644 index 0000000..a4baa70 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql @@ -0,0 +1,50 @@ +drop table ldap_oc_mappings; +drop sequence ldap_oc_mappings_id_seq; +create table ldap_oc_mappings + ( + id serial not null primary key, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + delete_proc varchar(255), + expect_return int not null +); + +drop table ldap_attr_mappings; +drop sequence ldap_attr_mappings_id_seq; +create table ldap_attr_mappings + ( + id serial not null primary key, + oc_map_id integer not null references ldap_oc_mappings(id), + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order int not null, + expect_return int not null +); + +drop table ldap_entries; +drop sequence ldap_entries_id_seq; +create table ldap_entries + ( + id serial not null primary key, + dn varchar(255) not null, + oc_map_id integer not null references ldap_oc_mappings(id), + parent int NOT NULL, + keyval int NOT NULL, + UNIQUE ( oc_map_id, keyval ), + UNIQUE ( dn ) +); + +drop table ldap_entry_objclasses; +create table ldap_entry_objclasses + ( + entry_id integer not null references ldap_entries(id), + oc_name varchar(64) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql new file mode 100644 index 0000000..eff0a9e --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql @@ -0,0 +1,4 @@ +DROP TABLE ldap_entry_objclasses; +DROP TABLE ldap_attr_mappings; +DROP TABLE ldap_entries; +DROP TABLE ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf new file mode 100644 index 0000000..70a8dee --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf @@ -0,0 +1,35 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname PostgreSQL +dbuser postgres +dbpasswd postgres +insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)" +upper_func "upper" +strcast_func "text" +concat_pattern "?||?" +has_ldapinfo_dn_ru no + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql new file mode 100644 index 0000000..e1c57e7 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql @@ -0,0 +1,55 @@ +drop table persons; +drop sequence persons_id_seq; +create table persons ( + id serial not null primary key, + name varchar(255) not null, + surname varchar(255) not null, + password varchar(64) +); + +drop table institutes; +drop sequence institutes_id_seq; +create table institutes ( + id serial not null primary key, + name varchar(255) +); + +drop table documents; +drop sequence documents_id_seq; +create table documents ( + id serial not null primary key, + title varchar(255) not null, + abstract varchar(255) +); + +drop table authors_docs; +create table authors_docs ( + pers_id int not null, + doc_id int not null, + primary key ( pers_id, doc_id ) +); + +drop table phones; +drop sequence phones_id_seq; +create table phones ( + id serial not null primary key, + phone varchar(255) not null , + pers_id int not null +); + +drop table certs; +drop sequence certs_id_seq; +CREATE TABLE certs ( + id int not null primary key, + cert bytea not null, + pers_id int not null +); + +drop table referrals; +drop sequence referrals_id_seq; +create table referrals ( + id serial not null primary key, + name varchar(255) not null, + url varchar(255) not null +); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql new file mode 100644 index 0000000..0e661d4 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql @@ -0,0 +1,21 @@ +insert into institutes (id,name) values (1,'Example'); + +insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit'); +insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy'); +insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); + +insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/'); + +insert into certs (id,cert,pers_id) values (1,decode('MIIDazCCAtSgAwIBAgIBAjANBgkqhkiG9w0BAQQFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjETMBEGA1UEAxMKRXhhbXBsZSBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20wHhcNMDMxMDE3MTYzMzE5WhcNMDQxMDE2MTYzMzE5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjEYMBYGA1UEAxMPVXJzdWxhIEhhbXBzdGVyMR8wHQYJKoZIhvcNAQkBFhB1aGFtQGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDuxgp5ELV9LmhxWMpV7qc4028QQT3+zzFDXhruuXE7ji2n3S3ea8bOwDtJh+qnsDe561DhHHHlgIjMKCiDEizYMpxvJPYEXmvp0huRkMgpKZgmel95BSkt6TYmJ0erS3aoimOHLEFimmnTLolNRMiWqNBvqwobx940PGwUWEePKQIDAQABo4H/MIH8MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBSjI94TbBmuDEeUUOiC37EK0Uf0XjCBoQYDVR0jBIGZMIGWgBRLbyEaNiTSkPlDsFNHLX3hwOaYI6F7pHkwdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExHzAdBgNVBAoTFk9wZW5MREFQIEV4YW1wbGUsIEx0ZC4xEzARBgNVBAMTCkV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAIgUcARb3OlWYNbmr1nmqESuxLn16uqI1Ot6WkcICvpkdQ+Bo+R9AP05xpoXocZtKdNvBu3FNxB/jFkiOcLU2lX7Px1Ijnsjh60qVRy9HOsHCungIKlGcnXLKHmKu0y//5jds/HnaJsGcHI5JRG7CBJbW+wrwge3trJ1xHJI8prN','base64'),3); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql new file mode 100644 index 0000000..c061ff8 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql @@ -0,0 +1,13 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; +DROP TABLE referrals; +DROP FUNCTION create_person (); +DROP FUNCTION update_person_cn (varchar, int); +DROP FUNCTION add_phone (varchar, int); +DROP FUNCTION create_doc (); +DROP FUNCTION create_o (); +DROP FUNCTION create_referral (); + diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql new file mode 100644 index 0000000..d645cf2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql @@ -0,0 +1,146 @@ +-- mappings + +-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry +-- id a unique number identifying the objectClass +-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +-- keytbl the name of the table that is referenced for the primary key of an entry +-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id" +-- create_proc a procedure to create the entry +-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted +-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (1,'inetOrgPerson','persons','id','SELECT create_person()','DELETE FROM persons WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (2,'document','documents','id','SELECT create_doc()','DELETE FROM documents WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (3,'organization','institutes','id','SELECT create_o()','DELETE FROM institutes WHERE id=?',0); + +insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (4,'referral','referrals','id','SELECT create_referral()','DELETE FROM referrals WHERE id=?',0); + +-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data. +-- id a unique number identifying the attribute +-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for +-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema +-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion) +-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion) +-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion) +-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to +-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2) +-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (1,1,'cn','text(persons.name||'' ''||persons.surname)','persons',NULL,'SELECT update_person_cn(?,?)','SELECT 1 FROM persons WHERE persons.name=? AND persons.id=? AND 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (2,1,'telephoneNumber','phones.phone','persons,phones','phones.pers_id=persons.id','SELECT add_phone(?,?)','DELETE FROM phones WHERE phone=? AND pers_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (3,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?','UPDATE persons SET name='''' WHERE (name=? OR name='''') AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (4,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?','UPDATE persons SET surname='''' WHERE (surname=? OR surname='''') AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?','UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons','seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',NULL,'DELETE from authors_docs WHERE authors_docs.doc_id=(SELECT documents.id FROM documents,ldap_entries AS seeAlso WHERE seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND seeAlso.dn=?) AND authors_docs.pers_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?','UPDATE documents SET abstract='''' WHERE abstract=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?','UPDATE documents SET title='''' WHERE title=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons','documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id','INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)','DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,'SELECT 1 FROM documents WHERE title=? AND id=? AND 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?','UPDATE institutes SET name='''' WHERE name=? AND id=?',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass','institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',NULL,'SELECT 1 FROM institutes WHERE lower(name)=? AND id=? and 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?','SELECT 1 FROM referrals WHERE name=? AND id=? and 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?','SELECT 1 FROM referrals WHERE url=? and id=? and 1=0',3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (15,1,'userCertificate','certs.cert','persons,certs','certs.pers_id=persons.id',NULL,NULL,3,0); + +-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context +-- id a unique number > 0 identifying the entry +-- dn the DN of the entry, in "pretty" form +-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass) +-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database +-- keyval the value of the "keytbl.keycol" defined for this objectClass +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (1,'dc=example,dc=com',3,0,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (5,'documentTitle=book1,dc=example,dc=com',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (6,'documentTitle=book2,dc=example,dc=com',2,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (7,'ou=Referral,dc=example,dc=com',4,1,1); + +-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass) +-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added +-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema +insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject'); + +insert into ldap_entry_objclasses (entry_id,oc_name) values (4,'pkiUser'); + +insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject'); + +-- procedures +-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion +create function create_person () returns int +as ' + select setval (''persons_id_seq'', (select case when max(id) is null then 1 else max(id) end from persons)); + insert into persons (id,name,surname) + values ((select case when max(id) is null then 1 else nextval(''persons_id_seq'') end from persons),'''',''''); + select max(id) from persons +' language 'sql'; + +create function update_person_cn (varchar, int) returns int +as ' + update persons set name = ( + select case + when position('' '' in $1) = 0 then $1 + else substr($1, 1, position('' '' in $1) - 1) + end + ),surname = ( + select case + when position('' '' in $1) = 0 then '''' + else substr($1, position('' '' in $1) + 1) + end + ) where id = $2; + select $2 as return +' language 'sql'; + +create function add_phone (varchar, int) returns int +as ' + select setval (''phones_id_seq'', (select case when max(id) is null then 1 else max(id) end from phones)); + insert into phones (id,phone,pers_id) + values (nextval(''phones_id_seq''),$1,$2); + select max(id) from phones +' language 'sql'; + +create function create_doc () returns int +as ' + select setval (''documents_id_seq'', (select case when max(id) is null then 1 else max(id) end from documents)); + insert into documents (id,title,abstract) + values ((select case when max(id) is null then 1 else nextval(''documents_id_seq'') end from documents),'''',''''); + select max(id) from documents +' language 'sql'; + +create function create_o () returns int +as ' + select setval (''institutes_id_seq'', (select case when max(id) is null then 1 else max(id) end from institutes)); + insert into institutes (id,name) + values ((select case when max(id) is null then 1 else nextval(''institutes_id_seq'') end from institutes),''''); + select max(id) from institutes +' language 'sql'; + +create function create_referral () returns int +as ' + select setval (''referrals_id_seq'', (select case when max(id) is null then 1 else max(id) end from referrals)); + insert into referrals (id,name,url) + values ((select case when max(id) is null then 1 else nextval(''referrals_id_seq'') end from referrals),'''',''''); + select max(id) from referrals +' language 'sql'; + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql new file mode 100644 index 0000000..055e9df --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql @@ -0,0 +1,66 @@ + +create table ldap_oc_mappings + ( + id integer not null primary key, + name varchar(64) not null, + keytbl varchar(64) not null, + keycol varchar(64) not null, + create_proc varchar(255), + delete_proc varchar(255), + expect_return tinyint not null +); + +create table ldap_attr_mappings + ( + id integer not null primary key, + oc_map_id integer not null, + name varchar(255) not null, + sel_expr varchar(255) not null, + sel_expr_u varchar(255), + from_tbls varchar(255) not null, + join_where varchar(255), + add_proc varchar(255), + delete_proc varchar(255), + param_order tinyint not null, + expect_return tinyint not null, + foreign key (oc_map_id) references ldap_oc_mappings(id) +); + +create table ldap_entries + ( + id integer not null primary key, + dn varchar(255) not null, + dn_ru varchar(255), + oc_map_id integer not null, + parent int NOT NULL , + keyval int NOT NULL, + foreign key (oc_map_id) references ldap_oc_mappings(id) +); + +create index ldap_entriesx1 on ldap_entries(dn_ru); + +create unique index unq1_ldap_entries on ldap_entries + ( + oc_map_id, + keyval + ); + +create unique index unq2_ldap_entries on ldap_entries + ( + dn + ); + +create table ldap_referrals + ( + entry_id integer not null, + url varchar(4096) not null, + foreign key (entry_id) references ldap_entries(id) +); + +create table ldap_entry_objclasses + ( + entry_id integer not null, + oc_name varchar(64), + foreign key (entry_id) references ldap_entries(id) + ); + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql new file mode 100644 index 0000000..7aa0b83 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql @@ -0,0 +1,9 @@ +DROP TABLE ldap_referrals; + +DROP TABLE ldap_entry_objclasses; + +DROP TABLE ldap_attr_mappings; + +DROP TABLE ldap_entries; + +DROP TABLE ldap_oc_mappings; diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh new file mode 100755 index 0000000..947db21 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh @@ -0,0 +1,4 @@ +ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql +ttIsql -connStr "DSN=ldap_tt" -f testdb_create.sql +ttIsql -connStr "DSN=ldap_tt" -f testdb_data.sql +ttIsql -connStr "DSN=ldap_tt" -f testdb_metadata.sql diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile new file mode 100644 index 0000000..1b0b1ee --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile @@ -0,0 +1,48 @@ +## Copyright 1997-2022 The OpenLDAP Foundation, All Rights Reserved. +## COPYING RESTRICTIONS APPLY, see COPYRIGHT file + +# +# Build TimesTen ODBC Sample Programs for Solaris 2.5.1. +# (c) Copyright 1996-1998, TimesTen Performance Software. +# All rights reserved. +## Note: This file was contributed by Sam Drake of TimesTen Performance +## Software for use and redistribution as an integral part of +## OpenLDAP Software. -Kdz + +CPLUSPLUS = CC +TTCLASSES = ../../../../../../../../../cs/classes +ODBC = /opt/TimesTen4.1/32 +CFLAGS = -g -I$(ODBC)/include -I. -I$(TTCLASSES) -DUNIX +LDFLAGS = -g +DIRLIBS = $(TTCLASSES)/ttclasses.a -L $(ODBC)/lib -R $(ODBC)/lib -ltten -lpthread -lm -lrt +XLALIB = -L $(ODBC)/lib -lxla + +DIRPROGS= dnreverse + +DNREVERSE= dnreverse.o + +# +# Top-level targets +# + +all: $(DIRPROGS) + +direct: $(DIRPROGS) + +clean: + rm -rf $(DIRPROGS) *.o + + +# +# Direct-linked programs +# + +dnreverse: $(DNREVERSE) + $(CPLUSPLUS) -o dnreverse $(LDFLAGS) $(DNREVERSE) $(DIRLIBS) $(XLALIB) + +# +# .o files +# + +dnreverse.o: dnreverse.cpp + $(CPLUSPLUS) $(CFLAGS) -c dnreverse.cpp diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp new file mode 100644 index 0000000..7407b4e --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp @@ -0,0 +1,387 @@ +// Copyright 1997-2022 The OpenLDAP Foundation, All Rights Reserved. +// COPYING RESTRICTIONS APPLY, see COPYRIGHT file + +// (c) Copyright 1999-2001 TimesTen Performance Software. All rights reserved. + +//// Note: This file was contributed by Sam Drake of TimesTen Performance +//// Software for use and redistribution as an integral part of +//// OpenLDAP Software. -Kdz + +#include <stdlib.h> + +#include <TTConnectionPool.h> +#include <TTConnection.h> +#include <TTCmd.h> +#include <TTXla.h> + +#include <signal.h> + +TTConnectionPool pool; +TTXlaConnection conn; +TTConnection conn2; +TTCmd assignDn_ru; +TTCmd getNullDNs; + +//---------------------------------------------------------------------- +// This class contains all the logic to be implemented whenever +// the SCOTT.MYDATA table is changed. This is the table that is +// created by "sample.cpp", one of the other TTClasses demos. +// That application should be executed before this one in order to +// create and populate the table. +//---------------------------------------------------------------------- + +class LDAPEntriesHandler: public TTXlaTableHandler { +private: + // Definition of the columns in the table + int Id; + int Dn; + int Oc_map_id; + int Parent; + int Keyval; + int Dn_ru; + +protected: + +public: + LDAPEntriesHandler(TTXlaConnection& conn, const char* ownerP, const char* nameP); + ~LDAPEntriesHandler(); + + virtual void HandleDelete(ttXlaUpdateDesc_t*); + virtual void HandleInsert(ttXlaUpdateDesc_t*); + virtual void HandleUpdate(ttXlaUpdateDesc_t*); + + static void ReverseAndUpper(char* dnP, int id, bool commit=true); + +}; + +LDAPEntriesHandler::LDAPEntriesHandler(TTXlaConnection& conn, + const char* ownerP, const char* nameP) : + TTXlaTableHandler(conn, ownerP, nameP) +{ + Id = Dn = Oc_map_id = Parent = Keyval = Dn_ru = -1; + + // We are looking for several particular named columns. We need to get + // the ordinal position of the columns by name for later use. + + Id = tbl.getColNumber("ID"); + if (Id < 0) { + cerr << "target table has no 'ID' column" << endl; + exit(1); + } + Dn = tbl.getColNumber("DN"); + if (Dn < 0) { + cerr << "target table has no 'DN' column" << endl; + exit(1); + } + Oc_map_id = tbl.getColNumber("OC_MAP_ID"); + if (Oc_map_id < 0) { + cerr << "target table has no 'OC_MAP_ID' column" << endl; + exit(1); + } + Parent = tbl.getColNumber("PARENT"); + if (Parent < 0) { + cerr << "target table has no 'PARENT' column" << endl; + exit(1); + } + Keyval = tbl.getColNumber("KEYVAL"); + if (Keyval < 0) { + cerr << "target table has no 'KEYVAL' column" << endl; + exit(1); + } + Dn_ru = tbl.getColNumber("DN_RU"); + if (Dn_ru < 0) { + cerr << "target table has no 'DN_RU' column" << endl; + exit(1); + } + +} + +LDAPEntriesHandler::~LDAPEntriesHandler() +{ + +} + +void LDAPEntriesHandler::ReverseAndUpper(char* dnP, int id, bool commit) +{ + TTStatus stat; + char dn_rn[512]; + int i; + int j; + + // Reverse and upper case the given DN + + for ((j=0, i = strlen(dnP)-1); i > -1; (j++, i--)) { + dn_rn[j] = toupper(*(dnP+i)); + } + dn_rn[j] = '\0'; + + + // Update the database + + try { + assignDn_ru.setParam(1, (char*) &dn_rn[0]); + assignDn_ru.setParam(2, id); + assignDn_ru.Execute(stat); + } + catch (TTStatus stat) { + cerr << "Error updating id " << id << " ('" << dnP << "' to '" + << dn_rn << "'): " << stat; + exit(1); + } + + // Commit the transaction + + if (commit) { + try { + conn2.Commit(stat); + } + catch (TTStatus stat) { + cerr << "Error committing update: " << stat; + exit(1); + } + } + +} + + + +void LDAPEntriesHandler::HandleInsert(ttXlaUpdateDesc_t* p) +{ + char* dnP; + int id; + + row.Get(Dn, &dnP); + cerr << "DN '" << dnP << "': Inserted "; + row.Get(Id, &id); + + ReverseAndUpper(dnP, id); + +} + +void LDAPEntriesHandler::HandleUpdate(ttXlaUpdateDesc_t* p) +{ + char* newDnP; + char* oldDnP; + char oDn[512]; + int id; + + // row is 'old'; row2 is 'new' + row.Get(Dn, &oldDnP); + strcpy(oDn, oldDnP); + row.Get(Id, &id); + row2.Get(Dn, &newDnP); + + cerr << "old DN '" << oDn << "' / new DN '" << newDnP << "' : Updated "; + + if (strcmp(oDn, newDnP) != 0) { + // The DN field changed, update it + cerr << "(new DN: '" << newDnP << "')"; + ReverseAndUpper(newDnP, id); + } + else { + // The DN field did NOT change, leave it alone + } + + cerr << endl; + +} + +void LDAPEntriesHandler::HandleDelete(ttXlaUpdateDesc_t* p) +{ + char* dnP; + + row.Get(Dn, &dnP); + cerr << "DN '" << dnP << "': Deleted "; +} + + + + +//---------------------------------------------------------------------- + +int pleaseStop = 0; + +extern "C" { + void + onintr(int sig) + { + pleaseStop = 1; + cerr << "Stopping...\n"; + } +}; + +//---------------------------------------------------------------------- + +int +main(int argc, char* argv[]) +{ + + char* ownerP; + + TTXlaTableList list(&conn); // List of tables to monitor + + // Handlers, one for each table we want to monitor + + LDAPEntriesHandler* sampP = NULL; + + // Misc stuff + + TTStatus stat; + + ttXlaUpdateDesc_t ** arry; + + int records; + + SQLUBIGINT oldsize; + int j; + + if (argc < 2) { + cerr << "syntax: " << argv[0] << " <username>" << endl; + exit(3); + } + + ownerP = argv[1]; + + signal(SIGINT, onintr); /* signal for CTRL-C */ +#ifdef _WIN32 + signal(SIGBREAK, onintr); /* signal for CTRL-BREAK */ +#endif + + // Before we do anything related to XLA, first we connect + // to the database. This is the connection we will use + // to perform non-XLA operations on the tables. + + try { + cerr << "Connecting..." << endl; + + conn2.Connect("DSN=ldap_tt", stat); + } + catch (TTStatus stat) { + cerr << "Error connecting to TimesTen: " << stat; + exit(1); + } + + try { + assignDn_ru.Prepare(&conn2, + "update ldap_entries set dn_ru=? where id=?", + "", stat); + getNullDNs.Prepare(&conn2, + "select dn, id from ldap_entries " + "where dn_ru is null " + "for update", + "", stat); + conn2.Commit(stat); + } + catch (TTStatus stat) { + cerr << "Error preparing update: " << stat; + exit(1); + } + + // If there are any entries with a NULL reversed/upper cased DN, + // fix them now. + + try { + cerr << "Fixing NULL reversed DNs" << endl; + getNullDNs.Execute(stat); + for (int k = 0;; k++) { + getNullDNs.FetchNext(stat); + if (stat.rc == SQL_NO_DATA_FOUND) break; + char* dnP; + int id; + getNullDNs.getColumn(1, &dnP); + getNullDNs.getColumn(2, &id); + // cerr << "Id " << id << ", Dn '" << dnP << "'" << endl; + LDAPEntriesHandler::ReverseAndUpper(dnP, id, false); + if (k % 1000 == 0) + cerr << "."; + } + getNullDNs.Close(stat); + conn2.Commit(stat); + } + catch (TTStatus stat) { + cerr << "Error updating NULL rows: " << stat; + exit(1); + } + + + // Go ahead and start up the change monitoring application + + cerr << "Starting change monitoring..." << endl; + try { + conn.Connect("DSN=ldap_tt", stat); + } + catch (TTStatus stat) { + cerr << "Error connecting to TimesTen: " << stat; + exit(1); + } + + /* set and configure size of buffer */ + conn.setXlaBufferSize((SQLUBIGINT) 1000000, &oldsize, stat); + if (stat.rc) { + cerr << "Error setting buffer size " << stat << endl; + exit(1); + } + + // Make a handler to process changes to the MYDATA table and + // add the handler to the list of all handlers + + sampP = new LDAPEntriesHandler(conn, ownerP, "ldap_entries"); + if (!sampP) { + cerr << "Could not create LDAPEntriesHandler" << endl; + exit(3); + } + list.add(sampP); + + // Enable transaction logging for the table we're interested in + + sampP->EnableTracking(stat); + + // Get updates. Dispatch them to the appropriate handler. + // This loop will handle updates to all the tables. + + while (pleaseStop == 0) { + conn.fetchUpdates(&arry, 1000, &records, stat); + if (stat.rc) { + cerr << "Error fetching updates" << stat << endl; + exit(1); + } + + // Interpret the updates + + for(j=0;j < records;j++){ + ttXlaUpdateDesc_t *p; + + p = arry[j]; + + list.HandleChange(p, stat); + + } // end for each record fetched + + if (records) { + cerr << "Processed " << records << " records\n"; + } + + if (records == 0) { +#ifdef _WIN32 + Sleep(250); +#else + struct timeval t; + t.tv_sec = 0; + t.tv_usec = 250000; // .25 seconds + select(0, NULL, NULL, NULL, &t); +#endif + } + } // end while pleasestop == 0 + + + // When we get to here, the program is exiting. + + list.del(sampP); // Take the table out of the list + delete sampP; + + conn.setXlaBufferSize(oldsize, NULL, stat); + + return 0; + +} + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf new file mode 100644 index 0000000..f93de8b --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf @@ -0,0 +1,31 @@ +# $OpenLDAP$ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /usr/local/etc/openldap/schema/core.schema +include /usr/local/etc/openldap/schema/cosine.schema +include /usr/local/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /usr/local/var/slapd.pid +argsfile /usr/local/var/slapd.args + +####################################################################### +# sql database definitions +####################################################################### + +database sql +suffix "o=sql,c=RU" +rootdn "cn=root,o=sql,c=RU" +rootpw secret +dbname ldap_tt +dbuser root +dbpasswd +subtree_cond "ldap_entries.dn LIKE ?" +insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)" diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql new file mode 100644 index 0000000..768aec8 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql @@ -0,0 +1,36 @@ +CREATE TABLE persons ( + id int NOT NULL primary key, + name varchar(255) NOT NULL +) +unique hash on (id) pages=100; + +CREATE TABLE institutes ( + id int NOT NULL primary key, + name varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE documents ( + id int NOT NULL primary key, + title varchar(255) NOT NULL, + abstract varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL, + PRIMARY KEY + ( + pers_id, + doc_id + ) +) unique hash on (pers_id, doc_id) pages=100; + +CREATE TABLE phones ( + id int NOT NULL primary key, + phone varchar(255) NOT NULL , + pers_id int NOT NULL +) +unique hash on (id) pages=100; + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql new file mode 100644 index 0000000..f141f41 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql @@ -0,0 +1,16 @@ +insert into institutes (id,name) values (1,'sql'); + +insert into persons (id,name) values (1,'Mitya Kovalev'); +insert into persons (id,name) values (2,'Torvlobnor Puzdoy'); +insert into persons (id,name) values (3,'Akakiy Zinberstein'); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql new file mode 100644 index 0000000..17b12af --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql new file mode 100644 index 0000000..f9e3419 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql @@ -0,0 +1,108 @@ + +insert into ldap_oc_mappings +(id,name, keytbl, keycol, create_proc, +delete_proc,expect_return) +values +(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();', +NULL,0); + +insert into ldap_oc_mappings +(id, name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(2, 'document','documents','id', NULL, NULL, 0); + +insert into ldap_oc_mappings +(id,name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(3,'organization','institutes','id', NULL, NULL, 0); + + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls,join_where,add_proc, +delete_proc,param_order,expect_return) +values +(1, 1, 'cn', 'persons.name', 'persons',NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id,name, sel_expr, from_tbls, +join_where, add_proc,delete_proc,param_order,expect_return) +values +(2, 1, 'telephoneNumber','phones.phone','persons,phones', +'phones.pers_id=persons.id', NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(3, 1, 'sn', 'persons.name','persons', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(4, 2, 'description', 'documents.abstract','documents', NULL, +NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(5, 2, 'documentTitle','documents.title','documents',NULL, +NULL, NULL, 3, 0); + +-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs', +-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id', +-- NULL,NULL,3,0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +-- entries + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(1, 'o=sql,c=RU', 3, 0, 1); + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,o=sql,c=RU',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,o=sql,c=RU',2,1,2); + + +-- referrals + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +insert into ldap_referrals (entry_id,url) +values (4,'ldap://localhost:9012'); diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh new file mode 100755 index 0000000..c4c5df2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh @@ -0,0 +1,4 @@ +ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql +ttIsql -connStr "DSN=ldap_tt" -f tttestdb_create.sql +ttIsql -connStr "DSN=ldap_tt" -f tttestdb_data.sql +ttIsql -connStr "DSN=ldap_tt" -f tttestdb_metadata.sql diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql new file mode 100644 index 0000000..f5955d2 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql @@ -0,0 +1,42 @@ +CREATE TABLE persons ( + id int NOT NULL primary key, + name varchar(255) NOT NULL, + name_u varchar(255), + title varchar(255), + title_U varchar(255), + organization varchar(255) +) +unique hash on (id) pages=100; +create index personsx1 on persons(title_U); +create index personsx2 on persons(name_u); + +CREATE TABLE institutes ( + id int NOT NULL primary key, + name varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE documents ( + id int NOT NULL primary key, + title varchar(255) NOT NULL, + abstract varchar(255) +) +unique hash on (id) pages=100; + +CREATE TABLE authors_docs ( + pers_id int NOT NULL, + doc_id int NOT NULL, + PRIMARY KEY + ( + pers_id, + doc_id + ) +) unique hash on (pers_id, doc_id) pages=100; + +CREATE TABLE phones ( + id int NOT NULL primary key, + phone varchar(255) NOT NULL , + pers_id int NOT NULL +) +unique hash on (id) pages=100; + diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql new file mode 100644 index 0000000..ca75339 --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql @@ -0,0 +1,20 @@ +insert into institutes (id,name) values (1,'sql'); + +insert into persons (id,name, title, title_U, organization) values +(1,'Mitya Kovalev', 'Engineer', 'ENGINEER', 'Development'); +insert into persons (id,name, title, title_U, organization) values +(2,'Torvlobnor Puzdoy', 'Engineer', 'ENGINEER', 'Sales'); +insert into persons (id,name, title, title_U, organization) values +(3,'Akakiy Zinberstein', 'Engineer', 'ENGINEER', 'Marketing'); +update persons set name_u = upper(name); + +insert into phones (id,phone,pers_id) values (1,'332-2334',1); +insert into phones (id,phone,pers_id) values (2,'222-3234',1); +insert into phones (id,phone,pers_id) values (3,'545-4563',2); + +insert into documents (id,abstract,title) values (1,'abstract1','book1'); +insert into documents (id,abstract,title) values (2,'abstract2','book2'); + +insert into authors_docs (pers_id,doc_id) values (1,1); +insert into authors_docs (pers_id,doc_id) values (1,2); +insert into authors_docs (pers_id,doc_id) values (2,1); diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql new file mode 100644 index 0000000..17b12af --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql @@ -0,0 +1,5 @@ +DROP TABLE persons; +DROP TABLE institutes; +DROP TABLE documents; +DROP TABLE authors_docs; +DROP TABLE phones; diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql new file mode 100644 index 0000000..69bda8a --- /dev/null +++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql @@ -0,0 +1,122 @@ + +insert into ldap_oc_mappings +(id,name, keytbl, keycol, create_proc, +delete_proc,expect_return) +values +(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();', +NULL,0); + +insert into ldap_oc_mappings +(id, name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(2, 'document','documents','id', NULL, NULL, 0); + +insert into ldap_oc_mappings +(id,name, keytbl, keycol,create_proc,delete_proc,expect_return) +values +(3,'organization','institutes','id', NULL, NULL, 0); + + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls, +join_where,add_proc, delete_proc,param_order,expect_return) +values +(1, 1, 'cn', 'persons.name', 'persons.name_u','persons', +NULL, NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls,join_where, +add_proc, delete_proc,param_order,expect_return) +values +(10, 1, 'title', 'persons.title', 'persons.title_u', 'persons',NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id,name, sel_expr, from_tbls, +join_where, add_proc,delete_proc,param_order,expect_return) +values +(2, 1, 'telephoneNumber','phones.phone','persons,phones', +'phones.pers_id=persons.id', NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(3, 1, 'sn', 'persons.name','persons', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(30, 1, 'ou', 'persons.organization','persons', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(4, 2, 'description', 'documents.abstract','documents', NULL, +NULL, NULL, 3, 0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where, +add_proc,delete_proc,param_order,expect_return) +values +(5, 2, 'documentTitle','documents.title','documents',NULL, +NULL, NULL, 3, 0); + +-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs', +-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id', +-- NULL,NULL,3,0); + +insert into ldap_attr_mappings +(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc, +delete_proc,param_order,expect_return) +values +(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL, +NULL, 3, 0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) +values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons', + 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id', + NULL,NULL,3,0); + +-- entries + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(1, 'o=sql,c=RU', 3, 0, 1); + +insert into ldap_entries +(id, dn, oc_map_id, parent, keyval) +values +(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (5,'documentTitle=book1,o=sql,c=RU',2,1,1); + +insert into ldap_entries (id,dn,oc_map_id,parent,keyval) +values (6,'documentTitle=book2,o=sql,c=RU',2,1,2); + + +-- referrals + +insert into ldap_entry_objclasses (entry_id,oc_name) +values (4,'referral'); + +insert into ldap_referrals (entry_id,url) +values (4,'http://localhost'); diff --git a/servers/slapd/back-sql/schema-map.c b/servers/slapd/back-sql/schema-map.c new file mode 100644 index 0000000..f6294bb --- /dev/null +++ b/servers/slapd/back-sql/schema-map.c @@ -0,0 +1,1012 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" + +#include "lutil.h" +#include "slap.h" +#include "proto-sql.h" + +#define BACKSQL_DUPLICATE (-1) + +/* NOTE: by default, cannot just compare pointers because + * objectClass/attributeType order would be machine-dependent + * (and tests would fail!); however, if you don't want to run + * tests, or see attributeTypes written in the same order + * they are defined, define */ +/* #undef BACKSQL_USE_PTR_CMP */ + +/* + * Uses the pointer to the ObjectClass structure + */ +static int +backsql_cmp_oc( const void *v_m1, const void *v_m2 ) +{ + const backsql_oc_map_rec *m1 = v_m1, + *m2 = v_m2; + +#ifdef BACKSQL_USE_PTR_CMP + return SLAP_PTRCMP( m1->bom_oc, m2->bom_oc ); +#else /* ! BACKSQL_USE_PTR_CMP */ + return ber_bvcmp( &m1->bom_oc->soc_cname, &m2->bom_oc->soc_cname ); +#endif /* ! BACKSQL_USE_PTR_CMP */ +} + +static int +backsql_cmp_oc_id( const void *v_m1, const void *v_m2 ) +{ + const backsql_oc_map_rec *m1 = v_m1, + *m2 = v_m2; + + return ( m1->bom_id < m2->bom_id ? -1 : ( m1->bom_id > m2->bom_id ? 1 : 0 ) ); +} + +/* + * Uses the pointer to the AttributeDescription structure + */ +static int +backsql_cmp_attr( const void *v_m1, const void *v_m2 ) +{ + const backsql_at_map_rec *m1 = v_m1, + *m2 = v_m2; + + if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) { +#ifdef BACKSQL_USE_PTR_CMP + return SLAP_PTRCMP( m1->bam_ad->ad_type, m2->bam_ad->ad_type ); +#else /* ! BACKSQL_USE_PTR_CMP */ + return ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname ); +#endif /* ! BACKSQL_USE_PTR_CMP */ + } + +#ifdef BACKSQL_USE_PTR_CMP + return SLAP_PTRCMP( m1->bam_ad, m2->bam_ad ); +#else /* ! BACKSQL_USE_PTR_CMP */ + return ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname ); +#endif /* ! BACKSQL_USE_PTR_CMP */ +} + +int +backsql_dup_attr( void *v_m1, void *v_m2 ) +{ + backsql_at_map_rec *m1 = v_m1, + *m2 = v_m2; + + if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) { +#ifdef BACKSQL_USE_PTR_CMP + assert( m1->bam_ad->ad_type == m2->bam_ad->ad_type ); +#else /* ! BACKSQL_USE_PTR_CMP */ + assert( ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname ) == 0 ); +#endif /* ! BACKSQL_USE_PTR_CMP */ + + } else { +#ifdef BACKSQL_USE_PTR_CMP + assert( m1->bam_ad == m2->bam_ad ); +#else /* ! BACKSQL_USE_PTR_CMP */ + assert( ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname ) == 0 ); +#endif /* ! BACKSQL_USE_PTR_CMP */ + } + + /* duplicate definitions of attributeTypes are appended; + * this allows to define multiple rules for the same + * attributeType. Use with care! */ + for ( ; m1->bam_next ; m1 = m1->bam_next ); + + m1->bam_next = m2; + m2->bam_next = NULL; + + return BACKSQL_DUPLICATE; +} + +static int +backsql_make_attr_query( + backsql_info *bi, + backsql_oc_map_rec *oc_map, + backsql_at_map_rec *at_map ) +{ + struct berbuf bb = BB_NULL; + + backsql_strfcat_x( &bb, NULL, "lblbbbblblbcbl", + (ber_len_t)STRLENOF( "SELECT " ), "SELECT ", + &at_map->bam_sel_expr, + (ber_len_t)STRLENOF( " " ), " ", + &bi->sql_aliasing, + &bi->sql_aliasing_quote, + &at_map->bam_ad->ad_cname, + &bi->sql_aliasing_quote, + (ber_len_t)STRLENOF( " FROM " ), " FROM ", + &at_map->bam_from_tbls, + (ber_len_t)STRLENOF( " WHERE " ), " WHERE ", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( "=?" ), "=?" ); + + if ( !BER_BVISNULL( &at_map->bam_join_where ) ) { + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( " AND " ), " AND ", + &at_map->bam_join_where ); + } + + backsql_strfcat_x( &bb, NULL, "lbbb", + (ber_len_t)STRLENOF( " ORDER BY " ), " ORDER BY ", + &bi->sql_aliasing_quote, + &at_map->bam_ad->ad_cname, + &bi->sql_aliasing_quote ); + + at_map->bam_query = bb.bb_val.bv_val; + +#ifdef BACKSQL_COUNTQUERY + /* Query to count how many rows will be returned. + + SELECT COUNT(*) FROM <from_tbls> WHERE <keytbl>.<keycol>=? + [ AND <join_where> ] + + */ + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "lblbcbl", + (ber_len_t)STRLENOF( "SELECT COUNT(*) FROM " ), + "SELECT COUNT(*) FROM ", + &at_map->bam_from_tbls, + (ber_len_t)STRLENOF( " WHERE " ), " WHERE ", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( "=?" ), "=?" ); + + if ( !BER_BVISNULL( &at_map->bam_join_where ) ) { + backsql_strfcat_x( &bb, NULL, "lb", + (ber_len_t)STRLENOF( " AND " ), " AND ", + &at_map->bam_join_where ); + } + + at_map->bam_countquery = bb.bb_val.bv_val; +#endif /* BACKSQL_COUNTQUERY */ + + return 0; +} + +static int +backsql_add_sysmaps( backsql_info *bi, backsql_oc_map_rec *oc_map ) +{ + backsql_at_map_rec *at_map; + char s[LDAP_PVT_INTTYPE_CHARS(long)]; + struct berval sbv; + struct berbuf bb; + + sbv.bv_val = s; + sbv.bv_len = snprintf( s, sizeof( s ), BACKSQL_IDNUMFMT, oc_map->bom_id ); + + /* extra objectClasses */ + at_map = (backsql_at_map_rec *)ch_calloc(1, + sizeof( backsql_at_map_rec ) ); + at_map->bam_ad = slap_schema.si_ad_objectClass; + at_map->bam_true_ad = slap_schema.si_ad_objectClass; + ber_str2bv( "ldap_entry_objclasses.oc_name", 0, 1, + &at_map->bam_sel_expr ); + ber_str2bv( "ldap_entry_objclasses,ldap_entries", 0, 1, + &at_map->bam_from_tbls ); + + bb.bb_len = at_map->bam_from_tbls.bv_len + 1; + bb.bb_val = at_map->bam_from_tbls; + backsql_merge_from_clause( bi, &bb, &oc_map->bom_keytbl ); + at_map->bam_from_tbls = bb.bb_val; + + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "lbcblb", + (ber_len_t)STRLENOF( "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=" ), + "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( " and ldap_entries.oc_map_id=" ), + " and ldap_entries.oc_map_id=", + &sbv ); + at_map->bam_join_where = bb.bb_val; + + at_map->bam_oc = oc_map->bom_oc; + + at_map->bam_add_proc = NULL; + { + char tmp[STRLENOF("INSERT INTO ldap_entry_objclasses " + "(entry_id,oc_name) VALUES " + "((SELECT id FROM ldap_entries " + "WHERE oc_map_id= " + "AND keyval=?),?)") + LDAP_PVT_INTTYPE_CHARS(unsigned long)]; + snprintf( tmp, sizeof(tmp), + "INSERT INTO ldap_entry_objclasses " + "(entry_id,oc_name) VALUES " + "((SELECT id FROM ldap_entries " + "WHERE oc_map_id=" BACKSQL_IDNUMFMT " " + "AND keyval=?),?)", oc_map->bom_id ); + at_map->bam_add_proc = ch_strdup( tmp ); + } + + at_map->bam_delete_proc = NULL; + { + char tmp[STRLENOF("DELETE FROM ldap_entry_objclasses " + "WHERE entry_id=(SELECT id FROM ldap_entries " + "WHERE oc_map_id= " + "AND keyval=?) AND oc_name=?") + LDAP_PVT_INTTYPE_CHARS(unsigned long)]; + snprintf( tmp, sizeof(tmp), + "DELETE FROM ldap_entry_objclasses " + "WHERE entry_id=(SELECT id FROM ldap_entries " + "WHERE oc_map_id=" BACKSQL_IDNUMFMT " " + "AND keyval=?) AND oc_name=?", + oc_map->bom_id ); + at_map->bam_delete_proc = ch_strdup( tmp ); + } + + at_map->bam_param_order = 0; + at_map->bam_expect_return = 0; + at_map->bam_next = NULL; + + backsql_make_attr_query( bi, oc_map, at_map ); + if ( ldap_avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) { + Debug( LDAP_DEBUG_TRACE, "backsql_add_sysmaps(): " + "duplicate attribute \"%s\" in objectClass \"%s\" map\n", + at_map->bam_ad->ad_cname.bv_val, + oc_map->bom_oc->soc_cname.bv_val ); + } + + /* FIXME: we need to correct the objectClass join_where + * after the attribute query is built */ + ch_free( at_map->bam_join_where.bv_val ); + BER_BVZERO( &bb.bb_val ); + bb.bb_len = 0; + backsql_strfcat_x( &bb, NULL, "lbcblb", + (ber_len_t)STRLENOF( /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=" ), + /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=", + &oc_map->bom_keytbl, + '.', + &oc_map->bom_keycol, + (ber_len_t)STRLENOF( " AND ldap_entries.oc_map_id=" ), + " AND ldap_entries.oc_map_id=", + &sbv ); + at_map->bam_join_where = bb.bb_val; + + return 1; +} + +struct backsql_attr_schema_info { + backsql_info *bas_bi; + SQLHDBC bas_dbh; + SQLHSTMT bas_sth; + backsql_key_t *bas_oc_id; + int bas_rc; +}; + +static int +backsql_oc_get_attr_mapping( void *v_oc, void *v_bas ) +{ + RETCODE rc; + BACKSQL_ROW_NTS at_row; + backsql_oc_map_rec *oc_map = (backsql_oc_map_rec *)v_oc; + backsql_at_map_rec *at_map; + struct backsql_attr_schema_info *bas = (struct backsql_attr_schema_info *)v_bas; + + /* bas->bas_oc_id has been bound to bas->bas_sth */ + *bas->bas_oc_id = oc_map->bom_id; + + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "executing at_query\n" + " \"%s\"\n" + " for objectClass \"%s\"\n" + " with param oc_id=" BACKSQL_IDNUMFMT "\n", + bas->bas_bi->sql_at_query, + BACKSQL_OC_NAME( oc_map ), + *bas->bas_oc_id ); + + rc = SQLExecute( bas->bas_sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "error executing at_query\n" + " \"%s\"\n" + " for objectClass \"%s\"\n" + " with param oc_id=" BACKSQL_IDNUMFMT "\n", + bas->bas_bi->sql_at_query, + BACKSQL_OC_NAME( oc_map ), + *bas->bas_oc_id ); + backsql_PrintErrors( bas->bas_bi->sql_db_env, + bas->bas_dbh, bas->bas_sth, rc ); + bas->bas_rc = LDAP_OTHER; + return BACKSQL_AVL_STOP; + } + + backsql_BindRowAsStrings( bas->bas_sth, &at_row ); + for ( ; rc = SQLFetch( bas->bas_sth ), BACKSQL_SUCCESS( rc ); ) { + const char *text = NULL; + struct berval bv; + struct berbuf bb = BB_NULL; + AttributeDescription *ad = NULL; + + { + struct { + int idx; + char *name; + } required[] = { + { 0, "name" }, + { 1, "sel_expr" }, + { 2, "from" }, + { -1, NULL }, + }; + int i; + + for ( i = 0; required[ i ].name != NULL; i++ ) { + if ( at_row.value_len[ i ] <= 0 ) { + Debug( LDAP_DEBUG_ANY, + "backsql_oc_get_attr_mapping(): " + "required column #%d \"%s\" is empty\n", + required[ i ].idx, required[ i ].name ); + bas->bas_rc = LDAP_OTHER; + return BACKSQL_AVL_STOP; + } + } + } + + Debug(LDAP_DEBUG_TRACE, + "attributeType: " "name=\"%s\" " "sel_expr=\"%s\" " "from=\"%s\" " "join_where=\"%s\" " "add_proc=\"%s\" " "delete_proc=\"%s\" " "sel_expr_u=\"%s\"\n", + at_row.cols[0], at_row.cols[1], at_row.cols[2], + at_row.cols[3] ? at_row.cols[3] : "", + at_row.cols[4] ? at_row.cols[4] : "", + at_row.cols[5] ? at_row.cols[5] : "", + at_row.cols[8] ? at_row.cols[8] : "" ); + + rc = slap_str2ad( at_row.cols[ 0 ], &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "attribute \"%s\" for objectClass \"%s\" " + "is not defined in schema: %s\n", + at_row.cols[ 0 ], + BACKSQL_OC_NAME( oc_map ), text ); + bas->bas_rc = LDAP_CONSTRAINT_VIOLATION; + return BACKSQL_AVL_STOP; + } + at_map = (backsql_at_map_rec *)ch_calloc( 1, + sizeof( backsql_at_map_rec ) ); + at_map->bam_ad = ad; + at_map->bam_true_ad = ad; + if ( slap_syntax_is_binary( ad->ad_type->sat_syntax ) + && !slap_ad_is_binary( ad ) ) + { + char buf[ SLAP_TEXT_BUFLEN ]; + struct berval bv; + const char *text = NULL; + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%s;binary", + ad->ad_cname.bv_val ); + at_map->bam_true_ad = NULL; + bas->bas_rc = slap_bv2ad( &bv, &at_map->bam_true_ad, &text ); + if ( bas->bas_rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "unable to fetch attribute \"%s\": %s (%d)\n", + buf, text, rc ); + ch_free( at_map ); + return BACKSQL_AVL_STOP; + } + } + + ber_str2bv( at_row.cols[ 1 ], 0, 1, &at_map->bam_sel_expr ); + if ( at_row.value_len[ 8 ] <= 0 ) { + BER_BVZERO( &at_map->bam_sel_expr_u ); + + } else { + ber_str2bv( at_row.cols[ 8 ], 0, 1, + &at_map->bam_sel_expr_u ); + } + + ber_str2bv( at_row.cols[ 2 ], 0, 0, &bv ); + backsql_merge_from_clause( bas->bas_bi, &bb, &bv ); + at_map->bam_from_tbls = bb.bb_val; + if ( at_row.value_len[ 3 ] <= 0 ) { + BER_BVZERO( &at_map->bam_join_where ); + + } else { + ber_str2bv( at_row.cols[ 3 ], 0, 1, + &at_map->bam_join_where ); + } + at_map->bam_add_proc = NULL; + if ( at_row.value_len[ 4 ] > 0 ) { + at_map->bam_add_proc = ch_strdup( at_row.cols[ 4 ] ); + } + at_map->bam_delete_proc = NULL; + if ( at_row.value_len[ 5 ] > 0 ) { + at_map->bam_delete_proc = ch_strdup( at_row.cols[ 5 ] ); + } + if ( lutil_atoix( &at_map->bam_param_order, at_row.cols[ 6 ], 0 ) != 0 ) { + /* error */ + } + if ( lutil_atoix( &at_map->bam_expect_return, at_row.cols[ 7 ], 0 ) != 0 ) { + /* error */ + } + backsql_make_attr_query( bas->bas_bi, oc_map, at_map ); + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "preconstructed query \"%s\"\n", + at_map->bam_query ); + at_map->bam_next = NULL; + if ( ldap_avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): " + "duplicate attribute \"%s\" " + "in objectClass \"%s\" map\n", + at_map->bam_ad->ad_cname.bv_val, + oc_map->bom_oc->soc_cname.bv_val ); + ch_free( at_map ); + } + + if ( !BER_BVISNULL( &bas->bas_bi->sql_upper_func ) && + BER_BVISNULL( &at_map->bam_sel_expr_u ) ) + { + struct berbuf bb = BB_NULL; + + backsql_strfcat_x( &bb, NULL, "bcbc", + &bas->bas_bi->sql_upper_func, + '(' /* ) */ , + &at_map->bam_sel_expr, + /* ( */ ')' ); + at_map->bam_sel_expr_u = bb.bb_val; + } + } + backsql_FreeRow( &at_row ); + SQLFreeStmt( bas->bas_sth, SQL_CLOSE ); + + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(\"%s\"): " + "autoadding 'objectClass' and 'ref' mappings\n", + BACKSQL_OC_NAME( oc_map ) ); + + (void)backsql_add_sysmaps( bas->bas_bi, oc_map ); + + return BACKSQL_AVL_CONTINUE; +} + + +int +backsql_load_schema_map( backsql_info *bi, SQLHDBC dbh ) +{ + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + BACKSQL_ROW_NTS oc_row; + backsql_key_t oc_id; + backsql_oc_map_rec *oc_map; + struct backsql_attr_schema_info bas; + + int delete_proc_idx = 5; + int create_hint_idx = delete_proc_idx + 2; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_load_schema_map()\n" ); + + /* + * TimesTen : See if the ldap_entries.dn_ru field exists in the schema + */ + if ( !BACKSQL_DONTCHECK_LDAPINFO_DN_RU( bi ) ) { + rc = backsql_Prepare( dbh, &sth, + backsql_check_dn_ru_query, 0 ); + if ( rc == SQL_SUCCESS ) { + /* Yes, the field exists */ + bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU; + Debug( LDAP_DEBUG_TRACE, "ldapinfo.dn_ru field exists " + "in the schema\n" ); + } else { + /* No such field exists */ + bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU; + } + + SQLFreeStmt( sth, SQL_DROP ); + } + + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): oc_query \"%s\"\n", + bi->sql_oc_query ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_oc_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error preparing oc_query: \"%s\"\n", + bi->sql_oc_query ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + return LDAP_OTHER; + } + + rc = SQLExecute( sth ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error executing oc_query: \n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + return LDAP_OTHER; + } + + backsql_BindRowAsStrings( sth, &oc_row ); + rc = SQLFetch( sth ); + + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + delete_proc_idx++; + create_hint_idx++; + } + + for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) { + { + struct { + int idx; + char *name; + } required[] = { + { 0, "id" }, + { 1, "name" }, + { 2, "keytbl" }, + { 3, "keycol" }, + { -1, "expect_return" }, + { -1, NULL }, + }; + int i; + + required[4].idx = delete_proc_idx + 1; + + for ( i = 0; required[ i ].name != NULL; i++ ) { + if ( oc_row.value_len[ required[ i ].idx ] <= 0 ) { + Debug( LDAP_DEBUG_ANY, + "backsql_load_schema_map(): " + "required column #%d \"%s\" is empty\n", + required[ i ].idx, required[ i ].name ); + return LDAP_OTHER; + } + } + } + + Debug(LDAP_DEBUG_TRACE, + "objectClass: " "id=\"%s\" " "name=\"%s\" " "keytbl=\"%s\" " "keycol=\"%s\" " "create_proc=\"%s\" " "create_keyval=\"%s\" " "delete_proc=\"%s\" " "expect_return=\"%s\"" "create_hint=\"%s\" \n", + oc_row.cols[0], oc_row.cols[1], oc_row.cols[2], + oc_row.cols[3], + oc_row.cols[4] ? oc_row.cols[4] : "", + (BACKSQL_CREATE_NEEDS_SELECT(bi) && oc_row.cols[5]) ? oc_row.cols[5] : "", + oc_row.cols[delete_proc_idx] ? oc_row.cols[delete_proc_idx] : "", + oc_row.cols[delete_proc_idx + 1], + ((oc_row.ncols > create_hint_idx) && oc_row.cols[create_hint_idx]) ? oc_row.cols[create_hint_idx] : "" ); + + oc_map = (backsql_oc_map_rec *)ch_calloc( 1, + sizeof( backsql_oc_map_rec ) ); + + if ( BACKSQL_STR2ID( &oc_map->bom_id, oc_row.cols[ 0 ], 0 ) != 0 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "unable to parse id=\"%s\"\n", + oc_row.cols[ 0 ] ); + ch_free( oc_map ); + return LDAP_OTHER; + } + + oc_map->bom_oc = oc_find( oc_row.cols[ 1 ] ); + if ( oc_map->bom_oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "objectClass \"%s\" is not defined in schema\n", + oc_row.cols[ 1 ] ); + ch_free( oc_map ); + return LDAP_OTHER; /* undefined objectClass ? */ + } + + ber_str2bv( oc_row.cols[ 2 ], 0, 1, &oc_map->bom_keytbl ); + ber_str2bv( oc_row.cols[ 3 ], 0, 1, &oc_map->bom_keycol ); + oc_map->bom_create_proc = ( oc_row.value_len[ 4 ] <= 0 ) ? NULL + : ch_strdup( oc_row.cols[ 4 ] ); + + if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) { + oc_map->bom_create_keyval = ( oc_row.value_len[ 5 ] <= 0 ) + ? NULL : ch_strdup( oc_row.cols[ 5 ] ); + } + oc_map->bom_delete_proc = ( oc_row.value_len[ delete_proc_idx ] <= 0 ) ? NULL + : ch_strdup( oc_row.cols[ delete_proc_idx ] ); + if ( lutil_atoix( &oc_map->bom_expect_return, oc_row.cols[ delete_proc_idx + 1 ], 0 ) != 0 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "unable to parse expect_return=\"%s\" for objectClass \"%s\"\n", + oc_row.cols[ delete_proc_idx + 1 ], oc_row.cols[ 1 ] ); + ch_free( oc_map ); + return LDAP_OTHER; + } + + if ( ( oc_row.ncols > create_hint_idx ) && + ( oc_row.value_len[ create_hint_idx ] > 0 ) ) + { + const char *text; + + oc_map->bom_create_hint = NULL; + rc = slap_str2ad( oc_row.cols[ create_hint_idx ], + &oc_map->bom_create_hint, &text ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "load_schema_map(): " + "error matching " + "AttributeDescription %s " + "in create_hint: %s (%d)\n", + oc_row.cols[ create_hint_idx ], + text, rc ); + backsql_PrintErrors( bi->sql_db_env, dbh, + sth, rc ); + ch_free( oc_map ); + return LDAP_OTHER; + } + } + + /* + * FIXME: first attempt to check for offending + * instructions in {create|delete}_proc + */ + + oc_map->bom_attrs = NULL; + if ( ldap_avl_insert( &bi->sql_oc_by_oc, oc_map, backsql_cmp_oc, ldap_avl_dup_error ) == -1 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "duplicate objectClass \"%s\" in objectClass map\n", + oc_map->bom_oc->soc_cname.bv_val ); + ch_free( oc_map ); + return LDAP_OTHER; + } + if ( ldap_avl_insert( &bi->sql_oc_by_id, oc_map, backsql_cmp_oc_id, ldap_avl_dup_error ) == -1 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "duplicate objectClass \"%s\" in objectClass by ID map\n", + oc_map->bom_oc->soc_cname.bv_val ); + return LDAP_OTHER; + } + oc_id = oc_map->bom_id; + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "objectClass \"%s\":\n keytbl=\"%s\" keycol=\"%s\"\n", + BACKSQL_OC_NAME( oc_map ), + oc_map->bom_keytbl.bv_val, oc_map->bom_keycol.bv_val ); + if ( oc_map->bom_create_proc ) { + Debug( LDAP_DEBUG_TRACE, " create_proc=\"%s\"\n", + oc_map->bom_create_proc ); + } + if ( oc_map->bom_create_keyval ) { + Debug( LDAP_DEBUG_TRACE, " create_keyval=\"%s\"\n", + oc_map->bom_create_keyval ); + } + if ( oc_map->bom_create_hint ) { + Debug( LDAP_DEBUG_TRACE, " create_hint=\"%s\"\n", + oc_map->bom_create_hint->ad_cname.bv_val ); + } + if ( oc_map->bom_delete_proc ) { + Debug( LDAP_DEBUG_TRACE, " delete_proc=\"%s\"\n", + oc_map->bom_delete_proc ); + } + Debug( LDAP_DEBUG_TRACE, " expect_return: " + "add=%d, del=%d; attributes:\n", + BACKSQL_IS_ADD( oc_map->bom_expect_return ), + BACKSQL_IS_DEL( oc_map->bom_expect_return ) ); + } + + backsql_FreeRow( &oc_row ); + SQLFreeStmt( sth, SQL_DROP ); + + /* prepare for attribute fetching */ + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): at_query \"%s\"\n", + bi->sql_at_query ); + + rc = backsql_Prepare( dbh, &sth, bi->sql_at_query, 0 ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error preparing at_query: \"%s\"\n", + bi->sql_at_query ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + return LDAP_OTHER; + } + + rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT, &oc_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): " + "error binding param \"oc_id\" for at_query\n" ); + backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + return LDAP_OTHER; + } + + bas.bas_bi = bi; + bas.bas_dbh = dbh; + bas.bas_sth = sth; + bas.bas_oc_id = &oc_id; + bas.bas_rc = LDAP_SUCCESS; + + (void)ldap_avl_apply( bi->sql_oc_by_oc, backsql_oc_get_attr_mapping, + &bas, BACKSQL_AVL_STOP, AVL_INORDER ); + + SQLFreeStmt( sth, SQL_DROP ); + + bi->sql_flags |= BSQLF_SCHEMA_LOADED; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_load_schema_map()\n" ); + + return bas.bas_rc; +} + +backsql_oc_map_rec * +backsql_oc2oc( backsql_info *bi, ObjectClass *oc ) +{ + backsql_oc_map_rec tmp, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_oc2oc(): " + "searching for objectclass with name=\"%s\"\n", + oc->soc_cname.bv_val ); +#endif /* BACKSQL_TRACE */ + + tmp.bom_oc = oc; + res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc ); +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): " + "found name=\"%s\", id=%d\n", + BACKSQL_OC_NAME( res ), res->bom_id ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): " + "not found\n" ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +backsql_oc_map_rec * +backsql_name2oc( backsql_info *bi, struct berval *oc_name ) +{ + backsql_oc_map_rec tmp, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>oc_with_name(): " + "searching for objectclass with name=\"%s\"\n", + oc_name->bv_val ); +#endif /* BACKSQL_TRACE */ + + tmp.bom_oc = oc_bvfind( oc_name ); + if ( tmp.bom_oc == NULL ) { + return NULL; + } + + res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc ); +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): " + "found name=\"%s\", id=%d\n", + BACKSQL_OC_NAME( res ), res->bom_id ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): " + "not found\n" ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +backsql_oc_map_rec * +backsql_id2oc( backsql_info *bi, unsigned long id ) +{ + backsql_oc_map_rec tmp, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>oc_with_id(): " + "searching for objectclass with id=%lu\n", id ); +#endif /* BACKSQL_TRACE */ + + tmp.bom_id = id; + res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_id, &tmp, + backsql_cmp_oc_id ); + +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): " + "found name=\"%s\", id=%lu\n", + BACKSQL_OC_NAME( res ), res->bom_id ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): " + "id=%lu not found\n", res->bom_id ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +backsql_at_map_rec * +backsql_ad2at( backsql_oc_map_rec* objclass, AttributeDescription *ad ) +{ + backsql_at_map_rec tmp = { 0 }, *res; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_ad2at(): " + "searching for attribute \"%s\" for objectclass \"%s\"\n", + ad->ad_cname.bv_val, BACKSQL_OC_NAME( objclass ) ); +#endif /* BACKSQL_TRACE */ + + tmp.bam_ad = ad; + res = (backsql_at_map_rec *)ldap_avl_find( objclass->bom_attrs, &tmp, + backsql_cmp_attr ); + +#ifdef BACKSQL_TRACE + if ( res != NULL ) { + Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): " + "found name=\"%s\", sel_expr=\"%s\"\n", + res->bam_ad->ad_cname.bv_val, + res->bam_sel_expr.bv_val ); + } else { + Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): " + "not found\n" ); + } +#endif /* BACKSQL_TRACE */ + + return res; +} + +/* attributeType inheritance */ +struct supad2at_t { + backsql_at_map_rec **ret; + AttributeDescription *ad; + unsigned n; +}; + +#define SUPAD2AT_STOP (-1) + +static int +supad2at_f( void *v_at, void *v_arg ) +{ + backsql_at_map_rec *at = (backsql_at_map_rec *)v_at; + struct supad2at_t *va = (struct supad2at_t *)v_arg; + + if ( is_at_subtype( at->bam_ad->ad_type, va->ad->ad_type ) ) { + backsql_at_map_rec **ret = NULL; + unsigned i; + + /* if already listed, holler! (should never happen) */ + if ( va->ret ) { + for ( i = 0; i < va->n; i++ ) { + if ( va->ret[ i ]->bam_ad == at->bam_ad ) { + break; + } + } + + if ( i < va->n ) { + return 0; + } + } + + ret = ch_realloc( va->ret, + sizeof( backsql_at_map_rec * ) * ( va->n + 2 ) ); + if ( ret == NULL ) { + ch_free( va->ret ); + va->ret = NULL; + va->n = 0; + return SUPAD2AT_STOP; + } + + ret[ va->n ] = at; + va->n++; + ret[ va->n ] = NULL; + va->ret = ret; + } + + return 0; +} + +/* + * stores in *pret a NULL terminated array of pointers + * to backsql_at_map_rec whose attributeType is supad->ad_type + * or derived from it + */ +int +backsql_supad2at( backsql_oc_map_rec *objclass, AttributeDescription *supad, + backsql_at_map_rec ***pret ) +{ + struct supad2at_t va = { 0 }; + int rc; + + assert( objclass != NULL ); + assert( supad != NULL ); + assert( pret != NULL ); + + *pret = NULL; + + va.ad = supad; + + rc = ldap_avl_apply( objclass->bom_attrs, supad2at_f, &va, + SUPAD2AT_STOP, AVL_INORDER ); + if ( rc == SUPAD2AT_STOP ) { + return -1; + } + + *pret = va.ret; + + return 0; +} + +static void +backsql_free_attr( void *v_at ) +{ + backsql_at_map_rec *at = v_at; + + Debug( LDAP_DEBUG_TRACE, "==>free_attr(): \"%s\"\n", + at->bam_ad->ad_cname.bv_val ); + ch_free( at->bam_sel_expr.bv_val ); + if ( !BER_BVISNULL( &at->bam_from_tbls ) ) { + ch_free( at->bam_from_tbls.bv_val ); + } + if ( !BER_BVISNULL( &at->bam_join_where ) ) { + ch_free( at->bam_join_where.bv_val ); + } + if ( at->bam_add_proc != NULL ) { + ch_free( at->bam_add_proc ); + } + if ( at->bam_delete_proc != NULL ) { + ch_free( at->bam_delete_proc ); + } + if ( at->bam_query != NULL ) { + ch_free( at->bam_query ); + } + +#ifdef BACKSQL_COUNTQUERY + if ( at->bam_countquery != NULL ) { + ch_free( at->bam_countquery ); + } +#endif /* BACKSQL_COUNTQUERY */ + + /* TimesTen */ + if ( !BER_BVISNULL( &at->bam_sel_expr_u ) ) { + ch_free( at->bam_sel_expr_u.bv_val ); + } + + if ( at->bam_next ) { + backsql_free_attr( at->bam_next ); + } + + ch_free( at ); + + Debug( LDAP_DEBUG_TRACE, "<==free_attr()\n" ); +} + +static void +backsql_free_oc( void *v_oc ) +{ + backsql_oc_map_rec *oc = v_oc; + + Debug( LDAP_DEBUG_TRACE, "==>free_oc(): \"%s\"\n", + BACKSQL_OC_NAME( oc ) ); + ldap_avl_free( oc->bom_attrs, backsql_free_attr ); + ch_free( oc->bom_keytbl.bv_val ); + ch_free( oc->bom_keycol.bv_val ); + if ( oc->bom_create_proc != NULL ) { + ch_free( oc->bom_create_proc ); + } + if ( oc->bom_create_keyval != NULL ) { + ch_free( oc->bom_create_keyval ); + } + if ( oc->bom_delete_proc != NULL ) { + ch_free( oc->bom_delete_proc ); + } + ch_free( oc ); + + Debug( LDAP_DEBUG_TRACE, "<==free_oc()\n" ); +} + +int +backsql_destroy_schema_map( backsql_info *bi ) +{ + Debug( LDAP_DEBUG_TRACE, "==>destroy_schema_map()\n" ); + ldap_avl_free( bi->sql_oc_by_oc, 0 ); + ldap_avl_free( bi->sql_oc_by_id, backsql_free_oc ); + Debug( LDAP_DEBUG_TRACE, "<==destroy_schema_map()\n" ); + return 0; +} + diff --git a/servers/slapd/back-sql/search.c b/servers/slapd/back-sql/search.c new file mode 100644 index 0000000..d4177f6 --- /dev/null +++ b/servers/slapd/back-sql/search.c @@ -0,0 +1,2874 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" +#include "ac/ctype.h" + +#include "lutil.h" +#include "slap.h" +#include "proto-sql.h" + +static int backsql_process_filter( backsql_srch_info *bsi, Filter *f ); +static int backsql_process_filter_eq( backsql_srch_info *bsi, + backsql_at_map_rec *at, + int casefold, struct berval *filter_value ); +static int backsql_process_filter_like( backsql_srch_info *bsi, + backsql_at_map_rec *at, + int casefold, struct berval *filter_value ); +static int backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f, + backsql_at_map_rec *at ); + +/* For LDAP_CONTROL_PAGEDRESULTS, a 32 bit cookie is available to keep track of + the state of paged results. The ldap_entries.id and oc_map_id values of the + last entry returned are used as the cookie, so 6 bits are used for the OC id + and the other 26 for ldap_entries ID number. If your max(oc_map_id) is more + than 63, you will need to steal more bits from ldap_entries ID number and + put them into the OC ID part of the cookie. */ + +/* NOTE: not supported when BACKSQL_ARBITRARY_KEY is defined */ +#ifndef BACKSQL_ARBITRARY_KEY +#define SQL_TO_PAGECOOKIE(id, oc) (((id) << 6 ) | ((oc) & 0x3F)) +#define PAGECOOKIE_TO_SQL_ID(pc) ((pc) >> 6) +#define PAGECOOKIE_TO_SQL_OC(pc) ((pc) & 0x3F) + +static int parse_paged_cookie( Operation *op, SlapReply *rs ); + +static void send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid ); +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +/* Look for chars that need to be escaped, return count of them. + * If out is non-NULL, copy escape'd val to it. + */ +static int +backsql_val_escape( Operation *op, struct berval *in, struct berval *out ) +{ + char *ptr, *end; + int q = 0; + + ptr = in->bv_val; + end = ptr + in->bv_len; + while (ptr < end) { + if ( *ptr == '\'' ) + q++; + ptr++; + } + if ( q && out ) { + char *dst; + out->bv_len = in->bv_len + q; + out->bv_val = op->o_tmpalloc( out->bv_len + 1, op->o_tmpmemctx ); + ptr = in->bv_val; + dst = out->bv_val; + while (ptr < end ) { + if ( *ptr == '\'' ) + *dst++ = '\''; + *dst++ = *ptr++; + } + *dst = '\0'; + } + return q; +} + +static int +backsql_attrlist_add( backsql_srch_info *bsi, AttributeDescription *ad ) +{ + int n_attrs = 0; + AttributeName *an = NULL; + + if ( bsi->bsi_attrs == NULL ) { + return 1; + } + + /* + * clear the list (retrieve all attrs) + */ + if ( ad == NULL ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + bsi->bsi_flags |= BSQL_SF_ALL_ATTRS; + return 1; + } + + /* strip ';binary' */ + if ( slap_ad_is_binary( ad ) ) { + ad = ad->ad_type->sat_ad; + } + + for ( ; !BER_BVISNULL( &bsi->bsi_attrs[ n_attrs ].an_name ); n_attrs++ ) { + an = &bsi->bsi_attrs[ n_attrs ]; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): " + "attribute \"%s\" is in list\n", + an->an_name.bv_val ); + /* + * We can live with strcmp because the attribute + * list has been normalized before calling be_search + */ + if ( !BACKSQL_NCMP( &an->an_name, &ad->ad_cname ) ) { + return 1; + } + } + + Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): " + "adding \"%s\" to list\n", ad->ad_cname.bv_val ); + + an = (AttributeName *)bsi->bsi_op->o_tmprealloc( bsi->bsi_attrs, + sizeof( AttributeName ) * ( n_attrs + 2 ), + bsi->bsi_op->o_tmpmemctx ); + if ( an == NULL ) { + return -1; + } + + an[ n_attrs ].an_name = ad->ad_cname; + an[ n_attrs ].an_desc = ad; + BER_BVZERO( &an[ n_attrs + 1 ].an_name ); + + bsi->bsi_attrs = an; + + return 1; +} + +/* + * Initializes the search structure. + * + * If get_base_id != 0, the field bsi_base_id is filled + * with the entryID of bsi_base_ndn; it must be freed + * by backsql_free_entryID() when no longer required. + * + * NOTE: base must be normalized + */ +int +backsql_init_search( + backsql_srch_info *bsi, + struct berval *nbase, + int scope, + time_t stoptime, + Filter *filter, + SQLHDBC dbh, + Operation *op, + SlapReply *rs, + AttributeName *attrs, + unsigned flags ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + int rc = LDAP_SUCCESS; + + bsi->bsi_base_ndn = nbase; + bsi->bsi_use_subtree_shortcut = 0; + BER_BVZERO( &bsi->bsi_base_id.eid_dn ); + BER_BVZERO( &bsi->bsi_base_id.eid_ndn ); + bsi->bsi_scope = scope; + bsi->bsi_filter = filter; + bsi->bsi_dbh = dbh; + bsi->bsi_op = op; + bsi->bsi_rs = rs; + bsi->bsi_flags = BSQL_SF_NONE; + + bsi->bsi_attrs = NULL; + + if ( BACKSQL_FETCH_ALL_ATTRS( bi ) ) { + /* + * if requested, simply try to fetch all attributes + */ + bsi->bsi_flags |= BSQL_SF_ALL_ATTRS; + + } else { + if ( BACKSQL_FETCH_ALL_USERATTRS( bi ) ) { + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + } else if ( BACKSQL_FETCH_ALL_OPATTRS( bi ) ) { + bsi->bsi_flags |= BSQL_SF_ALL_OPER; + } + + if ( attrs == NULL ) { + /* NULL means all user attributes */ + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + } else { + AttributeName *p; + int got_oc = 0; + + bsi->bsi_attrs = (AttributeName *)bsi->bsi_op->o_tmpalloc( + sizeof( AttributeName ), + bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_attrs[ 0 ].an_name ); + + for ( p = attrs; !BER_BVISNULL( &p->an_name ); p++ ) { + if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) { + /* handle "*" */ + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + + } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) { + /* handle "+" */ + bsi->bsi_flags |= BSQL_SF_ALL_OPER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + + } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_no_attrs ) == 0 ) { + /* ignore "1.1" */ + continue; + + } else if ( p->an_desc == slap_schema.si_ad_objectClass ) { + got_oc = 1; + } + + backsql_attrlist_add( bsi, p->an_desc ); + } + + if ( got_oc == 0 && !( bsi->bsi_flags & BSQL_SF_ALL_USER ) ) { + /* add objectClass if not present, + * because it is required to understand + * if an entry is a referral, an alias + * or so... */ + backsql_attrlist_add( bsi, slap_schema.si_ad_objectClass ); + } + } + + if ( !BSQL_ISF_ALL_ATTRS( bsi ) && bi->sql_anlist ) { + AttributeName *p; + + /* use hints if available */ + for ( p = bi->sql_anlist; !BER_BVISNULL( &p->an_name ); p++ ) { + if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) { + /* handle "*" */ + bsi->bsi_flags |= BSQL_SF_ALL_USER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + + } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) { + /* handle "+" */ + bsi->bsi_flags |= BSQL_SF_ALL_OPER; + + /* if all attrs are requested, there's + * no need to continue */ + if ( BSQL_ISF_ALL_ATTRS( bsi ) ) { + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + bsi->bsi_attrs = NULL; + break; + } + continue; + } + + backsql_attrlist_add( bsi, p->an_desc ); + } + + } + } + + bsi->bsi_id_list = NULL; + bsi->bsi_id_listtail = &bsi->bsi_id_list; + bsi->bsi_n_candidates = 0; + bsi->bsi_stoptime = stoptime; + BER_BVZERO( &bsi->bsi_sel.bb_val ); + bsi->bsi_sel.bb_len = 0; + BER_BVZERO( &bsi->bsi_from.bb_val ); + bsi->bsi_from.bb_len = 0; + BER_BVZERO( &bsi->bsi_join_where.bb_val ); + bsi->bsi_join_where.bb_len = 0; + BER_BVZERO( &bsi->bsi_flt_where.bb_val ); + bsi->bsi_flt_where.bb_len = 0; + bsi->bsi_filter_oc = NULL; + + if ( BACKSQL_IS_GET_ID( flags ) ) { + int matched = BACKSQL_IS_MATCHED( flags ); + int getentry = BACKSQL_IS_GET_ENTRY( flags ); + int gotit = 0; + + assert( op->o_bd->be_private != NULL ); + + rc = backsql_dn2id( op, rs, dbh, nbase, &bsi->bsi_base_id, + matched, 1 ); + + /* the entry is collected either if requested for by getentry + * or if get noSuchObject and requested to climb the tree, + * so that a matchedDN or a referral can be returned */ + if ( ( rc == LDAP_NO_SUCH_OBJECT && matched ) || getentry ) { + if ( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) ) { + assert( bsi->bsi_e != NULL ); + + if ( dn_match( nbase, &bsi->bsi_base_id.eid_ndn ) ) + { + gotit = 1; + } + + /* + * let's see if it is a referral and, in case, get it + */ + backsql_attrlist_add( bsi, slap_schema.si_ad_ref ); + rc = backsql_id2entry( bsi, &bsi->bsi_base_id ); + if ( rc == LDAP_SUCCESS ) { + if ( is_entry_referral( bsi->bsi_e ) ) + { + BerVarray erefs = get_entry_referrals( op, bsi->bsi_e ); + if ( erefs ) { + rc = rs->sr_err = LDAP_REFERRAL; + rs->sr_ref = referral_rewrite( erefs, + &bsi->bsi_e->e_nname, + &op->o_req_dn, + scope ); + ber_bvarray_free( erefs ); + + } else { + rc = rs->sr_err = LDAP_OTHER; + rs->sr_text = "bad referral object"; + } + + } else if ( !gotit ) { + rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + } + + } else { + rs->sr_err = rc; + } + } + + if ( gotit && BACKSQL_IS_GET_OC( flags ) ) { + bsi->bsi_base_id.eid_oc = backsql_id2oc( bi, + bsi->bsi_base_id.eid_oc_id ); + if ( bsi->bsi_base_id.eid_oc == NULL ) { + /* error? */ + backsql_free_entryID( &bsi->bsi_base_id, 1, + op->o_tmpmemctx ); + rc = rs->sr_err = LDAP_OTHER; + } + } + } + + bsi->bsi_status = rc; + + switch ( rc ) { + case LDAP_SUCCESS: + case LDAP_REFERRAL: + break; + + default: + bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, + bsi->bsi_op->o_tmpmemctx ); + break; + } + + return rc; +} + +static int +backsql_process_filter_list( backsql_srch_info *bsi, Filter *f, int op ) +{ + int res; + + if ( !f ) { + return 0; + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ ); + + while ( 1 ) { + res = backsql_process_filter( bsi, f ); + if ( res < 0 ) { + /* + * TimesTen : If the query has no answers, + * don't bother to run the query. + */ + return -1; + } + + f = f->f_next; + if ( f == NULL ) { + break; + } + + switch ( op ) { + case LDAP_FILTER_AND: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "l", + (ber_len_t)STRLENOF( " AND " ), + " AND " ); + break; + + case LDAP_FILTER_OR: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "l", + (ber_len_t)STRLENOF( " OR " ), + " OR " ); + break; + } + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "c", /* ( */ ')' ); + + return 1; +} + +static int +backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f, + backsql_at_map_rec *at ) +{ + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + int i; + int casefold = 0; + int escaped = 0; + struct berval escval, *fvalue; + + if ( !f ) { + return 0; + } + + /* always uppercase strings by now */ +#ifdef BACKSQL_UPPERCASE_FILTER + if ( f->f_sub_desc->ad_type->sat_substr && + SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr, + bi->sql_caseIgnoreMatch ) ) +#endif /* BACKSQL_UPPERCASE_FILTER */ + { + casefold = 1; + } + + if ( f->f_sub_desc->ad_type->sat_substr && + SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr, + bi->sql_telephoneNumberMatch ) ) + { + + struct berval bv; + ber_len_t i, s, a; + + /* + * to check for matching telephone numbers + * with intermixed chars, e.g. val='1234' + * use + * + * val LIKE '%1%2%3%4%' + */ + + BER_BVZERO( &bv ); + if ( f->f_sub_initial.bv_val ) { + bv.bv_len += f->f_sub_initial.bv_len + backsql_val_escape( NULL, &f->f_sub_initial, NULL ); + } + if ( f->f_sub_any != NULL ) { + for ( a = 0; f->f_sub_any[ a ].bv_val != NULL; a++ ) { + bv.bv_len += f->f_sub_any[ a ].bv_len + backsql_val_escape( NULL, &f->f_sub_any[ a ], NULL ); + } + } + if ( f->f_sub_final.bv_val ) { + bv.bv_len += f->f_sub_final.bv_len + backsql_val_escape( NULL, &f->f_sub_final, NULL ); + } + bv.bv_len = 2 * bv.bv_len - 1; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + s = 0; + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + fvalue = &f->f_sub_initial; + escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval ); + if ( escaped ) + fvalue = &escval; + bv.bv_val[ s ] = fvalue->bv_val[ 0 ]; + for ( i = 1; i < fvalue->bv_len; i++ ) { + bv.bv_val[ s + 2 * i - 1 ] = '%'; + bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ]; + } + bv.bv_val[ s + 2 * i - 1 ] = '%'; + s += 2 * i; + if ( escaped ) + bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx ); + } + + if ( f->f_sub_any != NULL ) { + for ( a = 0; !BER_BVISNULL( &f->f_sub_any[ a ] ); a++ ) { + fvalue = &f->f_sub_any[ a ]; + escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval ); + if ( escaped ) + fvalue = &escval; + bv.bv_val[ s ] = fvalue->bv_val[ 0 ]; + for ( i = 1; i < fvalue->bv_len; i++ ) { + bv.bv_val[ s + 2 * i - 1 ] = '%'; + bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ]; + } + bv.bv_val[ s + 2 * i - 1 ] = '%'; + s += 2 * i; + if ( escaped ) + bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx ); + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + fvalue = &f->f_sub_final; + escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval ); + if ( escaped ) + fvalue = &escval; + bv.bv_val[ s ] = fvalue->bv_val[ 0 ]; + for ( i = 1; i < fvalue->bv_len; i++ ) { + bv.bv_val[ s + 2 * i - 1 ] = '%'; + bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ]; + } + bv.bv_val[ s + 2 * i - 1 ] = '%'; + s += 2 * i; + if ( escaped ) + bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx ); + } + + bv.bv_val[ s - 1 ] = '\0'; + + (void)backsql_process_filter_like( bsi, at, casefold, &bv ); + ch_free( bv.bv_val ); + + return 1; + } + + /* + * When dealing with case-sensitive strings + * we may omit normalization; however, normalized + * SQL filters are more liberal. + */ + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ ); + + /* TimesTen */ + Debug( LDAP_DEBUG_TRACE, "backsql_process_sub_filter(%s):\n", + at->bam_ad->ad_cname.bv_val ); + Debug(LDAP_DEBUG_TRACE, " expr: '%s%s%s'\n", at->bam_sel_expr.bv_val, + at->bam_sel_expr_u.bv_val ? "' '" : "", + at->bam_sel_expr_u.bv_val ? at->bam_sel_expr_u.bv_val : "" ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + /* + * If a pre-upper-cased version of the column + * or a precompiled upper function exists, use it + */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &at->bam_sel_expr_u, + (ber_len_t)STRLENOF( " LIKE '" ), + " LIKE '" ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &at->bam_sel_expr, + (ber_len_t)STRLENOF( " LIKE '" ), " LIKE '" ); + } + + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + ber_len_t start; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, + "==>backsql_process_sub_filter(%s): " + "sub_initial=\"%s\"\n", at->bam_ad->ad_cname.bv_val, + f->f_sub_initial.bv_val ); +#endif /* BACKSQL_TRACE */ + + fvalue = &f->f_sub_initial; + escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval ); + if ( escaped ) + fvalue = &escval; + start = bsi->bsi_flt_where.bb_val.bv_len; + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "b", + fvalue ); + if ( escaped ) + bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + } + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", '%' ); + + if ( f->f_sub_any != NULL ) { + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[ i ] ); i++ ) { + ber_len_t start; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, + "==>backsql_process_sub_filter(%s): " + "sub_any[%d]=\"%s\"\n", at->bam_ad->ad_cname.bv_val, + i, f->f_sub_any[ i ].bv_val ); +#endif /* BACKSQL_TRACE */ + + fvalue = &f->f_sub_any[ i ]; + escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval ); + if ( escaped ) + fvalue = &escval; + start = bsi->bsi_flt_where.bb_val.bv_len; + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bc", + fvalue, + '%' ); + if ( escaped ) + bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + /* + * Note: toupper('%') = '%' + */ + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + } + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + ber_len_t start; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, + "==>backsql_process_sub_filter(%s): " + "sub_final=\"%s\"\n", at->bam_ad->ad_cname.bv_val, + f->f_sub_final.bv_val ); +#endif /* BACKSQL_TRACE */ + + fvalue = &f->f_sub_final; + escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval ); + if ( escaped ) + fvalue = &escval; + start = bsi->bsi_flt_where.bb_val.bv_len; + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "b", + fvalue ); + if ( escaped ) + bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx ); + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + } + } + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( /* (' */ "')" ), /* (' */ "')" ); + + return 1; +} + +static int +backsql_merge_from_tbls( backsql_srch_info *bsi, struct berval *from_tbls ) +{ + if ( BER_BVISNULL( from_tbls ) ) { + return LDAP_SUCCESS; + } + + if ( !BER_BVISNULL( &bsi->bsi_from.bb_val ) ) { + char *start, *end; + struct berval tmp; + + ber_dupbv_x( &tmp, from_tbls, bsi->bsi_op->o_tmpmemctx ); + + for ( start = tmp.bv_val, end = strchr( start, ',' ); start; ) { + if ( end ) { + end[0] = '\0'; + } + + if ( strstr( bsi->bsi_from.bb_val.bv_val, start) == NULL ) + { + backsql_strfcat_x( &bsi->bsi_from, + bsi->bsi_op->o_tmpmemctx, + "cs", ',', start ); + } + + if ( end ) { + /* in case there are spaces after the comma... */ + for ( start = &end[1]; isspace( start[0] ); start++ ); + if ( start[0] ) { + end = strchr( start, ',' ); + } else { + start = NULL; + } + } else { + start = NULL; + } + } + + bsi->bsi_op->o_tmpfree( tmp.bv_val, bsi->bsi_op->o_tmpmemctx ); + + } else { + backsql_strfcat_x( &bsi->bsi_from, + bsi->bsi_op->o_tmpmemctx, + "b", from_tbls ); + } + + return LDAP_SUCCESS; +} + +static int +backsql_process_filter( backsql_srch_info *bsi, Filter *f ) +{ + backsql_at_map_rec **vat = NULL; + AttributeDescription *ad = NULL; + unsigned i; + int done = 0; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter()\n" ); + if ( f->f_choice == SLAPD_FILTER_COMPUTED ) { + struct berval flt; + char *msg = NULL; + + switch ( f->f_result ) { + case LDAP_COMPARE_TRUE: + BER_BVSTR( &flt, "10=10" ); + msg = "TRUE"; + break; + + case LDAP_COMPARE_FALSE: + BER_BVSTR( &flt, "11=0" ); + msg = "FALSE"; + break; + + case SLAPD_COMPARE_UNDEFINED: + BER_BVSTR( &flt, "12=0" ); + msg = "UNDEFINED"; + break; + + default: + rc = -1; + goto done; + } + + Debug( LDAP_DEBUG_TRACE, "backsql_process_filter(): " + "filter computed (%s)\n", msg ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, "b", &flt ); + rc = 1; + goto done; + } + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "1=0" ), "1=0" ); + done = 1; + rc = 1; + goto done; + } + + switch( f->f_choice ) { + case LDAP_FILTER_OR: + rc = backsql_process_filter_list( bsi, f->f_or, + LDAP_FILTER_OR ); + done = 1; + break; + + case LDAP_FILTER_AND: + rc = backsql_process_filter_list( bsi, f->f_and, + LDAP_FILTER_AND ); + done = 1; + break; + + case LDAP_FILTER_NOT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "NOT (" /* ) */ ), + "NOT (" /* ) */ ); + rc = backsql_process_filter( bsi, f->f_not ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", /* ( */ ')' ); + done = 1; + break; + + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + break; + + case LDAP_FILTER_EXT: + ad = f->f_mra->ma_desc; + if ( f->f_mr_dnattrs ) { + /* + * if dn attrs filtering is requested, better return + * success and let test_filter() deal with candidate + * selection; otherwise we'd need to set conditions + * on the contents of the DN, e.g. "SELECT ... FROM + * ldap_entries AS attributeName WHERE attributeName.dn + * like '%attributeName=value%'" + */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "1=1" ), "1=1" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + } + break; + + default: + ad = f->f_av_desc; + break; + } + + if ( rc == -1 ) { + goto done; + } + + if ( done ) { + rc = 1; + goto done; + } + + /* + * Turn structuralObjectClass into objectClass + */ + if ( ad == slap_schema.si_ad_objectClass + || ad == slap_schema.si_ad_structuralObjectClass ) + { + /* + * If the filter is LDAP_FILTER_PRESENT, then it's done; + * otherwise, let's see if we are lucky: filtering + * for "structural" objectclass or ancestor... + */ + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + { + ObjectClass *oc = oc_bvfind( &f->f_av_value ); + + if ( oc == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "backsql_process_filter(): " + "unknown objectClass \"%s\" " + "in filter\n", + f->f_av_value.bv_val ); + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } + + /* + * "structural" objectClass inheritance: + * - a search for "person" will also return + * "inetOrgPerson" + * - a search for "top" will return everything + */ + if ( is_object_subclass( oc, bsi->bsi_oc->bom_oc ) ) { + static struct berval ldap_entry_objclasses = BER_BVC( "ldap_entry_objclasses" ); + + backsql_merge_from_tbls( bsi, &ldap_entry_objclasses ); + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "lbl", + (ber_len_t)STRLENOF( "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */ ), + "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */, + &bsi->bsi_oc->bom_oc->soc_cname, + (ber_len_t)STRLENOF( /* ((' */ "'))" ), + /* ((' */ "'))" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + } + + break; + } + + case LDAP_FILTER_PRESENT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "3=3" ), "3=3" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + + /* FIXME: LDAP_FILTER_EXT? */ + + default: + Debug( LDAP_DEBUG_TRACE, + "backsql_process_filter(): " + "illegal/unhandled filter " + "on objectClass attribute" ); + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } + + } else if ( ad == slap_schema.si_ad_entryUUID ) { + unsigned long oc_id; +#ifdef BACKSQL_ARBITRARY_KEY + struct berval keyval; +#else /* ! BACKSQL_ARBITRARY_KEY */ + unsigned long keyval; + char keyvalbuf[LDAP_PVT_INTTYPE_CHARS(unsigned long)]; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + backsql_entryUUID_decode( &f->f_av_value, &oc_id, &keyval ); + + if ( oc_id != bsi->bsi_oc->bom_id ) { + bsi->bsi_status = LDAP_SUCCESS; + rc = -1; + goto done; + } + +#ifdef BACKSQL_ARBITRARY_KEY + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bcblbc", + &bsi->bsi_oc->bom_keytbl, '.', + &bsi->bsi_oc->bom_keycol, + STRLENOF( " LIKE '" ), " LIKE '", + &keyval, '\'' ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + snprintf( keyvalbuf, sizeof( keyvalbuf ), "%lu", keyval ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bcbcs", + &bsi->bsi_oc->bom_keytbl, '.', + &bsi->bsi_oc->bom_keycol, '=', keyvalbuf ); +#endif /* ! BACKSQL_ARBITRARY_KEY */ + break; + + case LDAP_FILTER_PRESENT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "4=4" ), "4=4" ); + break; + + default: + rc = -1; + goto done; + } + + bsi->bsi_flags |= BSQL_SF_FILTER_ENTRYUUID; + rc = 1; + goto done; + +#ifdef BACKSQL_SYNCPROV + } else if ( ad == slap_schema.si_ad_entryCSN ) { + /* + * support for syncrepl as provider... + */ +#if 0 + if ( !bsi->bsi_op->o_sync ) { + /* unsupported at present... */ + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } +#endif + + bsi->bsi_flags |= ( BSQL_SF_FILTER_ENTRYCSN | BSQL_SF_RETURN_ENTRYUUID); + + /* if doing a syncrepl, try to return as much as possible, + * and always match the filter */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "5=5" ), "5=5" ); + + /* save for later use in operational attributes */ + /* FIXME: saves only the first occurrence, because + * the filter during updates is written as + * "(&(entryCSN<={contextCSN})(entryCSN>={oldContextCSN})({filter}))" + * so we want our fake entryCSN to match the greatest + * value + */ + if ( bsi->bsi_op->o_private == NULL ) { + bsi->bsi_op->o_private = &f->f_av_value; + } + bsi->bsi_status = LDAP_SUCCESS; + + rc = 1; + goto done; +#endif /* BACKSQL_SYNCPROV */ + + } else if ( ad == slap_schema.si_ad_hasSubordinates || ad == NULL ) { + /* + * FIXME: this is not robust; e.g. a filter + * '(!(hasSubordinates=TRUE))' fails because + * in SQL it would read 'NOT (1=1)' instead + * of no condition. + * Note however that hasSubordinates is boolean, + * so a more appropriate filter would be + * '(hasSubordinates=FALSE)' + * + * A more robust search for hasSubordinates + * would * require joining the ldap_entries table + * selecting if there are descendants of the + * candidate. + */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "6=6" ), "6=6" ); + if ( ad == slap_schema.si_ad_hasSubordinates ) { + /* + * instruct candidate selection algorithm + * and attribute list to try to detect + * if an entry has subordinates + */ + bsi->bsi_flags |= BSQL_SF_FILTER_HASSUBORDINATE; + + } else { + /* + * clear attributes to fetch, to require ALL + * and try extended match on all attributes + */ + backsql_attrlist_add( bsi, NULL ); + } + rc = 1; + goto done; + } + + /* + * attribute inheritance: + */ + if ( backsql_supad2at( bsi->bsi_oc, ad, &vat ) ) { + bsi->bsi_status = LDAP_OTHER; + rc = -1; + goto done; + } + + if ( vat == NULL ) { + /* search anyway; other parts of the filter + * may succeed */ + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "7=7" ), "7=7" ); + bsi->bsi_status = LDAP_SUCCESS; + rc = 1; + goto done; + } + + /* if required, open extra level of parens */ + done = 0; + if ( vat[0]->bam_next || vat[1] ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", '(' ); + done = 1; + } + + i = 0; +next:; + /* apply attr */ + if ( backsql_process_filter_attr( bsi, f, vat[i] ) == -1 ) { + return -1; + } + + /* if more definitions of the same attr, apply */ + if ( vat[i]->bam_next ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + STRLENOF( " OR " ), " OR " ); + vat[i] = vat[i]->bam_next; + goto next; + } + + /* if more descendants of the same attr, apply */ + i++; + if ( vat[i] ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + STRLENOF( " OR " ), " OR " ); + goto next; + } + + /* if needed, close extra level of parens */ + if ( done ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "c", ')' ); + } + + rc = 1; + +done:; + if ( vat ) { + ch_free( vat ); + } + + Debug( LDAP_DEBUG_TRACE, + "<==backsql_process_filter() %s\n", + rc == 1 ? "succeeded" : "failed" ); + + return rc; +} + +static int +backsql_process_filter_eq( backsql_srch_info *bsi, backsql_at_map_rec *at, + int casefold, struct berval *filter_value ) +{ + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ber_len_t start; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbl", + '(', /* ) */ + &at->bam_sel_expr_u, + (ber_len_t)STRLENOF( "='" ), + "='" ); + + start = bsi->bsi_flt_where.bb_val.bv_len; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cblbl", + '(', /* ) */ + &at->bam_sel_expr, + (ber_len_t)STRLENOF( "='" ), "='", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + } + + return 1; +} + +static int +backsql_process_filter_like( backsql_srch_info *bsi, backsql_at_map_rec *at, + int casefold, struct berval *filter_value ) +{ + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ber_len_t start; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbl", + '(', /* ) */ + &at->bam_sel_expr_u, + (ber_len_t)STRLENOF( " LIKE '%" ), + " LIKE '%" ); + + start = bsi->bsi_flt_where.bb_val.bv_len; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "%')" ), + /* (' */ "%')" ); + + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cblbl", + '(', /* ) */ + &at->bam_sel_expr, + (ber_len_t)STRLENOF( " LIKE '%" ), + " LIKE '%", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "%')" ), + /* (' */ "%')" ); + } + + return 1; +} + +static int +backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f, backsql_at_map_rec *at ) +{ + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + int casefold = 0; + struct berval *filter_value = NULL; + MatchingRule *matching_rule = NULL; + struct berval ordering = BER_BVC("<="); + struct berval escval; + int escaped = 0; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter_attr(%s)\n", + at->bam_ad->ad_cname.bv_val ); + + /* + * need to add this attribute to list of attrs to load, + * so that we can do test_filter() later + */ + backsql_attrlist_add( bsi, at->bam_ad ); + + backsql_merge_from_tbls( bsi, &at->bam_from_tbls ); + + if ( !BER_BVISNULL( &at->bam_join_where ) + && strstr( bsi->bsi_join_where.bb_val.bv_val, + at->bam_join_where.bv_val ) == NULL ) + { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "lb", + (ber_len_t)STRLENOF( " AND " ), " AND ", + &at->bam_join_where ); + } + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "1=0" ), "1=0" ); + return 1; + } + + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + filter_value = &f->f_av_value; + matching_rule = at->bam_ad->ad_type->sat_equality; + + goto equality_match; + + /* fail over into next case */ + + case LDAP_FILTER_EXT: + filter_value = &f->f_mra->ma_value; + matching_rule = f->f_mr_rule; + +equality_match:; + /* always uppercase strings by now */ +#ifdef BACKSQL_UPPERCASE_FILTER + if ( SLAP_MR_ASSOCIATED( matching_rule, + bi->sql_caseIgnoreMatch ) ) +#endif /* BACKSQL_UPPERCASE_FILTER */ + { + casefold = 1; + } + + escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval ); + if ( escaped ) + filter_value = &escval; + + /* FIXME: directoryString filtering should use a similar + * approach to deal with non-prettified values like + * " A non prettified value ", by using a LIKE + * filter with all whitespaces collapsed to a single '%' */ + if ( SLAP_MR_ASSOCIATED( matching_rule, + bi->sql_telephoneNumberMatch ) ) + { + struct berval bv; + ber_len_t i; + + /* + * to check for matching telephone numbers + * with intermized chars, e.g. val='1234' + * use + * + * val LIKE '%1%2%3%4%' + */ + + bv.bv_len = 2 * filter_value->bv_len - 1; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + bv.bv_val[ 0 ] = filter_value->bv_val[ 0 ]; + for ( i = 1; i < filter_value->bv_len; i++ ) { + bv.bv_val[ 2 * i - 1 ] = '%'; + bv.bv_val[ 2 * i ] = filter_value->bv_val[ i ]; + } + bv.bv_val[ 2 * i - 1 ] = '\0'; + + (void)backsql_process_filter_like( bsi, at, casefold, &bv ); + ch_free( bv.bv_val ); + + break; + } + + /* NOTE: this is required by objectClass inheritance + * and auxiliary objectClass use in filters for slightly + * more efficient candidate selection. */ + /* FIXME: a bit too many specializations to deal with + * very specific cases... */ + if ( at->bam_ad == slap_schema.si_ad_objectClass + || at->bam_ad == slap_schema.si_ad_structuralObjectClass ) + { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "lbl", + (ber_len_t)STRLENOF( "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */ ), + "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */, + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + break; + } + + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + (void)backsql_process_filter_eq( bsi, at, casefold, filter_value ); + break; + + case LDAP_FILTER_GE: + ordering.bv_val = ">="; + + /* fall thru to next case */ + + case LDAP_FILTER_LE: + filter_value = &f->f_av_value; + + /* always uppercase strings by now */ +#ifdef BACKSQL_UPPERCASE_FILTER + if ( at->bam_ad->ad_type->sat_ordering && + SLAP_MR_ASSOCIATED( at->bam_ad->ad_type->sat_ordering, + bi->sql_caseIgnoreMatch ) ) +#endif /* BACKSQL_UPPERCASE_FILTER */ + { + casefold = 1; + } + + escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval ); + if ( escaped ) + filter_value = &escval; + + /* + * FIXME: should we uppercase the operands? + */ + if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) { + ber_len_t start; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbbc", + '(', /* ) */ + &at->bam_sel_expr_u, + &ordering, + '\'' ); + + start = bsi->bsi_flt_where.bb_val.bv_len; + + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + + ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] ); + + } else { + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "cbbcbl", + '(' /* ) */ , + &at->bam_sel_expr, + &ordering, + '\'', + filter_value, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* ( */ "')" ); + } + break; + + case LDAP_FILTER_PRESENT: + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "lbl", + (ber_len_t)STRLENOF( "NOT (" /* ) */), + "NOT (", /* ) */ + &at->bam_sel_expr, + (ber_len_t)STRLENOF( /* ( */ " IS NULL)" ), + /* ( */ " IS NULL)" ); + break; + + case LDAP_FILTER_SUBSTRINGS: + backsql_process_sub_filter( bsi, f, at ); + break; + + case LDAP_FILTER_APPROX: + /* we do our best */ + + filter_value = &f->f_av_value; + escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval ); + if ( escaped ) + filter_value = &escval; + /* + * maybe we should check type of at->sel_expr here somehow, + * to know whether upper_func is applicable, but for now + * upper_func stuff is made for Oracle, where UPPER is + * safely applicable to NUMBER etc. + */ + (void)backsql_process_filter_like( bsi, at, 1, filter_value ); + break; + + default: + /* unhandled filter type; should not happen */ + assert( 0 ); + backsql_strfcat_x( &bsi->bsi_flt_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "8=8" ), "8=8" ); + break; + + } + + if ( escaped ) + bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_process_filter_attr(%s)\n", + at->bam_ad->ad_cname.bv_val ); + + return 1; +} + +static int +backsql_srch_query( backsql_srch_info *bsi, struct berval *query ) +{ + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + int rc; + + assert( query != NULL ); + BER_BVZERO( query ); + + bsi->bsi_use_subtree_shortcut = 0; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_srch_query()\n" ); + BER_BVZERO( &bsi->bsi_sel.bb_val ); + BER_BVZERO( &bsi->bsi_sel.bb_val ); + bsi->bsi_sel.bb_len = 0; + BER_BVZERO( &bsi->bsi_from.bb_val ); + bsi->bsi_from.bb_len = 0; + BER_BVZERO( &bsi->bsi_join_where.bb_val ); + bsi->bsi_join_where.bb_len = 0; + BER_BVZERO( &bsi->bsi_flt_where.bb_val ); + bsi->bsi_flt_where.bb_len = 0; + + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "lbcbc", + (ber_len_t)STRLENOF( "SELECT DISTINCT ldap_entries.id," ), + "SELECT DISTINCT ldap_entries.id,", + &bsi->bsi_oc->bom_keytbl, + '.', + &bsi->bsi_oc->bom_keycol, + ',' ); + + if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) { + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "blbl", + &bi->sql_strcast_func, + (ber_len_t)STRLENOF( "('" /* ') */ ), + "('" /* ') */ , + &bsi->bsi_oc->bom_oc->soc_cname, + (ber_len_t)STRLENOF( /* (' */ "')" ), + /* (' */ "')" ); + } else { + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "cbc", + '\'', + &bsi->bsi_oc->bom_oc->soc_cname, + '\'' ); + } + + backsql_strfcat_x( &bsi->bsi_sel, + bsi->bsi_op->o_tmpmemctx, + "b", + &bi->sql_dn_oc_aliasing ); + backsql_strfcat_x( &bsi->bsi_from, + bsi->bsi_op->o_tmpmemctx, + "lb", + (ber_len_t)STRLENOF( " FROM ldap_entries," ), + " FROM ldap_entries,", + &bsi->bsi_oc->bom_keytbl ); + + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "lbcbl", + (ber_len_t)STRLENOF( " WHERE " ), " WHERE ", + &bsi->bsi_oc->bom_keytbl, + '.', + &bsi->bsi_oc->bom_keycol, + (ber_len_t)STRLENOF( "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " ), + "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " ); + + switch ( bsi->bsi_scope ) { + case LDAP_SCOPE_BASE: + if ( BACKSQL_CANUPPERCASE( bi ) ) { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn)=?" ), + "(ldap_entries.dn)=?" ); + } else { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.dn=?" ), + "ldap_entries.dn=?" ); + } + break; + + case BACKSQL_SCOPE_BASE_LIKE: + if ( BACKSQL_CANUPPERCASE( bi ) ) { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ), + "(ldap_entries.dn) LIKE ?" ); + } else { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ), + "ldap_entries.dn LIKE ?" ); + } + break; + + case LDAP_SCOPE_ONELEVEL: + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.parent=?" ), + "ldap_entries.parent=?" ); + break; + + case LDAP_SCOPE_SUBORDINATE: + case LDAP_SCOPE_SUBTREE: + if ( BACKSQL_USE_SUBTREE_SHORTCUT( bi ) ) { + int i; + BackendDB *bd = bsi->bsi_op->o_bd; + + assert( bd->be_nsuffix != NULL ); + + for ( i = 0; !BER_BVISNULL( &bd->be_nsuffix[ i ] ); i++ ) + { + if ( dn_match( &bd->be_nsuffix[ i ], + bsi->bsi_base_ndn ) ) + { + /* pass this to the candidate selection + * routine so that the DN is not bound + * to the select statement */ + bsi->bsi_use_subtree_shortcut = 1; + break; + } + } + } + + if ( bsi->bsi_use_subtree_shortcut ) { + /* Skip the base DN filter, as every entry will match it */ + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "9=9"), "9=9"); + + } else if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) { + /* This should always be true... */ + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "b", + &bi->sql_subtree_cond ); + + } else if ( BACKSQL_CANUPPERCASE( bi ) ) { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "bl", + &bi->sql_upper_func, + (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ), + "(ldap_entries.dn) LIKE ?" ); + + } else { + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ), + "ldap_entries.dn LIKE ?" ); + } + + break; + + default: + assert( 0 ); + } + +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results are in effect, ignore low ldap_entries.id numbers */ + if ( get_pagedresults(bsi->bsi_op) > SLAP_CONTROL_IGNORED ) { + unsigned long lowid = 0; + + /* Pick up the previous ldap_entries.id if the previous page ended in this objectClass */ + if ( bsi->bsi_oc->bom_id == PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie ) ) + { + lowid = PAGECOOKIE_TO_SQL_ID( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie ); + } + + if ( lowid ) { + char lowidstring[48]; + int lowidlen; + + lowidlen = snprintf( lowidstring, sizeof( lowidstring ), + " AND ldap_entries.id>%lu", lowid ); + backsql_strfcat_x( &bsi->bsi_join_where, + bsi->bsi_op->o_tmpmemctx, + "l", + (ber_len_t)lowidlen, + lowidstring ); + } + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + rc = backsql_process_filter( bsi, bsi->bsi_filter ); + if ( rc > 0 ) { + struct berbuf bb = BB_NULL; + + backsql_strfcat_x( &bb, + bsi->bsi_op->o_tmpmemctx, + "bbblb", + &bsi->bsi_sel.bb_val, + &bsi->bsi_from.bb_val, + &bsi->bsi_join_where.bb_val, + (ber_len_t)STRLENOF( " AND " ), " AND ", + &bsi->bsi_flt_where.bb_val ); + + *query = bb.bb_val; + + } else if ( rc < 0 ) { + /* + * Indicates that there's no possible way the filter matches + * anything. No need to issue the query + */ + free( query->bv_val ); + BER_BVZERO( query ); + } + + bsi->bsi_op->o_tmpfree( bsi->bsi_sel.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_sel.bb_val ); + bsi->bsi_sel.bb_len = 0; + bsi->bsi_op->o_tmpfree( bsi->bsi_from.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_from.bb_val ); + bsi->bsi_from.bb_len = 0; + bsi->bsi_op->o_tmpfree( bsi->bsi_join_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_join_where.bb_val ); + bsi->bsi_join_where.bb_len = 0; + bsi->bsi_op->o_tmpfree( bsi->bsi_flt_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &bsi->bsi_flt_where.bb_val ); + bsi->bsi_flt_where.bb_len = 0; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_srch_query() returns %s\n", + query->bv_val ? query->bv_val : "NULL" ); + + return ( rc <= 0 ? 1 : 0 ); +} + +static int +backsql_oc_get_candidates( void *v_oc, void *v_bsi ) +{ + backsql_oc_map_rec *oc = v_oc; + backsql_srch_info *bsi = v_bsi; + Operation *op = bsi->bsi_op; + backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private; + struct berval query; + SQLHSTMT sth = SQL_NULL_HSTMT; + RETCODE rc; + int res; + BACKSQL_ROW_NTS row; + int i; + int j; + int n_candidates = bsi->bsi_n_candidates; + + /* + * + 1 because we need room for '%'; + * + 1 because we need room for ',' for LDAP_SCOPE_SUBORDINATE; + * this makes a subtree + * search for a DN BACKSQL_MAX_DN_LEN long legal + * if it returns that DN only + */ + char tmp_base_ndn[ BACKSQL_MAX_DN_LEN + 1 + 1 ]; + + bsi->bsi_status = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_oc_get_candidates(): oc=\"%s\"\n", + BACKSQL_OC_NAME( oc ) ); + + /* check for abandon */ + if ( op->o_abandon ) { + bsi->bsi_status = SLAPD_ABANDON; + return BACKSQL_AVL_STOP; + } + +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results have already completed this objectClass, skip it */ + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + if ( oc->bom_id < PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)op->o_pagedresults_state)->ps_cookie ) ) + { + return BACKSQL_AVL_CONTINUE; + } + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + if ( bsi->bsi_n_candidates == -1 ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "unchecked limit has been overcome\n" ); + /* should never get here */ + assert( 0 ); + bsi->bsi_status = LDAP_ADMINLIMIT_EXCEEDED; + return BACKSQL_AVL_STOP; + } + + bsi->bsi_oc = oc; + res = backsql_srch_query( bsi, &query ); + if ( res ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error while constructing query for objectclass \"%s\"\n", + oc->bom_oc->soc_cname.bv_val ); + /* + * FIXME: need to separate errors from legally + * impossible filters + */ + switch ( bsi->bsi_status ) { + case LDAP_SUCCESS: + case LDAP_UNDEFINED_TYPE: + case LDAP_NO_SUCH_OBJECT: + /* we are conservative... */ + default: + bsi->bsi_status = LDAP_SUCCESS; + /* try next */ + return BACKSQL_AVL_CONTINUE; + + case LDAP_ADMINLIMIT_EXCEEDED: + case LDAP_OTHER: + /* don't try any more */ + return BACKSQL_AVL_STOP; + } + } + + if ( BER_BVISNULL( &query ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "could not construct query for objectclass \"%s\"\n", + oc->bom_oc->soc_cname.bv_val ); + bsi->bsi_status = LDAP_SUCCESS; + return BACKSQL_AVL_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, "Constructed query: %s\n", + query.bv_val ); + + rc = backsql_Prepare( bsi->bsi_dbh, &sth, query.bv_val, 0 ); + bsi->bsi_op->o_tmpfree( query.bv_val, bsi->bsi_op->o_tmpmemctx ); + BER_BVZERO( &query ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error preparing query\n" ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, "id: '" BACKSQL_IDNUMFMT "'\n", + bsi->bsi_oc->bom_id ); + + rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT, + &bsi->bsi_oc->bom_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding objectclass id parameter\n" ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + switch ( bsi->bsi_scope ) { + case LDAP_SCOPE_BASE: + case BACKSQL_SCOPE_BASE_LIKE: + /* + * We do not accept DNs longer than BACKSQL_MAX_DN_LEN; + * however this should be handled earlier + */ + if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) { + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + AC_MEMCPY( tmp_base_ndn, bsi->bsi_base_ndn->bv_val, + bsi->bsi_base_ndn->bv_len + 1 ); + + /* uppercase DN only if the stored DN can be uppercased + * for comparison */ + if ( BACKSQL_CANUPPERCASE( bi ) ) { + ldap_pvt_str2upper( tmp_base_ndn ); + } + + Debug( LDAP_DEBUG_TRACE, "(base)dn: \"%s\"\n", + tmp_base_ndn ); + + rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT, + tmp_base_ndn, BACKSQL_MAX_DN_LEN ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding base_ndn parameter\n" ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, + sth, rc ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + break; + + case LDAP_SCOPE_SUBORDINATE: + case LDAP_SCOPE_SUBTREE: + { + /* if short-cutting the search base, + * don't bind any parameter */ + if ( bsi->bsi_use_subtree_shortcut ) { + break; + } + + /* + * We do not accept DNs longer than BACKSQL_MAX_DN_LEN; + * however this should be handled earlier + */ + if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) { + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + /* + * Sets the parameters for the SQL built earlier + * NOTE that all the databases could actually use + * the TimesTen version, which would be cleaner + * and would also eliminate the need for the + * subtree_cond line in the configuration file. + * For now, I'm leaving it the way it is, + * so non-TimesTen databases use the original code. + * But at some point this should get cleaned up. + * + * If "dn" is being used, do a suffix search. + * If "dn_ru" is being used, do a prefix search. + */ + if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) { + tmp_base_ndn[ 0 ] = '\0'; + + for ( i = 0, j = bsi->bsi_base_ndn->bv_len - 1; + j >= 0; i++, j--) { + tmp_base_ndn[ i ] = bsi->bsi_base_ndn->bv_val[ j ]; + } + + if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) { + tmp_base_ndn[ i++ ] = ','; + } + + tmp_base_ndn[ i ] = '%'; + tmp_base_ndn[ i + 1 ] = '\0'; + + } else { + i = 0; + + tmp_base_ndn[ i++ ] = '%'; + + if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) { + tmp_base_ndn[ i++ ] = ','; + } + + AC_MEMCPY( &tmp_base_ndn[ i ], bsi->bsi_base_ndn->bv_val, + bsi->bsi_base_ndn->bv_len + 1 ); + } + + /* uppercase DN only if the stored DN can be uppercased + * for comparison */ + if ( BACKSQL_CANUPPERCASE( bi ) ) { + ldap_pvt_str2upper( tmp_base_ndn ); + } + + if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) { + Debug( LDAP_DEBUG_TRACE, "(children)dn: \"%s\"\n", + tmp_base_ndn ); + } else { + Debug( LDAP_DEBUG_TRACE, "(sub)dn: \"%s\"\n", + tmp_base_ndn ); + } + + rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT, + tmp_base_ndn, BACKSQL_MAX_DN_LEN ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding base_ndn parameter (2)\n" ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, + sth, rc ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + break; + } + + case LDAP_SCOPE_ONELEVEL: + assert( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) ); + + Debug( LDAP_DEBUG_TRACE, "(one)id=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(bsi->bsi_base_id.eid_id) ); + rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT, + &bsi->bsi_base_id.eid_id ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error binding base id parameter\n" ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + break; + } + + rc = SQLExecute( sth ); + if ( !BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "error executing query\n" ); + backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc ); + SQLFreeStmt( sth, SQL_DROP ); + bsi->bsi_status = LDAP_OTHER; + return BACKSQL_AVL_CONTINUE; + } + + backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx ); + rc = SQLFetch( sth ); + for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) { + struct berval dn, pdn, ndn; + backsql_entryID *c_id = NULL; + int ret; + + ber_str2bv( row.cols[ 3 ], 0, 0, &dn ); + + if ( backsql_api_odbc2dn( bsi->bsi_op, bsi->bsi_rs, &dn ) ) { + continue; + } + + ret = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx ); + if ( dn.bv_val != row.cols[ 3 ] ) { + free( dn.bv_val ); + } + + if ( ret != LDAP_SUCCESS ) { + continue; + } + + if ( bi->sql_baseObject && dn_match( &ndn, &bi->sql_baseObject->e_nname ) ) { + goto cleanup; + } + + c_id = (backsql_entryID *)op->o_tmpcalloc( 1, + sizeof( backsql_entryID ), op->o_tmpmemctx ); +#ifdef BACKSQL_ARBITRARY_KEY + ber_str2bv_x( row.cols[ 0 ], 0, 1, &c_id->eid_id, + op->o_tmpmemctx ); + ber_str2bv_x( row.cols[ 1 ], 0, 1, &c_id->eid_keyval, + op->o_tmpmemctx ); +#else /* ! BACKSQL_ARBITRARY_KEY */ + if ( BACKSQL_STR2ID( &c_id->eid_id, row.cols[ 0 ], 0 ) != 0 ) { + goto cleanup; + } + if ( BACKSQL_STR2ID( &c_id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) { + goto cleanup; + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + c_id->eid_oc = bsi->bsi_oc; + c_id->eid_oc_id = bsi->bsi_oc->bom_id; + + c_id->eid_dn = pdn; + c_id->eid_ndn = ndn; + + /* append at end of list ... */ + c_id->eid_next = NULL; + *bsi->bsi_id_listtail = c_id; + bsi->bsi_id_listtail = &c_id->eid_next; + + Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): " + "added entry id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDFMT " dn=\"%s\"\n", + BACKSQL_IDARG(c_id->eid_id), + BACKSQL_IDARG(c_id->eid_keyval), + row.cols[ 3 ] ); + + /* count candidates, for unchecked limit */ + bsi->bsi_n_candidates--; + if ( bsi->bsi_n_candidates == -1 ) { + break; + } + continue; + +cleanup:; + if ( !BER_BVISNULL( &pdn ) ) { + op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &ndn ) ) { + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + if ( c_id != NULL ) { + ch_free( c_id ); + } + } + backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx ); + SQLFreeStmt( sth, SQL_DROP ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_oc_get_candidates(): %d\n", + n_candidates - bsi->bsi_n_candidates ); + + return ( bsi->bsi_n_candidates == -1 ? BACKSQL_AVL_STOP : BACKSQL_AVL_CONTINUE ); +} + +int +backsql_search( Operation *op, SlapReply *rs ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + SQLHDBC dbh = SQL_NULL_HDBC; + int sres; + Entry user_entry = { 0 }, + base_entry = { 0 }; + int manageDSAit = get_manageDSAit( op ); + time_t stoptime = 0; + backsql_srch_info bsi = { 0 }; + backsql_entryID *eid = NULL; + struct berval nbase = BER_BVNULL; +#ifndef BACKSQL_ARBITRARY_KEY + ID lastid = 0; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + Debug( LDAP_DEBUG_TRACE, "==>backsql_search(): " + "base=\"%s\", filter=\"%s\", scope=%d,", + op->o_req_ndn.bv_val, + op->ors_filterstr.bv_val, + op->ors_scope ); + Debug( LDAP_DEBUG_TRACE, " deref=%d, attrsonly=%d, " + "attributes to load: %s\n", + op->ors_deref, + op->ors_attrsonly, + op->ors_attrs == NULL ? "all" : "custom list" ); + + if ( op->o_req_ndn.bv_len > BACKSQL_MAX_DN_LEN ) { + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "search base length (%ld) exceeds max length (%d)\n", + op->o_req_ndn.bv_len, BACKSQL_MAX_DN_LEN ); + /* + * FIXME: a LDAP_NO_SUCH_OBJECT could be appropriate + * since it is impossible that such a long DN exists + * in the backend + */ + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + return 1; + } + + sres = backsql_get_db_conn( op, &dbh ); + if ( sres != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "could not get connection handle - exiting\n" ); + rs->sr_err = sres; + rs->sr_text = sres == LDAP_OTHER ? "SQL-backend error" : NULL; + send_ldap_result( op, rs ); + return 1; + } + + /* compute it anyway; root does not use it */ + stoptime = op->o_time + op->ors_tlimit; + + /* init search */ + bsi.bsi_e = &base_entry; + rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn, + op->ors_scope, + stoptime, op->ors_filter, + dbh, op, rs, op->ors_attrs, + ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_REFERRAL: + if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) && + dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) ) + { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + rs->sr_matched = NULL; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + break; + } + + /* an entry was created; free it */ + entry_clean( bsi.bsi_e ); + + /* fall thru */ + + default: + if ( !BER_BVISNULL( &base_entry.e_nname ) + && !access_allowed( op, &base_entry, + slap_schema.si_ad_entry, NULL, + ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + rs->sr_matched = NULL; + rs->sr_text = NULL; + } + + send_ldap_result( op, rs ); + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + if ( !BER_BVISNULL( &base_entry.e_nname ) ) { + entry_clean( &base_entry ); + } + + goto done; + } + /* NOTE: __NEW__ "search" access is required + * on searchBase object */ + { + slap_mask_t mask; + + if ( get_assert( op ) && + ( test_filter( op, &base_entry, get_assertion( op ) ) + != LDAP_COMPARE_TRUE ) ) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + + } + if ( ! access_allowed_mask( op, &base_entry, + slap_schema.si_ad_entry, + NULL, ACL_SEARCH, NULL, &mask ) ) + { + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = NULL; + } + send_ldap_result( op, rs ); + goto done; + } + } + + bsi.bsi_e = NULL; + + bsi.bsi_n_candidates = + ( op->ors_limit == NULL /* isroot == TRUE */ ? -2 : + ( op->ors_limit->lms_s_unchecked == -1 ? -2 : + ( op->ors_limit->lms_s_unchecked ) ) ); + +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results are in effect, check the paging cookie */ + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + rs->sr_err = parse_paged_cookie( op, rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + switch ( bsi.bsi_scope ) { + case LDAP_SCOPE_BASE: + case BACKSQL_SCOPE_BASE_LIKE: + /* + * probably already found... + */ + bsi.bsi_id_list = &bsi.bsi_base_id; + bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next; + break; + + case LDAP_SCOPE_SUBTREE: + /* + * if baseObject is defined, and if it is the root + * of the search, add it to the candidate list + */ + if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &bsi.bsi_base_id.eid_id ) ) + { + bsi.bsi_id_list = &bsi.bsi_base_id; + bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next; + } + + /* FALLTHRU */ + default: + + /* + * for each objectclass we try to construct query which gets IDs + * of entries matching LDAP query filter and scope (or at least + * candidates), and get the IDs. Do this in ID order for paging. + */ + ldap_avl_apply( bi->sql_oc_by_id, backsql_oc_get_candidates, + &bsi, BACKSQL_AVL_STOP, AVL_INORDER ); + + /* check for abandon */ + if ( op->o_abandon ) { + eid = bsi.bsi_id_list; + rs->sr_err = SLAPD_ABANDON; + goto send_results; + } + } + + if ( op->ors_limit != NULL /* isroot == FALSE */ + && op->ors_limit->lms_s_unchecked != -1 + && bsi.bsi_n_candidates == -1 ) + { + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + goto done; + } + + /* + * now we load candidate entries (only those attributes + * mentioned in attrs and filter), test it against full filter + * and then send to client; don't free entry_id if baseObject... + */ + for ( eid = bsi.bsi_id_list; + eid != NULL; + eid = backsql_free_entryID( + eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) ) + { + int rc; + Attribute *a_hasSubordinate = NULL, + *a_entryUUID = NULL, + *a_entryCSN = NULL, + **ap = NULL; + Entry *e = NULL; + + /* check for abandon */ + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto send_results; + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + rs->sr_ctrls = NULL; + rs->sr_ref = rs->sr_v2ref; + goto send_results; + } + + Debug(LDAP_DEBUG_TRACE, "backsql_search(): loading data " + "for entry id=" BACKSQL_IDFMT " oc_id=" BACKSQL_IDNUMFMT ", keyval=" BACKSQL_IDFMT "\n", + BACKSQL_IDARG(eid->eid_id), + eid->eid_oc_id, + BACKSQL_IDARG(eid->eid_keyval) ); + + /* check scope */ + switch ( op->ors_scope ) { + case LDAP_SCOPE_BASE: + case BACKSQL_SCOPE_BASE_LIKE: + if ( !dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) { + goto next_entry2; + } + break; + + case LDAP_SCOPE_ONE: + { + struct berval rdn = eid->eid_ndn; + + rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," ); + if ( !dnIsOneLevelRDN( &rdn ) ) { + goto next_entry2; + } + /* fall thru */ + } + + case LDAP_SCOPE_SUBORDINATE: + /* discard the baseObject entry */ + if ( dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) { + goto next_entry2; + } + /* FALLTHRU */ + case LDAP_SCOPE_SUBTREE: + /* FIXME: this should never fail... */ + if ( !dnIsSuffix( &eid->eid_ndn, &op->o_req_ndn ) ) { + goto next_entry2; + } + break; + } + + if ( BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) { + /* don't recollect baseObject... */ + e = bi->sql_baseObject; + + } else if ( eid == &bsi.bsi_base_id ) { + /* don't recollect searchBase object... */ + e = &base_entry; + + } else { + bsi.bsi_e = &user_entry; + rc = backsql_id2entry( &bsi, eid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "error %d in backsql_id2entry() " + "- skipping\n", rc ); + continue; + } + e = &user_entry; + } + + if ( !manageDSAit && + op->ors_scope != LDAP_SCOPE_BASE && + op->ors_scope != BACKSQL_SCOPE_BASE_LIKE && + is_entry_referral( e ) ) + { + BerVarray refs; + + refs = get_entry_referrals( op, e ); + if ( !refs ) { + backsql_srch_info bsi2 = { 0 }; + Entry user_entry2 = { 0 }; + + /* retry with the full entry... */ + bsi2.bsi_e = &user_entry2; + rc = backsql_init_search( &bsi2, + &e->e_nname, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, + dbh, op, rs, NULL, + BACKSQL_ISF_GET_ENTRY ); + if ( rc == LDAP_SUCCESS ) { + if ( is_entry_referral( &user_entry2 ) ) + { + refs = get_entry_referrals( op, + &user_entry2 ); + } else { + rs->sr_err = LDAP_OTHER; + } + backsql_entry_clean( op, &user_entry2 ); + } + if ( bsi2.bsi_attrs != NULL ) { + op->o_tmpfree( bsi2.bsi_attrs, + op->o_tmpmemctx ); + } + } + + if ( refs ) { + rs->sr_ref = referral_rewrite( refs, + &e->e_name, + &op->o_req_dn, + op->ors_scope ); + ber_bvarray_free( refs ); + } + + if ( rs->sr_ref ) { + rs->sr_err = LDAP_REFERRAL; + + } else { + rs->sr_text = "bad referral object"; + } + + rs->sr_entry = e; + rs->sr_matched = user_entry.e_name.bv_val; + send_search_reference( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + rs->sr_entry = NULL; + if ( rs->sr_err == LDAP_REFERRAL ) { + rs->sr_err = LDAP_SUCCESS; + } + + goto next_entry; + } + + /* + * We use this flag since we need to parse the filter + * anyway; we should have used the frontend API function + * filter_has_subordinates() + */ + if ( bsi.bsi_flags & BSQL_SF_FILTER_HASSUBORDINATE ) { + rc = backsql_has_children( op, dbh, &e->e_nname ); + + switch ( rc ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + a_hasSubordinate = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE ); + if ( a_hasSubordinate != NULL ) { + for ( ap = &user_entry.e_attrs; + *ap; + ap = &(*ap)->a_next ); + + *ap = a_hasSubordinate; + } + rc = 0; + break; + + default: + Debug(LDAP_DEBUG_TRACE, + "backsql_search(): " + "has_children failed( %d)\n", + rc ); + rc = 1; + goto next_entry; + } + } + + if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYUUID ) { + a_entryUUID = backsql_operational_entryUUID( bi, eid ); + if ( a_entryUUID != NULL ) { + if ( ap == NULL ) { + ap = &user_entry.e_attrs; + } + + for ( ; *ap; ap = &(*ap)->a_next ); + + *ap = a_entryUUID; + } + } + +#ifdef BACKSQL_SYNCPROV + if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYCSN ) { + a_entryCSN = backsql_operational_entryCSN( op ); + if ( a_entryCSN != NULL ) { + if ( ap == NULL ) { + ap = &user_entry.e_attrs; + } + + for ( ; *ap; ap = &(*ap)->a_next ); + + *ap = a_entryCSN; + } + } +#endif /* BACKSQL_SYNCPROV */ + + if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) + { +#ifndef BACKSQL_ARBITRARY_KEY + /* If paged results are in effect, see if the page limit was exceeded */ + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) + { + e = NULL; + send_paged_response( op, rs, &lastid ); + goto done; + } + lastid = SQL_TO_PAGECOOKIE( eid->eid_id, eid->eid_oc_id ); + } +#endif /* ! BACKSQL_ARBITRARY_KEY */ + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_entry = e; + e->e_private = (void *)eid; + rs->sr_flags = ( e == &user_entry ) ? REP_ENTRY_MODIFIABLE : 0; + /* FIXME: need the whole entry (ITS#3480) */ + rs->sr_err = send_search_entry( op, rs ); + e->e_private = NULL; + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + rs->sr_operational_attrs = NULL; + + switch ( rs->sr_err ) { + case LDAP_UNAVAILABLE: + /* + * FIXME: send_search_entry failed; + * better stop + */ + Debug( LDAP_DEBUG_TRACE, "backsql_search(): " + "connection lost\n" ); + goto end_of_search; + + case LDAP_SIZELIMIT_EXCEEDED: + case LDAP_BUSY: + goto send_results; + } + } + +next_entry:; + if ( e == &user_entry ) { + backsql_entry_clean( op, &user_entry ); + } + +next_entry2:; + } + +end_of_search:; + if ( rs->sr_nentries > 0 ) { + rs->sr_ref = rs->sr_v2ref; + rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS + : LDAP_REFERRAL; + + } else { + rs->sr_err = bsi.bsi_status; + } + +send_results:; + if ( rs->sr_err != SLAPD_ABANDON ) { +#ifndef BACKSQL_ARBITRARY_KEY + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + send_paged_response( op, rs, NULL ); + } else +#endif /* ! BACKSQL_ARBITRARY_KEY */ + { + send_ldap_result( op, rs ); + } + } + + /* cleanup in case of abandon */ + for ( ; eid != NULL; + eid = backsql_free_entryID( + eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) ) + ; + + backsql_entry_clean( op, &base_entry ); + + /* in case we got here accidentally */ + backsql_entry_clean( op, &user_entry ); + + if ( rs->sr_v2ref ) { + ber_bvarray_free( rs->sr_v2ref ); + rs->sr_v2ref = NULL; + } + +#ifdef BACKSQL_SYNCPROV + if ( op->o_sync ) { + Operation op2 = *op; + SlapReply rs2 = { REP_RESULT }; + Entry *e = entry_alloc(); + slap_callback cb = { 0 }; + + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = select_backend( &op->o_bd->be_nsuffix[0], 0 ); + op2.ora_e = e; + op2.o_callback = &cb; + + ber_dupbv( &e->e_name, op->o_bd->be_suffix ); + ber_dupbv( &e->e_nname, op->o_bd->be_nsuffix ); + + cb.sc_response = slap_null_cb; + + op2.o_bd->be_add( &op2, &rs2 ); + + if ( op2.ora_e == e ) + entry_free( e ); + } +#endif /* BACKSQL_SYNCPROV */ + +done:; + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &nbase ) + && nbase.bv_val != op->o_req_ndn.bv_val ) + { + ch_free( nbase.bv_val ); + } + + /* restore scope ... FIXME: this should be done before ANY + * frontend call that uses op */ + if ( op->ors_scope == BACKSQL_SCOPE_BASE_LIKE ) { + op->ors_scope = LDAP_SCOPE_BASE; + } + + SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK ); + Debug( LDAP_DEBUG_TRACE, "<==backsql_search()\n" ); + + return rs->sr_err; +} + +/* return LDAP_SUCCESS IFF we can retrieve the specified entry. + */ +int +backsql_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + backsql_srch_info bsi = { 0 }; + SQLHDBC dbh = SQL_NULL_HDBC; + int rc; + SlapReply rs = { 0 }; + AttributeName anlist[ 2 ]; + + *ent = NULL; + + rc = backsql_get_db_conn( op, &dbh ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( at ) { + anlist[ 0 ].an_name = at->ad_cname; + anlist[ 0 ].an_desc = at; + BER_BVZERO( &anlist[ 1 ].an_name ); + } + + bsi.bsi_e = entry_alloc(); + rc = backsql_init_search( &bsi, + ndn, + LDAP_SCOPE_BASE, + (time_t)(-1), NULL, + dbh, op, &rs, at ? anlist : NULL, + BACKSQL_ISF_GET_ENTRY ); + + if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) { + (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx ); + } + + if ( rc == LDAP_SUCCESS ) { + +#if 0 /* not supported at present */ + /* find attribute values */ + if ( is_entry_alias( bsi.bsi_e ) ) { + Debug( LDAP_DEBUG_ACL, + "<= backsql_entry_get: entry is an alias\n" ); + rc = LDAP_ALIAS_PROBLEM; + goto return_results; + } +#endif + + if ( is_entry_referral( bsi.bsi_e ) ) { + Debug( LDAP_DEBUG_ACL, + "<= backsql_entry_get: entry is a referral\n" ); + rc = LDAP_REFERRAL; + goto return_results; + } + + if ( oc && !is_entry_objectclass( bsi.bsi_e, oc, 0 ) ) { + Debug( LDAP_DEBUG_ACL, + "<= backsql_entry_get: " + "failed to find objectClass\n" ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + + *ent = bsi.bsi_e; + } + +return_results:; + if ( bsi.bsi_attrs != NULL ) { + op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx ); + } + + if ( rc != LDAP_SUCCESS ) { + if ( bsi.bsi_e ) { + entry_free( bsi.bsi_e ); + } + } + + return rc; +} + +void +backsql_entry_clean( + Operation *op, + Entry *e ) +{ + void *ctx; + + ctx = ldap_pvt_thread_pool_context(); + + if ( ctx == NULL || ctx != op->o_tmpmemctx ) { + if ( !BER_BVISNULL( &e->e_name ) ) { + op->o_tmpfree( e->e_name.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &e->e_name ); + } + + if ( !BER_BVISNULL( &e->e_nname ) ) { + op->o_tmpfree( e->e_nname.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &e->e_nname ); + } + } + + entry_clean( e ); +} + +int +backsql_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + backsql_entry_clean( op, e ); + + entry_free( e ); + + return 0; +} + +#ifndef BACKSQL_ARBITRARY_KEY +/* This function is copied verbatim from back-bdb/search.c */ +static int +parse_paged_cookie( Operation *op, SlapReply *rs ) +{ + int rc = LDAP_SUCCESS; + PagedResultsState *ps = op->o_pagedresults_state; + + /* this function must be invoked only if the pagedResults + * control has been detected, parsed and partially checked + * by the frontend */ + assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ); + + /* cookie decoding/checks deferred to backend... */ + if ( ps->ps_cookieval.bv_len ) { + PagedResultsCookie reqcookie; + if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie )); + + if ( reqcookie > ps->ps_cookie ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + + } else if ( reqcookie < ps->ps_cookie ) { + rs->sr_text = "paged results cookie is invalid or old"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + } else { + /* Initial request. Initialize state. */ + ps->ps_cookie = 0; + ps->ps_count = 0; + } + +done:; + + return rc; +} + +/* This function is copied nearly verbatim from back-bdb/search.c */ +static void +send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid ) +{ + LDAPControl ctrl, *ctrls[2]; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie respcookie; + struct berval cookie; + + Debug(LDAP_DEBUG_ARGS, + "send_paged_response: lastid=0x%08lx nentries=%d\n", + lastid ? *lastid : 0, rs->sr_nentries ); + + BER_BVZERO( &ctrl.ldctl_value ); + ctrls[0] = &ctrl; + ctrls[1] = NULL; + + ber_init2( ber, NULL, LBER_USE_DER ); + + if ( lastid ) { + respcookie = ( PagedResultsCookie )(*lastid); + cookie.bv_len = sizeof( respcookie ); + cookie.bv_val = (char *)&respcookie; + + } else { + respcookie = ( PagedResultsCookie )0; + BER_BVSTR( &cookie, "" ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = respcookie; + op->o_conn->c_pagedresults_state.ps_count = + ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + /* return size of 0 -- no estimate */ + ber_printf( ber, "{iO}", 0, &cookie ); + + if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) { + goto done; + } + + ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrls[0]->ldctl_iscritical = 0; + + rs->sr_ctrls = ctrls; + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + rs->sr_ctrls = NULL; + +done: + (void) ber_free_buf( ber ); +} +#endif /* ! BACKSQL_ARBITRARY_KEY */ diff --git a/servers/slapd/back-sql/sql-wrap.c b/servers/slapd/back-sql/sql-wrap.c new file mode 100644 index 0000000..289b590 --- /dev/null +++ b/servers/slapd/back-sql/sql-wrap.c @@ -0,0 +1,529 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * Portions Copyright 2004 Mark Adamson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati and Mark Adamson. + */ + +#include "portable.h" + +#include <stdio.h> +#include "ac/string.h" +#include <sys/types.h> + +#include "slap.h" +#include "proto-sql.h" + +#define MAX_ATTR_LEN 16384 + +void +backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc ) +{ + SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH]; /* msg. buffer */ + SQLCHAR state[SQL_SQLSTATE_SIZE]; /* statement buf. */ + SDWORD iSqlCode; /* return code */ + SWORD len = SQL_MAX_MESSAGE_LENGTH - 1; /* return length */ + + Debug( LDAP_DEBUG_TRACE, "Return code: %d\n", rc ); + + for ( ; rc = SQLError( henv, hdbc, sth, state, &iSqlCode, msg, + SQL_MAX_MESSAGE_LENGTH - 1, &len ), BACKSQL_SUCCESS( rc ); ) + { + Debug( LDAP_DEBUG_TRACE, + " nativeErrCode=%d SQLengineState=%s msg=\"%s\"\n", + (int)iSqlCode, state, msg ); + } +} + +RETCODE +backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char *query, int timeout ) +{ + RETCODE rc; + + rc = SQLAllocStmt( dbh, sth ); + if ( rc != SQL_SUCCESS ) { + return rc; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_Prepare()\n" ); +#endif /* BACKSQL_TRACE */ + +#ifdef BACKSQL_MSSQL_WORKAROUND + { + char drv_name[ 30 ]; + SWORD len; + + SQLGetInfo( dbh, SQL_DRIVER_NAME, drv_name, sizeof( drv_name ), &len ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): driver name=\"%s\"\n", + drv_name ); +#endif /* BACKSQL_TRACE */ + + ldap_pvt_str2upper( drv_name ); + if ( !strncmp( drv_name, "SQLSRV32.DLL", STRLENOF( "SQLSRV32.DLL" ) ) ) { + /* + * stupid default result set in MS SQL Server + * does not support multiple active statements + * on the same connection -- so we are trying + * to make it not to use default result set... + */ + Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): " + "enabling MS SQL Server default result " + "set workaround\n" ); + rc = SQLSetStmtOption( *sth, SQL_CONCURRENCY, + SQL_CONCUR_ROWVER ); + if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) { + Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): " + "SQLSetStmtOption(SQL_CONCURRENCY," + "SQL_CONCUR_ROWVER) failed:\n" ); + backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc ); + SQLFreeStmt( *sth, SQL_DROP ); + return rc; + } + } + } +#endif /* BACKSQL_MSSQL_WORKAROUND */ + + if ( timeout > 0 ) { + Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): " + "setting query timeout to %d sec.\n", + timeout ); + rc = SQLSetStmtOption( *sth, SQL_QUERY_TIMEOUT, timeout ); + if ( rc != SQL_SUCCESS ) { + backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc ); + SQLFreeStmt( *sth, SQL_DROP ); + return rc; + } + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_Prepare() calling SQLPrepare()\n" ); +#endif /* BACKSQL_TRACE */ + + return SQLPrepare( *sth, (SQLCHAR *)query, SQL_NTS ); +} + +RETCODE +backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx ) +{ + RETCODE rc; + + if ( row == NULL ) { + return SQL_ERROR; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==> backsql_BindRowAsStrings()\n" ); +#endif /* BACKSQL_TRACE */ + + rc = SQLNumResultCols( sth, &row->ncols ); + if ( rc != SQL_SUCCESS ) { +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings(): " + "SQLNumResultCols() failed:\n" ); +#endif /* BACKSQL_TRACE */ + + backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, sth, rc ); + + } else { + SQLCHAR colname[ 64 ]; + SQLSMALLINT name_len, col_type, col_scale, col_null; + SQLLEN col_prec; + int i; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "ncols=%d\n", (int)row->ncols ); +#endif /* BACKSQL_TRACE */ + + row->col_names = (BerVarray)ber_memcalloc_x( row->ncols + 1, + sizeof( struct berval ), ctx ); + if ( row->col_names == NULL ) { + goto nomem; + } + + row->col_prec = (UDWORD *)ber_memcalloc_x( row->ncols, + sizeof( UDWORD ), ctx ); + if ( row->col_prec == NULL ) { + goto nomem; + } + + row->col_type = (SQLSMALLINT *)ber_memcalloc_x( row->ncols, + sizeof( SQLSMALLINT ), ctx ); + if ( row->col_type == NULL ) { + goto nomem; + } + + row->cols = (char **)ber_memcalloc_x( row->ncols + 1, + sizeof( char * ), ctx ); + if ( row->cols == NULL ) { + goto nomem; + } + + row->value_len = (SQLLEN *)ber_memcalloc_x( row->ncols, + sizeof( SQLLEN ), ctx ); + if ( row->value_len == NULL ) { + goto nomem; + } + + if ( 0 ) { +nomem: + ber_memfree_x( row->col_names, ctx ); + row->col_names = NULL; + ber_memfree_x( row->col_prec, ctx ); + row->col_prec = NULL; + ber_memfree_x( row->col_type, ctx ); + row->col_type = NULL; + ber_memfree_x( row->cols, ctx ); + row->cols = NULL; + ber_memfree_x( row->value_len, ctx ); + row->value_len = NULL; + + Debug( LDAP_DEBUG_ANY, "backsql_BindRowAsStrings: " + "out of memory\n" ); + + return LDAP_NO_MEMORY; + } + + for ( i = 0; i < row->ncols; i++ ) { + SQLSMALLINT TargetType; + + rc = SQLDescribeCol( sth, (SQLSMALLINT)(i + 1), &colname[ 0 ], + (SQLUINTEGER)( sizeof( colname ) - 1 ), + &name_len, &col_type, + &col_prec, &col_scale, &col_null ); + /* FIXME: test rc? */ + + ber_str2bv_x( (char *)colname, 0, 1, + &row->col_names[ i ], ctx ); +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "col_name=%s, col_prec[%d]=%d\n", + colname, (int)(i + 1), (int)col_prec ); +#endif /* BACKSQL_TRACE */ + if ( col_type != SQL_CHAR && col_type != SQL_VARCHAR ) + { + col_prec = MAX_ATTR_LEN; + } + + row->cols[ i ] = (char *)ber_memcalloc_x( col_prec + 1, + sizeof( char ), ctx ); + row->col_prec[ i ] = col_prec; + row->col_type[ i ] = col_type; + + /* + * ITS#3386, ITS#3113 - 20070308 + * Note: there are many differences between various DPMS and ODBC + * Systems; some support SQL_C_BLOB, SQL_C_BLOB_LOCATOR. YMMV: + * This has only been tested on Linux/MySQL/UnixODBC + * For BINARY-type Fields (BLOB, etc), read the data as BINARY + */ + if ( BACKSQL_IS_BINARY( col_type ) ) { +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "col_name=%s, col_type[%d]=%d: reading binary data\n", + colname, (int)(i + 1), (int)col_type); +#endif /* BACKSQL_TRACE */ + TargetType = SQL_C_BINARY; + + } else { + /* Otherwise read it as Character data */ +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: " + "col_name=%s, col_type[%d]=%d: reading character data\n", + colname, (int)(i + 1), (int)col_type); +#endif /* BACKSQL_TRACE */ + TargetType = SQL_C_CHAR; + } + + rc = SQLBindCol( sth, (SQLUSMALLINT)(i + 1), + TargetType, + (SQLPOINTER)row->cols[ i ], + col_prec + 1, + &row->value_len[ i ] ); + + /* FIXME: test rc? */ + } + + BER_BVZERO( &row->col_names[ i ] ); + row->cols[ i ] = NULL; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<== backsql_BindRowAsStrings()\n" ); +#endif /* BACKSQL_TRACE */ + + return rc; +} + +RETCODE +backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row ) +{ + return backsql_BindRowAsStrings_x( sth, row, NULL ); +} + +RETCODE +backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx ) +{ + if ( row->cols == NULL ) { + return SQL_ERROR; + } + + ber_bvarray_free_x( row->col_names, ctx ); + ber_memfree_x( row->col_prec, ctx ); + ber_memfree_x( row->col_type, ctx ); + ber_memvfree_x( (void **)row->cols, ctx ); + ber_memfree_x( row->value_len, ctx ); + + return SQL_SUCCESS; +} + + +RETCODE +backsql_FreeRow( BACKSQL_ROW_NTS *row ) +{ + return backsql_FreeRow_x( row, NULL ); +} + +static void +backsql_close_db_handle( SQLHDBC dbh ) +{ + if ( dbh == SQL_NULL_HDBC ) { + return; + } + + Debug( LDAP_DEBUG_TRACE, "==>backsql_close_db_handle(%p)\n", + (void *)dbh ); + + /* + * Default transact is SQL_ROLLBACK; commit is required only + * by write operations, and it is explicitly performed after + * each atomic operation succeeds. + */ + + /* TimesTen */ + SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK ); + SQLDisconnect( dbh ); + SQLFreeConnect( dbh ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_close_db_handle(%p)\n", + (void *)dbh ); +} + +int +backsql_conn_destroy( + backsql_info *bi ) +{ + return 0; +} + +int +backsql_init_db_env( backsql_info *bi ) +{ + RETCODE rc; + int ret = SQL_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_init_db_env()\n" ); + + rc = SQLAllocEnv( &bi->sql_db_env ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "init_db_env: SQLAllocEnv failed:\n" ); + backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, + SQL_NULL_HENV, rc ); + ret = SQL_ERROR; + } + + Debug( LDAP_DEBUG_TRACE, "<==backsql_init_db_env()=%d\n", ret ); + + return ret; +} + +int +backsql_free_db_env( backsql_info *bi ) +{ + Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_env()\n" ); + + (void)SQLFreeEnv( bi->sql_db_env ); + bi->sql_db_env = SQL_NULL_HENV; + + /* + * stop, if frontend waits for all threads to shutdown + * before calling this -- then what are we going to delete?? + * everything is already deleted... + */ + Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_env()\n" ); + + return SQL_SUCCESS; +} + +static int +backsql_open_db_handle( + backsql_info *bi, + SQLHDBC *dbhp ) +{ + /* TimesTen */ + char DBMSName[ 32 ]; + int rc; + + assert( dbhp != NULL ); + *dbhp = SQL_NULL_HDBC; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_open_db_handle()\n" ); + + rc = SQLAllocConnect( bi->sql_db_env, dbhp ); + if ( !BACKSQL_SUCCESS( rc ) ) { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "SQLAllocConnect() failed:\n" ); + backsql_PrintErrors( bi->sql_db_env, SQL_NULL_HDBC, + SQL_NULL_HENV, rc ); + return LDAP_UNAVAILABLE; + } + + rc = SQLConnect( *dbhp, + (SQLCHAR*)bi->sql_dbname, SQL_NTS, + (SQLCHAR*)bi->sql_dbuser, SQL_NTS, + (SQLCHAR*)bi->sql_dbpasswd, SQL_NTS ); + if ( rc != SQL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "SQLConnect() to database \"%s\" %s.\n", + bi->sql_dbname, + rc == SQL_SUCCESS_WITH_INFO ? + "succeeded with info" : "failed" ); + backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc ); + if ( rc != SQL_SUCCESS_WITH_INFO ) { + SQLFreeConnect( *dbhp ); + return LDAP_UNAVAILABLE; + } + } + + /* + * TimesTen : Turn off autocommit. We must explicitly + * commit any transactions. + */ + SQLSetConnectOption( *dbhp, SQL_AUTOCOMMIT, + BACKSQL_AUTOCOMMIT_ON( bi ) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF ); + + /* + * See if this connection is to TimesTen. If it is, + * remember that fact for later use. + */ + /* Assume until proven otherwise */ + bi->sql_flags &= ~BSQLF_USE_REVERSE_DN; + DBMSName[ 0 ] = '\0'; + rc = SQLGetInfo( *dbhp, SQL_DBMS_NAME, (PTR)&DBMSName, + sizeof( DBMSName ), NULL ); + if ( rc == SQL_SUCCESS ) { + if ( strcmp( DBMSName, "TimesTen" ) == 0 || + strcmp( DBMSName, "Front-Tier" ) == 0 ) + { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "TimesTen database!\n" ); + bi->sql_flags |= BSQLF_USE_REVERSE_DN; + } + + } else { + Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): " + "SQLGetInfo() failed.\n" ); + backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc ); + SQLDisconnect( *dbhp ); + SQLFreeConnect( *dbhp ); + return LDAP_UNAVAILABLE; + } + /* end TimesTen */ + + Debug( LDAP_DEBUG_TRACE, "<==backsql_open_db_handle()\n" ); + + return LDAP_SUCCESS; +} + +static void *backsql_db_conn_dummy; + +static void +backsql_db_conn_keyfree( + void *key, + void *data ) +{ + (void)backsql_close_db_handle( (SQLHDBC)data ); +} + +int +backsql_free_db_conn( Operation *op, SQLHDBC dbh ) +{ + Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_conn()\n" ); + + (void)backsql_close_db_handle( dbh ); + ldap_pvt_thread_pool_setkey( op->o_threadctx, + &backsql_db_conn_dummy, (void *)SQL_NULL_HDBC, + backsql_db_conn_keyfree, NULL, NULL ); + + Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_conn()\n" ); + + return LDAP_SUCCESS; +} + +int +backsql_get_db_conn( Operation *op, SQLHDBC *dbhp ) +{ + backsql_info *bi = (backsql_info *)op->o_bd->be_private; + int rc = LDAP_SUCCESS; + SQLHDBC dbh = SQL_NULL_HDBC; + + Debug( LDAP_DEBUG_TRACE, "==>backsql_get_db_conn()\n" ); + + assert( dbhp != NULL ); + *dbhp = SQL_NULL_HDBC; + + if ( op->o_threadctx ) { + void *data = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, + &backsql_db_conn_dummy, &data, NULL ); + dbh = (SQLHDBC)data; + + } else { + dbh = bi->sql_dbh; + } + + if ( dbh == SQL_NULL_HDBC ) { + rc = backsql_open_db_handle( bi, &dbh ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( op->o_threadctx ) { + void *data = (void *)dbh; + + ldap_pvt_thread_pool_setkey( op->o_threadctx, + &backsql_db_conn_dummy, data, + backsql_db_conn_keyfree, NULL, NULL ); + + } else { + bi->sql_dbh = dbh; + } + } + + *dbhp = dbh; + + Debug( LDAP_DEBUG_TRACE, "<==backsql_get_db_conn()\n" ); + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-sql/util.c b/servers/slapd/back-sql/util.c new file mode 100644 index 0000000..94a00ca --- /dev/null +++ b/servers/slapd/back-sql/util.c @@ -0,0 +1,572 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999 Dmitry Kovalev. + * Portions Copyright 2002 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Dmitry Kovalev for inclusion + * by OpenLDAP Software. Additional significant contributors include + * Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <sys/types.h> +#include "ac/string.h" +#include "ac/ctype.h" +#include "ac/stdarg.h" + +#include "slap.h" +#include "proto-sql.h" +#include "lutil.h" + +#define BACKSQL_MAX(a,b) ((a)>(b)?(a):(b)) +#define BACKSQL_MIN(a,b) ((a)<(b)?(a):(b)) + +#define BACKSQL_STR_GROW 256 + +const char backsql_def_oc_query[] = + "SELECT id,name,keytbl,keycol,create_proc,delete_proc,expect_return " + "FROM ldap_oc_mappings"; +const char backsql_def_needs_select_oc_query[] = + "SELECT id,name,keytbl,keycol,create_proc,create_keyval,delete_proc," + "expect_return FROM ldap_oc_mappings"; +const char backsql_def_at_query[] = + "SELECT name,sel_expr,from_tbls,join_where,add_proc,delete_proc," + "param_order,expect_return,sel_expr_u FROM ldap_attr_mappings " + "WHERE oc_map_id=?"; +const char backsql_def_delentry_stmt[] = "DELETE FROM ldap_entries WHERE id=?"; +const char backsql_def_renentry_stmt[] = + "UPDATE ldap_entries SET dn=?,parent=?,keyval=? WHERE id=?"; +const char backsql_def_insentry_stmt[] = + "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) " + "VALUES (?,?,?,?)"; +const char backsql_def_delobjclasses_stmt[] = "DELETE FROM ldap_entry_objclasses " + "WHERE entry_id=?"; +const char backsql_def_subtree_cond[] = "ldap_entries.dn LIKE CONCAT('%',?)"; +const char backsql_def_upper_subtree_cond[] = "(ldap_entries.dn) LIKE CONCAT('%',?)"; +const char backsql_id_query[] = "SELECT id,keyval,oc_map_id,dn FROM ldap_entries WHERE "; +/* better ?||? or cast(?||? as varchar) */ +const char backsql_def_concat_func[] = "CONCAT(?,?)"; + +/* TimesTen */ +const char backsql_check_dn_ru_query[] = "SELECT dn_ru FROM ldap_entries"; + +struct berbuf * +backsql_strcat_x( struct berbuf *dest, void *memctx, ... ) +{ + va_list strs; + ber_len_t cdlen, cslen, grow; + char *cstr; + + assert( dest != NULL ); + assert( dest->bb_val.bv_val == NULL + || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_strcat()\n" ); +#endif /* BACKSQL_TRACE */ + + va_start( strs, memctx ); + if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) { + dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx ); + dest->bb_val.bv_len = 0; + dest->bb_len = BACKSQL_STR_GROW; + } + cdlen = dest->bb_val.bv_len; + while ( ( cstr = va_arg( strs, char * ) ) != NULL ) { + cslen = strlen( cstr ); + grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen ); + if ( dest->bb_len - cdlen <= cslen ) { + char *tmp_dest; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): " + "buflen=%d, cdlen=%d, cslen=%d " + "-- reallocating dest\n", + dest->bb_len, cdlen + 1, cslen ); +#endif /* BACKSQL_TRACE */ + + tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val, + dest->bb_len + grow * sizeof( char ), memctx ); + if ( tmp_dest == NULL ) { + Debug( LDAP_DEBUG_ANY, "backsql_strcat(): " + "could not reallocate string buffer.\n" ); + va_end( strs ); + return NULL; + } + dest->bb_val.bv_val = tmp_dest; + dest->bb_len += grow; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): " + "new buflen=%d, dest=%p\n", + dest->bb_len, dest ); +#endif /* BACKSQL_TRACE */ + } + AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 ); + cdlen += cslen; + } + va_end( strs ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_strcat() (dest=\"%s\")\n", + dest->bb_val.bv_val ); +#endif /* BACKSQL_TRACE */ + + dest->bb_val.bv_len = cdlen; + + return dest; +} + +struct berbuf * +backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... ) +{ + va_list strs; + ber_len_t cdlen; + + assert( dest != NULL ); + assert( fmt != NULL ); + assert( dest->bb_len == 0 || dest->bb_len > dest->bb_val.bv_len ); + assert( dest->bb_val.bv_val == NULL + || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_strfcat()\n" ); +#endif /* BACKSQL_TRACE */ + + va_start( strs, fmt ); + if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) { + dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx ); + dest->bb_val.bv_len = 0; + dest->bb_len = BACKSQL_STR_GROW; + } + + cdlen = dest->bb_val.bv_len; + for ( ; fmt[0]; fmt++ ) { + ber_len_t cslen, grow; + char *cstr, cc[ 2 ] = { '\0', '\0' }; + struct berval *cbv; + + switch ( fmt[ 0 ] ) { + + /* berval */ + case 'b': + cbv = va_arg( strs, struct berval * ); + cstr = cbv->bv_val; + cslen = cbv->bv_len; + break; + + /* length + string */ + case 'l': + cslen = va_arg( strs, ber_len_t ); + cstr = va_arg( strs, char * ); + break; + + /* string */ + case 's': + cstr = va_arg( strs, char * ); + cslen = strlen( cstr ); + break; + + /* char */ + case 'c': + /* + * `char' is promoted to `int' when passed through `...' + */ + cc[0] = va_arg( strs, int ); + cstr = cc; + cslen = 1; + break; + + default: + assert( 0 ); + } + + grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen ); + if ( dest->bb_len - cdlen <= cslen ) { + char *tmp_dest; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): " + "buflen=%d, cdlen=%d, cslen=%d " + "-- reallocating dest\n", + dest->bb_len, cdlen + 1, cslen ); +#endif /* BACKSQL_TRACE */ + + tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val, + ( dest->bb_len ) + grow * sizeof( char ), memctx ); + if ( tmp_dest == NULL ) { + Debug( LDAP_DEBUG_ANY, "backsql_strfcat(): " + "could not reallocate string buffer.\n" ); + va_end( strs ); + return NULL; + } + dest->bb_val.bv_val = tmp_dest; + dest->bb_len += grow * sizeof( char ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): " + "new buflen=%d, dest=%p\n", dest->bb_len, dest ); +#endif /* BACKSQL_TRACE */ + } + + assert( cstr != NULL ); + + AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 ); + cdlen += cslen; + } + + va_end( strs ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_strfcat() (dest=\"%s\")\n", + dest->bb_val.bv_val ); +#endif /* BACKSQL_TRACE */ + + dest->bb_val.bv_len = cdlen; + + return dest; +} + +int +backsql_entry_addattr( + Entry *e, + AttributeDescription *ad, + struct berval *val, + void *memctx ) +{ + int rc; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): %s=%s\n", + e->e_name.bv_val, ad->ad_cname.bv_val, val->bv_val ); +#endif /* BACKSQL_TRACE */ + + rc = attr_merge_normalize_one( e, ad, val, memctx ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): " + "failed to merge value \"%s\" for attribute \"%s\"\n", + e->e_name.bv_val, val->bv_val, ad->ad_cname.bv_val ); + return rc; + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_entry_addattr(\"%s\")\n", + e->e_name.bv_val ); +#endif /* BACKSQL_TRACE */ + + return LDAP_SUCCESS; +} + +static char * +backsql_get_table_spec( backsql_info *bi, char **p ) +{ + char *s, *q; + struct berbuf res = BB_NULL; + + assert( p != NULL ); + assert( *p != NULL ); + + s = *p; + while ( **p && **p != ',' ) { + (*p)++; + } + + if ( **p ) { + *(*p)++ = '\0'; + } + +#define BACKSQL_NEXT_WORD { \ + while ( *s && isspace( (unsigned char)*s ) ) s++; \ + if ( !*s ) return res.bb_val.bv_val; \ + q = s; \ + while ( *q && !isspace( (unsigned char)*q ) ) q++; \ + if ( *q ) *q++='\0'; \ + } + + BACKSQL_NEXT_WORD; + /* table name */ + backsql_strcat_x( &res, NULL, s, NULL ); + s = q; + + BACKSQL_NEXT_WORD; + if ( strcasecmp( s, "AS" ) == 0 ) { + s = q; + BACKSQL_NEXT_WORD; + } + + /* oracle doesn't understand "AS" :( and other RDBMSes don't need it */ + backsql_strfcat_x( &res, NULL, "lbbsb", + STRLENOF( " " ), " ", + &bi->sql_aliasing, + &bi->sql_aliasing_quote, + s, + &bi->sql_aliasing_quote ); + + return res.bb_val.bv_val; +} + +int +backsql_merge_from_clause( + backsql_info *bi, + struct berbuf *dest_from, + struct berval *src_from ) +{ + char *s, *p, *srcc, *pos, e; + struct berbuf res = BB_NULL; + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "==>backsql_merge_from_clause(): " + "dest_from=\"%s\",src_from=\"%s\"\n", + dest_from ? dest_from->bb_val.bv_val : "<NULL>", + src_from->bv_val ); +#endif /* BACKSQL_TRACE */ + + srcc = ch_strdup( src_from->bv_val ); + p = srcc; + + if ( dest_from != NULL ) { + res = *dest_from; + } + + while ( *p ) { + s = backsql_get_table_spec( bi, &p ); + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "backsql_merge_from_clause(): " + "p=\"%s\" s=\"%s\"\n", p, s ); +#endif /* BACKSQL_TRACE */ + + if ( BER_BVISNULL( &res.bb_val ) ) { + backsql_strcat_x( &res, NULL, s, NULL ); + + } else { + pos = strstr( res.bb_val.bv_val, s ); + if ( pos == NULL || ( ( e = pos[ strlen( s ) ] ) != '\0' && e != ',' ) ) { + backsql_strfcat_x( &res, NULL, "cs", ',', s ); + } + } + + if ( s ) { + ch_free( s ); + } + } + +#ifdef BACKSQL_TRACE + Debug( LDAP_DEBUG_TRACE, "<==backsql_merge_from_clause()\n" ); +#endif /* BACKSQL_TRACE */ + + free( srcc ); + *dest_from = res; + + return 1; +} + +/* + * splits a pattern in components separated by '?' + * (double ?? are turned into single ? and left in the string) + * expected contains the number of expected occurrences of '?' + * (a negative value means parse as many as possible) + */ + +int +backsql_split_pattern( + const char *_pattern, + BerVarray *split_pattern, + int expected ) +{ + char *pattern, *start, *end; + struct berval bv; + int rc = 0; + +#define SPLIT_CHAR '?' + + assert( _pattern != NULL ); + assert( split_pattern != NULL ); + + pattern = ch_strdup( _pattern ); + + start = pattern; + end = strchr( start, SPLIT_CHAR ); + for ( ; start; expected-- ) { + char *real_end = end; + ber_len_t real_len; + + if ( real_end == NULL ) { + real_end = start + strlen( start ); + + } else if ( real_end[ 1 ] == SPLIT_CHAR ) { + expected++; + AC_MEMCPY( real_end, real_end + 1, strlen( real_end ) ); + end = strchr( real_end + 1, SPLIT_CHAR ); + continue; + } + + real_len = real_end - start; + if ( real_len == 0 ) { + ber_str2bv( "", 0, 1, &bv ); + } else { + ber_str2bv( start, real_len, 1, &bv ); + } + + ber_bvarray_add( split_pattern, &bv ); + + if ( expected == 0 ) { + if ( end != NULL ) { + rc = -1; + goto done; + } + break; + } + + if ( end != NULL ) { + start = end + 1; + end = strchr( start, SPLIT_CHAR ); + } + } + +done:; + + ch_free( pattern ); + + return rc; +} + +int +backsql_prepare_pattern( + BerVarray split_pattern, + BerVarray values, + struct berval *res ) +{ + int i; + struct berbuf bb = BB_NULL; + + assert( res != NULL ); + + for ( i = 0; values[i].bv_val; i++ ) { + if ( split_pattern[i].bv_val == NULL ) { + ch_free( bb.bb_val.bv_val ); + return -1; + } + backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] ); + backsql_strfcat_x( &bb, NULL, "b", &values[ i ] ); + } + + if ( split_pattern[ i ].bv_val == NULL ) { + ch_free( bb.bb_val.bv_val ); + return -1; + } + + backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] ); + + *res = bb.bb_val; + + return 0; +} + +int +backsql_entryUUID( + backsql_info *bi, + backsql_entryID *id, + struct berval *entryUUID, + void *memctx ) +{ + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + struct berval uuid; +#ifdef BACKSQL_ARBITRARY_KEY + int i; + ber_len_t l, lmax; +#endif /* BACKSQL_ARBITRARY_KEY */ + + /* entryUUID is generated as "%08x-%04x-%04x-0000-eaddrXXX" + * with eid_oc_id as %08x and hi and lo eid_id as %04x-%04x */ + assert( bi != NULL ); + assert( id != NULL ); + assert( entryUUID != NULL ); + +#ifdef BACKSQL_ARBITRARY_KEY + snprintf( uuidbuf, sizeof( uuidbuf ), + "%08x-0000-0000-0000-000000000000", + ( id->eid_oc_id & 0xFFFFFFFF ) ); + lmax = id->eid_keyval.bv_len < 12 ? id->eid_keyval.bv_len : 12; + for ( l = 0, i = 9; l < lmax; l++, i += 2 ) { + switch ( i ) { + case STRLENOF( "00000000-0000" ): + case STRLENOF( "00000000-0000-0000" ): + case STRLENOF( "00000000-0000-0000-0000" ): + uuidbuf[ i++ ] = '-'; + /* FALLTHRU */ + + default: + snprintf( &uuidbuf[ i ], 3, "%2x", id->eid_keyval.bv_val[ l ] ); + break; + } + } +#else /* ! BACKSQL_ARBITRARY_KEY */ + /* note: works only with 32 bit architectures... */ + snprintf( uuidbuf, sizeof( uuidbuf ), + "%08x-%04x-%04x-0000-000000000000", + ( (unsigned)id->eid_oc_id & 0xFFFFFFFF ), + ( ( (unsigned)id->eid_keyval & 0xFFFF0000 ) >> 020 /* 16 */ ), + ( (unsigned)id->eid_keyval & 0xFFFF ) ); +#endif /* ! BACKSQL_ARBITRARY_KEY */ + + uuid.bv_val = uuidbuf; + uuid.bv_len = strlen( uuidbuf ); + + ber_dupbv_x( entryUUID, &uuid, memctx ); + + return 0; +} + +int +backsql_entryUUID_decode( + struct berval *entryUUID, + unsigned long *oc_id, +#ifdef BACKSQL_ARBITRARY_KEY + struct berval *keyval +#else /* ! BACKSQL_ARBITRARY_KEY */ + unsigned long *keyval +#endif /* ! BACKSQL_ARBITRARY_KEY */ + ) +{ +#if 0 + fprintf( stderr, "==> backsql_entryUUID_decode()\n" ); +#endif + + *oc_id = ( entryUUID->bv_val[0] << 030 /* 24 */ ) + + ( entryUUID->bv_val[1] << 020 /* 16 */ ) + + ( entryUUID->bv_val[2] << 010 /* 8 */ ) + + entryUUID->bv_val[3]; + +#ifdef BACKSQL_ARBITRARY_KEY + /* FIXME */ +#else /* ! BACKSQL_ARBITRARY_KEY */ + *keyval = ( entryUUID->bv_val[4] << 030 /* 24 */ ) + + ( entryUUID->bv_val[5] << 020 /* 16 */ ) + + ( entryUUID->bv_val[6] << 010 /* 8 */ ) + + entryUUID->bv_val[7]; +#endif /* ! BACKSQL_ARBITRARY_KEY */ + +#if 0 + fprintf( stderr, "<== backsql_entryUUID_decode(): oc=%lu id=%lu\n", + *oc_id, *keyval ); +#endif + + return LDAP_SUCCESS; +} + |