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
36typedef int (ldap_back_exop_f)( Operation *op, SlapReply *rs, ldapconn_t **lc );
37
38static ldap_back_exop_f ldap_back_exop_passwd;
39static ldap_back_exop_f ldap_back_exop_generic;
40
41static 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
49static int
50ldap_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
83done:;
84	if ( lc != NULL ) {
85		ldap_back_release_conn( li, lc );
86	}
87
88	return rc;
89}
90
91int
92ldap_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
114static int
115ldap_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
193retry:
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
305static int
306ldap_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
325retry:
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