xref: /netbsd-src/external/bsd/openldap/dist/libraries/libldap/sasl.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: sasl.c,v 1.1.1.2 2010/03/08 02:14:20 lukem Exp $	*/
2 
3 /* OpenLDAP: pkg/ldap/libraries/libldap/sasl.c,v 1.64.2.7 2009/10/31 00:11:22 quanah Exp */
4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5  *
6  * Copyright 1998-2009 The OpenLDAP Foundation.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted only as authorized by the OpenLDAP
11  * Public License.
12  *
13  * A copy of this license is available in the file LICENSE in the
14  * top-level directory of the distribution or, alternatively, at
15  * <http://www.OpenLDAP.org/license.html>.
16  */
17 
18 /*
19  *	BindRequest ::= SEQUENCE {
20  *		version		INTEGER,
21  *		name		DistinguishedName,	 -- who
22  *		authentication	CHOICE {
23  *			simple		[0] OCTET STRING -- passwd
24  *			krbv42ldap	[1] OCTET STRING -- OBSOLETE
25  *			krbv42dsa	[2] OCTET STRING -- OBSOLETE
26  *			sasl		[3] SaslCredentials	-- LDAPv3
27  *		}
28  *	}
29  *
30  *	BindResponse ::= SEQUENCE {
31  *		COMPONENTS OF LDAPResult,
32  *		serverSaslCreds		OCTET STRING OPTIONAL -- LDAPv3
33  *	}
34  *
35  */
36 
37 #include "portable.h"
38 
39 #include <stdio.h>
40 
41 #include <ac/socket.h>
42 #include <ac/stdlib.h>
43 #include <ac/string.h>
44 #include <ac/time.h>
45 #include <ac/errno.h>
46 
47 #include "ldap-int.h"
48 
49 /*
50  * ldap_sasl_bind - bind to the ldap server (and X.500).
51  * The dn (usually NULL), mechanism, and credentials are provided.
52  * The message id of the request initiated is provided upon successful
53  * (LDAP_SUCCESS) return.
54  *
55  * Example:
56  *	ldap_sasl_bind( ld, NULL, "mechanism",
57  *		cred, NULL, NULL, &msgid )
58  */
59 
60 int
61 ldap_sasl_bind(
62 	LDAP			*ld,
63 	LDAP_CONST char	*dn,
64 	LDAP_CONST char	*mechanism,
65 	struct berval	*cred,
66 	LDAPControl		**sctrls,
67 	LDAPControl		**cctrls,
68 	int				*msgidp )
69 {
70 	BerElement	*ber;
71 	int rc;
72 	ber_int_t id;
73 
74 	Debug( LDAP_DEBUG_TRACE, "ldap_sasl_bind\n", 0, 0, 0 );
75 
76 	assert( ld != NULL );
77 	assert( LDAP_VALID( ld ) );
78 	assert( msgidp != NULL );
79 
80 	/* check client controls */
81 	rc = ldap_int_client_controls( ld, cctrls );
82 	if( rc != LDAP_SUCCESS ) return rc;
83 
84 	if( mechanism == LDAP_SASL_SIMPLE ) {
85 		if( dn == NULL && cred != NULL && cred->bv_len ) {
86 			/* use default binddn */
87 			dn = ld->ld_defbinddn;
88 		}
89 
90 	} else if( ld->ld_version < LDAP_VERSION3 ) {
91 		ld->ld_errno = LDAP_NOT_SUPPORTED;
92 		return ld->ld_errno;
93 	}
94 
95 	if ( dn == NULL ) {
96 		dn = "";
97 	}
98 
99 	/* create a message to send */
100 	if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) {
101 		ld->ld_errno = LDAP_NO_MEMORY;
102 		return ld->ld_errno;
103 	}
104 
105 	assert( LBER_VALID( ber ) );
106 
107 	LDAP_NEXT_MSGID( ld, id );
108 	if( mechanism == LDAP_SASL_SIMPLE ) {
109 		/* simple bind */
110 		rc = ber_printf( ber, "{it{istON}" /*}*/,
111 			id, LDAP_REQ_BIND,
112 			ld->ld_version, dn, LDAP_AUTH_SIMPLE,
113 			cred );
114 
115 	} else if ( cred == NULL || cred->bv_val == NULL ) {
116 		/* SASL bind w/o credentials */
117 		rc = ber_printf( ber, "{it{ist{sN}N}" /*}*/,
118 			id, LDAP_REQ_BIND,
119 			ld->ld_version, dn, LDAP_AUTH_SASL,
120 			mechanism );
121 
122 	} else {
123 		/* SASL bind w/ credentials */
124 		rc = ber_printf( ber, "{it{ist{sON}N}" /*}*/,
125 			id, LDAP_REQ_BIND,
126 			ld->ld_version, dn, LDAP_AUTH_SASL,
127 			mechanism, cred );
128 	}
129 
130 	if( rc == -1 ) {
131 		ld->ld_errno = LDAP_ENCODING_ERROR;
132 		ber_free( ber, 1 );
133 		return( -1 );
134 	}
135 
136 	/* Put Server Controls */
137 	if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) {
138 		ber_free( ber, 1 );
139 		return ld->ld_errno;
140 	}
141 
142 	if ( ber_printf( ber, /*{*/ "N}" ) == -1 ) {
143 		ld->ld_errno = LDAP_ENCODING_ERROR;
144 		ber_free( ber, 1 );
145 		return ld->ld_errno;
146 	}
147 
148 
149 	/* send the message */
150 	*msgidp = ldap_send_initial_request( ld, LDAP_REQ_BIND, dn, ber, id );
151 
152 	if(*msgidp < 0)
153 		return ld->ld_errno;
154 
155 	return LDAP_SUCCESS;
156 }
157 
158 
159 int
160 ldap_sasl_bind_s(
161 	LDAP			*ld,
162 	LDAP_CONST char	*dn,
163 	LDAP_CONST char	*mechanism,
164 	struct berval	*cred,
165 	LDAPControl		**sctrls,
166 	LDAPControl		**cctrls,
167 	struct berval	**servercredp )
168 {
169 	int	rc, msgid;
170 	LDAPMessage	*result;
171 	struct berval	*scredp = NULL;
172 
173 	Debug( LDAP_DEBUG_TRACE, "ldap_sasl_bind_s\n", 0, 0, 0 );
174 
175 	/* do a quick !LDAPv3 check... ldap_sasl_bind will do the rest. */
176 	if( servercredp != NULL ) {
177 		if (ld->ld_version < LDAP_VERSION3) {
178 			ld->ld_errno = LDAP_NOT_SUPPORTED;
179 			return ld->ld_errno;
180 		}
181 		*servercredp = NULL;
182 	}
183 
184 	rc = ldap_sasl_bind( ld, dn, mechanism, cred, sctrls, cctrls, &msgid );
185 
186 	if ( rc != LDAP_SUCCESS ) {
187 		return( rc );
188 	}
189 
190 #ifdef LDAP_CONNECTIONLESS
191 	if (LDAP_IS_UDP(ld)) {
192 		return( rc );
193 	}
194 #endif
195 
196 	if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1 || !result ) {
197 		return( ld->ld_errno );	/* ldap_result sets ld_errno */
198 	}
199 
200 	/* parse the results */
201 	scredp = NULL;
202 	if( servercredp != NULL ) {
203 		rc = ldap_parse_sasl_bind_result( ld, result, &scredp, 0 );
204 	}
205 
206 	if ( rc != LDAP_SUCCESS ) {
207 		ldap_msgfree( result );
208 		return( rc );
209 	}
210 
211 	rc = ldap_result2error( ld, result, 1 );
212 
213 	if ( rc == LDAP_SUCCESS || rc == LDAP_SASL_BIND_IN_PROGRESS ) {
214 		if( servercredp != NULL ) {
215 			*servercredp = scredp;
216 			scredp = NULL;
217 		}
218 	}
219 
220 	if ( scredp != NULL ) {
221 		ber_bvfree(scredp);
222 	}
223 
224 	return rc;
225 }
226 
227 
228 /*
229 * Parse BindResponse:
230 *
231 *   BindResponse ::= [APPLICATION 1] SEQUENCE {
232 *     COMPONENTS OF LDAPResult,
233 *     serverSaslCreds  [7] OCTET STRING OPTIONAL }
234 *
235 *   LDAPResult ::= SEQUENCE {
236 *     resultCode      ENUMERATED,
237 *     matchedDN       LDAPDN,
238 *     errorMessage    LDAPString,
239 *     referral        [3] Referral OPTIONAL }
240 */
241 
242 int
243 ldap_parse_sasl_bind_result(
244 	LDAP			*ld,
245 	LDAPMessage		*res,
246 	struct berval	**servercredp,
247 	int				freeit )
248 {
249 	ber_int_t errcode;
250 	struct berval* scred;
251 
252 	ber_tag_t tag;
253 	BerElement	*ber;
254 
255 	Debug( LDAP_DEBUG_TRACE, "ldap_parse_sasl_bind_result\n", 0, 0, 0 );
256 
257 	assert( ld != NULL );
258 	assert( LDAP_VALID( ld ) );
259 	assert( res != NULL );
260 
261 	if( servercredp != NULL ) {
262 		if( ld->ld_version < LDAP_VERSION2 ) {
263 			return LDAP_NOT_SUPPORTED;
264 		}
265 		*servercredp = NULL;
266 	}
267 
268 	if( res->lm_msgtype != LDAP_RES_BIND ) {
269 		ld->ld_errno = LDAP_PARAM_ERROR;
270 		return ld->ld_errno;
271 	}
272 
273 	scred = NULL;
274 
275 	if ( ld->ld_error ) {
276 		LDAP_FREE( ld->ld_error );
277 		ld->ld_error = NULL;
278 	}
279 	if ( ld->ld_matched ) {
280 		LDAP_FREE( ld->ld_matched );
281 		ld->ld_matched = NULL;
282 	}
283 
284 	/* parse results */
285 
286 	ber = ber_dup( res->lm_ber );
287 
288 	if( ber == NULL ) {
289 		ld->ld_errno = LDAP_NO_MEMORY;
290 		return ld->ld_errno;
291 	}
292 
293 	if ( ld->ld_version < LDAP_VERSION2 ) {
294 		tag = ber_scanf( ber, "{iA}",
295 			&errcode, &ld->ld_error );
296 
297 		if( tag == LBER_ERROR ) {
298 			ber_free( ber, 0 );
299 			ld->ld_errno = LDAP_DECODING_ERROR;
300 			return ld->ld_errno;
301 		}
302 
303 	} else {
304 		ber_len_t len;
305 
306 		tag = ber_scanf( ber, "{eAA" /*}*/,
307 			&errcode, &ld->ld_matched, &ld->ld_error );
308 
309 		if( tag == LBER_ERROR ) {
310 			ber_free( ber, 0 );
311 			ld->ld_errno = LDAP_DECODING_ERROR;
312 			return ld->ld_errno;
313 		}
314 
315 		tag = ber_peek_tag(ber, &len);
316 
317 		if( tag == LDAP_TAG_REFERRAL ) {
318 			/* skip 'em */
319 			if( ber_scanf( ber, "x" ) == LBER_ERROR ) {
320 				ber_free( ber, 0 );
321 				ld->ld_errno = LDAP_DECODING_ERROR;
322 				return ld->ld_errno;
323 			}
324 
325 			tag = ber_peek_tag(ber, &len);
326 		}
327 
328 		if( tag == LDAP_TAG_SASL_RES_CREDS ) {
329 			if( ber_scanf( ber, "O", &scred ) == LBER_ERROR ) {
330 				ber_free( ber, 0 );
331 				ld->ld_errno = LDAP_DECODING_ERROR;
332 				return ld->ld_errno;
333 			}
334 		}
335 	}
336 
337 	ber_free( ber, 0 );
338 
339 	if ( servercredp != NULL ) {
340 		*servercredp = scred;
341 
342 	} else if ( scred != NULL ) {
343 		ber_bvfree( scred );
344 	}
345 
346 	ld->ld_errno = errcode;
347 
348 	if ( freeit ) {
349 		ldap_msgfree( res );
350 	}
351 
352 	return( LDAP_SUCCESS );
353 }
354 
355 int
356 ldap_pvt_sasl_getmechs ( LDAP *ld, char **pmechlist )
357 {
358 	/* we need to query the server for supported mechs anyway */
359 	LDAPMessage *res, *e;
360 	char *attrs[] = { "supportedSASLMechanisms", NULL };
361 	char **values, *mechlist;
362 	int rc;
363 
364 	Debug( LDAP_DEBUG_TRACE, "ldap_pvt_sasl_getmech\n", 0, 0, 0 );
365 
366 	rc = ldap_search_s( ld, "", LDAP_SCOPE_BASE,
367 		NULL, attrs, 0, &res );
368 
369 	if ( rc != LDAP_SUCCESS ) {
370 		return ld->ld_errno;
371 	}
372 
373 	e = ldap_first_entry( ld, res );
374 	if ( e == NULL ) {
375 		ldap_msgfree( res );
376 		if ( ld->ld_errno == LDAP_SUCCESS ) {
377 			ld->ld_errno = LDAP_NO_SUCH_OBJECT;
378 		}
379 		return ld->ld_errno;
380 	}
381 
382 	values = ldap_get_values( ld, e, "supportedSASLMechanisms" );
383 	if ( values == NULL ) {
384 		ldap_msgfree( res );
385 		ld->ld_errno = LDAP_NO_SUCH_ATTRIBUTE;
386 		return ld->ld_errno;
387 	}
388 
389 	mechlist = ldap_charray2str( values, " " );
390 	if ( mechlist == NULL ) {
391 		LDAP_VFREE( values );
392 		ldap_msgfree( res );
393 		ld->ld_errno = LDAP_NO_MEMORY;
394 		return ld->ld_errno;
395 	}
396 
397 	LDAP_VFREE( values );
398 	ldap_msgfree( res );
399 
400 	*pmechlist = mechlist;
401 
402 	return LDAP_SUCCESS;
403 }
404 
405 /*
406  * ldap_sasl_interactive_bind_s - interactive SASL authentication
407  *
408  * This routine uses interactive callbacks.
409  *
410  * LDAP_SUCCESS is returned upon success, the ldap error code
411  * otherwise.
412  */
413 int
414 ldap_sasl_interactive_bind_s(
415 	LDAP *ld,
416 	LDAP_CONST char *dn, /* usually NULL */
417 	LDAP_CONST char *mechs,
418 	LDAPControl **serverControls,
419 	LDAPControl **clientControls,
420 	unsigned flags,
421 	LDAP_SASL_INTERACT_PROC *interact,
422 	void *defaults )
423 {
424 	int rc;
425 	char *smechs = NULL;
426 
427 #if defined( LDAP_R_COMPILE ) && defined( HAVE_CYRUS_SASL )
428 	ldap_pvt_thread_mutex_lock( &ldap_int_sasl_mutex );
429 #endif
430 #ifdef LDAP_CONNECTIONLESS
431 	if( LDAP_IS_UDP(ld) ) {
432 		/* Just force it to simple bind, silly to make the user
433 		 * ask all the time. No, we don't ever actually bind, but I'll
434 		 * let the final bind handler take care of saving the cdn.
435 		 */
436 		rc = ldap_simple_bind( ld, dn, NULL );
437 		rc = rc < 0 ? rc : 0;
438 		goto done;
439 	} else
440 #endif
441 
442 #ifdef HAVE_CYRUS_SASL
443 	if( mechs == NULL || *mechs == '\0' ) {
444 		mechs = ld->ld_options.ldo_def_sasl_mech;
445 	}
446 #endif
447 
448 	if( mechs == NULL || *mechs == '\0' ) {
449 		rc = ldap_pvt_sasl_getmechs( ld, &smechs );
450 		if( rc != LDAP_SUCCESS ) {
451 			goto done;
452 		}
453 
454 		Debug( LDAP_DEBUG_TRACE,
455 			"ldap_sasl_interactive_bind_s: server supports: %s\n",
456 			smechs, 0, 0 );
457 
458 		mechs = smechs;
459 
460 	} else {
461 		Debug( LDAP_DEBUG_TRACE,
462 			"ldap_sasl_interactive_bind_s: user selected: %s\n",
463 			mechs, 0, 0 );
464 	}
465 
466 	rc = ldap_int_sasl_bind( ld, dn, mechs,
467 		serverControls, clientControls,
468 		flags, interact, defaults );
469 
470 done:
471 #if defined( LDAP_R_COMPILE ) && defined( HAVE_CYRUS_SASL )
472 	ldap_pvt_thread_mutex_unlock( &ldap_int_sasl_mutex );
473 #endif
474 	if ( smechs ) LDAP_FREE( smechs );
475 
476 	return rc;
477 }
478 
479 #ifdef HAVE_CYRUS_SASL
480 
481 #ifdef HAVE_SASL_SASL_H
482 #include <sasl/sasl.h>
483 #else
484 #include <sasl.h>
485 #endif
486 
487 #endif /* HAVE_CYRUS_SASL */
488 
489 static int
490 sb_sasl_generic_remove( Sockbuf_IO_Desc *sbiod );
491 
492 static int
493 sb_sasl_generic_setup( Sockbuf_IO_Desc *sbiod, void *arg )
494 {
495 	struct sb_sasl_generic_data	*p;
496 	struct sb_sasl_generic_install	*i;
497 
498 	assert( sbiod != NULL );
499 
500 	i = (struct sb_sasl_generic_install *)arg;
501 
502 	p = LBER_MALLOC( sizeof( *p ) );
503 	if ( p == NULL )
504 		return -1;
505 	p->ops = i->ops;
506 	p->ops_private = i->ops_private;
507 	p->sbiod = sbiod;
508 	p->flags = 0;
509 	ber_pvt_sb_buf_init( &p->sec_buf_in );
510 	ber_pvt_sb_buf_init( &p->buf_in );
511 	ber_pvt_sb_buf_init( &p->buf_out );
512 
513 	sbiod->sbiod_pvt = p;
514 
515 	p->ops->init( p, &p->min_send, &p->max_send, &p->max_recv );
516 
517 	if ( ber_pvt_sb_grow_buffer( &p->sec_buf_in, p->min_send ) < 0 ) {
518 		sb_sasl_generic_remove( sbiod );
519 		sock_errset(ENOMEM);
520 		return -1;
521 	}
522 
523 	return 0;
524 }
525 
526 static int
527 sb_sasl_generic_remove( Sockbuf_IO_Desc *sbiod )
528 {
529 	struct sb_sasl_generic_data	*p;
530 
531 	assert( sbiod != NULL );
532 
533 	p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt;
534 
535 	p->ops->fini(p);
536 
537 	ber_pvt_sb_buf_destroy( &p->sec_buf_in );
538 	ber_pvt_sb_buf_destroy( &p->buf_in );
539 	ber_pvt_sb_buf_destroy( &p->buf_out );
540 	LBER_FREE( p );
541 	sbiod->sbiod_pvt = NULL;
542 	return 0;
543 }
544 
545 static ber_len_t
546 sb_sasl_generic_pkt_length(
547 	struct sb_sasl_generic_data *p,
548 	const unsigned char *buf,
549 	int debuglevel )
550 {
551 	ber_len_t		size;
552 
553 	assert( buf != NULL );
554 
555 	size = buf[0] << 24
556 		| buf[1] << 16
557 		| buf[2] << 8
558 		| buf[3];
559 
560 	if ( size > p->max_recv ) {
561 		/* somebody is trying to mess me up. */
562 		ber_log_printf( LDAP_DEBUG_ANY, debuglevel,
563 			"sb_sasl_generic_pkt_length: "
564 			"received illegal packet length of %lu bytes\n",
565 			(unsigned long)size );
566 		size = 16; /* this should lead to an error. */
567 	}
568 
569 	return size + 4; /* include the size !!! */
570 }
571 
572 /* Drop a processed packet from the input buffer */
573 static void
574 sb_sasl_generic_drop_packet (
575 	struct sb_sasl_generic_data *p,
576 	int debuglevel )
577 {
578 	ber_slen_t			len;
579 
580 	len = p->sec_buf_in.buf_ptr - p->sec_buf_in.buf_end;
581 	if ( len > 0 )
582 		AC_MEMCPY( p->sec_buf_in.buf_base, p->sec_buf_in.buf_base +
583 			p->sec_buf_in.buf_end, len );
584 
585 	if ( len >= 4 ) {
586 		p->sec_buf_in.buf_end = sb_sasl_generic_pkt_length(p,
587 			(unsigned char *) p->sec_buf_in.buf_base, debuglevel);
588 	}
589 	else {
590 		p->sec_buf_in.buf_end = 0;
591 	}
592 	p->sec_buf_in.buf_ptr = len;
593 }
594 
595 static ber_slen_t
596 sb_sasl_generic_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
597 {
598 	struct sb_sasl_generic_data	*p;
599 	ber_slen_t			ret, bufptr;
600 
601 	assert( sbiod != NULL );
602 	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
603 
604 	p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt;
605 
606 	/* Are there anything left in the buffer? */
607 	ret = ber_pvt_sb_copy_out( &p->buf_in, buf, len );
608 	bufptr = ret;
609 	len -= ret;
610 
611 	if ( len == 0 )
612 		return bufptr;
613 
614 	p->ops->reset_buf( p, &p->buf_in );
615 
616 	/* Read the length of the packet */
617 	while ( p->sec_buf_in.buf_ptr < 4 ) {
618 		ret = LBER_SBIOD_READ_NEXT( sbiod, p->sec_buf_in.buf_base +
619 			p->sec_buf_in.buf_ptr,
620 			4 - p->sec_buf_in.buf_ptr );
621 #ifdef EINTR
622 		if ( ( ret < 0 ) && ( errno == EINTR ) )
623 			continue;
624 #endif
625 		if ( ret <= 0 )
626 			return bufptr ? bufptr : ret;
627 
628 		p->sec_buf_in.buf_ptr += ret;
629 	}
630 
631 	/* The new packet always starts at p->sec_buf_in.buf_base */
632 	ret = sb_sasl_generic_pkt_length(p, (unsigned char *) p->sec_buf_in.buf_base,
633 		sbiod->sbiod_sb->sb_debug );
634 
635 	/* Grow the packet buffer if neccessary */
636 	if ( ( p->sec_buf_in.buf_size < (ber_len_t) ret ) &&
637 		ber_pvt_sb_grow_buffer( &p->sec_buf_in, ret ) < 0 )
638 	{
639 		sock_errset(ENOMEM);
640 		return -1;
641 	}
642 	p->sec_buf_in.buf_end = ret;
643 
644 	/* Did we read the whole encrypted packet? */
645 	while ( p->sec_buf_in.buf_ptr < p->sec_buf_in.buf_end ) {
646 		/* No, we have got only a part of it */
647 		ret = p->sec_buf_in.buf_end - p->sec_buf_in.buf_ptr;
648 
649 		ret = LBER_SBIOD_READ_NEXT( sbiod, p->sec_buf_in.buf_base +
650 			p->sec_buf_in.buf_ptr, ret );
651 #ifdef EINTR
652 		if ( ( ret < 0 ) && ( errno == EINTR ) )
653 			continue;
654 #endif
655 		if ( ret <= 0 )
656 			return bufptr ? bufptr : ret;
657 
658 		p->sec_buf_in.buf_ptr += ret;
659    	}
660 
661 	/* Decode the packet */
662 	ret = p->ops->decode( p, &p->sec_buf_in, &p->buf_in );
663 
664 	/* Drop the packet from the input buffer */
665 	sb_sasl_generic_drop_packet( p, sbiod->sbiod_sb->sb_debug );
666 
667 	if ( ret != 0 ) {
668 		ber_log_printf( LDAP_DEBUG_ANY, sbiod->sbiod_sb->sb_debug,
669 			"sb_sasl_generic_read: failed to decode packet\n" );
670 		sock_errset(EIO);
671 		return -1;
672 	}
673 
674 	bufptr += ber_pvt_sb_copy_out( &p->buf_in, (char*) buf + bufptr, len );
675 
676 	return bufptr;
677 }
678 
679 static ber_slen_t
680 sb_sasl_generic_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
681 {
682 	struct sb_sasl_generic_data	*p;
683 	int				ret;
684 	ber_len_t			len2;
685 
686 	assert( sbiod != NULL );
687 	assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
688 
689 	p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt;
690 
691 	/* Is there anything left in the buffer? */
692 	if ( p->buf_out.buf_ptr != p->buf_out.buf_end ) {
693 		ret = ber_pvt_sb_do_write( sbiod, &p->buf_out );
694 		if ( ret < 0 ) return ret;
695 
696 		/* Still have something left?? */
697 		if ( p->buf_out.buf_ptr != p->buf_out.buf_end ) {
698 			sock_errset(EAGAIN);
699 			return -1;
700 		}
701 	}
702 
703 	len2 = p->max_send - 100;	/* For safety margin */
704 	len2 = len > len2 ? len2 : len;
705 
706 	/* If we're just retrying a partial write, tell the
707 	 * caller it's done. Let them call again if there's
708 	 * still more left to write.
709 	 */
710 	if ( p->flags & LDAP_PVT_SASL_PARTIAL_WRITE ) {
711 		p->flags ^= LDAP_PVT_SASL_PARTIAL_WRITE;
712 		return len2;
713 	}
714 
715 	/* now encode the next packet. */
716 	p->ops->reset_buf( p, &p->buf_out );
717 
718 	ret = p->ops->encode( p, buf, len2, &p->buf_out );
719 
720 	if ( ret != 0 ) {
721 		ber_log_printf( LDAP_DEBUG_ANY, sbiod->sbiod_sb->sb_debug,
722 			"sb_sasl_generic_write: failed to encode packet\n" );
723 		sock_errset(EIO);
724 		return -1;
725 	}
726 
727 	ret = ber_pvt_sb_do_write( sbiod, &p->buf_out );
728 
729 	if ( ret < 0 ) {
730 		/* error? */
731 		int err = sock_errno();
732 		/* caller can retry this */
733 		if ( err == EAGAIN || err == EWOULDBLOCK || err == EINTR )
734 			p->flags |= LDAP_PVT_SASL_PARTIAL_WRITE;
735 		return ret;
736 	} else if ( p->buf_out.buf_ptr != p->buf_out.buf_end ) {
737 		/* partial write? pretend nothing got written */
738 		len2 = 0;
739 		p->flags |= LDAP_PVT_SASL_PARTIAL_WRITE;
740 	}
741 
742 	/* return number of bytes encoded, not written, to ensure
743 	 * no byte is encoded twice (even if only sent once).
744 	 */
745 	return len2;
746 }
747 
748 static int
749 sb_sasl_generic_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg )
750 {
751 	struct sb_sasl_generic_data	*p;
752 
753 	p = (struct sb_sasl_generic_data *)sbiod->sbiod_pvt;
754 
755 	if ( opt == LBER_SB_OPT_DATA_READY ) {
756 		if ( p->buf_in.buf_ptr != p->buf_in.buf_end ) return 1;
757 	}
758 
759 	return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg );
760 }
761 
762 Sockbuf_IO ldap_pvt_sockbuf_io_sasl_generic = {
763 	sb_sasl_generic_setup,		/* sbi_setup */
764 	sb_sasl_generic_remove,		/* sbi_remove */
765 	sb_sasl_generic_ctrl,		/* sbi_ctrl */
766 	sb_sasl_generic_read,		/* sbi_read */
767 	sb_sasl_generic_write,		/* sbi_write */
768 	NULL			/* sbi_close */
769 };
770 
771 int ldap_pvt_sasl_generic_install(
772 	Sockbuf *sb,
773 	struct sb_sasl_generic_install *install_arg )
774 {
775 	Debug( LDAP_DEBUG_TRACE, "ldap_pvt_sasl_generic_install\n",
776 		0, 0, 0 );
777 
778 	/* don't install the stuff unless security has been negotiated */
779 
780 	if ( !ber_sockbuf_ctrl( sb, LBER_SB_OPT_HAS_IO,
781 			&ldap_pvt_sockbuf_io_sasl_generic ) )
782 	{
783 #ifdef LDAP_DEBUG
784 		ber_sockbuf_add_io( sb, &ber_sockbuf_io_debug,
785 			LBER_SBIOD_LEVEL_APPLICATION, (void *)"sasl_generic_" );
786 #endif
787 		ber_sockbuf_add_io( sb, &ldap_pvt_sockbuf_io_sasl_generic,
788 			LBER_SBIOD_LEVEL_APPLICATION, install_arg );
789 	}
790 
791 	return LDAP_SUCCESS;
792 }
793 
794 void ldap_pvt_sasl_generic_remove( Sockbuf *sb )
795 {
796 	ber_sockbuf_remove_io( sb, &ldap_pvt_sockbuf_io_sasl_generic,
797 		LBER_SBIOD_LEVEL_APPLICATION );
798 #ifdef LDAP_DEBUG
799 	ber_sockbuf_remove_io( sb, &ber_sockbuf_io_debug,
800 		LBER_SBIOD_LEVEL_APPLICATION );
801 #endif
802 }
803