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