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