Home | History | Annotate | Line # | Download | only in back-sql
      1 /*	$NetBSD: config.c,v 1.4 2025/09/05 21:16:31 christos Exp $	*/
      2 
      3 /* $OpenLDAP$ */
      4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      5  *
      6  * Copyright 1999-2024 The OpenLDAP Foundation.
      7  * Portions Copyright 1999 Dmitry Kovalev.
      8  * Portions Copyright 2002 Pierangelo Masarati.
      9  * Portions Copyright 2004 Mark Adamson.
     10  * All rights reserved.
     11  *
     12  * Redistribution and use in source and binary forms, with or without
     13  * modification, are permitted only as authorized by the OpenLDAP
     14  * Public License.
     15  *
     16  * A copy of this license is available in the file LICENSE in the
     17  * top-level directory of the distribution or, alternatively, at
     18  * <http://www.OpenLDAP.org/license.html>.
     19  */
     20 /* ACKNOWLEDGEMENTS:
     21  * This work was initially developed by Dmitry Kovalev for inclusion
     22  * by OpenLDAP Software.  Additional significant contributors include
     23  * Pierangelo Masarati.
     24  */
     25 
     26 #include <sys/cdefs.h>
     27 __RCSID("$NetBSD: config.c,v 1.4 2025/09/05 21:16:31 christos Exp $");
     28 
     29 #include "portable.h"
     30 
     31 #include <stdio.h>
     32 #include "ac/string.h"
     33 #include <sys/types.h>
     34 
     35 #include "slap.h"
     36 #include "slap-config.h"
     37 #include "ldif.h"
     38 #include "lutil.h"
     39 #include "proto-sql.h"
     40 
     41 static int
     42 create_baseObject(
     43 	BackendDB	*be,
     44 	const char	*fname,
     45 	int		lineno );
     46 
     47 static int
     48 read_baseObject(
     49 	BackendDB	*be,
     50 	const char	*fname );
     51 
     52 static ConfigDriver sql_cf_gen;
     53 
     54 enum {
     55 	BSQL_CONCAT_PATT = 1,
     56 	BSQL_CREATE_NEEDS_SEL,
     57 	BSQL_UPPER_NEEDS_CAST,
     58 	BSQL_HAS_LDAPINFO_DN_RU,
     59 	BSQL_FAIL_IF_NO_MAPPING,
     60 	BSQL_ALLOW_ORPHANS,
     61 	BSQL_BASE_OBJECT,
     62 	BSQL_LAYER,
     63 	BSQL_SUBTREE_SHORTCUT,
     64 	BSQL_FETCH_ALL_ATTRS,
     65 	BSQL_FETCH_ATTRS,
     66 	BSQL_CHECK_SCHEMA,
     67 	BSQL_ALIASING_KEYWORD,
     68 	BSQL_AUTOCOMMIT
     69 };
     70 
     71 static ConfigTable sqlcfg[] = {
     72 	{ "dbhost", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET,
     73 		(void *)offsetof(struct backsql_info, sql_dbhost),
     74 		"( OLcfgDbAt:6.1 NAME 'olcDbHost' "
     75 			"DESC 'Hostname of SQL server' "
     76 			"EQUALITY caseExactMatch "
     77 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
     78 	{ "dbname", "name", 2, 2, 0, ARG_STRING|ARG_OFFSET,
     79 		(void *)offsetof(struct backsql_info, sql_dbname),
     80 		"( OLcfgDbAt:6.2 NAME 'olcDbName' "
     81 			"DESC 'Name of SQL database' "
     82 			"EQUALITY caseExactMatch "
     83 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
     84 	{ "dbuser", "username", 2, 2, 0, ARG_STRING|ARG_OFFSET,
     85 		(void *)offsetof(struct backsql_info, sql_dbuser),
     86 		"( OLcfgDbAt:6.3 NAME 'olcDbUser' "
     87 			"DESC 'Username for SQL session' "
     88 			"EQUALITY caseExactMatch "
     89 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
     90 	{ "dbpasswd", "password", 2, 2, 0, ARG_STRING|ARG_OFFSET,
     91 		(void *)offsetof(struct backsql_info, sql_dbpasswd),
     92 		"( OLcfgDbAt:6.4 NAME 'olcDbPass' "
     93 			"DESC 'Password for SQL session' "
     94 			"EQUALITY caseExactMatch "
     95 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
     96 	{ "concat_pattern", "pattern", 2, 2, 0,
     97 		ARG_STRING|ARG_MAGIC|BSQL_CONCAT_PATT, (void *)sql_cf_gen,
     98 		"( OLcfgDbAt:6.20 NAME 'olcSqlConcatPattern' "
     99 			"DESC 'Pattern used to concatenate strings' "
    100 			"EQUALITY caseExactMatch "
    101 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    102 	{ "subtree_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
    103 		(void *)offsetof(struct backsql_info, sql_subtree_cond),
    104 		"( OLcfgDbAt:6.21 NAME 'olcSqlSubtreeCond' "
    105 			"DESC 'Where-clause template for a subtree search condition' "
    106 			"EQUALITY caseExactMatch "
    107 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    108 	{ "children_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
    109 		(void *)offsetof(struct backsql_info, sql_children_cond),
    110 		"( OLcfgDbAt:6.22 NAME 'olcSqlChildrenCond' "
    111 			"DESC 'Where-clause template for a children search condition' "
    112 			"EQUALITY caseExactMatch "
    113 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    114 	{ "dn_match_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
    115 		(void *)offsetof(struct backsql_info, sql_dn_match_cond),
    116 		"( OLcfgDbAt:6.23 NAME 'olcSqlDnMatchCond' "
    117 			"DESC 'Where-clause template for a DN match search condition' "
    118 			"EQUALITY caseExactMatch "
    119 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    120 	{ "oc_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
    121 		(void *)offsetof(struct backsql_info, sql_oc_query),
    122 		"( OLcfgDbAt:6.24 NAME 'olcSqlOcQuery' "
    123 			"DESC 'Query used to collect objectClass mapping data' "
    124 			"EQUALITY caseExactMatch "
    125 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    126 	{ "at_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
    127 		(void *)offsetof(struct backsql_info, sql_at_query),
    128 		"( OLcfgDbAt:6.25 NAME 'olcSqlAtQuery' "
    129 			"DESC 'Query used to collect attributeType mapping data' "
    130 			"EQUALITY caseExactMatch "
    131 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    132 	{ "insentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
    133 		(void *)offsetof(struct backsql_info, sql_insentry_stmt),
    134 		"( OLcfgDbAt:6.26 NAME 'olcSqlInsEntryStmt' "
    135 			"DESC 'Statement used to insert a new entry' "
    136 			"EQUALITY caseExactMatch "
    137 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    138 	{ "create_needs_select", "yes|no", 2, 2, 0,
    139 		ARG_ON_OFF|ARG_MAGIC|BSQL_CREATE_NEEDS_SEL, (void *)sql_cf_gen,
    140 		"( OLcfgDbAt:6.27 NAME 'olcSqlCreateNeedsSelect' "
    141 			"DESC 'Whether entry creation needs a subsequent select' "
    142 			"EQUALITY booleanMatch "
    143 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    144 	{ "upper_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
    145 		(void *)offsetof(struct backsql_info, sql_upper_func),
    146 		"( OLcfgDbAt:6.28 NAME 'olcSqlUpperFunc' "
    147 			"DESC 'Function that converts a value to uppercase' "
    148 			"EQUALITY caseExactMatch "
    149 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    150 	{ "upper_needs_cast", "yes|no", 2, 2, 0,
    151 		ARG_ON_OFF|ARG_MAGIC|BSQL_UPPER_NEEDS_CAST, (void *)sql_cf_gen,
    152 		"( OLcfgDbAt:6.29 NAME 'olcSqlUpperNeedsCast' "
    153 			"DESC 'Whether olcSqlUpperFunc needs an explicit cast' "
    154 			"EQUALITY booleanMatch "
    155 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    156 	{ "strcast_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
    157 		(void *)offsetof(struct backsql_info, sql_strcast_func),
    158 		"( OLcfgDbAt:6.30 NAME 'olcSqlStrcastFunc' "
    159 			"DESC 'Function that converts a value to a string' "
    160 			"EQUALITY caseExactMatch "
    161 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    162 	{ "delentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
    163 		(void *)offsetof(struct backsql_info, sql_delentry_stmt),
    164 		"( OLcfgDbAt:6.31 NAME 'olcSqlDelEntryStmt' "
    165 			"DESC 'Statement used to delete an existing entry' "
    166 			"EQUALITY caseExactMatch "
    167 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    168 	{ "renentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
    169 		(void *)offsetof(struct backsql_info, sql_renentry_stmt),
    170 		"( OLcfgDbAt:6.32 NAME 'olcSqlRenEntryStmt' "
    171 			"DESC 'Statement used to rename an entry' "
    172 			"EQUALITY caseExactMatch "
    173 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    174 	{ "delobjclasses_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
    175 		(void *)offsetof(struct backsql_info, sql_delobjclasses_stmt),
    176 		"( OLcfgDbAt:6.33 NAME 'olcSqlDelObjclassesStmt' "
    177 			"DESC 'Statement used to delete the ID of an entry' "
    178 			"EQUALITY caseExactMatch "
    179 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    180 	{ "has_ldapinfo_dn_ru", "yes|no", 2, 2, 0,
    181 		ARG_ON_OFF|ARG_MAGIC|BSQL_HAS_LDAPINFO_DN_RU, (void *)sql_cf_gen,
    182 		"( OLcfgDbAt:6.34 NAME 'olcSqlHasLDAPinfoDnRu' "
    183 			"DESC 'Whether the dn_ru column is present' "
    184 			"EQUALITY booleanMatch "
    185 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    186 	{ "fail_if_no_mapping", "yes|no", 2, 2, 0,
    187 		ARG_ON_OFF|ARG_MAGIC|BSQL_FAIL_IF_NO_MAPPING, (void *)sql_cf_gen,
    188 		"( OLcfgDbAt:6.35 NAME 'olcSqlFailIfNoMapping' "
    189 			"DESC 'Whether to fail on unknown attribute mappings' "
    190 			"EQUALITY booleanMatch "
    191 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    192 	{ "allow_orphans", "yes|no", 2, 2, 0,
    193 		ARG_ON_OFF|ARG_MAGIC|BSQL_ALLOW_ORPHANS, (void *)sql_cf_gen,
    194 		"( OLcfgDbAt:6.36 NAME 'olcSqlAllowOrphans' "
    195 			"DESC 'Whether to allow adding entries with no parent' "
    196 			"EQUALITY booleanMatch "
    197 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    198 	{ "baseobject", "[file]", 1, 2, 0,
    199 		ARG_STRING|ARG_MAGIC|BSQL_BASE_OBJECT, (void *)sql_cf_gen,
    200 		"( OLcfgDbAt:6.37 NAME 'olcSqlBaseObject' "
    201 			"DESC 'Manage an in-memory baseObject entry' "
    202 			"EQUALITY caseExactMatch "
    203 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    204 	{ "sqllayer", "name", 2, 0, 0,
    205 		ARG_MAGIC|BSQL_LAYER, (void *)sql_cf_gen,
    206 		"( OLcfgDbAt:6.38 NAME 'olcSqlLayer' "
    207 			"DESC 'Helper used to map DNs between LDAP and SQL' "
    208 			"EQUALITY caseExactMatch "
    209 			"SYNTAX OMsDirectoryString )", NULL, NULL },
    210 	{ "use_subtree_shortcut", "yes|no", 2, 2, 0,
    211 		ARG_ON_OFF|ARG_MAGIC|BSQL_SUBTREE_SHORTCUT, (void *)sql_cf_gen,
    212 		"( OLcfgDbAt:6.39 NAME 'olcSqlUseSubtreeShortcut' "
    213 			"DESC 'Collect all entries when searchBase is DB suffix' "
    214 			"EQUALITY booleanMatch "
    215 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    216 	{ "fetch_all_attrs", "yes|no", 2, 2, 0,
    217 		ARG_ON_OFF|ARG_MAGIC|BSQL_FETCH_ALL_ATTRS, (void *)sql_cf_gen,
    218 		"( OLcfgDbAt:6.40 NAME 'olcSqlFetchAllAttrs' "
    219 			"DESC 'Require all attributes to always be loaded' "
    220 			"EQUALITY booleanMatch "
    221 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    222 	{ "fetch_attrs", "attrlist", 2, 0, 0,
    223 		ARG_MAGIC|BSQL_FETCH_ATTRS, (void *)sql_cf_gen,
    224 		"( OLcfgDbAt:6.41 NAME 'olcSqlFetchAttrs' "
    225 			"DESC 'Set of attributes to always fetch' "
    226 			"EQUALITY caseIgnoreMatch "
    227 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    228 	{ "check_schema", "yes|no", 2, 2, 0,
    229 		ARG_ON_OFF|ARG_MAGIC|BSQL_CHECK_SCHEMA, (void *)sql_cf_gen,
    230 		"( OLcfgDbAt:6.42 NAME 'olcSqlCheckSchema' "
    231 			"DESC 'Check schema after modifications' "
    232 			"EQUALITY booleanMatch "
    233 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    234 	{ "aliasing_keyword", "string", 2, 2, 0,
    235 		ARG_STRING|ARG_MAGIC|BSQL_ALIASING_KEYWORD, (void *)sql_cf_gen,
    236 		"( OLcfgDbAt:6.43 NAME 'olcSqlAliasingKeyword' "
    237 			"DESC 'The aliasing keyword' "
    238 			"EQUALITY caseExactMatch "
    239 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    240 	{ "aliasing_quote", "string", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
    241 		(void *)offsetof(struct backsql_info, sql_aliasing_quote),
    242 		"( OLcfgDbAt:6.44 NAME 'olcSqlAliasingQuote' "
    243 			"DESC 'Quoting char of the aliasing keyword' "
    244 			"EQUALITY caseIgnoreMatch "
    245 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    246 	{ "autocommit", "yes|no", 2, 2, 0,
    247 		ARG_ON_OFF|ARG_MAGIC|BSQL_AUTOCOMMIT, (void *)sql_cf_gen,
    248 		"( OLcfgDbAt:6.45 NAME 'olcSqlAutocommit' "
    249 			"EQUALITY booleanMatch "
    250 			"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
    251 	{ "id_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
    252 		(void *)offsetof(struct backsql_info, sql_id_query),
    253 		"( OLcfgDbAt:6.46 NAME 'olcSqlIdQuery' "
    254 			"DESC 'Query used to collect entryID mapping data' "
    255 			"EQUALITY caseExactMatch "
    256 			"SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
    257 	{ NULL, NULL, 0, 0, 0, ARG_IGNORED,
    258 		NULL, NULL, NULL, NULL }
    259 };
    260 
    261 static ConfigOCs sqlocs[] = {
    262 	{
    263 		"( OLcfgDbOc:6.1 "
    264 		"NAME 'olcSqlConfig' "
    265 		"DESC 'SQL backend configuration' "
    266 		"SUP olcDatabaseConfig "
    267 		"MUST olcDbName "
    268 		"MAY ( olcDbHost $ olcDbUser $ olcDbPass $ olcSqlConcatPattern $ "
    269 		"olcSqlSubtreeCond $ olcsqlChildrenCond $ olcSqlDnMatchCond $ "
    270 		"olcSqlOcQuery $ olcSqlAtQuery $ olcSqlInsEntryStmt $ "
    271 		"olcSqlCreateNeedsSelect $ olcSqlUpperFunc $ olcSqlUpperNeedsCast $ "
    272 		"olcSqlStrCastFunc $ olcSqlDelEntryStmt $ olcSqlRenEntryStmt $ "
    273 		"olcSqlDelObjClassesStmt $ olcSqlHasLDAPInfoDnRu $ "
    274 		"olcSqlFailIfNoMapping $ olcSqlAllowOrphans $ olcSqlBaseObject $ "
    275 		"olcSqlLayer $ olcSqlUseSubtreeShortcut $ olcSqlFetchAllAttrs $ "
    276 		"olcSqlFetchAttrs $ olcSqlCheckSchema $ olcSqlAliasingKeyword $ "
    277 		"olcSqlAliasingQuote $ olcSqlAutocommit $ olcSqlIdQuery ) )",
    278 			Cft_Database, sqlcfg },
    279 	{ NULL, Cft_Abstract, NULL }
    280 };
    281 
    282 static int
    283 sql_cf_gen( ConfigArgs *c )
    284 {
    285 	backsql_info 	*bi = (backsql_info *)c->be->be_private;
    286 	int rc = 0;
    287 
    288 	if ( c->op == SLAP_CONFIG_EMIT ) {
    289 		switch( c->type ) {
    290 		case BSQL_CONCAT_PATT:
    291 			if ( bi->sql_concat_patt ) {
    292 				c->value_string = ch_strdup( bi->sql_concat_patt );
    293 			} else {
    294 				rc = 1;
    295 			}
    296 			break;
    297 		case BSQL_CREATE_NEEDS_SEL:
    298 			if ( bi->sql_flags & BSQLF_CREATE_NEEDS_SELECT )
    299 				c->value_int = 1;
    300 			break;
    301 		case BSQL_UPPER_NEEDS_CAST:
    302 			if ( bi->sql_flags & BSQLF_UPPER_NEEDS_CAST )
    303 				c->value_int = 1;
    304 			break;
    305 		case BSQL_HAS_LDAPINFO_DN_RU:
    306 			if ( !(bi->sql_flags & BSQLF_DONTCHECK_LDAPINFO_DN_RU) )
    307 				return 1;
    308 			if ( bi->sql_flags & BSQLF_HAS_LDAPINFO_DN_RU )
    309 				c->value_int = 1;
    310 			break;
    311 		case BSQL_FAIL_IF_NO_MAPPING:
    312 			if ( bi->sql_flags & BSQLF_FAIL_IF_NO_MAPPING )
    313 				c->value_int = 1;
    314 			break;
    315 		case BSQL_ALLOW_ORPHANS:
    316 			if ( bi->sql_flags & BSQLF_ALLOW_ORPHANS )
    317 				c->value_int = 1;
    318 			break;
    319 		case BSQL_SUBTREE_SHORTCUT:
    320 			if ( bi->sql_flags & BSQLF_USE_SUBTREE_SHORTCUT )
    321 				c->value_int = 1;
    322 			break;
    323 		case BSQL_FETCH_ALL_ATTRS:
    324 			if ( bi->sql_flags & BSQLF_FETCH_ALL_ATTRS )
    325 				c->value_int = 1;
    326 			break;
    327 		case BSQL_CHECK_SCHEMA:
    328 			if ( bi->sql_flags & BSQLF_CHECK_SCHEMA )
    329 				c->value_int = 1;
    330 			break;
    331 		case BSQL_AUTOCOMMIT:
    332 			if ( bi->sql_flags & BSQLF_AUTOCOMMIT_ON )
    333 				c->value_int = 1;
    334 			break;
    335 		case BSQL_BASE_OBJECT:
    336 			if ( bi->sql_base_ob_file ) {
    337 				c->value_string = ch_strdup( bi->sql_base_ob_file );
    338 			} else if ( bi->sql_baseObject ) {
    339 				c->value_string = ch_strdup( "TRUE" );
    340 			} else {
    341 				rc = 1;
    342 			}
    343 			break;
    344 		case BSQL_LAYER:
    345 			if ( bi->sql_api ) {
    346 				backsql_api *ba;
    347 				struct berval bv;
    348 				char *ptr;
    349 				int i;
    350 				for ( ba = bi->sql_api; ba; ba = ba->ba_next ) {
    351 					bv.bv_len = strlen( ba->ba_name );
    352 					if ( ba->ba_argc ) {
    353 						for ( i = 0; i<ba->ba_argc; i++ )
    354 							bv.bv_len += strlen( ba->ba_argv[i] ) + 3;
    355 					}
    356 					bv.bv_val = ch_malloc( bv.bv_len + 1 );
    357 					ptr = lutil_strcopy( bv.bv_val, ba->ba_name );
    358 					if ( ba->ba_argc ) {
    359 						for ( i = 0; i<ba->ba_argc; i++ ) {
    360 							*ptr++ = ' ';
    361 							*ptr++ = '"';
    362 							ptr = lutil_strcopy( ptr, ba->ba_argv[i] );
    363 							*ptr++ = '"';
    364 						}
    365 					}
    366 					ber_bvarray_add( &c->rvalue_vals, &bv );
    367 				}
    368 			} else {
    369 				rc = 1;
    370 			}
    371 			break;
    372 		case BSQL_ALIASING_KEYWORD:
    373 			if ( !BER_BVISNULL( &bi->sql_aliasing )) {
    374 				struct berval bv;
    375 				bv = bi->sql_aliasing;
    376 				bv.bv_len--;
    377 				value_add_one( &c->rvalue_vals, &bv );
    378 			} else {
    379 				rc = 1;
    380 			}
    381 			break;
    382 		case BSQL_FETCH_ATTRS:
    383 			if ( bi->sql_anlist ||
    384 				( bi->sql_flags & (BSQLF_FETCH_ALL_USERATTRS|
    385 								   BSQLF_FETCH_ALL_OPATTRS)))
    386 			{
    387 				char buf[BUFSIZ*2], *ptr;
    388 				struct berval bv;
    389 #   define WHATSLEFT    ((ber_len_t) (&buf[sizeof( buf )] - ptr))
    390 				ptr = buf;
    391 				if ( bi->sql_anlist ) {
    392 					ptr = anlist_unparse( bi->sql_anlist, ptr, WHATSLEFT );
    393 					if ( ptr == NULL )
    394 						return 1;
    395 				}
    396 				if ( bi->sql_flags & BSQLF_FETCH_ALL_USERATTRS ) {
    397 					if ( WHATSLEFT <= STRLENOF( ",*" )) return 1;
    398 					if ( ptr != buf ) *ptr++ = ',';
    399 					*ptr++ = '*';
    400 				}
    401 				if ( bi->sql_flags & BSQLF_FETCH_ALL_OPATTRS ) {
    402 					if ( WHATSLEFT <= STRLENOF( ",+" )) return 1;
    403 					if ( ptr != buf ) *ptr++ = ',';
    404 					*ptr++ = '+';
    405 				}
    406 				bv.bv_val = buf;
    407 				bv.bv_len = ptr - buf;
    408 				value_add_one( &c->rvalue_vals, &bv );
    409 			}
    410 			break;
    411 		}
    412 		return rc;
    413 	} else if ( c->op == LDAP_MOD_DELETE ) {	/* FIXME */
    414 		return -1;
    415 	}
    416 
    417 	switch( c->type ) {
    418 	case BSQL_CONCAT_PATT:
    419 		if ( backsql_split_pattern( c->argv[ 1 ], &bi->sql_concat_func, 2 ) ) {
    420 			snprintf( c->cr_msg, sizeof( c->cr_msg ),
    421 				"%s: unable to parse pattern \"%s\"",
    422 				c->log, c->argv[ 1 ] );
    423 			Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
    424 			return -1;
    425 		}
    426 		bi->sql_concat_patt = c->value_string;
    427 		break;
    428 	case BSQL_CREATE_NEEDS_SEL:
    429 		if ( c->value_int )
    430 			bi->sql_flags |= BSQLF_CREATE_NEEDS_SELECT;
    431 		else
    432 			bi->sql_flags &= ~BSQLF_CREATE_NEEDS_SELECT;
    433 		break;
    434 	case BSQL_UPPER_NEEDS_CAST:
    435 		if ( c->value_int )
    436 			bi->sql_flags |= BSQLF_UPPER_NEEDS_CAST;
    437 		else
    438 			bi->sql_flags &= ~BSQLF_UPPER_NEEDS_CAST;
    439 		break;
    440 	case BSQL_HAS_LDAPINFO_DN_RU:
    441 		bi->sql_flags |= BSQLF_DONTCHECK_LDAPINFO_DN_RU;
    442 		if ( c->value_int )
    443 			bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU;
    444 		else
    445 			bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU;
    446 		break;
    447 	case BSQL_FAIL_IF_NO_MAPPING:
    448 		if ( c->value_int )
    449 			bi->sql_flags |= BSQLF_FAIL_IF_NO_MAPPING;
    450 		else
    451 			bi->sql_flags &= ~BSQLF_FAIL_IF_NO_MAPPING;
    452 		break;
    453 	case BSQL_ALLOW_ORPHANS:
    454 		if ( c->value_int )
    455 			bi->sql_flags |= BSQLF_ALLOW_ORPHANS;
    456 		else
    457 			bi->sql_flags &= ~BSQLF_ALLOW_ORPHANS;
    458 		break;
    459 	case BSQL_SUBTREE_SHORTCUT:
    460 		if ( c->value_int )
    461 			bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT;
    462 		else
    463 			bi->sql_flags &= ~BSQLF_USE_SUBTREE_SHORTCUT;
    464 		break;
    465 	case BSQL_FETCH_ALL_ATTRS:
    466 		if ( c->value_int )
    467 			bi->sql_flags |= BSQLF_FETCH_ALL_ATTRS;
    468 		else
    469 			bi->sql_flags &= ~BSQLF_FETCH_ALL_ATTRS;
    470 		break;
    471 	case BSQL_CHECK_SCHEMA:
    472 		if ( c->value_int )
    473 			bi->sql_flags |= BSQLF_CHECK_SCHEMA;
    474 		else
    475 			bi->sql_flags &= ~BSQLF_CHECK_SCHEMA;
    476 		break;
    477 	case BSQL_AUTOCOMMIT:
    478 		if ( c->value_int )
    479 			bi->sql_flags |= BSQLF_AUTOCOMMIT_ON;
    480 		else
    481 			bi->sql_flags &= ~BSQLF_AUTOCOMMIT_ON;
    482 		break;
    483 	case BSQL_BASE_OBJECT:
    484 		if ( c->be->be_nsuffix == NULL ) {
    485 			snprintf( c->cr_msg, sizeof( c->cr_msg ),
    486 				"%s: suffix must be set", c->log );
    487 			Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
    488 			rc = ARG_BAD_CONF;
    489 			break;
    490 		}
    491 		if ( bi->sql_baseObject ) {
    492 			Debug( LDAP_DEBUG_CONFIG,
    493 				"%s: "
    494 				"\"baseObject\" already provided (will be overwritten)\n",
    495 				c->log );
    496 			entry_free( bi->sql_baseObject );
    497 		}
    498 		if ( c->argc == 2 && !strcmp( c->argv[1], "TRUE" ))
    499 			c->argc = 1;
    500 		switch( c->argc ) {
    501 		case 1:
    502 			return create_baseObject( c->be, c->fname, c->lineno );
    503 
    504 		case 2:
    505 			rc = read_baseObject( c->be, c->argv[ 1 ] );
    506 			if ( rc == 0 ) {
    507 				ch_free( bi->sql_base_ob_file );
    508 				bi->sql_base_ob_file = c->value_string;
    509 			}
    510 			return rc;
    511 
    512 		default:
    513 			snprintf( c->cr_msg, sizeof( c->cr_msg ),
    514 				"%s: trailing values in directive", c->log );
    515 			Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
    516 			return 1;
    517 		}
    518 		break;
    519 	case BSQL_LAYER:
    520 		if ( backsql_api_config( bi, c->argv[ 1 ], c->argc - 2, &c->argv[ 2 ] ) )
    521 		{
    522 			snprintf( c->cr_msg, sizeof( c->cr_msg ),
    523 				"%s: unable to load sql layer", c->log );
    524 			Debug( LDAP_DEBUG_ANY, "%s \"%s\"\n",
    525 				c->cr_msg, c->argv[1] );
    526 			return 1;
    527 		}
    528 		break;
    529 	case BSQL_ALIASING_KEYWORD:
    530 		if ( ! BER_BVISNULL( &bi->sql_aliasing ) ) {
    531 			ch_free( bi->sql_aliasing.bv_val );
    532 		}
    533 
    534 		ber_str2bv( c->argv[ 1 ], strlen( c->argv[ 1 ] ) + 1, 1,
    535 			&bi->sql_aliasing );
    536 		/* add a trailing space... */
    537 		bi->sql_aliasing.bv_val[ bi->sql_aliasing.bv_len - 1] = ' ';
    538 		break;
    539 	case BSQL_FETCH_ATTRS: {
    540 		char		*str, *s, *next;
    541 		const char	*delimstr = ",";
    542 
    543 		str = ch_strdup( c->argv[ 1 ] );
    544 		for ( s = ldap_pvt_strtok( str, delimstr, &next );
    545 				s != NULL;
    546 				s = ldap_pvt_strtok( NULL, delimstr, &next ) )
    547 		{
    548 			if ( strlen( s ) == 1 ) {
    549 				if ( *s == '*' ) {
    550 					bi->sql_flags |= BSQLF_FETCH_ALL_USERATTRS;
    551 					c->argv[ 1 ][ s - str ] = ',';
    552 
    553 				} else if ( *s == '+' ) {
    554 					bi->sql_flags |= BSQLF_FETCH_ALL_OPATTRS;
    555 					c->argv[ 1 ][ s - str ] = ',';
    556 				}
    557 			}
    558 		}
    559 		ch_free( str );
    560 		bi->sql_anlist = str2anlist( bi->sql_anlist, c->argv[ 1 ], delimstr );
    561 		if ( bi->sql_anlist == NULL ) {
    562 			return -1;
    563 		}
    564 		}
    565 		break;
    566 	}
    567 	return rc;
    568 }
    569 
    570 /*
    571  * Read the entries specified in fname and merge the attributes
    572  * to the user defined baseObject entry. Note that if we find any errors
    573  * what so ever, we will discard the entire entries, print an
    574  * error message and return.
    575  */
    576 static int
    577 read_baseObject(
    578 	BackendDB	*be,
    579 	const char	*fname )
    580 {
    581 	backsql_info 	*bi = (backsql_info *)be->be_private;
    582 	LDIFFP		*fp;
    583 	int		rc = 0, lmax = 0, ldifrc;
    584 	unsigned long	lineno = 0;
    585 	char		*buf = NULL;
    586 
    587 	assert( fname != NULL );
    588 
    589 	fp = ldif_open( fname, "r" );
    590 	if ( fp == NULL ) {
    591 		Debug( LDAP_DEBUG_ANY,
    592 			"could not open back-sql baseObject "
    593 			"attr file \"%s\" - absolute path?\n",
    594 			fname );
    595 		perror( fname );
    596 		return LDAP_OTHER;
    597 	}
    598 
    599 	bi->sql_baseObject = entry_alloc();
    600 	if ( bi->sql_baseObject == NULL ) {
    601 		Debug( LDAP_DEBUG_ANY,
    602 			"read_baseObject_file: entry_alloc failed" );
    603 		ldif_close( fp );
    604 		return LDAP_NO_MEMORY;
    605 	}
    606 	bi->sql_baseObject->e_name = be->be_suffix[0];
    607 	bi->sql_baseObject->e_nname = be->be_nsuffix[0];
    608 	bi->sql_baseObject->e_attrs = NULL;
    609 
    610 	while (( ldifrc = ldif_read_record( fp, &lineno, &buf, &lmax )) > 0 ) {
    611 		Entry		*e = str2entry( buf );
    612 		Attribute	*a;
    613 
    614 		if( e == NULL ) {
    615 			fprintf( stderr, "back-sql baseObject: "
    616 					"could not parse entry (line=%lu)\n",
    617 					lineno );
    618 			rc = LDAP_OTHER;
    619 			break;
    620 		}
    621 
    622 		/* make sure the DN is the database's suffix */
    623 		if ( !be_issuffix( be, &e->e_nname ) ) {
    624 			fprintf( stderr,
    625 				"back-sql: invalid baseObject - "
    626 				"dn=\"%s\" (line=%lu)\n",
    627 				e->e_name.bv_val, lineno );
    628 			entry_free( e );
    629 			rc = LDAP_OTHER;
    630 			break;
    631 		}
    632 
    633 		/*
    634 		 * we found a valid entry, so walk thru all the attributes in the
    635 		 * entry, and add each attribute type and description to baseObject
    636 		 */
    637 		for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
    638 			if ( attr_merge( bi->sql_baseObject, a->a_desc,
    639 						a->a_vals,
    640 						( a->a_nvals == a->a_vals ) ?
    641 						NULL : a->a_nvals ) )
    642 			{
    643 				rc = LDAP_OTHER;
    644 				break;
    645 			}
    646 		}
    647 
    648 		entry_free( e );
    649 		if ( rc ) {
    650 			break;
    651 		}
    652 	}
    653 
    654 	if ( ldifrc < 0 )
    655 		rc = LDAP_OTHER;
    656 
    657 	if ( rc ) {
    658 		entry_free( bi->sql_baseObject );
    659 		bi->sql_baseObject = NULL;
    660 	}
    661 
    662 	ch_free( buf );
    663 
    664 	ldif_close( fp );
    665 
    666 	Debug( LDAP_DEBUG_CONFIG, "back-sql baseObject file \"%s\" read.\n",
    667 			fname );
    668 
    669 	return rc;
    670 }
    671 
    672 static int
    673 create_baseObject(
    674 	BackendDB	*be,
    675 	const char	*fname,
    676 	int		lineno )
    677 {
    678 	backsql_info 	*bi = (backsql_info *)be->be_private;
    679 	LDAPRDN		rdn;
    680 	char		*p;
    681 	int		rc, iAVA;
    682 	char		buf[1024];
    683 
    684 	snprintf( buf, sizeof(buf),
    685 			"dn: %s\n"
    686 			"objectClass: extensibleObject\n"
    687 			"description: builtin baseObject for back-sql\n"
    688 			"description: all entries mapped "
    689 				"in table \"ldap_entries\" "
    690 				"must have "
    691 				"\"" BACKSQL_BASEOBJECT_IDSTR "\" "
    692 				"in the \"parent\" column",
    693 			be->be_suffix[0].bv_val );
    694 
    695 	bi->sql_baseObject = str2entry( buf );
    696 	if ( bi->sql_baseObject == NULL ) {
    697 		Debug( LDAP_DEBUG_TRACE,
    698 			"<==backsql_db_config (%s line %d): "
    699 			"unable to parse baseObject entry\n",
    700 			fname, lineno );
    701 		return 1;
    702 	}
    703 
    704 	if ( BER_BVISEMPTY( &be->be_suffix[ 0 ] ) ) {
    705 		return 0;
    706 	}
    707 
    708 	rc = ldap_bv2rdn( &be->be_suffix[ 0 ], &rdn, (char **)&p,
    709 			LDAP_DN_FORMAT_LDAP );
    710 	if ( rc != LDAP_SUCCESS ) {
    711 		Debug(LDAP_DEBUG_TRACE,
    712 		      "<==backsql_db_config (%s line %d): unable to extract RDN " "from baseObject DN \"%s\" (%d: %s)\n",
    713 		      fname, lineno, be->be_suffix[0].bv_val, rc,
    714 		      ldap_err2string(rc) );
    715 		return 1;
    716 	}
    717 
    718 	for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) {
    719 		LDAPAVA				*ava = rdn[ iAVA ];
    720 		AttributeDescription		*ad = NULL;
    721 		slap_syntax_transform_func	*transf = NULL;
    722 		struct berval			bv = BER_BVNULL;
    723 		const char			*text = NULL;
    724 
    725 		assert( ava != NULL );
    726 
    727 		rc = slap_bv2ad( &ava->la_attr, &ad, &text );
    728 		if ( rc != LDAP_SUCCESS ) {
    729 			Debug(LDAP_DEBUG_TRACE,
    730 			      "<==backsql_db_config (%s line %d): AttributeDescription of naming " "attribute #%d from baseObject " "DN \"%s\": %d: %s\n",
    731 			      fname, lineno, iAVA, be->be_suffix[0].bv_val,
    732 			      rc, ldap_err2string(rc) );
    733 			return 1;
    734 		}
    735 
    736 		transf = ad->ad_type->sat_syntax->ssyn_pretty;
    737 		if ( transf ) {
    738 			/*
    739 	 		 * transform value by pretty function
    740 			 *	if value is empty, use empty_bv
    741 			 */
    742 			rc = ( *transf )( ad->ad_type->sat_syntax,
    743 				ava->la_value.bv_len
    744 					? &ava->la_value
    745 					: (struct berval *) &slap_empty_bv,
    746 				&bv, NULL );
    747 
    748 			if ( rc != LDAP_SUCCESS ) {
    749 				Debug(LDAP_DEBUG_TRACE,
    750 				      "<==backsql_db_config (%s line %d): " "prettying of attribute #%d " "from baseObject " "DN \"%s\" failed: %d: %s\n",
    751 				      fname, lineno, iAVA,
    752 				      be->be_suffix[0].bv_val, rc,
    753 				      ldap_err2string(rc) );
    754 				return 1;
    755 			}
    756 		}
    757 
    758 		if ( !BER_BVISNULL( &bv ) ) {
    759 			if ( ava->la_flags & LDAP_AVA_FREE_VALUE ) {
    760 				ber_memfree( ava->la_value.bv_val );
    761 			}
    762 			ava->la_value = bv;
    763 			ava->la_flags |= LDAP_AVA_FREE_VALUE;
    764 		}
    765 
    766 		attr_merge_normalize_one( bi->sql_baseObject,
    767 				ad, &ava->la_value, NULL );
    768 	}
    769 
    770 	ldap_rdnfree( rdn );
    771 
    772 	return 0;
    773 }
    774 
    775 int backsql_init_cf( BackendInfo *bi )
    776 {
    777 	int rc;
    778 
    779 	bi->bi_cf_ocs = sqlocs;
    780 	rc = config_register_schema( sqlcfg, sqlocs );
    781 	if ( rc ) return rc;
    782 	return 0;
    783 }
    784