xref: /netbsd-src/external/bsd/openldap/dist/servers/slapd/txn.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
1 /*	$NetBSD: txn.c,v 1.3 2021/08/14 16:14:58 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-2021 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.3 2021/08/14 16:14:58 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 
txn_start_extop(Operation * op,SlapReply * rs)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 
txn_spec_ctrl(Operation * op,SlapReply * rs,LDAPControl * ctrl)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 
txn_result(Operation * op,SlapReply * rs)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 
txn_put_ctrls(Operation * op,BerElement * ber,txn_rctrls * tr)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 
txn_end_extop(Operation * op,SlapReply * rs)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 			if ( rc ) {
269 				struct berval *bv = NULL;
270 				BerElementBuffer berbuf;
271 				BerElement *ber = (BerElement *)&berbuf;
272 
273 				ber_init_w_nullc( ber, LBER_USE_DER );
274 				ber_printf( ber, "{i", o->o_msgid );
275 				if ( cb.sc_private )
276 					txn_put_ctrls( op, ber, cb.sc_private );
277 				ber_printf( ber, "}" );
278 				ber_flatten( ber, &bv );
279 				ber_free_buf( ber );
280 				rs->sr_rspdata = bv;
281 				o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_ABORT, &txn );
282 				goto drain;
283 			}
284 		}
285 		if ( cb.sc_private ) {
286 			struct berval *bv = NULL;
287 			BerElementBuffer berbuf;
288 			BerElement *ber = (BerElement *)&berbuf;
289 
290 			ber_init_w_nullc( ber, LBER_USE_DER );
291 			ber_printf( ber, "{" );
292 			txn_put_ctrls( op, ber, cb.sc_private );
293 			ber_printf( ber, "}" );
294 			ber_flatten( ber, &bv );
295 			ber_free_buf( ber );
296 			rs->sr_rspdata = bv;
297 		}
298 		o = p;
299 		rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_COMMIT, &txn );
300 		if ( rc ) {
301 			rs->sr_text = "transaction commit failed";
302 			rc = LDAP_OTHER;
303 		}
304 	} else {
305 		rs->sr_text = "transaction aborted";
306 		rc = LDAP_SUCCESS;
307 	}
308 
309 drain:
310 	/* drain txn ops list */
311 	while (( o = LDAP_STAILQ_FIRST( &c->c_txn_ops )) != NULL ) {
312 		LDAP_STAILQ_REMOVE_HEAD( &c->c_txn_ops, o_next );
313 		LDAP_STAILQ_NEXT( o, o_next ) = NULL;
314 		slap_op_free( o, NULL );
315 	}
316 
317 settled:
318 	assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) );
319 	assert( c->c_txn == CONN_TXN_SETTLE );
320 	c->c_txn = CONN_TXN_INACTIVE;
321 	c->c_txn_backend = NULL;
322 
323 done:
324 	/* release connection lock */
325 	ldap_pvt_thread_mutex_unlock( &c->c_mutex );
326 
327 	return rc;
328 }
329 
txn_preop(Operation * op,SlapReply * rs)330 int txn_preop( Operation *op, SlapReply *rs )
331 {
332 	/* acquire connection lock */
333 	ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
334 	if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
335 		rs->sr_text = "invalid transaction identifier";
336 		rs->sr_err = LDAP_TXN_ID_INVALID;
337 		goto txnReturn;
338 	}
339 
340 	if( op->o_conn->c_txn_backend == NULL ) {
341 		op->o_conn->c_txn_backend = op->o_bd;
342 
343 	} else if( op->o_conn->c_txn_backend != op->o_bd ) {
344 		rs->sr_text = "transaction cannot span multiple database contexts";
345 		rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
346 		goto txnReturn;
347 	}
348 
349 	if ( !SLAP_TXNS( op->o_bd )) {
350 		rs->sr_text = "backend doesn't support transactions";
351 		rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
352 		goto txnReturn;
353 	}
354 
355 	/* insert operation into transaction */
356 	LDAP_STAILQ_REMOVE( &op->o_conn->c_ops, op, Operation, o_next );
357 	LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_txn_ops, op, o_next );
358 
359 txnReturn:
360 	/* release connection lock */
361 	ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
362 
363 	if ( op->o_tag != LDAP_REQ_EXTENDED )
364 		send_ldap_result( op, rs );
365 	if ( !rs->sr_err )
366 		rs->sr_err = LDAP_TXN_SPECIFY_OKAY;
367 	return rs->sr_err;
368 }
369