Пример #1
0
INTERNAL void rpc__dg_scall_timer
(
    dce_pointer_t p
)
{
    rpc_dg_scall_p_t scall = (rpc_dg_scall_p_t) p;
    static rpc_clock_t rpc_c_dg_scall_max_idle_time = RPC_CLOCK_SEC(10);
    static com_timeout_params_t scall_com_timeout_params[] = {
        /*  0 min */        {RPC_CLOCK_SEC(2)},
        /*  1 */            {RPC_CLOCK_SEC(4)},
        /*  2 */            {RPC_CLOCK_SEC(8)},
        /*  3 */            {RPC_CLOCK_SEC(15)},
        /*  4 */            {RPC_CLOCK_SEC(30)},
        /*  5 def */        {RPC_CLOCK_SEC(2*30)},
        /*  6 */            {RPC_CLOCK_SEC(3*30)},
        /*  7 */            {RPC_CLOCK_SEC(5*30)},
        /*  8 */            {RPC_CLOCK_SEC(9*30)},
        /*  9 */            {RPC_CLOCK_SEC(17*30)},
        /* 10 infinite */   {RPC_CLOCK_SEC(0)}
    };

    RPC_DG_CALL_LOCK(&scall->c);

    if (scall->c.stop_timer)
    {
        rpc__timer_clear(&scall->c.timer);
        RPC_DG_SCALL_RELEASE(&scall);
        return;
    }

    switch (scall->c.state)
    {
        case rpc_e_dg_cs_init:
            /*
             * Nothing to do in this state.
             */
            break;

        case rpc_e_dg_cs_idle:
            /*
             * If the call has been idle for a long time, stop caching
             * it.  In the case of a callback SCALL, do nothing; the
             * originating CCALL's processing dictates when this cached
             * SCALL finally gets freed.  If for some reason the
             * uncache couldn't complete, we'll try again on the next tick.
             */

            if (! scall->c.is_cbk)
            {
                if (rpc__clock_aged(scall->c.state_timestamp,
                                    rpc_c_dg_scall_max_idle_time))
                {
                    if (scall_uncache(scall))
                        return;
                }
            }
            break;

        case rpc_e_dg_cs_xmit:
            /*
             * Retransmit frags if necessary.
             */
            rpc__dg_call_xmitq_timer(&scall->c);
            break;

        case rpc_e_dg_cs_recv:
            /*
             * Check to see if the client is alive.  If we have not
             * received anything from the client in "max_recv_idle_time" and
             * the receive stream is not complete assume that the client
             * is dead.  In the case of a callback SCALL, do nothing;
             * the originating CCALL's processing dictates when this
             * cached SCALL finally gets freed.
             */
            if (! scall->c.is_cbk)
            {
                if (! scall->c.rq.all_pkts_recvd
                    && rpc__clock_aged
                        (scall->c.last_rcv_timestamp,
                         scall_com_timeout_params[scall->c.com_timeout_knob]
                                                    .max_recv_idle_time)
                    && scall->c.com_timeout_knob != rpc_c_binding_infinite_timeout)
                {
                    boolean b;

                    /*
                     * We need the global lock because we are about to
                     * modify an SCT entry. We have to violate the locking
                     * hierarchy to get the global lock.  If we can't
                     * get the global lock, just give up.  We'll try
                     * again later.  Otherwise, we will uncache the scall
                     * and stop its timer processing.
                     */

                    RPC_TRY_LOCK(&b);
                    if (b)
                    {
                        rpc__dg_scall_orphan_call(scall);
                        RPC_DG_CALL_UNLOCK(&scall->c);
                        RPC_UNLOCK(0);
                        return;
                    }
                }
            }
            break;

        case rpc_e_dg_cs_final:
            /*
             * Retransmit response if necessary; eventually give up and change to
             * the idle state.
             */
            rpc__dg_call_xmitq_timer(&scall->c);
            if (scall->c.status != rpc_s_ok
                && ! RPC_DG_HDR_FLAG_IS_SET(&scall->c.xq.hdr, RPC_C_DG_PF_IDEMPOTENT))
            {
                RPC_DG_CALL_SET_STATE(&scall->c, rpc_e_dg_cs_idle);
                if (scall->c.xq.head != NULL)
                    rpc__dg_xmitq_free(&scall->c.xq, &scall->c);
            }
            break;

        case rpc_e_dg_cs_orphan:
            /*
             * Once the orphaned call has completed, free up the
             * the remaining resources.  As always, callbacks complicates
             * things, yielding a total of three scall scenarios:
             *      a)  a normal (server side) scall that has never
             *          been used in making a callback to a client
             *          (!scall->is_cbk && scall->cbk_ccall == NULL)
             *      b)  a normal (server side) scall that HAS
             *          been used in making a callback to a client
             *          (!scall->is_cbk && scall->cbk_ccall != NULL)
             *      c)  a callback scall (client side) that was the
             *          callback being executed
             *          (scall->is_cbk == true)
             *          (implicitly scall->cbk_ccall != NULL)
             *
             * The appropriate time for freeing up the remaining resources
             * is when the call executor (rpc__dg_execute_call) has
             * completed.  While it is possible to infer this condition
             * by examination of the scall's reference counts, it would
             * make this code fragment intolerably dependent on knowing
             * what/who has references to the scall under the various
             * scenarios.  Therefore we introduce and use the new flag:
             * scall->has_call_executor_ref.
             *
             * If for some reason the uncache couldn't complete, we'll
             * try again on the next tick.
             */

            if (! scall->has_call_executor_ref)
            {
                if (scall_uncache(scall))
                    return;
            }
            break;
    }

    RPC_DG_CALL_UNLOCK(&scall->c);
}
Пример #2
0
PRIVATE void conv_who_are_you_auth 
(
    handle_t h ATTRIBUTE_UNUSED, /* not really */
    dce_uuid_t *actuid,
    unsigned32 boot_time,
    ndr_byte *in_data,
    signed32 in_len,
    signed32 out_max_len,
    unsigned32 *seq,
    dce_uuid_t *cas_uuid,
    ndr_byte *out_data,
    signed32 *out_len,
    unsigned32 *st
)
{
    rpc_dg_ccall_p_t ccall;
    rpc_dg_auth_epv_p_t epv;
    ndr_byte *save_out_data = out_data;
    
    RPC_LOCK_ASSERT(0);
    
    if (! conv_common(actuid, boot_time, &ccall, st))
    {
        return;
    }

    *cas_uuid = rpc_g_dg_my_cas_uuid;
    *seq = ccall->c.call_seq;

    /*
     * If there's already a credentials buffer associated with this
     * call handle, free it.  We rely on the underlying security code
     * to do cacheing if appropriate.
     */
    if (ccall->auth_way_info != NULL)
    {
        RPC_MEM_FREE(ccall->auth_way_info, RPC_C_MEM_DG_EPAC);
        ccall->auth_way_info     = NULL;
        ccall->auth_way_info_len = 0;
    }

    /* 
     * Make sure that we really have an authenticated call here, 
     * lest we dereference null and blow up the process.
     */
    epv = ccall->c.auth_epv;
    if (epv == NULL) 
    {
        *st = rpc_s_binding_has_no_auth;
    } 
    else 
    {
	RPC_DG_CALL_UNLOCK(&(ccall->c));
	RPC_UNLOCK(0);
	
	(*epv->way_handler) (ccall->c.key_info, in_data, in_len,
            out_max_len, &out_data, out_len, st);

	RPC_LOCK(0);
	RPC_DG_CALL_LOCK(&(ccall->c));

        if (*out_len > out_max_len)
        {
            /*
             * If the credentials did not fit in the buffer provided,
             * the WAY handler will have alloced up a buffer big enough
             * to hold them, and returned a pointer to that storage in
             * out_data.  
             *
             * Stash a pointer to this buffer in the call handle, copy 
             * as much of the credentials as will fit in the real response 
             * packet, and return a status that indicates that the caller 
             * needs to fetch the rest of the credentials.
             */
            ccall->auth_way_info = out_data;
            ccall->auth_way_info_len = *out_len;

            memcpy(save_out_data, out_data, out_max_len);
            *out_len = out_max_len;

            *st = rpc_s_partial_credentials;
        }
    }
    RPC_DG_CCALL_RELEASE(&ccall);
}
Пример #3
0
PRIVATE void rpc__dg_execute_call
(
    dce_pointer_t scall_,
    boolean32 call_was_queued ATTRIBUTE_UNUSED
)
{
    ndr_format_t drep;
    unsigned32 st, reject_st;
    boolean broadcast;
    boolean idem = false;
    boolean maybe;
    boolean sent_response;
    boolean called_stub;
    rpc_dg_scall_p_t scall = (rpc_dg_scall_p_t) scall_;
    rpc_dg_pkt_hdr_p_t hdrp;
    rpc_iovector_elt_t iove;
    rpc_dg_recvq_elt_p_t rqe;
    unsigned16 ihint;
    rpc_dg_binding_server_p_t h;
    rpc_v2_server_stub_epv_t ss_epv;
    rpc_mgr_epv_t mgr_epv;
    rpc_if_rep_p_t ifspec;
    idl_uuid_t type;
    int force_way_auth;
    rpc_key_info_p_t key_info;
    rpc_dg_auth_epv_p_t auth_epv;
    unsigned16 opnum;
    unsigned32 flags;
    unsigned32 max_calls;
    unsigned32 max_rpc_size;
    rpc_if_callback_fn_t if_callback;
    int prev_cancel_state;

    /*
     * All of this code (99% of which is never executed) is in the fast path.
     *
     * NOTE: This routine is responsible for sending back a correct
     * cancel pending status to the client under all conditions
     * (to ensure that cancels don't get lost - i.e. forwarded to the
     * server, accepted, not delivered and then not reported as
     * a cancel pending).
     *
     * Any "reject response" to the client must be robust (at least for
     * Non-Idempotent calls).  This is necessary because the client may
     * have already received a fack causing it to free some pkts that it
     * would need to "rerun" the call (assuming the stub was never entered)
     * in the event that a reject was lost.
     *
     * Client's recover from lost responses to idempotent calls (including
     * proper cancel pending resetting) so we don't have to worry about
     * being robust in this situation.
     */

    /*
     * The caller of this routine is responsible for handing off a
     * call *reference* to us.  We will release our reference when
     * we're done.
     */

    RPC_DG_CALL_LOCK_ASSERT(&scall->c);

    /*
     * We are now executing.
     */
    scall->call_is_queued = false;

    /*
     * Initialize the iove, since in any failure case (i.e. orphan),
     * it may not be updated correctly; subsequent logic depends on freeing
     * things based on the proper state of the iove.
     */

    iove.buff_dealloc = NULL;

    /*
     * Initialize the "called_stub" flag to false.  If a call gets
     * rejected, and never enters the stub routine, it's up to us to
     * free the request RQE.
     */
    called_stub = false;

    /*
     * Initialize the "sent call response" flag to indicate a failure.
     * This is necessary so that failures resulting in END_OF_CALL
     * end up transitioning to the proper call state when we wrap-up
     * call processing (at the end of this routine).
     */
    sent_response = false;

    /*
     * Before continuing, it's likely that the call has been "opened
     * up" (due to a unlock/lock around call executor handoff) and we
     * need to check if it is safe to continue...
     */
    if (scall->c.state != rpc_e_dg_cs_recv)
        goto END_OF_CALL;

    /*
     * If this call does not yet have a reservation, make one now.  Any
     * call that was queued will not have a reservation; also, even if
     * a executor thread was initially available for the call, there
     * might not have been any reservations available at that time.
     * (Note that the call to make the reservation may block until a
     * reservation becomes available.)
     *
     * The make_reservation routine requires that the global lock be
     * held.  To respect the locking heirarchy, we need to juggle the
     * locks around a little, checking that the state of the call doesn't
     * change during the times when it's unlocked.
     */

    if (scall->c.n_resvs < scall->c.max_resvs)
    {
        RPC_DG_CALL_UNLOCK(&scall->c);
        RPC_LOCK(0);
        RPC_DG_CALL_LOCK(&scall->c);
        if (scall->c.state != rpc_e_dg_cs_recv)
        {
            RPC_UNLOCK(0);
            goto END_OF_CALL;
        }

        /*
         * We always start with the maximum reservation because we no longer
         * reset high_rcv_frag_size and snd_frag_size between the calls.
         * (The previous call may have used/advertised the larger fragment
         * size.)
         *
         * This is fine in the user space since the packet rationing will
         * never happen. (We assume that there are always enough packet
         * buffers available.)
         *
         * This may accelerate the packet rationing in the kernel, though
         * (iff MBF is turned on). Unfortunately, we can't start with the
         * minimum reservation in the kernel because the other end may be a
         * user space.
         */
        rpc__dg_pkt_adjust_reservation(&scall->c, scall->c.max_resvs, true);

        RPC_UNLOCK(0);

        /*
         * Since the call's been opened up, we need to check its status.
         */
        if (scall->c.state != rpc_e_dg_cs_recv)
        {
            RPC_DBG_GPRINTF((
                "(rpc__dg_execute_call) Cancelled while awaiting pkt reservation\n"));
            goto END_OF_CALL;
        }

        /*
         * Since this call did not have a reservation, any data received for
         * it was dropped, and the client was told not to send any more.
         * Since the call can now receive data, prod the client into
         * retransmitting.
         */
        rpc__dg_call_xmit_fack(&scall->c, NULL, ! scall->c.rq.recving_frags);
    }

    /*
     * Now's as good a time as any to enable direct cancel posting to
     * the thread (while we've got the call lock held).  It might have
     * been nice to defer this to just before the sstub dispatch, but
     * then we'd have to re-acquire the call lock.
     *
     * NOTE: This routine MUST call rpc_cthread_cancel_caf() before
     * returning (regardless of the return path)!  This requirement
     * exists because cancels may be (become) pending at any time and
     * must be flushed (otherwise subsequent calls using this thread
     * will inherit this call's cancel).
     */

    rpc__cthread_cancel_enable_post(&scall->c.c);

    /*
     * Create a server binding handle, if we don't already have one hanging
     * off the scall.  If we have a cached one, reinit it.
     */

    if (scall->h != NULL)
    {
        h = scall->h;
        RPC_DG_BINDING_SERVER_REINIT(h);
    }
    else
    {
        rpc_addr_p_t addr;

        rpc__naf_addr_copy(scall->c.addr, &addr, &st);
        h = (rpc_dg_binding_server_p_t) rpc__binding_alloc
            (true, &scall->c.call_object, RPC_C_PROTOCOL_ID_NCADG, addr, &st);
        if (st != rpc_s_ok)
        {
            RPC_DBG_GPRINTF((
                "(rpc__dg_execute_call) Can't allocate binding, st = 0x%x\n", st));
            goto END_OF_CALL;
        }

        RPC_DG_CALL_REFERENCE(&scall->c);
        h->scall = scall;

        if (!scall->c.is_cbk)
        {
            key_info = scall->scte->key_info;
            if (key_info != NULL)
            {
                rpc_auth_info_p_t auth_info = key_info->auth_info;
                h->c.c.auth_info = auth_info;
                RPC_DG_AUTH_REFERENCE(auth_info); /* for the handle */
            }
        }

        scall->h = h;
    }

    assert(RPC_DG_CALL_IS_SERVER(&scall->c));

    /*
     * Dequeue the first pkt off of the receive queue (including it's hdr).
     *
     * WARNING: we MUST use comm_receive_int() because comm_receive(),
     * while it would do the locking for us, doesn't return a useable iove
     * for 0 length data.
     *
     * We're supposed to be in the init state until we know we're accepting
     * the call (that means after a WAY callback if one is necessary).
     * Make certain this is the case following the receive.
     *
     * WARNING 2: Note that this call verifies the authenticity of the
     * packet it reads *except* in two cases:
     *
     *  - When the packet is from a call on an activity the server doesn't
     * currently know about (in which case we notice later on that the
     * authn_proto field in the header is non-zero).
     *
     *  - When the authentication check fails with a status code of
     * "rpc_s_dg_need_way_auth".  Note that in this event, the
     * "receive_int" is still viewed as having succeeded, albeit with
     * a non-zero status code.
     *
     * In either of these cases, a way_auth callback is made, and,
     * if it is successful, the authenticity check is retried
     * (further down in this function).
     */

    rpc__dg_call_receive_int(&scall->c, &iove, &st);
    force_way_auth = false;
    if (st == rpc_s_dg_need_way_auth) {
        RPC_DBG_PRINTF(rpc_e_dbg_general, 4,
            ("(rpc__dg_execute_call) will force way callback\n"));
        st = rpc_s_ok;
        /*
         * We don't own the rqe. It's still on recvq.
         */
        force_way_auth = true;
    }
    else if (st != rpc_s_ok)
    {
        RPC_DBG_GPRINTF((
            "(rpc__dg_execute_call) Receive failed st = 0x%x\n", st));
        goto END_OF_CALL;
    }

    rqe = RPC_DG_RECVQ_ELT_FROM_IOVECTOR_ELT(&iove);
    assert(rqe != NULL && rqe->hdrp != NULL);
    hdrp = rqe->hdrp;
    idem  = ((hdrp->flags & RPC_C_DG_PF_IDEMPOTENT) != 0);
    broadcast = ((hdrp->flags & RPC_C_DG_PF_BROADCAST) != 0);
    maybe  = ((hdrp->flags & RPC_C_DG_PF_MAYBE) != 0);

    if (scall->c.is_cbk)
    {
        RPC_DBG_PRINTF(rpc_e_dbg_general, 3,
            ("(rpc__dg_execute_call) Callback [%s]\n",
                rpc__dg_act_seq_string(hdrp)));
    }

    /*
     * Perform some of the request pkt verification that was defered.
     * This includes interface id and operation number.
     */

    if (!scall->c.is_cbk)
        key_info = scall->scte->key_info;
    else
        key_info = NULL;

    /*
     * Does the request specify authentication, do we not have auth info
     * yet, is the call not "maybe", and is this not a callback (!!!
     * for the callback case)?  If so, then get the auth info now.
     */
    if (hdrp->auth_proto != 0 &&
        key_info == NULL &&
        ! maybe &&
        ! scall->c.is_cbk)
    {
        rpc_authn_protocol_id_t authn_protocol;
        rpc_auth_info_p_t auth_info;

        assert(scall->c.key_info == NULL);

        /*
         * Get the appropiate DG auth EPV.  We need to convert the wire
         * auth protocol ID into the corresponding API value and then
         * get the EPV using that latter value.
         */
        authn_protocol = rpc__auth_cvt_id_wire_to_api(hdrp->auth_proto, &st);
        if (st != rpc_s_ok)
        {
            reject_st = rpc_s_unknown_reject;
            goto AFTER_CALL_TO_STUB;
        }
        auth_epv = (rpc_dg_auth_epv_p_t)
                        rpc__auth_rpc_prot_epv
                            (authn_protocol, RPC_C_PROTOCOL_ID_NCADG);
        if (auth_epv == NULL)
        {
            reject_st = rpc_s_unknown_reject;
            goto AFTER_CALL_TO_STUB;
        }

        /*
         * Call into auth service to create an auth info.
         *
         * This generates an auth_info and a key_info.  The auth_info
         * gets attached to the handle, while the key_info gets
         * attached to the scte and scall.
         */
        key_info = (*auth_epv->create) (&st);
        if (st != rpc_s_ok)
        {
            reject_st = rpc_s_unknown_reject;
            goto AFTER_CALL_TO_STUB;
        }
        scall->c.key_info = key_info;
        scall->c.auth_epv = auth_epv;
        /* we have one reference to the key_info already. */
        scall->scte->key_info = key_info;
        scall->scte->auth_epv = auth_epv;
        RPC_DG_KEY_REFERENCE(key_info); /* for the scte */

        /* fill in the auth_info in the handle */
        auth_info = key_info->auth_info;
        h->c.c.auth_info = auth_info;
        RPC_DG_AUTH_REFERENCE(auth_info); /* for the handle */
    }
    auth_epv = scall->c.auth_epv;

    /*
     * If the interface isn't valid, send a rejection.
     */
    rpc_object_inq_type(&scall->c.call_object, &type, &st);
    if (! (st == rpc_s_ok || st == rpc_s_object_not_found))
    {
        RPC_DBG_GPRINTF((
            "(rpc__dg_execute_call) rpc_object_inq_type failed, st=0x%x [%s]\n",
            st, rpc__dg_act_seq_string(hdrp)));
        reject_st = st;
        goto AFTER_CALL_TO_STUB;
    }

    ihint = hdrp->ihint;
    rpc__if_lookup2 (&hdrp->if_id, hdrp->if_vers, &type,
                     &ihint, &ifspec, &ss_epv, &mgr_epv,
                     &flags, &max_calls, &max_rpc_size,
                     &if_callback, &st);

    if (st != rpc_s_ok)
    {
        RPC_DBG_GPRINTF((
            "(rpc__dg_execute_call) rpc__if_lookup failed, st=0x%x [%s]\n",
            st, rpc__dg_act_seq_string(hdrp)));
        reject_st = st;
        goto AFTER_CALL_TO_STUB;
    }

    /*
     * The interface is valid, update the call ihint so we tell the client.
     */

    scall->c.call_ihint = ihint;

    /*
     * Extract a copy of the opnum from the packet header, and check to see that
     * it's appropriate for this interface.
     */

    opnum = hdrp->opnum;
    if (opnum >= ifspec->opcnt)
    {
        RPC_DBG_GPRINTF((
            "(rpc__dg_execute_call) Opnum (%u) out of range [%s]\n",
            opnum, rpc__dg_act_seq_string(hdrp)));
        reject_st = rpc_s_op_rng_error;
        goto AFTER_CALL_TO_STUB;
    }

    /*
     * To guarantee at-most-once semantics for non-idempotent RPCs, we
     * must ensure that the call is filtered based on a WAY validated
     * sequence number.  If we don't have such a sequence number, then
     * call back to client to get one (the returned WAY validated seq
     * must match this RPC's seq - i.e. it must be the RPC that the client
     * is currently performing).  Note that we may do a way_auth
     * callback even when we wouldn't otherwise do it because the
     * underlying authentication layers decided one was needed.
     *
     * The analogous processing for non-idempotent callbacks (from a
     * server manager to the client originating the call, who needs to
     * validate the callback's seq) was previously taken care of in the
     * do_request() processing (a WAY validated logical scte high_seq
     * was already known).
     *
     * Note also that maybe calls with large-INs are tagged as
     * non-idempotent but do not need to be protected against re-runs.
     * (The architecture specifies that maybe calls can *not* have
     * at-most-once semantics, but the implementation finds it more
     * convenient to use the non-idempotent code paths for handling
     * calls with large-INs.)  For this reason, avoid doing a WAY for
     * maybe calls (the client may not even be still running!).
     *
     * Release and reacquire the call lock while performing this
     * (slow path / lengthy) WAY and Auth processing.
     *
     * We perform the WAY RPC with general cancel delivery disabled.
     * The RPC prologue is suppose to be transparent and clients can
     * orphan the call if they get tired of waiting around.
     */

    if (! maybe &&
         (force_way_auth || key_info != NULL ||
         (! idem && ! scall->c.is_cbk)))
    {
        if (!force_way_auth && RPC_DG_SCT_IS_WAY_VALIDATED(scall->scte))
        {
            /*
             * We want to make this check because it's better to be safe
             * than sorry regarding at-most-once semantics.  It's
             * conceivable that the connection became WAY validated *after*
             * this call had passed it's initial filtering (if nothing
             * else, it should protect us from other potential coding
             * errors :-)
             */
            if (scall->c.call_seq != scall->scte->high_seq)
            {
                RPC_DBG_PRINTF(rpc_e_dbg_general, 2,
                    ("(execute_call) Old sequence, previous=%u [%s]\n",
                    scall->scte->high_seq, rpc__dg_act_seq_string(hdrp)));
                goto END_OF_CALL;
            }
        }
        else
        {
            boolean high_seq_was_way_validated =
                (boolean)(scall->scte->high_seq_is_way_validated);

            /*
             * WAY validate the connection and ensure that this call
             * is the current call.  Unlock the scall while performing the
             * WAY validation.
             */
            rpc_dg_sct_elt_p_t  scte;

            RPC_DG_CALL_UNLOCK(&scall->c);

            /*
             * The WAY validation routine must be called with the connection
             * unlocked.  Due to locking hierarchy and the fact that we
             * unlocked the scall, we've opened up a window... check if
             * it's safe to continue.
             */
            RPC_LOCK(0);
            RPC_DG_CALL_LOCK(&scall->c);
            if (scall->c.state != rpc_e_dg_cs_recv)
            {
                RPC_UNLOCK(0);
                goto END_OF_CALL;
            }
            scte = scall->scte;
            RPC_DG_CALL_UNLOCK(&scall->c);

            rpc__dg_sct_way_validate(scte, force_way_auth, &st);

            RPC_UNLOCK(0);

            RPC_DG_CALL_LOCK(&scall->c);

            /*
             * Before continuing, we've "opened up" the call (due to
             * the unlock/lock) and we need to check if it is safe to
             * continue...
             */
            if (scall->c.state != rpc_e_dg_cs_recv)
                goto END_OF_CALL;

            if (st != rpc_s_ok)
            {
                reject_st = rpc_s_who_are_you_failed;
                goto AFTER_CALL_TO_STUB;
            }
            else
            {
                if (scall->c.call_seq != scall->scte->high_seq)
                {
                    RPC_DBG_PRINTF(rpc_e_dbg_general, 2,
                        ("(rpc__dg_execute_call) Old sequence, previous=%u [%s]\n",
                        scall->scte->high_seq, rpc__dg_act_seq_string(hdrp)));
                    goto END_OF_CALL;
                }
            }

            /*
             * If high_seq_was_way_validated, rpc__dg_call_receive_int()
             * has already verified the packet by calling
             * (*auth_epv->recv_ck)().
             * It's ok to call it again here except when using
             * pkt_privacy where the packet body is already decrypted.
             * For consistency, we don't verify the packet if it's
             * already done.
             */
            if (key_info != NULL && !force_way_auth
                && !high_seq_was_way_validated)
            {
                unsigned32 blocksize = auth_epv->blocksize;
                char *cksum;
                int raw_bodysize;

                /*
                 * This must be a single buffer fragment.
                 * The very first fragment!
                 */
                if (rqe->hdrp == NULL || rqe->frag_len != rqe->pkt_len)
                {
                    reject_st = rpc_s_who_are_you_failed;
                    goto AFTER_CALL_TO_STUB;
                }

                /*
                 * It's not really necessary to round up the packet body
                 * length here because the sender includes the length of
                 * padding before the auth trailer in the packet body length.
                 * However, I think, that's a wrong behavior and we shouldn't
                 * rely on it.
                 */
                raw_bodysize = ((rqe->hdrp->len + blocksize - 1)
                                / blocksize) * blocksize;

                /*
                 * Now that we have obtained authentication
                 * credentials, go back and verify that cksum is
                 * entirely contained inside the packet, and the
                 * auth_type is what we expected.  This "shouldn't
                 * fail" unless someone's playing games with us.
                 */

                if (((RPC_C_DG_RAW_PKT_HDR_SIZE + raw_bodysize +
                    auth_epv->overhead) > rqe->frag_len) ||
                    (rqe->hdrp->auth_proto != auth_epv->auth_proto))
                {
                    st = nca_s_proto_error;
                }
                else
                {
                    /*
                     * Adjust the packet buffer's pkt_len,
                     * i.e., excluding the auth trailer.
                     * Also adjust data_len in the iovector.
                     */
                    rqe->pkt_len = raw_bodysize + RPC_C_DG_RAW_PKT_HDR_SIZE;
                    iove.data_len = raw_bodysize;

                    cksum = rqe->pkt->body.args + raw_bodysize;
                    RPC_DBG_PRINTF(rpc_e_dbg_general, 4,
                        ("(rpc__dg_execute_call) calling recv_ck now\n"));
                    (*auth_epv->recv_ck) (key_info, rqe, cksum, &st);
                }
                if (st != rpc_s_ok)
                {
                    RPC_DBG_PRINTF(rpc_e_dbg_general, 2,
                        ("(rpc__dg_execute_call) pkt didn't verify -- %x\n", st));
                    reject_st = rpc_s_who_are_you_failed;
                    goto AFTER_CALL_TO_STUB;
                }
            }
            else if (key_info != NULL && force_way_auth)
            {
                /*
                 * Call rpc__dg_call_receive_int() again. This time,
                 * (*auth_epv->recv_ck)() is supposed to succeed.
                 */
                rpc__dg_call_receive_int(&scall->c, &iove, &st);
                force_way_auth = false;
                if (st == rpc_s_dg_need_way_auth) {
                    /*
                     * We still don't own the rqe...
                     */
                    force_way_auth = true;
                }
                if (st != rpc_s_ok)
                {
                    RPC_DBG_GPRINTF((
"(rpc__dg_execute_call) Receive failed st = 0x%x after forced WAY auth callback\n", st));
                    reject_st = rpc_s_who_are_you_failed;
                    goto AFTER_CALL_TO_STUB;
                }
                assert(rqe == RPC_DG_RECVQ_ELT_FROM_IOVECTOR_ELT(&iove));
            }
        }
    }

    assert(force_way_auth == false);

    /*
     * If we get here, we're accepting the call and we're gonna dispatch
     * to the server stub!  Setup the required args for the dispatch
     * (the iove was done above) and run call the server stub.
     */

    RPC_DG_HDR_INQ_DREP(&drep, hdrp);

    /*
     * The packet rationing code needs to know that we no longer need
     * to worry about  doing WAYs.
     */
    scall->c.rq.is_way_validated = true;

    /*
     * Unlock the call lock while in the stub.
     */
    RPC_DG_CALL_UNLOCK(&scall->c);

    /*
     * Note: the stubs are absolutely, positively required to free the
     * provided iove described buffer (assuming the len > 0), even if
     * the stub detects and returns an error condition.   Set the
     * "called_stub" flag to true so that we know we don't have to worry
     * about freeing the RQE ourselves.
     */
    called_stub = true;

    /*
     * As required by the packet rationing rules, if the I/O vector element
     * has no data, free it up now because the server stub doesn't bother
     * to free such elements.  Note that we needed the element until
     * now for the info that was in its packet header.
     */

    if (iove.data_len == 0 && iove.buff_dealloc != NULL)
        RPC_FREE_IOVE_BUFFER(&iove);

    switch (ifspec->stub_rtl_if_vers)
    {
        /*
         * If this is an old v0 or v1 stub runtime interface.  Do the
         * dirty work out of line.
         */
        case RPC_C_STUB_RTL_IF_VERS_NCS_1_0:
        case RPC_C_STUB_RTL_IF_VERS_NCS_1_5:
            if (rpc_g_dg_pre_v2_server_call_p == NULL)
            {
                /*
                 * rpc_m_pre_v2_ss
                 * "(%s) Can't handle pre-v2 server stubs"
                 */
                rpc_dce_svc_printf (
                    __FILE__, __LINE__,
                    "%s",
                    rpc_svc_server_call,
                    svc_c_sev_fatal | svc_c_action_abort,
                    rpc_m_pre_v2_ss,
                    "rpc__dg_execute_call" );
            }

            prev_cancel_state = dcethread_enableinterrupt_throw(0);
            (*rpc_g_dg_pre_v2_server_call_p)(
                ifspec,
                opnum,
                (handle_t) h,
                (rpc_call_handle_t) scall,
                &iove,
                drep,
                ss_epv,
                mgr_epv,
                &reject_st);
            dcethread_enableinterrupt_throw(prev_cancel_state);
            break;

        /*
         * This is the v2 (new) stub runtime interface.
         */
        case RPC_C_STUB_RTL_IF_VERS_DCE_1_0:
            prev_cancel_state = dcethread_enableinterrupt_throw(0);
            (*(ss_epv[opnum]))(
                    (handle_t) h,
                    (rpc_call_handle_t) scall,
                    &iove,
                    &drep,
                    &ndr_g_transfer_syntax,
                    mgr_epv,
                    &reject_st);
            dcethread_enableinterrupt_throw(prev_cancel_state);
            break;

        /*
         * Unknown version
         */

        default:
            RPC_DBG_GPRINTF((
                "(rpc__dg_execute_call) Unknown rtl/if version. 0x%x\n",
               ifspec->stub_rtl_if_vers));
            RPC_DG_CALL_LOCK(&scall->c);

            if (iove.buff_dealloc != NULL)
                RPC_FREE_IOVE_BUFFER(&iove);

            goto END_OF_CALL;
    }

    /*
     * While the stub may have returned due to call orphaning, this will
     * not typically be the case.  Even if it completed succesfully
     * we could become orphaned further down in this processing (e.g.
     * in xmitq_push).  Defer orphan checking and cleanup till we only
     * have to do it once; the extra work done if we are orphaned won't
     * kill us.
     */

    /*
     * Acquire the call lock since we need it for several pieces of
     * processing from here on in.
     *
     * Before continuing, we've "opened up" the call (due to the
     * unlock/lock) and we need to check if it is safe to continue...
     */

    RPC_DG_CALL_LOCK(&scall->c);
    if (scall->c.state != rpc_e_dg_cs_recv
        && scall->c.state != rpc_e_dg_cs_xmit)
    {
        goto END_OF_CALL;
    }

    /*
     * Error cases detected before we get to calling the stub and that want
     * to send a "reject" re-enter here.
     */
AFTER_CALL_TO_STUB:

    RPC_DG_CALL_LOCK_ASSERT(&scall->c);

    /*
     * If this was a broadcast request and we're either rejecting the call
     * or the call faulted, just skip to the end.
     */

    if (broadcast &&
        (reject_st != rpc_s_ok ||
         RPC_DG_HDR_INQ_PTYPE(&scall->c.xq.hdr) == RPC_C_DG_PT_FAULT))
    {
        goto END_OF_CALL;
    }

    /*
     * The stub was obligated to call the iove's dealloc routine,
     * so we don't have to free that.  We don't need the recvq anymore.
     * In normal cases, the list will already be empty, so having this
     * in the fast path doesn't hurt and (in the error cases) it frees
     * up resources while we potentially wait in xmitq_push() (or
     * awaiting a xqe for a reject or no [outs] response).
     */

    if (scall->c.rq.head != NULL)
        rpc__dg_recvq_free(&scall->c.rq);

    /*
     * If a reject condition exists, prepare the reject response.
     * Otherwise, handle the case where the stub has no [outs] and it's
     * not a maybe call; we still need to generate a response pkt.
     *
     * We depend on both of these response queuing operations
     * to only queue the response and not send it since we've yet
     * setup the return cancel_pending status for the client.
     */

    if (reject_st != rpc_s_ok)
    {
        /*
         * If the reject path caused us to jump over the call into the
         * stub, we need to free the request RQE here.
         *
         * If we are forced to do WAY auth and havn't done it so, don't free
         * it because we don't own the rqe.
         */

        if (! called_stub && !force_way_auth && iove.buff_dealloc != NULL)
            RPC_FREE_IOVE_BUFFER(&iove);

        queue_mapped_reject(scall, reject_st);
    }
    else
    {
        if (scall->c.state == rpc_e_dg_cs_recv && !maybe)
        {
            rpc_iovector_t  xmit_data;

            xmit_data.num_elt = 0;
            rpc__dg_call_transmit_int(&scall->c, &xmit_data, &st);
            /*
             * The transmit may fail because the call is already orphaned.
             * It may fail for some other reason as well.  In either case,
             * we're not gonna get a response to the client.  Just keep
             * falling through (other calls may fail as well) and clean up.
             */
        }
    }

    /*
     * At this point, we can stop accepting forwarded cancels.  Determine
     * the cancel pending disposition of the call and set the call's
     * xq cancel_pending flag accordingly so that the response (or at
     * least the last pkt of the response) gets sent with the proper
     * state.  This is the single point where the "send response"
     * path ensures that it has flushed any pending cancels from the
     * call executor thread; this includes cancels generated by
     * a received cancel-request or a cancel induced by orphan call
     * processing.
     *
     * We could have stopped accepting cancels as soon as the stub
     * returned, but we really wanted to wait till here before setting
     * up the return cancel_pending status.  After this, we shouldn't
     * screw around anymore with the xq (i.e. re-initing it).  There
     * should be a reject, fault or normal response queued up and
     * it should go out with the correct cancel_pending flag.
     * That is of course, unless that call has been orphaned, in which
     * case no further response of any kind will be sent to the client
     * (setting the cancel_pending flag will not affect the client;
     * which is a *requirement* under this condition).
     */

    if (rpc__cthread_cancel_caf(&scall->c.c))
    {
        RPC_DBG_PRINTF(rpc_e_dbg_cancel, 5,
            ("(rpc__dg_execute_call) setting cancel_pending\n"));
        scall->c.xq.base_flags2 |= RPC_C_DG_PF2_CANCEL_PENDING;
    }

    /*
     * Assuming that the call isn't already orphaned, finally push
     * out the remainder of the response.  The push may fail
     * because orphaning occurs during the push or for some
     * other reason; just continue to cleanup processing. Indicate
     * whether or not the response was sent so we can determine
     * the appropriate call state when we're done.
     */

    if (scall->c.state != rpc_e_dg_cs_orphan)
    {
        rpc__dg_call_xmitq_push(&scall->c, &st);
        if (st == rpc_s_ok)
            sent_response = true;
        else
            RPC_DBG_GPRINTF((
                "(rpc__dg_execute_call) xmitq_push returns 0x%x\n", st));
    }

    /*
     * Error cases that want to skip the reply-sending machinery re-enter here.
     */
END_OF_CALL:

    RPC_DG_CALL_LOCK_ASSERT(&scall->c);

    /*
     * End of the fast path.
     *
     * Any response has been sent (or at least all the pkts have been
     * sent once).  Perform final call wrap-up processing / state
     * transitioning.  In the event that we didn't take the send
     * response path, we still need to flush any pending cancels.
     * In the event that we took the send response path but the response
     * wasn't succesfully sent, we'll call the following twice but
     * that's ok.
     */

    if (! sent_response)
        (void) rpc__cthread_cancel_caf(&scall->c.c);

    /*
     * If the call is not "idempotent" we must defer complete end of
     * call processing until the client's ack is received.  (Note: "maybe"
     * and "broadcast" are tagged as "idempotent".)  For idempotent calls
     * with small outs, we can clean up right now (if the client never
     * gets the response, it can rerun the call).
     *
     * Idempotent calls with large outs are treated similarly to
     * non-idempotent calls.  We retain the outs until "acknowledged"
     * by the client or the retransmit logic gives up.  This is required
     * to prevent the undesireable situation of the client receiving
     * a "nocall" in response to a "ping" after the client has already
     * received some of the outs.
     *
     * If we didn't (seemingly) successfully send a response, skip the
     * final state (this covers orphan processing as well).  Furthermore,
     * if the call has been orphaned stay in that state.
     *
     * An orphaned call has already been disassociated from its SCTE
     * (ccall in the case of a cbk_scall) and there should be a maximum
     * of two references to the orphaned SCALL; the call executor's and
     * the timer thread.  The only actions required are to release any
     * remaining resources held by the call and release one reference
     * to the SCALL (the timer thread will eventually complete to job
     * of destroying the scall).
     */

    if ((! idem || RPC_DG_FLAG_IS_SET(scall->c.xq.base_flags, RPC_C_DG_PF_FRAG))
        && sent_response)
    {
        RPC_DG_CALL_SET_STATE(&scall->c, rpc_e_dg_cs_final);
    }
    else
    {
        /*
         * It's really the end of the call, so we can free the xmitq.
         */

        if (scall->c.xq.head != NULL)
            rpc__dg_xmitq_free(&scall->c.xq, &scall->c);

        /*
         * Typically, the call goes back to the idle state, ready to
         * handle the next call.  First, If this was a callback, update
         * the callback sequence number in the associated client callback
         * handle.
         *
         * If the call was orphaned, we can't to do either of the above
         * (we just want to let the scall's timer complete the job of
         * destroying the scall).
         */

        if (scall->c.state != rpc_e_dg_cs_orphan)
        {
            if (scall->c.is_cbk)
            {
                scall->cbk_ccall->c.high_seq = scall->c.call_seq;
            }

            RPC_DG_CALL_SET_STATE(&scall->c, rpc_e_dg_cs_idle);
        }
    }

    /*
     * Give up the packet reservation for this call.
     */

    rpc__dg_pkt_cancel_reservation(&scall->c);

    if (scall->c.is_cbk && scall->cbk_ccall != NULL)
    {
        /*
         * Update the original ccall's high_rcv_frag_size and snd_frag_size.
         */

        scall->cbk_ccall->c.rq.high_rcv_frag_size =
            scall->c.rq.high_rcv_frag_size;
        scall->cbk_ccall->c.xq.snd_frag_size = scall->c.xq.snd_frag_size;
    }
    /*
     * We're now done with our scall lock/reference.
     */

    scall->has_call_executor_ref = false;
    RPC_DG_SCALL_RELEASE(&scall);
}
Пример #4
0
PRIVATE void rpc__dg_binding_inq_client
(
    rpc_binding_rep_p_t binding_r,
    rpc_client_handle_t *client_h,
    unsigned32 *st
)
{       
    rpc_dg_binding_server_p_t shand = (rpc_dg_binding_server_p_t) binding_r;
    rpc_dg_scall_p_t scall = shand->scall;
    rpc_binding_handle_t h;
    dce_uuid_t cas_uuid;
    rpc_dg_client_rep_p_t client;
    unsigned32 temp_seq, tst;
                              
    *st = rpc_s_ok;

    /*
     * Lock down and make sure we're in an OK state.
     */

    RPC_LOCK(0);
    RPC_DG_CALL_LOCK(&scall->c);
                      
    if (scall->c.state == rpc_e_dg_cs_orphan)
    {
        *st = rpc_s_call_orphaned;
        RPC_DG_CALL_UNLOCK(&scall->c);
        RPC_UNLOCK(0);
        return;
    }
    
    /*
     * See if there is already a client handle associated with the scte
     * associated with this server binding handle.  If there is, just
     * return it.
     */

    if (scall->scte->client != NULL)
    {
        *client_h = (rpc_client_handle_t) scall->scte->client;
        RPC_DG_CALL_UNLOCK(&scall->c);
        RPC_UNLOCK(0);
        return;
    }

    /*
     * No client handle.  We need to do a call back to obtain a UUID
     * uniquely identifying this particular instance of the client.
     */

    h = rpc__dg_sct_make_way_binding(scall->scte, st);

    RPC_DG_CALL_UNLOCK(&scall->c);
    RPC_UNLOCK(0);

    if (h == NULL)
    {
        return;
    }

    RPC_DBG_PRINTF(rpc_e_dbg_general, 3, 
        ("(binding_inq_client) Doing whats-your-proc-id callback\n"));

    DCETHREAD_TRY
    {
        (*conv_v3_0_c_epv.conv_who_are_you2)
            (h, &scall->c.call_actid, rpc_g_dg_server_boot_time, 
            &temp_seq, &cas_uuid, st);
    }
    DCETHREAD_CATCH_ALL(THIS_CATCH)
    {
        *st = rpc_s_who_are_you_failed;
    }
    DCETHREAD_ENDTRY

    rpc_binding_free(&h, &tst);

    if (*st != rpc_s_ok)
        return;

    /*
     * Check to see if the UUID returned has already been built into
     * a client handle associated with another scte.  Since we have no
     * way of mapping actids to processes, we can't know that two actid
     * are in the same address space until we get the same address space
     * UUID from both.  In this case it is necessary to use the same
     * client handle for both actids.
     */
             
    RPC_LOCK(0);          
    RPC_DG_CALL_LOCK(&scall->c);

    if (scall->c.state == rpc_e_dg_cs_orphan)
    {
        *st = rpc_s_call_orphaned;
        RPC_DG_CALL_UNLOCK(&scall->c);
        RPC_UNLOCK(0);                                     
        return;
    }
    
    RPC_MUTEX_LOCK(monitor_mutex);

    client = find_client(&cas_uuid);

    if (client != NULL)
    {   
        client->refcnt++;
        scall->scte->client = client;
    }
    else
    {
        /*
         * If not, alloc up a client handle structure and thread
         * it onto the table.
         */

        unsigned16 probe;

        probe = CLIENT_HASH_PROBE(&cas_uuid, st);

        RPC_MEM_ALLOC(client, rpc_dg_client_rep_p_t, sizeof *client, 
            RPC_C_MEM_DG_CLIENT_REP, RPC_C_MEM_NOWAIT);

        client->next = client_table[probe];
        client->rundown = NULL;
        client->last_update = 0;
        client->cas_uuid = cas_uuid;

        client_table[probe] = client;
        scall->scte->client = client;
        client->refcnt = 2;
    }  

    RPC_MUTEX_UNLOCK(monitor_mutex);
    RPC_DG_CALL_UNLOCK(&scall->c);
    RPC_UNLOCK(0);                                     
    
    *client_h = (rpc_client_handle_t) client; 
}