xref: /netbsd-src/external/bsd/openldap/dist/servers/slapd/back-ldap/extended.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
1 /*	$NetBSD: extended.c,v 1.3 2021/08/14 16:14:59 christos Exp $	*/
2 
3 /* extended.c - ldap backend extended routines */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6  *
7  * Copyright 2003-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 /* ACKNOWLEDGEMENTS:
19  * This work was initially developed by the Howard Chu for inclusion
20  * in OpenLDAP Software and subsequently enhanced by Pierangelo
21  * Masarati.
22  */
23 
24 #include <sys/cdefs.h>
25 __RCSID("$NetBSD: extended.c,v 1.3 2021/08/14 16:14:59 christos Exp $");
26 
27 #include "portable.h"
28 
29 #include <stdio.h>
30 #include <ac/string.h>
31 
32 #include "slap.h"
33 #include "back-ldap.h"
34 #include "lber_pvt.h"
35 
36 typedef int (ldap_back_exop_f)( Operation *op, SlapReply *rs, ldapconn_t **lc );
37 
38 static ldap_back_exop_f ldap_back_exop_passwd;
39 static ldap_back_exop_f ldap_back_exop_generic;
40 
41 static struct exop {
42 	struct berval		oid;
43 	ldap_back_exop_f	*extended;
44 } exop_table[] = {
45 	{ BER_BVC(LDAP_EXOP_MODIFY_PASSWD),	ldap_back_exop_passwd },
46 	{ BER_BVNULL, NULL }
47 };
48 
49 static int
ldap_back_extended_one(Operation * op,SlapReply * rs,ldap_back_exop_f exop)50 ldap_back_extended_one( Operation *op, SlapReply *rs, ldap_back_exop_f exop )
51 {
52 	ldapinfo_t	*li = (ldapinfo_t *) op->o_bd->be_private;
53 
54 	ldapconn_t	*lc = NULL;
55 	LDAPControl	**ctrls = NULL, **oldctrls = NULL;
56 	int		rc;
57 
58 	/* FIXME: this needs to be called here, so it is
59 	 * called twice; maybe we could avoid the
60 	 * ldap_back_dobind() call inside each extended()
61 	 * call ... */
62 	if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
63 		return -1;
64 	}
65 
66 	ctrls = oldctrls = op->o_ctrls;
67 	if ( ldap_back_controls_add( op, rs, lc, &ctrls ) )
68 	{
69 		op->o_ctrls = oldctrls;
70 		send_ldap_extended( op, rs );
71 		rs->sr_text = NULL;
72 		/* otherwise frontend resends result */
73 		rc = rs->sr_err = SLAPD_ABANDON;
74 		goto done;
75 	}
76 
77 	op->o_ctrls = ctrls;
78 	rc = exop( op, rs, &lc );
79 
80 	op->o_ctrls = oldctrls;
81 	(void)ldap_back_controls_free( op, rs, &ctrls );
82 
83 done:;
84 	if ( lc != NULL ) {
85 		ldap_back_release_conn( li, lc );
86 	}
87 
88 	return rc;
89 }
90 
91 int
ldap_back_extended(Operation * op,SlapReply * rs)92 ldap_back_extended(
93 		Operation	*op,
94 		SlapReply	*rs )
95 {
96 	int	i;
97 
98 	RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
99 	rs->sr_flags &= ~REP_ENTRY_MASK;	/* paranoia */
100 
101 	for ( i = 0; exop_table[i].extended != NULL; i++ ) {
102 		if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) )
103 		{
104 			return ldap_back_extended_one( op, rs, exop_table[i].extended );
105 		}
106 	}
107 
108 	/* if we get here, the exop is known; the best that we can do
109 	 * is pass it thru as is */
110 	/* FIXME: maybe a list of OIDs to pass thru would be safer */
111 	return ldap_back_extended_one( op, rs, ldap_back_exop_generic );
112 }
113 
114 static int
ldap_back_exop_passwd(Operation * op,SlapReply * rs,ldapconn_t ** lcp)115 ldap_back_exop_passwd(
116 	Operation	*op,
117 	SlapReply	*rs,
118 	ldapconn_t	**lcp )
119 {
120 	ldapinfo_t	*li = (ldapinfo_t *) op->o_bd->be_private;
121 
122 	ldapconn_t	*lc = *lcp;
123 	req_pwdexop_s	*qpw = &op->oq_pwdexop;
124 	LDAPMessage	*res;
125 	ber_int_t	msgid;
126 	int		rc, isproxy, freedn = 0;
127 	int		do_retry = 1;
128 	char		*text = NULL;
129 	struct berval	dn = op->o_req_dn,
130 			ndn = op->o_req_ndn;
131 
132 	assert( lc != NULL );
133 	assert( rs->sr_ctrls == NULL );
134 
135 	if ( BER_BVISNULL( &ndn ) && op->ore_reqdata != NULL ) {
136 		/* NOTE: most of this code is mutated
137 		 * from slap_passwd_parse();
138 		 * But here we only need
139 		 * the first berval... */
140 
141 		ber_tag_t tag;
142 		ber_len_t len = -1;
143 		BerElementBuffer berbuf;
144 		BerElement *ber = (BerElement *)&berbuf;
145 
146 		struct berval	tmpid = BER_BVNULL;
147 
148 		if ( op->ore_reqdata->bv_len == 0 ) {
149 			return LDAP_PROTOCOL_ERROR;
150 		}
151 
152 		/* ber_init2 uses reqdata directly, doesn't allocate new buffers */
153 		ber_init2( ber, op->ore_reqdata, 0 );
154 
155 		tag = ber_scanf( ber, "{" /*}*/ );
156 
157 		if ( tag == LBER_ERROR ) {
158 			return LDAP_PROTOCOL_ERROR;
159 		}
160 
161 		tag = ber_peek_tag( ber, &len );
162 		if ( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_ID ) {
163 			tag = ber_get_stringbv( ber, &tmpid, LBER_BV_NOTERM );
164 
165 			if ( tag == LBER_ERROR ) {
166 				return LDAP_PROTOCOL_ERROR;
167 			}
168 		}
169 
170 		if ( !BER_BVISEMPTY( &tmpid ) ) {
171 			char idNull = tmpid.bv_val[tmpid.bv_len];
172 			tmpid.bv_val[tmpid.bv_len] = '\0';
173 			rs->sr_err = dnPrettyNormal( NULL, &tmpid, &dn,
174 				&ndn, op->o_tmpmemctx );
175 			tmpid.bv_val[tmpid.bv_len] = idNull;
176 			if ( rs->sr_err != LDAP_SUCCESS ) {
177 				/* should have been successfully parsed earlier! */
178 				return rs->sr_err;
179 			}
180 			freedn = 1;
181 
182 		} else {
183 			dn = op->o_dn;
184 			ndn = op->o_ndn;
185 		}
186 	}
187 
188 	isproxy = ber_bvcmp( &ndn, &op->o_ndn );
189 
190 	Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_passwd(\"%s\")%s\n",
191 		dn.bv_val, isproxy ? " (proxy)" : "" );
192 
193 retry:
194 	rc = ldap_passwd( lc->lc_ld,  &dn,
195 		qpw->rs_old.bv_val ? &qpw->rs_old : NULL,
196 		qpw->rs_new.bv_val ? &qpw->rs_new : NULL,
197 		op->o_ctrls, NULL, &msgid );
198 
199 	if ( rc == LDAP_SUCCESS ) {
200 		/* TODO: set timeout? */
201 		/* by now, make sure no timeout is used (ITS#6282) */
202 		struct timeval tv = { -1, 0 };
203 		if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) {
204 			ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc );
205 			rs->sr_err = rc;
206 
207 		} else {
208 			/* only touch when activity actually took place... */
209 			if ( li->li_idle_timeout ) {
210 				lc->lc_time = op->o_time;
211 			}
212 
213 			/* sigh. parse twice, because parse_passwd
214 			 * doesn't give us the err / match / msg info.
215 			 */
216 			rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err,
217 					(char **)&rs->sr_matched,
218 					&text,
219 					NULL, &rs->sr_ctrls, 0 );
220 
221 			if ( rc == LDAP_SUCCESS ) {
222 				if ( rs->sr_err == LDAP_SUCCESS ) {
223 					struct berval	newpw;
224 
225 					/* this never happens because
226 					 * the frontend	is generating
227 					 * the new password, so when
228 					 * the passwd exop is proxied,
229 					 * it never delegates password
230 					 * generation to the remote server
231 					 */
232 					rc = ldap_parse_passwd( lc->lc_ld, res,
233 							&newpw );
234 					if ( rc == LDAP_SUCCESS &&
235 							!BER_BVISNULL( &newpw ) )
236 					{
237 						rs->sr_type = REP_EXTENDED;
238 						rs->sr_rspdata = slap_passwd_return( &newpw );
239 						free( newpw.bv_val );
240 					}
241 
242 				} else {
243 					rc = rs->sr_err;
244 				}
245 			}
246 			ldap_msgfree( res );
247 		}
248 	}
249 
250 	if ( rc != LDAP_SUCCESS ) {
251 		rs->sr_err = slap_map_api2result( rs );
252 		if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) {
253 			do_retry = 0;
254 			if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
255 				goto retry;
256 			}
257 		}
258 
259 		if ( LDAP_BACK_QUARANTINE( li ) ) {
260 			ldap_back_quarantine( op, rs );
261 		}
262 
263 		if ( text ) rs->sr_text = text;
264 		send_ldap_extended( op, rs );
265 		/* otherwise frontend resends result */
266 		rc = rs->sr_err = SLAPD_ABANDON;
267 
268 	} else if ( LDAP_BACK_QUARANTINE( li ) ) {
269 		ldap_back_quarantine( op, rs );
270 	}
271 
272 	ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
273 	ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 );
274 	ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
275 
276 	if ( freedn ) {
277 		op->o_tmpfree( dn.bv_val, op->o_tmpmemctx );
278 		op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
279 	}
280 
281 	/* these have to be freed anyway... */
282 	if ( rs->sr_matched ) {
283 		free( (char *)rs->sr_matched );
284 		rs->sr_matched = NULL;
285 	}
286 
287 	if ( rs->sr_ctrls ) {
288 		ldap_controls_free( rs->sr_ctrls );
289 		rs->sr_ctrls = NULL;
290 	}
291 
292 	if ( text ) {
293 		free( text );
294 		rs->sr_text = NULL;
295 	}
296 
297 	/* in case, cleanup handler */
298 	if ( lc == NULL ) {
299 		*lcp = NULL;
300 	}
301 
302 	return rc;
303 }
304 
305 static int
ldap_back_exop_generic(Operation * op,SlapReply * rs,ldapconn_t ** lcp)306 ldap_back_exop_generic(
307 	Operation	*op,
308 	SlapReply	*rs,
309 	ldapconn_t	**lcp )
310 {
311 	ldapinfo_t	*li = (ldapinfo_t *) op->o_bd->be_private;
312 
313 	ldapconn_t	*lc = *lcp;
314 	LDAPMessage	*res;
315 	ber_int_t	msgid;
316 	int		rc;
317 	int		do_retry = 1;
318 	char		*text = NULL;
319 
320 	Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_generic(%s, \"%s\")\n",
321 		op->ore_reqoid.bv_val, op->o_req_dn.bv_val );
322 	assert( lc != NULL );
323 	assert( rs->sr_ctrls == NULL );
324 
325 retry:
326 	rc = ldap_extended_operation( lc->lc_ld,
327 		op->ore_reqoid.bv_val, op->ore_reqdata,
328 		op->o_ctrls, NULL, &msgid );
329 
330 	if ( rc == LDAP_SUCCESS ) {
331 		/* TODO: set timeout? */
332 		/* by now, make sure no timeout is used (ITS#6282) */
333 		struct timeval tv = { -1, 0 };
334 		if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) {
335 			ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc );
336 			rs->sr_err = rc;
337 
338 		} else {
339 			/* only touch when activity actually took place... */
340 			if ( li->li_idle_timeout ) {
341 				lc->lc_time = op->o_time;
342 			}
343 
344 			/* sigh. parse twice, because parse_passwd
345 			 * doesn't give us the err / match / msg info.
346 			 */
347 			rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err,
348 					(char **)&rs->sr_matched,
349 					&text,
350 					NULL, &rs->sr_ctrls, 0 );
351 			if ( rc == LDAP_SUCCESS ) {
352 				if ( rs->sr_err == LDAP_SUCCESS ) {
353 					rc = ldap_parse_extended_result( lc->lc_ld, res,
354 							(char **)&rs->sr_rspoid, &rs->sr_rspdata, 0 );
355 					if ( rc == LDAP_SUCCESS ) {
356 						rs->sr_type = REP_EXTENDED;
357 					}
358 
359 				} else {
360 					rc = rs->sr_err;
361 				}
362 			}
363 			ldap_msgfree( res );
364 		}
365 	}
366 
367 	if ( rc != LDAP_SUCCESS ) {
368 		rs->sr_err = slap_map_api2result( rs );
369 		if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) {
370 			do_retry = 0;
371 			if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
372 				goto retry;
373 			}
374 		}
375 
376 		if ( LDAP_BACK_QUARANTINE( li ) ) {
377 			ldap_back_quarantine( op, rs );
378 		}
379 
380 		if ( text ) rs->sr_text = text;
381 		send_ldap_extended( op, rs );
382 		/* otherwise frontend resends result */
383 		rc = rs->sr_err = SLAPD_ABANDON;
384 
385 	} else if ( LDAP_BACK_QUARANTINE( li ) ) {
386 		ldap_back_quarantine( op, rs );
387 	}
388 
389 	ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
390 	ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 );
391 	ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
392 
393 	/* these have to be freed anyway... */
394 	if ( rs->sr_matched ) {
395 		free( (char *)rs->sr_matched );
396 		rs->sr_matched = NULL;
397 	}
398 
399 	if ( rs->sr_ctrls ) {
400 		ldap_controls_free( rs->sr_ctrls );
401 		rs->sr_ctrls = NULL;
402 	}
403 
404 	if ( text ) {
405 		free( text );
406 		rs->sr_text = NULL;
407 	}
408 
409 	/* in case, cleanup handler */
410 	if ( lc == NULL ) {
411 		*lcp = NULL;
412 	}
413 
414 	return rc;
415 }
416