1/*	$NetBSD: bind.c,v 1.2 2021/08/14 16:14:58 christos Exp $	*/
2
3/* $OpenLDAP$ */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1998-2021 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#include <sys/cdefs.h>
19__RCSID("$NetBSD: bind.c,v 1.2 2021/08/14 16:14:58 christos Exp $");
20
21#include "portable.h"
22
23#include <ac/socket.h>
24#include <ac/errno.h>
25#include <ac/string.h>
26#include <ac/time.h>
27#include <ac/unistd.h>
28
29#include "lutil.h"
30#include "lload.h"
31
32struct berval mech_external = BER_BVC("EXTERNAL");
33
34int
35bind_mech_external(
36        LloadConnection *client,
37        LloadOperation *op,
38        struct berval *credentials )
39{
40    BerValue binddn;
41    void *ssl;
42    char *ptr, *message = "";
43    int result = LDAP_SUCCESS;
44
45    CONNECTION_ASSERT_LOCKED(client);
46    client->c_state = LLOAD_C_READY;
47    client->c_type = LLOAD_C_OPEN;
48
49    op->o_res = LLOAD_OP_COMPLETED;
50
51    /*
52     * We only support implicit assertion.
53     *
54     * Although RFC 4513 says the credentials field must be missing, RFC 4422
55     * doesn't and libsasl2 will pass a zero-length string to send. We have to
56     * allow that.
57     */
58    if ( !BER_BVISEMPTY( credentials ) ) {
59        result = LDAP_UNWILLING_TO_PERFORM;
60        message = "proxy authorization is not supported";
61        goto done;
62    }
63
64#ifdef HAVE_TLS
65    ssl = ldap_pvt_tls_sb_ctx( client->c_sb );
66    if ( !ssl || ldap_pvt_tls_get_peer_dn( ssl, &binddn, NULL, 0 ) ) {
67        result = LDAP_INVALID_CREDENTIALS;
68        message = "no externally negotiated identity";
69        goto done;
70    }
71    client->c_auth.bv_len = binddn.bv_len + STRLENOF("dn:");
72    client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 );
73
74    ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" );
75    ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len );
76    *ptr = '\0';
77
78    ber_memfree( binddn.bv_val );
79
80    if ( !ber_bvstrcasecmp( &client->c_auth, &lloadd_identity ) ) {
81        client->c_type = LLOAD_C_PRIVILEGED;
82    }
83#else /* ! HAVE_TLS */
84    result = LDAP_AUTH_METHOD_NOT_SUPPORTED;
85    message = "requested SASL mechanism not supported";
86#endif /* ! HAVE_TLS */
87
88done:
89    CONNECTION_UNLOCK(client);
90    operation_send_reject( op, result, message, 1 );
91    return LDAP_SUCCESS;
92}
93
94static int
95client_bind(
96        LloadOperation *op,
97        LloadConnection *upstream,
98        struct berval *binddn,
99        ber_tag_t tag,
100        struct berval *auth )
101{
102    ber_printf( upstream->c_pendingber, "t{titOtO}", LDAP_TAG_MESSAGE,
103            LDAP_TAG_MSGID, op->o_upstream_msgid,
104            LDAP_REQ_BIND, &op->o_request,
105            LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) );
106
107    return 0;
108}
109
110#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
111static int
112client_bind_as_vc(
113        LloadOperation *op,
114        LloadConnection *upstream,
115        struct berval *binddn,
116        ber_tag_t tag,
117        struct berval *auth )
118{
119    CONNECTION_LOCK(upstream);
120    ber_printf( upstream->c_pendingber, "t{tit{tst{{tOOtOtO}}}}", LDAP_TAG_MESSAGE,
121            LDAP_TAG_MSGID, op->o_upstream_msgid,
122            LDAP_REQ_EXTENDED,
123            LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_VERIFY_CREDENTIALS,
124            LDAP_TAG_EXOP_REQ_VALUE,
125            LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE, BER_BV_OPTIONAL( &upstream->c_vc_cookie ),
126            &binddn, tag, &auth,
127            LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) );
128    CONNECTION_UNLOCK(upstream);
129    return 0;
130}
131#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
132
133/*
134 * The client connection can be in the following states:
135 * 1) there are between zero and many non-bind operations pending
136 *    client->c_state == LLOAD_C_READY && client->c_pin_id == 0
137 * 2) there is one bind operation pending (waiting on an upstream response)
138 *    a) It is a simple bind
139 *    b) It is a SASL bind
140 * 3) there is one SASL bind in progress (received a LDAP_SASL_BIND_IN_PROGRESS
141 *    response)
142 *
143 * In cases 2 and 3, client->c_state == LLOAD_C_BINDING, a SASL bind is in
144 * progress/pending if c_sasl_bind_mech is set.
145 *
146 * In the first case, client_reset abandons all operations on the respective
147 * upstreams, case 2a has client_reset send an anonymous bind to upstream to
148 * terminate the bind. In cases 2b and 3, c_pin_id is set and we retrieve the
149 * op. The rest is the same for both.
150 *
151 * If c_pin_id is unset, we request an upstream connection assigned, otherwise,
152 * we try to reuse the pinned upstream. In the case of no upstream, we reject
153 * the request. A SASL bind request means we acquire a new pin_id if we don't
154 * have one already.
155 *
156 * We have to reset c_auth (which holds the current or pending identity) and
157 * make sure we set it up eventually:
158 * - In the case of a simple bind, we already know the final identity being
159 *   requested so we set it up immediately
160 * - In SASL binds, for mechanisms we implement ourselves (EXTERNAL), we set it
161 *   up at some point
162 * - Otherwise, we have to ask the upstream what it thinks as the bind
163 *   succeeds, we send an LDAP "Who Am I?" exop, this is one of the few
164 *   requests we send on our own. If we implement the mechanism, we provide the
165 *   identity (EXTERNAL uses the client certificate DN)
166 *
167 * At the end of the request processing, if nothing goes wrong, we're in state
168 * 2b (with c_pin_id set to the op's o_pin_id), or state 2a (we could reset
169 * c_pin_id/o_pin_id if we wanted but we don't always do that at the moment).
170 * If something does go wrong, we're either tearing down the client or we
171 * reject the request and switch to state 1 (clearing c_pin_id).
172 *
173 * As usual, we have to make any changes to the target connection before we've
174 * sent the PDU over it - while we are in charge of the read side and nothing
175 * happens there without our ceding control, the other read side could wake up
176 * at any time and preempt us.
177 *
178 * On a response (in handle_bind_response):
179 * - to a simple bind, clear c_auth on a failure otherwise keep it while we
180 *   just reset the client to state 1
181 * - failure response to a SASL bind - reset client to state 1
182 * - LDAP_SASL_BIND_IN_PROGRESS - clear o_*_msgid from the op (have to
183 *   remove+reinsert it from the respective c_ops!), we need it since it is the
184 *   vessel maintaining the pin between client and upstream
185 * - all of the above forward the response immediately
186 * - LDAP_SUCCESS for a SASL bind - we send a "Who Am I?" request to retrieve
187 *   the client's DN, only on receiving the response do we finalise the
188 *   exchange by forwarding the successful bind response
189 *
190 * We can't do the same for VC Exop since the exchange is finished at the end
191 * and we need a change to the VC Exop spec to have the server (optionally?)
192 * respond with the final authzid (saving us a roundtrip as well).
193 */
194int
195request_bind( LloadConnection *client, LloadOperation *op )
196{
197    LloadConnection *upstream = NULL;
198    BerElement *ber, *copy;
199    struct berval binddn, auth, mech = BER_BVNULL;
200    ber_int_t version;
201    ber_tag_t tag;
202    unsigned long pin;
203    int res, rc = LDAP_SUCCESS;
204
205    CONNECTION_LOCK(client);
206    pin = client->c_pin_id;
207
208    if ( pin ) {
209        LloadOperation *pinned_op, needle = {
210            .o_client_connid = client->c_connid,
211            .o_client_msgid = 0,
212            .o_pin_id = client->c_pin_id,
213        };
214
215        Debug( LDAP_DEBUG_CONNS, "request_bind: "
216                "client connid=%lu is pinned pin=%lu\n",
217                client->c_connid, pin );
218
219        pinned_op =
220                ldap_tavl_delete( &client->c_ops, &needle, operation_client_cmp );
221        if ( pinned_op ) {
222            assert( op->o_tag == pinned_op->o_tag );
223
224            pinned_op->o_client_msgid = op->o_client_msgid;
225
226            /* Preserve the new BerElement and its pointers, reclaim the old
227             * one in operation_destroy_from_client if it's still there */
228            needle.o_ber = pinned_op->o_ber;
229            pinned_op->o_ber = op->o_ber;
230            op->o_ber = needle.o_ber;
231
232            pinned_op->o_request = op->o_request;
233            pinned_op->o_ctrls = op->o_ctrls;
234
235            /* No one has seen this operation yet, plant the pin back in its stead */
236            client->c_n_ops_executing--;
237            op->o_res = LLOAD_OP_COMPLETED;
238            ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
239            op->o_client = NULL;
240            assert( op->o_upstream == NULL );
241
242            rc = ldap_tavl_insert( &client->c_ops, pinned_op, operation_client_cmp,
243                    ldap_avl_dup_error );
244            assert( rc == LDAP_SUCCESS );
245
246            /* No one has seen this operation yet */
247            op->o_refcnt--;
248            operation_destroy( op );
249
250            /* We didn't start a new operation, just continuing an existing one */
251            lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_received--;
252
253            op = pinned_op;
254        }
255    }
256
257    ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
258    client->c_n_ops_executing--;
259
260    client_reset( client );
261
262    client->c_state = LLOAD_C_BINDING;
263    client->c_type = LLOAD_C_OPEN;
264
265    if ( (copy = ber_alloc()) == NULL ) {
266        goto fail;
267    }
268    ber_init2( copy, &op->o_request, 0 );
269
270    tag = ber_get_int( copy, &version );
271    if ( tag == LBER_ERROR ) {
272        Debug( LDAP_DEBUG_PACKETS, "request_bind: "
273                "failed to parse version field\n" );
274        goto fail;
275    } else if ( version != LDAP_VERSION3 ) {
276        CONNECTION_UNLOCK(client);
277        operation_send_reject(
278                op, LDAP_PROTOCOL_ERROR, "LDAP version unsupported", 1 );
279        CONNECTION_LOCK(client);
280        goto fail;
281    }
282
283    tag = ber_get_stringbv( copy, &binddn, LBER_BV_NOTERM );
284    if ( tag == LBER_ERROR ) {
285        Debug( LDAP_DEBUG_PACKETS, "request_bind: "
286                "failed to parse bind name field\n" );
287        goto fail;
288    }
289
290    if ( !BER_BVISNULL( &client->c_auth ) ) {
291        ch_free( client->c_auth.bv_val );
292        BER_BVZERO( &client->c_auth );
293    }
294
295    tag = ber_skip_element( copy, &auth );
296    if ( tag == LDAP_AUTH_SIMPLE ) {
297        if ( !BER_BVISEMPTY( &binddn ) ) {
298            char *ptr;
299            client->c_auth.bv_len = STRLENOF("dn:") + binddn.bv_len;
300            client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 );
301
302            ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" );
303            ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len );
304            *ptr = '\0';
305        }
306
307        if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
308            ber_memfree( client->c_sasl_bind_mech.bv_val );
309            BER_BVZERO( &client->c_sasl_bind_mech );
310        }
311    } else if ( tag == LDAP_AUTH_SASL ) {
312        ber_init2( copy, &auth, 0 );
313
314        if ( ber_get_stringbv( copy, &mech, LBER_BV_NOTERM ) == LBER_ERROR ) {
315            goto fail;
316        }
317        if ( !ber_bvcmp( &mech, &mech_external ) ) {
318            struct berval credentials = BER_BVNULL;
319
320            ber_get_stringbv( copy, &credentials, LBER_BV_NOTERM );
321            rc = bind_mech_external( client, op, &credentials );
322
323            /* terminate the upstream side if client switched mechanisms */
324            if ( pin ) {
325                operation_abandon( op );
326            }
327
328            ber_free( copy, 0 );
329            return rc;
330        } else if ( BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
331            ber_dupbv( &client->c_sasl_bind_mech, &mech );
332        } else if ( ber_bvcmp( &mech, &client->c_sasl_bind_mech ) ) {
333            ber_bvreplace( &client->c_sasl_bind_mech, &mech );
334        }
335    } else {
336        goto fail;
337    }
338
339    rc = ldap_tavl_insert( &client->c_ops, op, operation_client_cmp, ldap_avl_dup_error );
340    assert( rc == LDAP_SUCCESS );
341    client->c_n_ops_executing++;
342    CONNECTION_UNLOCK(client);
343
344    if ( pin ) {
345        checked_lock( &op->o_link_mutex );
346        upstream = op->o_upstream;
347        checked_unlock( &op->o_link_mutex );
348
349        if ( upstream ) {
350            checked_lock( &upstream->c_io_mutex );
351            CONNECTION_LOCK(upstream);
352            if ( !IS_ALIVE( upstream, c_live ) ) {
353                CONNECTION_UNLOCK(upstream);
354                checked_unlock( &upstream->c_io_mutex );
355                upstream = NULL;
356            }
357        }
358    }
359
360    /* If we were pinned but lost the link, don't look for a new upstream, we
361     * have to reject the op and clear pin */
362    if ( upstream ) {
363        /* No need to do anything */
364    } else if ( !pin ) {
365        upstream = backend_select( op, &res );
366    } else {
367        Debug( LDAP_DEBUG_STATS, "request_bind: "
368                "connid=%lu, msgid=%d pinned upstream lost\n",
369                op->o_client_connid, op->o_client_msgid );
370        operation_send_reject( op, LDAP_OTHER,
371                "connection to the remote server has been severed", 1 );
372        pin = 0;
373        goto done;
374    }
375
376    if ( !upstream ) {
377        Debug( LDAP_DEBUG_STATS, "request_bind: "
378                "connid=%lu, msgid=%d no available connection found\n",
379                op->o_client_connid, op->o_client_msgid );
380        operation_send_reject( op, res, "no connections available", 1 );
381        assert( client->c_pin_id == 0 );
382        goto done;
383    }
384    assert_locked( &upstream->c_io_mutex );
385    /*
386     * At this point, either:
387     * - upstream is READY and pin == 0
388     * - upstream is BINDING, pin != 0 and op->o_upstream_msgid == 0
389     *
390     * A pinned upstream we marked for closing at some point ago should have
391     * closed by now.
392     */
393
394    ber = upstream->c_pendingber;
395    if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
396        checked_unlock( &upstream->c_io_mutex );
397        if ( !pin ) {
398            LloadBackend *b = upstream->c_backend;
399
400            upstream->c_n_ops_executing--;
401            CONNECTION_UNLOCK(upstream);
402
403            checked_lock( &b->b_mutex );
404            b->b_n_ops_executing--;
405            operation_update_backend_counters( op, b );
406            checked_unlock( &b->b_mutex );
407        } else {
408            CONNECTION_UNLOCK(upstream);
409        }
410
411        Debug( LDAP_DEBUG_ANY, "request_bind: "
412                "ber_alloc failed\n" );
413
414        operation_unlink( op );
415
416        CONNECTION_LOCK(client);
417        goto fail;
418    }
419    upstream->c_pendingber = ber;
420
421    if ( !pin ) {
422        lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_forwarded++;
423    }
424
425    if ( pin ) {
426        ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
427        if ( tag == LDAP_AUTH_SIMPLE ) {
428            pin = op->o_pin_id = 0;
429        }
430    } else if ( tag == LDAP_AUTH_SASL && !op->o_pin_id ) {
431        checked_lock( &lload_pin_mutex );
432        pin = op->o_pin_id = lload_next_pin++;
433        Debug( LDAP_DEBUG_CONNS, "request_bind: "
434                "client connid=%lu allocated pin=%lu linking it to upstream "
435                "connid=%lu\n",
436                op->o_client_connid, pin, upstream->c_connid );
437        checked_unlock( &lload_pin_mutex );
438    }
439
440    op->o_upstream = upstream;
441    op->o_upstream_connid = upstream->c_connid;
442    op->o_upstream_msgid = upstream->c_next_msgid++;
443    op->o_res = LLOAD_OP_FAILED;
444
445    /* Was it unlinked in the meantime? No need to send a response since the
446     * client is dead */
447    if ( !IS_ALIVE( op, o_refcnt ) ) {
448        LloadBackend *b = upstream->c_backend;
449
450        upstream->c_n_ops_executing--;
451        checked_unlock( &upstream->c_io_mutex );
452        CONNECTION_UNLOCK(upstream);
453
454        checked_lock( &b->b_mutex );
455        b->b_n_ops_executing--;
456        checked_unlock( &b->b_mutex );
457
458        assert( !IS_ALIVE( client, c_live ) );
459        checked_lock( &op->o_link_mutex );
460        if ( op->o_upstream ) {
461            op->o_upstream = NULL;
462        }
463        checked_unlock( &op->o_link_mutex );
464        rc = -1;
465        goto done;
466    }
467
468    if ( BER_BVISNULL( &mech ) ) {
469        if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
470            ber_memfree( upstream->c_sasl_bind_mech.bv_val );
471            BER_BVZERO( &upstream->c_sasl_bind_mech );
472        }
473    } else if ( ber_bvcmp( &upstream->c_sasl_bind_mech, &mech ) ) {
474        ber_bvreplace( &upstream->c_sasl_bind_mech, &mech );
475    }
476
477    Debug( LDAP_DEBUG_TRACE, "request_bind: "
478            "added bind from client connid=%lu to upstream connid=%lu "
479            "as msgid=%d\n",
480            op->o_client_connid, op->o_upstream_connid, op->o_upstream_msgid );
481    if ( ldap_tavl_insert( &upstream->c_ops, op, operation_upstream_cmp,
482                 ldap_avl_dup_error ) ) {
483        assert(0);
484    }
485    upstream->c_state = LLOAD_C_BINDING;
486    CONNECTION_UNLOCK(upstream);
487
488#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
489    if ( lload_features & LLOAD_FEATURE_VC ) {
490        rc = client_bind_as_vc( op, upstream, &binddn, tag, &auth );
491    } else
492#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
493    {
494        rc = client_bind( op, upstream, &binddn, tag, &auth );
495    }
496    checked_unlock( &upstream->c_io_mutex );
497
498done:
499
500    CONNECTION_LOCK(client);
501    if ( rc == LDAP_SUCCESS ) {
502        client->c_pin_id = pin;
503        CONNECTION_UNLOCK(client);
504
505        if ( upstream ) {
506            connection_write_cb( -1, 0, upstream );
507        }
508    } else {
509fail:
510        rc = -1;
511
512        client->c_pin_id = 0;
513        CONNECTION_DESTROY(client);
514    }
515
516    ber_free( copy, 0 );
517    return rc;
518}
519
520/*
521 * Remember the response, but first ask the server what
522 * authorization identity has been negotiated.
523 *
524 * Also, this request will fail if the server thinks a SASL
525 * confidentiality/integrity layer has been negotiated so we catch
526 * it early and no other clients are affected.
527 */
528int
529finish_sasl_bind(
530        LloadConnection *upstream,
531        LloadOperation *op,
532        BerElement *ber )
533{
534    BerElement *output;
535    LloadOperation *removed;
536    ber_int_t msgid;
537    int rc;
538
539    CONNECTION_ASSERT_LOCKED(upstream);
540    removed = ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
541    if ( !removed ) {
542        assert( upstream->c_state != LLOAD_C_BINDING );
543        /* FIXME: has client replaced this bind since? */
544        assert(0);
545    }
546    assert( removed == op && upstream->c_state == LLOAD_C_BINDING );
547
548    CONNECTION_UNLOCK(upstream);
549
550    checked_lock( &upstream->c_io_mutex );
551    output = upstream->c_pendingber;
552    if ( output == NULL && (output = ber_alloc()) == NULL ) {
553        checked_unlock( &upstream->c_io_mutex );
554        CONNECTION_LOCK_DESTROY(upstream);
555        return -1;
556    }
557    upstream->c_pendingber = output;
558
559    msgid = upstream->c_next_msgid++;
560    ber_printf( output, "t{tit{ts}}", LDAP_TAG_MESSAGE,
561            LDAP_TAG_MSGID, msgid,
562            LDAP_REQ_EXTENDED,
563            LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_WHO_AM_I );
564
565    /* Make sure noone flushes the buffer before we re-insert the operation */
566    CONNECTION_LOCK(upstream);
567    checked_unlock( &upstream->c_io_mutex );
568
569    op->o_upstream_msgid = msgid;
570
571    /* remember the response for later */
572    ber_free( op->o_ber, 1 );
573    op->o_ber = ber;
574
575    /* Could we have been unlinked in the meantime? */
576    rc = ldap_tavl_insert(
577            &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error );
578    assert( rc == LDAP_SUCCESS );
579
580    CONNECTION_UNLOCK(upstream);
581
582    Debug( LDAP_DEBUG_TRACE, "finish_sasl_bind: "
583            "SASL exchange in lieu of client connid=%lu to upstream "
584            "connid=%lu finished, resolving final authzid name msgid=%d\n",
585            op->o_client_connid, op->o_upstream_connid, op->o_upstream_msgid );
586
587    connection_write_cb( -1, 0, upstream );
588    return LDAP_SUCCESS;
589}
590
591int
592handle_bind_response(
593        LloadConnection *client,
594        LloadOperation *op,
595        BerElement *ber )
596{
597    LloadConnection *upstream;
598    BerValue response;
599    BerElement *copy;
600    LloadOperation *removed;
601    ber_int_t result;
602    ber_tag_t tag;
603    int rc = LDAP_SUCCESS;
604
605    if ( (copy = ber_alloc()) == NULL ) {
606        rc = -1;
607        goto done;
608    }
609
610    tag = ber_peek_element( ber, &response );
611    assert( tag == LDAP_RES_BIND );
612
613    ber_init2( copy, &response, 0 );
614
615    tag = ber_get_enum( copy, &result );
616    ber_free( copy, 0 );
617
618    if ( tag == LBER_ERROR ) {
619        rc = -1;
620        goto done;
621    }
622
623    Debug( LDAP_DEBUG_STATS, "handle_bind_response: "
624            "received response for bind request msgid=%d by client "
625            "connid=%lu, result=%d\n",
626            op->o_client_msgid, op->o_client_connid, result );
627
628    checked_lock( &op->o_link_mutex );
629    upstream = op->o_upstream;
630    checked_unlock( &op->o_link_mutex );
631    if ( !upstream ) {
632        return LDAP_SUCCESS;
633    }
634
635    CONNECTION_LOCK(upstream);
636    if ( !ldap_tavl_find( upstream->c_ops, op, operation_upstream_cmp ) ) {
637        /*
638         * operation might not be found because:
639         * - it has timed out (only happens when debugging/hung/...)
640         *   a response has been sent for us, we must not send another
641         * - it has been abandoned (new bind, unbind)
642         *   no response is expected
643         * - ???
644         */
645        CONNECTION_UNLOCK(upstream);
646        return LDAP_SUCCESS;
647    }
648
649    /*
650     * We might be marked for closing, forward the response if we can, but do
651     * no more if it's a SASL bind - just finish the operation and send failure
652     * in that case (since we can't resolve the bind identity correctly).
653     */
654    if ( upstream->c_state == LLOAD_C_CLOSING ) {
655        /* FIXME: this is too ad-hoc */
656        if ( ( result == LDAP_SUCCESS ||
657                     result == LDAP_SASL_BIND_IN_PROGRESS ) &&
658                !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
659            CONNECTION_UNLOCK(upstream);
660            operation_send_reject(
661                    op, LDAP_OTHER, "upstream connection is closing", 0 );
662
663            ber_free( ber, 1 );
664            return LDAP_SUCCESS;
665        }
666
667        assert( op->o_client_msgid && op->o_upstream_msgid );
668        op->o_pin_id = 0;
669
670    } else if ( result == LDAP_SASL_BIND_IN_PROGRESS ) {
671        ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
672        op->o_upstream_msgid = 0;
673        rc = ldap_tavl_insert(
674                &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error );
675        assert( rc == LDAP_SUCCESS );
676    } else {
677        int sasl_finished = 0;
678        if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
679            sasl_finished = 1;
680            ber_memfree( upstream->c_sasl_bind_mech.bv_val );
681            BER_BVZERO( &upstream->c_sasl_bind_mech );
682        }
683
684        assert( op->o_client_msgid && op->o_upstream_msgid );
685        op->o_pin_id = 0;
686
687        if ( (lload_features & LLOAD_FEATURE_PROXYAUTHZ) && sasl_finished &&
688                result == LDAP_SUCCESS ) {
689            return finish_sasl_bind( upstream, op, ber );
690        }
691        op->o_res = LLOAD_OP_COMPLETED;
692    }
693    CONNECTION_UNLOCK(upstream);
694
695    if ( !op->o_pin_id ) {
696        operation_unlink_upstream( op, upstream );
697    }
698
699    CONNECTION_LOCK(client);
700    removed = ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
701    assert( !removed || op == removed );
702
703    if ( client->c_state == LLOAD_C_BINDING ) {
704        assert( removed );
705        switch ( result ) {
706            case LDAP_SASL_BIND_IN_PROGRESS:
707                op->o_saved_msgid = op->o_client_msgid;
708                op->o_client_msgid = 0;
709                rc = ldap_tavl_insert( &client->c_ops, op, operation_client_cmp,
710                        ldap_avl_dup_error );
711                assert( rc == LDAP_SUCCESS );
712                break;
713            case LDAP_SUCCESS:
714            default: {
715                client->c_state = LLOAD_C_READY;
716                client->c_type = LLOAD_C_OPEN;
717                client->c_pin_id = 0;
718                client->c_n_ops_executing--;
719                if ( !BER_BVISNULL( &client->c_auth ) ) {
720                    if ( result != LDAP_SUCCESS ) {
721                        ber_memfree( client->c_auth.bv_val );
722                        BER_BVZERO( &client->c_auth );
723                    } else if ( !ber_bvstrcasecmp(
724                                        &client->c_auth, &lloadd_identity ) ) {
725                        client->c_type = LLOAD_C_PRIVILEGED;
726                    }
727                }
728                if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
729                    ber_memfree( client->c_sasl_bind_mech.bv_val );
730                    BER_BVZERO( &client->c_sasl_bind_mech );
731                }
732                break;
733            }
734        }
735    } else {
736        if ( removed ) {
737            client->c_n_ops_executing--;
738        }
739        assert( client->c_state == LLOAD_C_DYING ||
740                client->c_state == LLOAD_C_CLOSING );
741    }
742    CONNECTION_UNLOCK(client);
743
744done:
745    if ( rc ) {
746        operation_send_reject( op, LDAP_OTHER, "internal error", 1 );
747
748        ber_free( ber, 1 );
749        return LDAP_SUCCESS;
750    }
751    return forward_final_response( client, op, ber );
752}
753
754int
755handle_whoami_response(
756        LloadConnection *client,
757        LloadOperation *op,
758        BerElement *ber )
759{
760    LloadConnection *upstream;
761    BerValue matched, diagmsg;
762    BerElement *saved_response = op->o_ber;
763    LloadOperation *removed;
764    ber_int_t result;
765    ber_tag_t tag;
766    ber_len_t len;
767
768    Debug( LDAP_DEBUG_TRACE, "handle_whoami_response: "
769            "connid=%ld received whoami response in lieu of connid=%ld\n",
770            op->o_upstream_connid, client->c_connid );
771
772    tag = ber_scanf( ber, "{emm" /* "}" */,
773            &result, &matched, &diagmsg );
774    if ( tag == LBER_ERROR ) {
775        operation_send_reject( op, LDAP_OTHER, "upstream protocol error", 0 );
776        return -1;
777    }
778
779    checked_lock( &op->o_link_mutex );
780    upstream = op->o_upstream;
781    checked_unlock( &op->o_link_mutex );
782    if ( !upstream ) {
783        return LDAP_SUCCESS;
784    }
785
786    op->o_res = LLOAD_OP_COMPLETED;
787    /* Clear upstream status */
788    operation_unlink_upstream( op, upstream );
789
790    if ( result == LDAP_PROTOCOL_ERROR ) {
791        LloadBackend *b;
792
793        CONNECTION_LOCK(upstream);
794        b = upstream->c_backend;
795        Debug( LDAP_DEBUG_ANY, "handle_whoami_response: "
796                "Who Am I? extended operation not supported on backend %s, "
797                "proxyauthz with clients that do SASL binds will not work "
798                "msg=%s!\n",
799                b->b_uri.bv_val, diagmsg.bv_val );
800        CONNECTION_UNLOCK(upstream);
801        operation_send_reject( op, LDAP_OTHER, "upstream protocol error", 0 );
802        return -1;
803    }
804
805    tag = ber_peek_tag( ber, &len );
806
807    CONNECTION_LOCK(client);
808
809    assert( client->c_state == LLOAD_C_BINDING ||
810            client->c_state == LLOAD_C_CLOSING );
811
812    assert( BER_BVISNULL( &client->c_auth ) );
813    if ( !BER_BVISNULL( &client->c_auth ) ) {
814        ber_memfree( client->c_auth.bv_val );
815        BER_BVZERO( &client->c_auth );
816    }
817
818    if ( tag == LDAP_TAG_EXOP_RES_VALUE ) {
819        tag = ber_scanf( ber, "o", &client->c_auth );
820        if ( tag == LBER_ERROR ) {
821            CONNECTION_DESTROY(client);
822            return -1;
823        }
824    }
825
826    removed = ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
827    assert( !removed || op == removed );
828    op->o_pin_id = 0;
829    if ( removed ) {
830        client->c_n_ops_executing--;
831    }
832
833    Debug( LDAP_DEBUG_TRACE, "handle_whoami_response: "
834            "connid=%ld new authid=%s\n",
835            client->c_connid, client->c_auth.bv_val );
836
837    if ( client->c_state == LLOAD_C_BINDING ) {
838        client->c_state = LLOAD_C_READY;
839        client->c_type = LLOAD_C_OPEN;
840        client->c_pin_id = 0;
841        if ( !BER_BVISNULL( &client->c_auth ) &&
842                !ber_bvstrcasecmp( &client->c_auth, &lloadd_identity ) ) {
843            client->c_type = LLOAD_C_PRIVILEGED;
844        }
845        if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
846            ber_memfree( client->c_sasl_bind_mech.bv_val );
847            BER_BVZERO( &client->c_sasl_bind_mech );
848        }
849    }
850
851    CONNECTION_UNLOCK(client);
852
853    /* defer the disposal of ber to operation_destroy */
854    op->o_ber = ber;
855
856    return forward_final_response( client, op, saved_response );
857}
858
859#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
860int
861handle_vc_bind_response(
862        LloadConnection *client,
863        LloadOperation *op,
864        BerElement *ber )
865{
866    BerElement *output;
867    BerValue matched, diagmsg, creds = BER_BVNULL, controls = BER_BVNULL;
868    ber_int_t result;
869    ber_tag_t tag;
870    ber_len_t len;
871    int rc = 0;
872
873    tag = ber_scanf( ber, "{emm" /* "}" */,
874            &result, &matched, &diagmsg );
875    if ( tag == LBER_ERROR ) {
876        rc = -1;
877        goto done;
878    }
879
880    tag = ber_peek_tag( ber, &len );
881    if ( result == LDAP_PROTOCOL_ERROR ) {
882        LloadConnection *upstream;
883
884        checked_lock( &op->o_link_mutex );
885        upstream = op->o_upstream;
886        checked_unlock( &op->o_link_mutex );
887        if ( upstream ) {
888            LloadBackend *b;
889
890            CONNECTION_LOCK(upstream);
891            b = upstream->c_backend;
892            Debug( LDAP_DEBUG_ANY, "handle_vc_bind_response: "
893                    "VC extended operation not supported on backend %s\n",
894                    b->b_uri.bv_val );
895            CONNECTION_UNLOCK(upstream);
896        }
897    }
898
899    Debug( LDAP_DEBUG_STATS, "handle_vc_bind_response: "
900            "received response for bind request msgid=%d by client "
901            "connid=%lu, result=%d\n",
902            op->o_client_msgid, op->o_client_connid, result );
903
904    CONNECTION_LOCK(client);
905
906    if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE ) {
907        if ( !BER_BVISNULL( &client->c_vc_cookie ) ) {
908            ber_memfree( client->c_vc_cookie.bv_val );
909        }
910        tag = ber_scanf( ber, "o", &client->c_vc_cookie );
911        if ( tag == LBER_ERROR ) {
912            rc = -1;
913            CONNECTION_UNLOCK(client);
914            goto done;
915        }
916        tag = ber_peek_tag( ber, &len );
917    }
918
919    if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_SCREDS ) {
920        tag = ber_scanf( ber, "m", &creds );
921        if ( tag == LBER_ERROR ) {
922            rc = -1;
923            CONNECTION_UNLOCK(client);
924            goto done;
925        }
926        tag = ber_peek_tag( ber, &len );
927    }
928
929    if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS ) {
930        tag = ber_scanf( ber, "m", &controls );
931        if ( tag == LBER_ERROR ) {
932            rc = -1;
933            CONNECTION_UNLOCK(client);
934            goto done;
935        }
936    }
937
938    if ( client->c_state == LLOAD_C_BINDING ) {
939        switch ( result ) {
940            case LDAP_SASL_BIND_IN_PROGRESS:
941                break;
942            case LDAP_SUCCESS:
943            default: {
944                client->c_state = LLOAD_C_READY;
945                client->c_type = LLOAD_C_OPEN;
946                client->c_pin_id = 0;
947                if ( result != LDAP_SUCCESS ) {
948                    ber_memfree( client->c_auth.bv_val );
949                    BER_BVZERO( &client->c_auth );
950                } else if ( !ber_bvstrcasecmp(
951                                    &client->c_auth, &lloadd_identity ) ) {
952                    client->c_type = LLOAD_C_PRIVILEGED;
953                }
954                if ( !BER_BVISNULL( &client->c_vc_cookie ) ) {
955                    ber_memfree( client->c_vc_cookie.bv_val );
956                    BER_BVZERO( &client->c_vc_cookie );
957                }
958                if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
959                    ber_memfree( client->c_sasl_bind_mech.bv_val );
960                    BER_BVZERO( &client->c_sasl_bind_mech );
961                }
962                break;
963            }
964        }
965    } else {
966        assert( client->c_state == LLOAD_C_INVALID ||
967                client->c_state == LLOAD_C_CLOSING );
968    }
969    CONNECTION_UNLOCK(client);
970
971    checked_lock( &client->c_io_mutex );
972    output = client->c_pendingber;
973    if ( output == NULL && (output = ber_alloc()) == NULL ) {
974        rc = -1;
975        checked_unlock( &client->c_io_mutex );
976        goto done;
977    }
978    client->c_pendingber = output;
979
980    rc = ber_printf( output, "t{tit{eOOtO}tO}", LDAP_TAG_MESSAGE,
981            LDAP_TAG_MSGID, op->o_client_msgid, LDAP_RES_BIND,
982            result, &matched, &diagmsg,
983            LDAP_TAG_SASL_RES_CREDS, BER_BV_OPTIONAL( &creds ),
984            LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &controls ) );
985
986    checked_unlock( &client->c_io_mutex );
987    if ( rc >= 0 ) {
988        connection_write_cb( -1, 0, client );
989        rc = 0;
990    }
991
992done:
993    operation_unlink( op );
994    ber_free( ber, 1 );
995    return rc;
996}
997#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
998