Home | History | Annotate | Line # | Download | only in slapd
      1 /*	$NetBSD: txn.c,v 1.4 2025/09/05 21:16:26 christos Exp $	*/
      2 
      3 /* txn.c - LDAP Transactions */
      4 /* $OpenLDAP$ */
      5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      6  *
      7  * Copyright 1998-2024 The OpenLDAP Foundation.
      8  * All rights reserved.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted only as authorized by the OpenLDAP
     12  * Public License.
     13  *
     14  * A copy of this license is available in the file LICENSE in the
     15  * top-level directory of the distribution or, alternatively, at
     16  * <http://www.OpenLDAP.org/license.html>.
     17  */
     18 
     19 #include <sys/cdefs.h>
     20 __RCSID("$NetBSD: txn.c,v 1.4 2025/09/05 21:16:26 christos Exp $");
     21 
     22 #include "portable.h"
     23 
     24 #include <stdio.h>
     25 
     26 #include <ac/socket.h>
     27 #include <ac/string.h>
     28 #include <ac/unistd.h>
     29 
     30 #include "slap.h"
     31 
     32 #include <lber_pvt.h>
     33 #include <lutil.h>
     34 
     35 const struct berval slap_EXOP_TXN_START = BER_BVC(LDAP_EXOP_TXN_START);
     36 const struct berval slap_EXOP_TXN_END = BER_BVC(LDAP_EXOP_TXN_END);
     37 
     38 int txn_start_extop(
     39 	Operation *op, SlapReply *rs )
     40 {
     41 	int rc;
     42 	struct berval *bv;
     43 
     44 	Debug( LDAP_DEBUG_STATS, "%s TXN START\n",
     45 		op->o_log_prefix );
     46 
     47 	if( op->ore_reqdata != NULL ) {
     48 		rs->sr_text = "no request data expected";
     49 		return LDAP_PROTOCOL_ERROR;
     50 	}
     51 
     52 	op->o_bd = op->o_conn->c_authz_backend;
     53 	if( backend_check_restrictions( op, rs,
     54 		(struct berval *)&slap_EXOP_TXN_START ) != LDAP_SUCCESS )
     55 	{
     56 		return rs->sr_err;
     57 	}
     58 
     59 	/* acquire connection lock */
     60 	ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
     61 
     62 	if( op->o_conn->c_txn != CONN_TXN_INACTIVE ) {
     63 		rs->sr_text = "Too many transactions";
     64 		rc = LDAP_BUSY;
     65 		goto done;
     66 	}
     67 
     68 	assert( op->o_conn->c_txn_backend == NULL );
     69 	op->o_conn->c_txn = CONN_TXN_SPECIFY;
     70 
     71 	bv = (struct berval *) ch_malloc( sizeof (struct berval) );
     72 	bv->bv_len = 0;
     73 	bv->bv_val = NULL;
     74 
     75 	rs->sr_rspdata = bv;
     76 	rc = LDAP_SUCCESS;
     77 
     78 done:
     79 	/* release connection lock */
     80 	ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
     81 	return rc;
     82 }
     83 
     84 int txn_spec_ctrl(
     85 	Operation *op, SlapReply *rs, LDAPControl *ctrl )
     86 {
     87 	if ( !ctrl->ldctl_iscritical ) {
     88 		rs->sr_text = "txnSpec control must be marked critical";
     89 		return LDAP_PROTOCOL_ERROR;
     90 	}
     91 	if( op->o_txnSpec ) {
     92 		rs->sr_text = "txnSpec control provided multiple times";
     93 		return LDAP_PROTOCOL_ERROR;
     94 	}
     95 
     96 	if ( ctrl->ldctl_value.bv_val == NULL ) {
     97 		rs->sr_text = "no transaction identifier provided";
     98 		return LDAP_PROTOCOL_ERROR;
     99 	}
    100 	if ( ctrl->ldctl_value.bv_len != 0 ) {
    101 		rs->sr_text = "invalid transaction identifier";
    102 		return LDAP_TXN_ID_INVALID;
    103 	}
    104 
    105 	if ( op->o_preread ) { /* temporary limitation */
    106 		rs->sr_text = "cannot perform pre-read in transaction";
    107 		return LDAP_UNWILLING_TO_PERFORM;
    108 	}
    109 	if ( op->o_postread ) { /* temporary limitation */
    110 		rs->sr_text = "cannot perform post-read in transaction";
    111 		return LDAP_UNWILLING_TO_PERFORM;
    112 	}
    113 
    114 	op->o_txnSpec = SLAP_CONTROL_CRITICAL;
    115 	return LDAP_SUCCESS;
    116 }
    117 
    118 typedef struct txn_rctrls {
    119 	struct txn_rctrls *tr_next;
    120 	ber_int_t	tr_msgid;
    121 	LDAPControl ** tr_ctrls;
    122 } txn_rctrls;
    123 
    124 static int txn_result( Operation *op, SlapReply *rs )
    125 {
    126 	if ( rs->sr_ctrls ) {
    127 		txn_rctrls **t0, *tr;
    128 		for ( t0 = (txn_rctrls **) &op->o_callback->sc_private; *t0;
    129 			t0 = &(*t0)->tr_next )
    130 			;
    131 		tr = op->o_tmpalloc( sizeof( txn_rctrls ), op->o_tmpmemctx );
    132 		tr->tr_next = NULL;
    133 		*t0 = tr;
    134 		tr->tr_msgid = op->o_msgid;
    135 		tr->tr_ctrls = ldap_controls_dup( rs->sr_ctrls );
    136 	}
    137 	return rs->sr_err;
    138 }
    139 
    140 static int txn_put_ctrls( Operation *op, BerElement *ber, txn_rctrls *tr )
    141 {
    142 	txn_rctrls *next;
    143 	int i;
    144 	ber_printf( ber, "{" );
    145 	for ( ; tr; tr  = next ) {
    146 		next = tr->tr_next;
    147 		ber_printf( ber, "{it{", tr->tr_msgid, LDAP_TAG_CONTROLS );
    148 		for ( i = 0; tr->tr_ctrls[i]; i++ )
    149 			ldap_pvt_put_control( tr->tr_ctrls[i], ber );
    150 		ber_printf( ber, "}}" );
    151 		ldap_controls_free( tr->tr_ctrls );
    152 		op->o_tmpfree( tr, op->o_tmpmemctx );
    153 	}
    154 	ber_printf( ber, "}" );
    155 	return 0;
    156 }
    157 
    158 int txn_end_extop(
    159 	Operation *op, SlapReply *rs )
    160 {
    161 	int rc;
    162 	BerElementBuffer berbuf;
    163 	BerElement *ber = (BerElement *)&berbuf;
    164 	ber_tag_t tag;
    165 	ber_len_t len;
    166 	ber_int_t commit=1;
    167 	struct berval txnid;
    168 	Operation *o, *p;
    169 	Connection *c = op->o_conn;
    170 
    171 	Debug( LDAP_DEBUG_STATS, "%s TXN END\n",
    172 		op->o_log_prefix );
    173 
    174 	if( op->ore_reqdata == NULL ) {
    175 		rs->sr_text = "request data expected";
    176 		return LDAP_PROTOCOL_ERROR;
    177 	}
    178 	if( op->ore_reqdata->bv_len == 0 ) {
    179 		rs->sr_text = "empty request data";
    180 		return LDAP_PROTOCOL_ERROR;
    181 	}
    182 
    183 	op->o_bd = c->c_authz_backend;
    184 	if( backend_check_restrictions( op, rs,
    185 		(struct berval *)&slap_EXOP_TXN_END ) != LDAP_SUCCESS )
    186 	{
    187 		return rs->sr_err;
    188 	}
    189 
    190 	ber_init2( ber, op->ore_reqdata, 0 );
    191 
    192 	tag = ber_scanf( ber, "{" /*}*/ );
    193 	if( tag == LBER_ERROR ) {
    194 		rs->sr_text = "request data decoding error";
    195 		return LDAP_PROTOCOL_ERROR;
    196 	}
    197 
    198 	tag = ber_peek_tag( ber, &len );
    199 	if( tag == LBER_BOOLEAN ) {
    200 		tag = ber_scanf( ber, "b", &commit );
    201 		if( tag == LBER_ERROR ) {
    202 			rs->sr_text = "request data decoding error";
    203 			return LDAP_PROTOCOL_ERROR;
    204 		}
    205 	}
    206 
    207 	tag = ber_scanf( ber, /*{*/ "m}", &txnid );
    208 	if( tag == LBER_ERROR ) {
    209 		rs->sr_text = "request data decoding error";
    210 		return LDAP_PROTOCOL_ERROR;
    211 	}
    212 
    213 	if( txnid.bv_len ) {
    214 		rs->sr_text = "invalid transaction identifier";
    215 		return LDAP_TXN_ID_INVALID;
    216 	}
    217 
    218 	/* acquire connection lock */
    219 	ldap_pvt_thread_mutex_lock( &c->c_mutex );
    220 
    221 	if( c->c_txn != CONN_TXN_SPECIFY ) {
    222 		rs->sr_text = "invalid transaction identifier";
    223 		rc = LDAP_TXN_ID_INVALID;
    224 		goto done;
    225 	}
    226 	c->c_txn = CONN_TXN_SETTLE;
    227 
    228 	if( commit ) {
    229 		slap_callback cb = {0};
    230 		OpExtra *txn = NULL;
    231 		if ( op->o_abandon ) {
    232 			goto drain;
    233 		}
    234 
    235 		if( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ) {
    236 			/* no updates to commit */
    237 			rs->sr_text = "no updates to commit";
    238 			rc = LDAP_OPERATIONS_ERROR;
    239 			goto settled;
    240 		}
    241 
    242 		cb.sc_response = txn_result;
    243 		LDAP_STAILQ_FOREACH( o, &c->c_txn_ops, o_next ) {
    244 			o->o_bd = c->c_txn_backend;
    245 			p = o;
    246 			if ( !txn ) {
    247 				rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_BEGIN, &txn );
    248 				if ( rc ) {
    249 					rs->sr_text = "couldn't start DB transaction";
    250 					rc = LDAP_OTHER;
    251 					goto drain;
    252 				}
    253 			} else {
    254 				LDAP_SLIST_INSERT_HEAD( &o->o_extra, txn, oe_next );
    255 			}
    256 			cb.sc_next = o->o_callback;
    257 			o->o_callback = &cb;
    258 			{
    259 				SlapReply rs = {REP_RESULT};
    260 				int opidx = slap_req2op( o->o_tag );
    261 				assert( opidx != SLAP_OP_LAST );
    262 				o->o_threadctx = op->o_threadctx;
    263 				o->o_tid = op->o_tid;
    264 				ldap_pvt_thread_mutex_unlock( &c->c_mutex );
    265 				rc = (&o->o_bd->bd_info->bi_op_bind)[opidx]( o, &rs );
    266 				ldap_pvt_thread_mutex_lock( &c->c_mutex );
    267 			}
    268 			LDAP_SLIST_REMOVE( &o->o_extra, txn, OpExtra, oe_next );
    269 			if ( rc ) {
    270 				struct berval *bv = NULL;
    271 				BerElementBuffer berbuf;
    272 				BerElement *ber = (BerElement *)&berbuf;
    273 
    274 				ber_init_w_nullc( ber, LBER_USE_DER );
    275 				ber_printf( ber, "{i", o->o_msgid );
    276 				if ( cb.sc_private )
    277 					txn_put_ctrls( op, ber, cb.sc_private );
    278 				ber_printf( ber, "}" );
    279 				ber_flatten( ber, &bv );
    280 				ber_free_buf( ber );
    281 				rs->sr_rspdata = bv;
    282 				o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_ABORT, &txn );
    283 				goto drain;
    284 			}
    285 		}
    286 		if ( cb.sc_private ) {
    287 			struct berval *bv = NULL;
    288 			BerElementBuffer berbuf;
    289 			BerElement *ber = (BerElement *)&berbuf;
    290 
    291 			ber_init_w_nullc( ber, LBER_USE_DER );
    292 			ber_printf( ber, "{" );
    293 			txn_put_ctrls( op, ber, cb.sc_private );
    294 			ber_printf( ber, "}" );
    295 			ber_flatten( ber, &bv );
    296 			ber_free_buf( ber );
    297 			rs->sr_rspdata = bv;
    298 		}
    299 		o = p;
    300 		rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_COMMIT, &txn );
    301 		if ( rc ) {
    302 			rs->sr_text = "transaction commit failed";
    303 			rc = LDAP_OTHER;
    304 		}
    305 	} else {
    306 		rs->sr_text = "transaction aborted";
    307 		rc = LDAP_SUCCESS;
    308 	}
    309 
    310 drain:
    311 	/* drain txn ops list */
    312 	while (( o = LDAP_STAILQ_FIRST( &c->c_txn_ops )) != NULL ) {
    313 		int freevals = 1;
    314 
    315 		LDAP_STAILQ_REMOVE_HEAD( &c->c_txn_ops, o_next );
    316 		LDAP_STAILQ_NEXT( o, o_next ) = NULL;
    317 
    318 		switch ( o->o_tag ) {
    319 			case LDAP_REQ_ADD: {
    320 				if ( o->ora_e != NULL ) {
    321 					OpExtra *oex;
    322 					OpExtraDB *oexdb = NULL;
    323 					LDAP_SLIST_FOREACH(oex, &o->o_extra, oe_next) {
    324 						if ( oex->oe_key == (void *)do_add ) {
    325 							oexdb = (OpExtraDB *)oex;
    326 							break;
    327 						}
    328 					}
    329 					if ( oexdb && oexdb->oe_db ) {
    330 						BackendDB *bd = o->o_bd;
    331 						o->o_bd = oexdb->oe_db;
    332 
    333 						be_entry_release_w( o, o->ora_e );
    334 
    335 						o->ora_e = NULL;
    336 						o->o_bd = bd;
    337 					} else {
    338 						entry_free( o->ora_e );
    339 					}
    340 					if ( oexdb ) {
    341 						o->o_tmpfree( oexdb, o->o_tmpmemctx );
    342 					}
    343 				}
    344 				freevals = 0;
    345 				} /* fallthru */
    346 			case LDAP_REQ_MODIFY:
    347 			case LDAP_REQ_MODRDN:
    348 				if ( o->orr_modlist != NULL ) {
    349 					slap_mods_free( o->orr_modlist, freevals );
    350 				}
    351 				break;
    352 			case LDAP_REQ_DELETE:
    353 			case LDAP_REQ_EXTENDED:
    354 				break;
    355 			default:
    356 				assert( 0 );
    357 		}
    358 		o->o_tmpfree( o->o_req_dn.bv_val, o->o_tmpmemctx );
    359 		o->o_tmpfree( o->o_req_ndn.bv_val, o->o_tmpmemctx );
    360 		slap_op_free( o, NULL );
    361 	}
    362 
    363 settled:
    364 	assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) );
    365 	assert( c->c_txn == CONN_TXN_SETTLE );
    366 	c->c_txn = CONN_TXN_INACTIVE;
    367 	c->c_txn_backend = NULL;
    368 
    369 done:
    370 	/* release connection lock */
    371 	ldap_pvt_thread_mutex_unlock( &c->c_mutex );
    372 
    373 	return rc;
    374 }
    375 
    376 int txn_preop( Operation *op, SlapReply *rs )
    377 {
    378 	/* acquire connection lock */
    379 	ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
    380 	if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
    381 		rs->sr_text = "invalid transaction identifier";
    382 		rs->sr_err = LDAP_TXN_ID_INVALID;
    383 		goto txnReturn;
    384 	}
    385 
    386 	if( op->o_conn->c_txn_backend == NULL ) {
    387 		op->o_conn->c_txn_backend = op->o_bd;
    388 
    389 	} else if( op->o_conn->c_txn_backend != op->o_bd ) {
    390 		rs->sr_text = "transaction cannot span multiple database contexts";
    391 		rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
    392 		goto txnReturn;
    393 	}
    394 
    395 	if ( !SLAP_TXNS( op->o_bd )) {
    396 		rs->sr_text = "backend doesn't support transactions";
    397 		rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
    398 		goto txnReturn;
    399 	}
    400 
    401 	/* insert operation into transaction */
    402 	LDAP_STAILQ_REMOVE( &op->o_conn->c_ops, op, Operation, o_next );
    403 	LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_txn_ops, op, o_next );
    404 
    405 txnReturn:
    406 	/* release connection lock */
    407 	ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
    408 
    409 	if ( op->o_tag != LDAP_REQ_EXTENDED )
    410 		send_ldap_result( op, rs );
    411 	if ( !rs->sr_err )
    412 		rs->sr_err = LDAP_TXN_SPECIFY_OKAY;
    413 	return rs->sr_err;
    414 }
    415