Net2/usr/src/contrib/isode/quipu/dsa_chain.c

/* dsa_chain.c - take referral and chain if allowed */

#ifndef lint
static char *rcsid = "$Header: /f/osi/quipu/RCS/dsa_chain.c,v 7.5 91/02/22 09:39:06 mrose Interim $";
#endif

/*
 * $Header: /f/osi/quipu/RCS/dsa_chain.c,v 7.5 91/02/22 09:39:06 mrose Interim $
 *
 *
 * $Log:	dsa_chain.c,v $
 * Revision 7.5  91/02/22  09:39:06  mrose
 * Interim 6.8
 * 
 * Revision 7.4  90/10/17  11:54:03  mrose
 * sync
 * 
 * Revision 7.3  90/07/09  14:45:58  mrose
 * sync
 * 
 * Revision 7.2  89/12/19  16:20:26  mrose
 * sync
 * 
 * Revision 7.1  89/11/27  10:30:12  mrose
 * sync
 * 
 * Revision 7.0  89/11/23  22:17:21  mrose
 * Release 6.0
 * 
 */

/*
 *                                NOTICE
 *
 *    Acquisition, use, and distribution of this module and related
 *    materials are subject to the restrictions of a license agreement.
 *    Consult the Preface in the User's Manual for the full terms of
 *    this agreement.
 *
 */


#include "acsap.h"
#include "quipu/util.h"
#include "quipu/connection.h"

extern LLog * log_dsap;

extern int dn_print ();
extern char	* mydsaname;
extern DN	  mydsadn;
extern int	  no_dsp_chain;
extern AttributeType at_relaydsa;
extern int dn_print ();

struct oper_act	* task2oper();
struct di_block	* di_alloc();
struct di_block	* select_refer_dsa();
struct connection	* conn_alloc();
struct oper_act * oper_alloc();
struct PSAPaddr	* psap_cpy();
struct access_point *ap_cpy ();
extern Entry local_find_entry_aux();

struct connection	* make_conn_block(name, addr, conn_ctx)
DN			  name;
struct PSAPaddr		* addr;
char			  conn_ctx;
{
    struct connection	* cn;

    struct TSAPaddr *tb;
    struct TSAPaddr *ta;
    struct NSAPaddr *na;
    struct NSAPaddr *nb;
    extern struct PSAPaddr * mydsaaddr;
    int x,y;
    char onnet = FALSE;

    /*
    * Set up a new connection block and add it to the list.
    */

    if(dn_cmp(name, mydsadn) == 0)
    {
	LLOG(log_dsap, LLOG_FATAL, ("Trying to connect to self :-)"));
	return(NULLCONN);
    }

    if (! addr) {
        pslog (log_dsap,LLOG_EXCEPTIONS,"Invalid (accesspoint) reference",dn_print,(caddr_t)name);
	return(NULLCONN);
    }

    /* see if on the appropriate net */
    ta = & (addr->pa_addr.sa_addr);
    tb = & (mydsaaddr->pa_addr.sa_addr);

    /* compare ta and tb to see if they have a network in common */
    for (na=ta->ta_addrs , x = ta->ta_naddr - 1 ;
		x >= 0;
		na++, x-- ) {
	for (nb=tb->ta_addrs , y = tb->ta_naddr - 1 ;
			y >= 0;
			nb++, y-- ) {
		if (na->na_community == nb->na_community) {
			onnet = TRUE;
			break;
		}
	}
    }

    if (! onnet) {
	LLOG(log_dsap, LLOG_NOTICE, ("make_conn_block - no network in common"));
	return(NULLCONN);
    }
    
    if((cn = conn_alloc()) == NULLCONN)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("make_conn_block - conn_alloc() out of memory"));
	return(NULLCONN);
    }
    cn->cn_state = CN_WAITING;
    cn->cn_ctx = conn_ctx;
    cn->cn_initiator = TRUE;
    make_dsa_bind_arg(&(cn->cn_connect.cc_req));

    cn->cn_dn = dn_cpy(name);
    DLOG (log_dsap,LLOG_TRACE,( "Before psap_dup: %s", paddr2str(addr,NULLNA)));
    psap_dup(&(cn->cn_addr), addr);
    DLOG (log_dsap,LLOG_TRACE,( "After psap_dup:  %s", paddr2str(&(cn->cn_addr),NULLNA)));

    return(cn);
}

int	  link_op_to_conn(on)
struct oper_act	* on;
{
    char		  conn_ctx = DS_CTX_X500_DSP;
    struct di_block	* di;
    struct di_block	**next_di;
    struct connection	* cn;
    int			  do_conn;
    struct access_point * loop_ap;
    int 		res;

    sort_dsa_list (&on->on_dsas);

    /*
    *  Use an open connection if one is available.
    */

    next_di = &(on->on_dsas);
    for(di=on->on_dsas; di!=NULL_DI_BLOCK; di=di->di_next)
    {
	for(cn=connlist; cn!=NULLCONN; cn=cn->cn_next)
	{
	    /* Must be a suitable context */
	    if(cn->cn_ctx == DS_CTX_X500_DAP)
	    {
		LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - open conn has DAP context"));
		continue;
	    }

	    if((cn->cn_ctx == DS_CTX_X500_DSP)
		&& ((on->on_type == ON_TYPE_GET_EDB)
		    || (on->on_type == ON_TYPE_GET_DSA_INFO)
		    || (!cn->cn_initiator)))
	    {
		if (!cn->cn_initiator)
			LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - open conn has DSP context - we must initiate it"));
		else
			LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - open conn has DSP context - QUIPU context needed"));
		continue;
	    }

	    if((cn->cn_dn != NULLDN)
	      && (dn_cmp(cn->cn_dn, di->di_dn) == 0))
		break;

	}
	if(cn != NULLCONN)
	    break;

	next_di = &(di->di_next);
    }
    if(di != NULL_DI_BLOCK)
    {
	/* Got one - remove successful di_block and link op to conn */
	DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - Found suitable open connection"));
	(*next_di) = di->di_next;
	if (di->di_state == DI_DEFERRED) {
		/* We have an open connection, but not a cache entry */
		/*		(must have used an access point in the past) */
		/* Need to be careful about freeing - do it later ! */
		di->di_oper = NULLOPER;
	} else
		di_extract(di);
	on->on_conn = cn;
	on->on_next_conn = cn->cn_operlist;
	cn->cn_operlist = on;
	on->on_relay = FALSE;	/* No need we have an open connection */
	return(OK);
    }

    /*
    *  Use a waiting connection if one is available.
    */
    next_di = &(on->on_dsas);
    for(di=on->on_dsas; di!=NULL_DI_BLOCK; di=di->di_next)
    {
	for(cn=connwaitlist; cn!=NULLCONN; cn=cn->cn_next)
	{
	/*
	*  Could do some clever stuff here and convert a waiting 
	*  connection to QUIPU from X500 if possible and useful.
	*  Left as an exercise for the reader.
	*/
	    /* Must be a suitable context */
	    if(cn->cn_ctx == DS_CTX_X500_DAP)
		continue;

	    if((cn->cn_ctx == DS_CTX_X500_DSP)
		&& ((on->on_type == ON_TYPE_GET_EDB)
		    || (on->on_type == ON_TYPE_GET_DSA_INFO)))
		continue;

	    if((cn->cn_dn != NULLDN)
	      && (dn_cmp(cn->cn_dn, di->di_dn) == 0))
		break;
	}
	if(cn != NULLCONN)
	    break;

	next_di = &(di->di_next);
    }
    if(di != NULL_DI_BLOCK)
    {
	/* Got one - remove successful di_block and link op to conn */
	LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - Found suitable waiting connection"));
	(*next_di) = di->di_next;
	di_extract(di);
	on->on_conn = cn;
	on->on_next_conn = cn->cn_operlist;
	cn->cn_operlist = on;
	on->on_relay = FALSE;	/* No need we will chain sooner or later */
	return(OK);
    }

    DLOG(log_dsap, LLOG_DEBUG, ("Neither an open nor a waiting conn suitable"));

    next_di = &(on->on_dsas);
    for(di=on->on_dsas; di!=NULL_DI_BLOCK; di=(*next_di))
    {
	if(di->di_state == DI_DEFERRED)
	{
	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - deferred di_block"));
	    next_di = &(di->di_next);
	    continue;
	}

	if(di->di_state == DI_ACCESSPOINT)
	{
	    /* context problem:
		if we have not got the entry, we don't know which context it
		will accept.
		If the operation is a getedb, or getdsainfo 
			ASSUME Quipu context is OK
	    */
		
	    if((on->on_type == ON_TYPE_GET_EDB)
	          || (on->on_type == ON_TYPE_GET_DSA_INFO)) 
		conn_ctx = DS_CTX_QUIPU_DSP;
	    else {
		conn_ctx = DS_CTX_X500_DSP;
	    }

	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - make conn block from access point"));

	    /* There *should* only be one access point - 
	     * but QUIPU may give a choice try find one that will work...
             * This is *wrong* if it is a non specific subordinate reference.
             */
	    if (di->di_reftype == RT_NONSPECIFICSUBORDINATE) {
		    LLOG(log_dsap,LLOG_EXCEPTIONS,("Should try each access point - not just one !!!"));
		    /* throw back to user */
		    (*next_di) = di->di_next;
		    di_extract(di);
		    continue;
	    }

	    for (loop_ap=di->di_accesspoints; loop_ap != NULLACCESSPOINT; loop_ap=loop_ap->ap_next)
		    if((cn = make_conn_block(loop_ap->ap_name, loop_ap->ap_address, conn_ctx)) != NULLCONN) {
			on->on_relay = FALSE;	/* Made a connection block */
			break;
		    }

	    if (loop_ap == NULLACCESSPOINT) {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - make_conn_block failed 1"));
		(*next_di) = di->di_next;
		di_extract(di);
		continue;
	    }
	}

	if(di->di_state == DI_COMPLETE)
	{
	    /*
	    *  Open a quipu context connection if possible: this is so if
	    *  the entry for the dsa in question has object class quipuDSA.
	    */
	    if((res = quipu_ctx_supported(di->di_entry)) != 2)
	    {
		if((on->on_type == ON_TYPE_GET_EDB)
	          || (on->on_type == ON_TYPE_GET_DSA_INFO)
		  || (res == -1)) /* DAP only !!! */
		{
		    /* Ditch this di_block and carry on looking */
		    LLOG(log_dsap, LLOG_NOTICE, ("link_op_to_conn - avoiding non-quipu context for GetEDB"));
		    (*next_di) = di->di_next;
		    di_extract(di);
		    continue;
		}
		else
		{
		    DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - linking to a connection without a quipu context"));
		    conn_ctx = DS_CTX_X500_DSP;
		}
	    }
	    else
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - linking to a connection with a quipu context"));
		conn_ctx = DS_CTX_QUIPU_DSP;
	    }


	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - make conn block from entry"));

            if((cn = make_conn_block(di->di_dn, di->di_entry->e_dsainfo->dsa_addr, conn_ctx)) == NULLCONN)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - make_conn_block failed 2"));
		(*next_di) = di->di_next;
		di_extract(di);
		continue;
	    } else
		on->on_relay = FALSE;	/* Made a connection block */

	}

	/*
	*  Decide whether to request connection or place it
	*  on the list of waiting connections.
	*/
	switch(on->on_type)
	{
	case ON_TYPE_GET_DSA_INFO:
	    do_conn = (conns_used < MAX_CONNS);
	    break;
	case ON_TYPE_GET_EDB:
	case ON_TYPE_SHADOW:
	    do_conn = (conns_used < (MAX_CONNS - CONNS_RESERVED_DI - CONNS_RESERVED_X500));
	    break;
	default:
	    do_conn = (conns_used < (MAX_CONNS - CONNS_RESERVED_DI));
	    break;
	}

	if(do_conn)
	{
	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - about to request connection"));
	    if(conn_request(cn) != OK)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - conn_request failed"));
		(*next_di) = di->di_next;
		di_extract(di);
		continue;
	    }
	    DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - conn_request OK"));

	    cn->cn_next = connlist;
	    connlist = cn;
	    conns_used++;
	}
	else
	{
	    DLOG(log_dsap, LLOG_NOTICE, ("Waiting for a free connection slot"));
	    cn->cn_next = connwaitlist;
	    connwaitlist = cn;
	}

	(*next_di) = di->di_next;
	di_extract(di);
	on->on_conn = cn;
	on->on_next_conn = cn->cn_operlist;
	cn->cn_operlist = on;
	return(OK);
    }

    /*
    *  If we get this far it means that we are waiting for a dsa info
    *  operation to complete, or there are no di_blocks left to try.
    *  Callers of link_op_to_conn must check on_dsas to discover which it is.
    */
    DLOG(log_dsap, LLOG_NOTICE, ("link_op_to_conn: returning NOTOK"));
    return(NOTOK);
}

int	  oper_chain(on)
struct oper_act		* on;
{
    if(link_op_to_conn(on) == OK)
    {
	if(on->on_conn == NULLCONN)
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - link_op_to_conn: OK but no conn"));
	}
	else
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - link_op_to_conn: OK got conn"));

	    if(on->on_conn->cn_state == CN_OPEN)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - link_op_to_conn: OK got open conn"));
		if(oper_send_invoke(on) != OK)
		{
		    LLOG(log_dsap, LLOG_EXCEPTIONS, ("oper_chain - oper_send failed"));
		    /* Have another go? */
		    return(oper_chain(on));
		}
		else
		{ 
		    DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - oper_send succeeded"));
		}
	    }
	}

	on->on_state = ON_CHAINED;
    }
    else
    {
	/*
	*  If on->on_dsas is empty then chaining has failed
	*  otherwise the op is deferred.
	*/
	if(on->on_dsas == NULL_DI_BLOCK)
	{
	    if (relay_dsa (on) == NOTOK)
		return NOTOK;

	    LLOG(log_dsap, LLOG_NOTICE, ("Trying to relay..."));
	    return(oper_chain(on));
	}
	
	on->on_state = ON_DEFERRED;
    }

    return(OK);
}

int	  task_chain(tk, di)
register       struct task_act     * tk;
struct di_block	* di;
{
    struct oper_act	* on;
    struct DSError	* err = &(tk->tk_resp.di_error.de_err);
    struct di_block	* di_tmp;
    char		refer_ok = TRUE;

#ifdef DEBUG
    DLOG(log_dsap, LLOG_DEBUG, ("task_chain called with:"));
    di_list_log(di);
#endif

/* NB At some point this routine must assign the di_block list to
* either the task (if it is intended to geneate a referral) or to
* an operation hanging off that task if it is intended to chain the
* task. This is fine when there are no deferred di_blocks, but when
* there are then the information they will eventually contain is
* needed to make a full decision on whether to chain or refer.
* This needs a lot of thought to get right, for now the chain/refer
* decision is made once and for all on the basis of the information
* available now. Any information not available is assumed to force a
* referral (the safe option  -  until network connectivity is considered)!
* THis may introduce the unwelcome effect that a first request to a
* DSA may produce a referral where subsequent requests do not - so much
* for consistency but it won't happen that often if DSA info is cached
* sensibly.
*/

    /*
    *  Generate the referral which the DSA will pass back if
    *  chaining is disallowed or oper_chain fails for all
    *  DSAs listed.
    */

    sort_dsa_list (&di);

    if ((di_tmp = select_refer_dsa (di,tk)) == NULL_DI_BLOCK) {
	/* The remote END is probably unable to follow the referral - chain if allowed */
	refer_ok = FALSE;
	for(di_tmp=di; di_tmp!=NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
    	{
		if(di_tmp->di_state == DI_DEFERRED)
			continue;

#ifdef DEBUG
		DLOG(log_dsap, LLOG_DEBUG, ("About to call di2cref with:"));
		di_log(di_tmp);
#endif
		if(di2cref(di_tmp, err, tk->tk_conn->cn_ctx) == OK)
		    break;
	}
    } else if (di2cref(di_tmp, err, tk->tk_conn->cn_ctx) != OK)
		di_tmp = NULL_DI_BLOCK;	/* waiting... */

    if(di_tmp == NULL_DI_BLOCK)
    {
	/*
	*  Want to generate a referral - but all di_blocks (if any)
	*  are deferred. Would we be lying too much if we said the
	*  DSA was "busy" at this point???
	*/
	ds_error_free (err);
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_BUSY;
	di_desist(di);
	return(NOTOK);
    }

    /*
    *  If it would be inappropriate to chain this operation, then
    *  generate a referral from the di_block list.
    */

    if(chain_ok(tk,refer_ok,di_tmp->di_dn) == FALSE)
    {
	DLOG(log_dsap, LLOG_DEBUG, ("Referring!"));
	di_desist(di);
	return(NOTOK);
    }

    DLOG(log_dsap, LLOG_DEBUG, ("Chaining!"));
    /* Chain. Generate the new operation to send */
    if((on = task2oper(tk)) == NULLOPER)
    {
	DLOG(log_dsap, LLOG_DEBUG, ("Why did task2oper fail??"));
	ds_error_free (err);
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_UNAVAILABLE;
	di_desist(di);
	return(NOTOK);
    }

    if(ti_is_elem(tk->tk_dx.dx_arg.dca_charg.cha_trace,
		  tk->tk_dx.dx_arg.dca_charg.cha_trace->ti_next))
	{
	    DLOG (log_dsap,LLOG_NOTICE,("Loop found in oper_chain()"));
	    ds_error_free (&on->on_resp.di_error.de_err);
	    on->on_resp.di_error.de_err.dse_type = DSE_SERVICEERROR;
	    on->on_resp.di_error.de_err.ERR_SERVICE.DSE_sv_problem = DSE_SV_LOOPDETECT;
	    return(NOTOK);
	}

    on->on_next_task = tk->tk_operlist;
    tk->tk_operlist = on;
    on->on_task = tk;

    /* Hand control of di_blocks to the operation */
    on->on_dsas = di;
    for(di_tmp = di; di_tmp != NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
    {
	di_tmp->di_type = DI_OPERATION;
	di_tmp->di_oper = on;
    }

    if(oper_chain(on) != OK)
    {
	oper_task_extract(on);
	oper_free(on);
	return(NOTOK);
    }

    return(OK);
}

oper_rechain(on)
struct oper_act * on;
{
    struct DSE_referral         * ref = &(on->on_resp.di_error.de_err.ERR_REFERRAL);
    struct continuation_ref     * cref;
    register struct chain_arg	* cha = &(on->on_req.dca_charg);
    struct trace_info		* ti;
    struct di_block * ap2di();

    DLOG(log_dsap, LLOG_TRACE, ("Rechain an operation ..."));
    cref = ref->DSE_ref_candidates;
    
    if(cref == NULLCONTINUATIONREF)
    {
	LLOG(log_dsap, LLOG_FATAL, ("No continuation reference to rechain"));
	return(NOTOK);
    }

    cha->cha_target = dn_cpy(cref->cr_name);
    cha->cha_progress = cref->cr_progress;
    cha->cha_aliasderef = ((cha->cha_aliasedrdns = cref->cr_aliasedRDNs) != CR_NOALIASEDRDNS);

    if (cha->cha_aliasderef) {
	if ((on->on_arg->dca_dsarg.arg_type == OP_SEARCH) 
		&& (on->on_arg->dca_dsarg.arg_sr.sra_subset == SRA_ONELEVEL))
#ifdef COMPAT_6_0
	{
		on->on_arg->dca_dsarg.arg_sr.sra_subset = SRA_BASEOBJECT;
		cha->cha_entryonly = FALSE;
	}
#else
		cha->cha_entryonly = TRUE;
#endif
	else
		cha->cha_entryonly = FALSE;
    }

    cha->cha_returnrefs = FALSE;
    cha->cha_domaininfo = NULLPE;
    if((cha->cha_reftype = cref->cr_reftype) == RT_UNDEFINED)
	cha->cha_reftype = RT_SUPERIOR;

    DLOG(log_dsap, LLOG_DEBUG, ("oper_rechain - Setting trace info"));
    ti = (struct trace_info *) malloc(sizeof(struct trace_info));
    ti->ti_dsa = dn_cpy(on->on_conn->cn_dn); 
    ti->ti_target = dn_cpy(cref->cr_name);
    ti->ti_progress = cref->cr_progress;
    ti->ti_next = cha->cha_trace;
    cha->cha_trace = ti;

    if(ti_is_elem(ti,ti->ti_next))
	{
	    DLOG (log_dsap,LLOG_NOTICE,("Loop found in oper_rechain()"));
	    ds_error_free (&on->on_resp.di_error.de_err);
	    on->on_resp.di_error.de_err.dse_type = DSE_SERVICEERROR;
	    on->on_resp.di_error.de_err.ERR_SERVICE.DSE_sv_problem = DSE_SV_LOOPDETECT;
	    return(NOTOK);
	}

    oper_conn_extract(on);

    /*
    *  Problem - having converted to di_blocks it is harder to handle referrals
    *  Set up a single di_block with the address in the parent field ??
    */
    di_desist(on->on_dsas);
    if (cref->cr_reftype != RT_NONSPECIFICSUBORDINATE)
	    on->on_dsas = ap2di (cref->cr_accesspoints,cref->cr_name,FALSE,DI_OPERATION,on,cref->cr_reftype);
    else {
	    on->on_dsas = di_alloc();
	    on->on_dsas->di_target = dn_cpy(cref->cr_name);
	    on->on_dsas->di_dn = dn_cpy(cref->cr_accesspoints->ap_name);
	    DLOG(log_dsap, LLOG_DEBUG, ("oper_rechain allocates di_block with dn[%x]", on->on_dsas->di_dn));
	    on->on_dsas->di_type = DI_OPERATION;
	    on->on_dsas->di_reftype = RT_NONSPECIFICSUBORDINATE;
	    on->on_dsas->di_oper = on;
	    on->on_dsas->di_state = DI_ACCESSPOINT;
	    on->on_dsas->di_accesspoints = ap_cpy(cref->cr_accesspoints);
	    on->on_dsas->di_next = NULL_DI_BLOCK;
    }

    sort_dsa_list (&on->on_dsas);	/* might be able to turn DI_ACCESS into DI_COMPLETE */

    if (on->on_relay == FALSE) 		/* but not 2 ! */
	    on->on_relay = TRUE;	/* allow relay for new DSA set */

    return(oper_chain(on));
}

struct oper_act	* task2oper(tk)
struct task_act * tk;
{
    register struct chain_arg	* cha = &(tk->tk_dx.dx_arg.dca_charg);
    struct continuation_ref	* cref = tk->tk_resp.di_error.de_err.ERR_REFERRAL.DSE_ref_candidates;
    struct trace_info		* ti;
    struct oper_act		* on;

    DLOG(log_dsap, LLOG_TRACE, ("Chain a task ..."));
    
    if((on = oper_alloc()) == NULLOPER)
	return(NULLOPER);

    on->on_type = ON_TYPE_X500;

    cha->cha_target = NULLDN;
    if(cref->cr_name != NULLDN)
    {
	    cha->cha_target = dn_cpy(cref->cr_name);
    }
    cha->cha_progress = cref->cr_progress;
    cha->cha_aliasderef = ((cha->cha_aliasedrdns = cref->cr_aliasedRDNs) != CR_NOALIASEDRDNS);

    if (cha->cha_aliasderef) {
	if ((on->on_arg->dca_dsarg.arg_type == OP_SEARCH) 
		&& (on->on_arg->dca_dsarg.arg_sr.sra_subset == SRA_ONELEVEL)) {
#ifdef COMPAT_6_0
	{
		on->on_arg->dca_dsarg.arg_sr.sra_subset = SRA_BASEOBJECT;
		cha->cha_entryonly = FALSE;
	}
#else
		cha->cha_entryonly = TRUE;
#endif
	} else
		cha->cha_entryonly = FALSE;
    }

    cha->cha_returnrefs = FALSE;
    cha->cha_domaininfo = NULLPE;
    if((cha->cha_reftype = cref->cr_reftype) == RT_UNDEFINED)
	cha->cha_reftype = RT_SUPERIOR;

    DLOG(log_dsap, LLOG_DEBUG, ("Checking history of op"));
    if(tk->tk_conn->cn_ctx == DS_CTX_X500_DAP)
    {
	DLOG(log_dsap, LLOG_DEBUG, ("... user originated ..."));
	cha->cha_originator = dn_cpy(tk->tk_conn->cn_dn);
	cha->cha_trace = NULLTRACEINFO;
    }

    if(tk->tk_timed == FALSE)
    {
	cha->cha_timelimit = NULLCP;
    }
    else
    {
#ifdef CHAIN_ARGS_TIMEOUT
	struct UTCtime	ut;
	tm2ut(gmtime(&(tk->tk_timeout)), &(ut));
	cha->cha_timelimit = strdup(utct2str(&ut));
#else
	cha->cha_timelimit = NULLCP;
#endif
    }

    DLOG(log_dsap, LLOG_DEBUG, ("Setting trace info"));
    ti = (struct trace_info *) malloc(sizeof(struct trace_info));
    ti->ti_dsa = dn_cpy(mydsadn);
    ti->ti_target = dn_cpy(cref->cr_name);
    ti->ti_progress = cref->cr_progress;
    ti->ti_next = cha->cha_trace;
    cha->cha_trace = ti;

    on->on_arg = &(tk->tk_dx.dx_arg);

    return(on);
}

int     chain_ok(tk,refer_ok,dsadn)
struct task_act	* tk;
char refer_ok;
DN dsadn;
{
    struct common_args	* ca;
struct common_args	* get_ca_ref();

    ca = get_ca_ref(&(tk->tk_dx.dx_arg));

    /* if refer_ok is FALSE - we MUST chain unless prevented, otherwise operation will fail */

    DLOG (log_dsap,LLOG_TRACE,( "chain_ok: Checking if chaining is ok"));

    if ( ! refer_ok) {
	DLOG (log_dsap,LLOG_DEBUG,( "We MUST chain"));	
	
	if ((tk->tk_conn->cn_ctx != DS_CTX_X500_DAP) && no_dsp_chain)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of NO_DSP_CHAIN"));
		return(FALSE);
	}
	if(ca->ca_servicecontrol.svc_options & SVC_OPT_CHAININGPROHIBIT)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "But prohibited"));
		return(FALSE);
	}

	if(ca->ca_servicecontrol.svc_options & SVC_OPT_LOCALSCOPE)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "But out of scope"));
		return(FALSE); 
	}
	
    	DLOG (log_dsap,LLOG_DEBUG,( "Forced chain OK!"));
	return TRUE;
    }

    if (tk->tk_conn->cn_ctx != DS_CTX_X500_DAP) {
	if (no_dsp_chain)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of NO_DSP_CHAIN (2)"));
		return(FALSE);
	}

	if(! (ca->ca_servicecontrol.svc_options & SVC_OPT_PREFERCHAIN))
	{
		/* Don't send a self reference back to a remote DSA - chain if possible */
		/* Should not need it, when self reference bug is fixed ! */
		if ((tk->tk_conn->cn_initiator)
			|| (dn_cmp (dsadn, tk->tk_conn->cn_dn) == NOTOK)) {
				DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of preference"));
				return(FALSE);
			}
	}
    }

    if(ca->ca_servicecontrol.svc_options & SVC_OPT_CHAININGPROHIBIT)
    {
	DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of prohibition"));
	return(FALSE);
    }

    if(ca->ca_servicecontrol.svc_options & SVC_OPT_LOCALSCOPE)
    {
	DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of scope"));
	return(FALSE); 
    }

    switch (tk->tk_dx.dx_arg.dca_dsarg.arg_type) {
	case OP_ADDENTRY:
	case OP_REMOVEENTRY:
	case OP_MODIFYRDN:
	case OP_MODIFYENTRY:
		/* QUIPU DSAs will only allow modification over DAP */
		if (!(ca->ca_servicecontrol.svc_options & SVC_OPT_PREFERCHAIN)) {
			DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of authentication"));
			return(FALSE); 
		}
	default:
		break;
    }

    DLOG (log_dsap,LLOG_DEBUG,( "Chain OK!"));
    return(TRUE);
}

task_result_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;

    DLOG(log_dsap, LLOG_TRACE, ("task_result_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (result)extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote result and here it is.
	*  Attempt to tidy up and send result.
	*/
	ds_error_free (tk->tk_error);

	tk->tk_result = &(on->on_resp.di_result.dr_res);
	
	dsp_cache (&(tk->tk_dx.dx_arg.dca_dsarg),&(tk->tk_result->dcr_dsres),tk->tk_conn->cn_ctx, tk->tk_conn->cn_dn);

	task_conn_extract(tk);
	task_result(tk);
	oper_extract(on);
	task_extract(tk);
    }
}

task_error_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;

    DLOG(log_dsap, LLOG_TRACE, ("task_error_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (error) extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote result and got a remote error.
	*  If it is a referral, then rechain the operation if appropriate
	*  otherwise return the error.
	*/
	if ( !  ((on->on_resp.di_error.de_err.dse_type == DSE_SECURITYERROR)
	  	&& (on->on_resp.di_error.de_err.ERR_SECURITY.DSE_sc_problem == DSE_SC_AUTHENTICATION))) {
		/* If is not an authenticaton error, swap errors */
		ds_error_free(&(tk->tk_resp.di_error.de_err));
		tk->tk_error = &(on->on_resp.di_error.de_err);
	}

	if((on->on_resp.di_error.de_err.dse_type == DSE_DSAREFERRAL)
  	|| (on->on_resp.di_error.de_err.dse_type == DSE_REFERRAL))
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("Try rechaining"));
	    if(oper_rechain(on) == OK)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("Succeeded rechaining"));
		return;
	    }
	    DLOG(log_dsap, LLOG_DEBUG, ("Failed rechaining"));
	}

	task_conn_extract(tk);
	task_error(tk);
	oper_extract(on);
	task_extract(tk);
    }
}

task_fail_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct DSError	* err;

    DLOG(log_dsap, LLOG_TRACE, ("task_fail_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	if (on->on_state != ON_ABANDONED)
		LLOG(log_dsap, LLOG_EXCEPTIONS, ("task_fail_wakeup: no task"));
	oper_extract(on);
	return;
    }

    /*
    *  Were waiting for a remote result and got a remote failure.
    *  If it is a referral, then rechain the operation if appropriate
    *  otherwise return the error.
    */
    /*
    *  If the task does not have a suitable referral error set up
    *  then return serviceError invalid reference.
    */
    err = &(tk->tk_resp.di_error.de_err);
    if((err->dse_type != DSE_REFERRAL) && (err->dse_type != DSE_DSAREFERRAL))
    {
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_UNAVAILABLE;
    }
    task_conn_extract(tk);
    task_error(tk);
    oper_conn_extract(on);
    oper_task_extract(on);
    oper_extract(on);
    task_extract(tk);
}

task_dsa_info_wakeup(di)
struct di_block	* di;
{
    struct task_act	* tk = di->di_task;


    DLOG(log_dsap, LLOG_TRACE, ("task_dsa_info_wakeup"));

    /*
    * Were waiting for a reference to return.
    * Check if the reference now returned is acceptable.
    * If it is return a referral and unwrap everything,
    * otherwise try another di_block for the reference.
    */
    sort_dsa_list (&di);

    if (tk == NULLTASK)
	return;		/* already done it ! */

    if(di2cref(di, &(tk->tk_resp.di_error.de_err), tk->tk_conn->cn_ctx) != OK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("task_dsa_info_wakeup - reference not acceptable"));
	/* Remove di_block which generated unwanted referral wait */
	if(tk->tk_dsas == NULL_DI_BLOCK)
	{
	    /* No more dsas from which to request info to form referral */
	    tk->tk_resp.di_error.de_err.dse_type = DSE_SERVICEERROR;
	    tk->tk_resp.di_error.de_err.ERR_SERVICE.DSE_sv_problem = DSE_SV_INVALIDREFERENCE;
	    task_conn_extract(tk);
	    task_error(tk);
	    task_extract(tk);
	    return;
	}
	return;
    }

    task_conn_extract(tk);
    task_error(tk);
    task_extract(tk);
}

static struct access_point * di2ap (di)
struct di_block * di;
{
struct access_point *ap;

    switch(di->di_state)
    {
    case DI_ACCESSPOINT:
	return (ap_cpy(di->di_accesspoints));
    case DI_COMPLETE:
	if(di->di_entry == NULLENTRY)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2ap - di_entry NULL"));
	    return NULLACCESSPOINT;
	}
	if(di->di_entry->e_dsainfo == NULL)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2ap - e_dsainfo NULL"));
	    return NULLACCESSPOINT;
	}
	if(di->di_entry->e_dsainfo->dsa_addr == NULLPA)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2ap - dsa_addr NULL"));
	    return NULLACCESSPOINT;
	}
	ap = (struct access_point *) calloc(1, sizeof(struct access_point));
	ap->ap_name = dn_cpy(di->di_dn);
	ap->ap_address = psap_cpy(di->di_entry->e_dsainfo->dsa_addr);
	return (ap);
    default:
	return NULLACCESSPOINT;
    }
	
}

int	di2cref(di, err, ctx)
struct di_block	* di;
struct DSError	* err;
char ctx;
{
    struct continuation_ref     * cref;
    struct di_block * loop;
    struct access_point *ap_append(), *ap;

#ifdef DEBUG
    DLOG(log_dsap, LLOG_TRACE, ("di2cref"));
    di_log(di);
#endif

    switch(di->di_state)
    {
    case DI_ACCESSPOINT:
        DLOG(log_dsap, LLOG_TRACE, ("di2cref - generating referrral from di_accesspoints"));

	/* Should check context */
	err->dse_type = DSE_REFERRAL;
	err->ERR_REFERRAL.DSE_ref_prefix = NULLDN;

        cref = err->ERR_REFERRAL.DSE_ref_candidates = (struct continuation_ref *) calloc(1, sizeof(struct continuation_ref));
	cref->cr_accesspoints = ap_cpy(di->di_accesspoints);
	cref->cr_name = dn_cpy(di->di_target);
	if((cref->cr_rdn_resolved = di->di_rdn_resolved) <= 0)
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    cref->cr_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    cref->cr_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}
	cref->cr_aliasedRDNs = di->di_aliasedRDNs;
	cref->cr_reftype = di->di_reftype;
	break;
    case DI_COMPLETE:
        DLOG(log_dsap, LLOG_TRACE, ("di2cref - generating referrral from di_entry"));

	/* Should check context */
	err->dse_type = DSE_REFERRAL;
	err->ERR_REFERRAL.DSE_ref_prefix = NULLDN;

        cref = err->ERR_REFERRAL.DSE_ref_candidates = (struct continuation_ref *) calloc(1, sizeof(struct continuation_ref));
	if ((cref->cr_accesspoints = di2ap (di)) == NULLACCESSPOINT)
		return NOTOK;
	cref->cr_name = dn_cpy(di->di_target);
	if((cref->cr_rdn_resolved = di->di_rdn_resolved) <= 0)
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    cref->cr_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    cref->cr_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}
	cref->cr_aliasedRDNs = di->di_aliasedRDNs;
	cref->cr_reftype = di->di_reftype;
	break;
    case DI_DEFERRED:
	LLOG(log_dsap, LLOG_NOTICE, ("di2cref - Trying to turn deferred di_block into continuation reference!"));
	return(NOTOK);
    default:
        LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2cref - invalid di_state %d",di->di_state));
        return(NOTOK);
    }

    if (ctx == DS_CTX_QUIPU_DSP) {
	/* Make QSSR */
	/* append AP's from remaining di_blocks */
	LLOG (log_dsap, LLOG_TRACE, ("Making a QSSR"));
	for (loop=di->di_next; loop!=NULL_DI_BLOCK; loop=loop->di_next) {
		if (( ap = di2ap(loop)) == NULLACCESSPOINT)
			return OK;	/* Have finished - return OK */
		cref->cr_accesspoints = ap_append (cref->cr_accesspoints,ap);
	}
    }

    return OK;
}

oper_fail_wakeup(on)
struct oper_act	* on;
{
    DLOG(log_dsap, LLOG_TRACE, ("oper_fail_wakeup()"));
    /*
    *  Attempt to perform operation remotely has failed.
    *  Check the type of operation and take appropriate action.
    */

    if (on == NULLOPER) {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("No operation to fail"));
	return;
    }

    switch(on->on_type)
    {
    case ON_TYPE_X500:
	task_fail_wakeup(on);
	break;
    case ON_TYPE_SUBTASK:
	subtask_fail_wakeup(on);
	break;
    case ON_TYPE_BIND_COMPARE:
	bind_compare_fail_wakeup(on);
	break;
    case ON_TYPE_GET_DSA_INFO:
	dsa_info_fail_wakeup(on);
	break;
    case ON_TYPE_GET_EDB:
	get_edb_fail_wakeup(on);
	break;
    case ON_TYPE_SHADOW:
	shadow_fail_wakeup(on);
	break;
    default:
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("oper_fail_wakeup - op has invalid type"));
	break;
    }
}


subtask_chain(tk)
struct task_act * tk;
{
    struct ds_search_task	*refer;
    struct ds_search_task	*nref;
    struct ds_search_task	* trail = NULL_ST;
    struct ds_search_task 	* st_done();
    struct oper_act		* on;
    struct di_block     	* di;
    struct di_block     	* di_tmp;
    register struct chain_arg	* tkcha = &(tk->tk_dx.dx_arg.dca_charg);
    register struct chain_arg	* oncha;
    struct trace_info		* ti;
    struct DSError		err;
    struct common_args		* ca;
struct common_args		* get_ca_ref();

    ca = get_ca_ref(&(tk->tk_dx.dx_arg));


    if(tk->refer_st == NULL_ST)
	return;

    DLOG(log_dsap, LLOG_TRACE, ("Chain search subtasks ..."));

    for(refer = tk->refer_st; refer != NULL_ST; refer = nref)
    {
	nref = refer->st_next;
	if((di = refer->st_di) == NULL_DI_BLOCK)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("search referred without di_block list"));
	    continue;
	}

	sort_dsa_list (&di);

	err.ERR_REFERRAL.DSE_ref_candidates = NULLCONTINUATIONREF;
	if ((di_tmp = select_refer_dsa (di,tk)) == NULL_DI_BLOCK) {
		/* The remote END is probably unable to follow the referral - chain if allowed */
		for(di_tmp=di; di_tmp!=NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
	    	{
			if(di_tmp->di_state == DI_DEFERRED)
				continue;

#ifdef DEBUG
			DLOG(log_dsap, LLOG_DEBUG, ("About to call di2cref with:"));
			di_log(di_tmp);
#endif
			if(di2cref(di_tmp, &err, tk->tk_conn->cn_ctx) == OK)
			    break;
		}
	} else 
		(void) di2cref(di_tmp, &err, tk->tk_conn->cn_ctx);

	on = oper_alloc();
	on->on_type = ON_TYPE_SUBTASK;
	on->on_dsas = di;
	for(di_tmp=di; di_tmp!=NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
	{
	    di_tmp->di_type = DI_OPERATION;
	    di_tmp->di_oper = on;
	}
	on->on_subtask = refer;
	on->on_task = tk;
	on->on_next_task = tk->tk_operlist;
	tk->tk_operlist = on;

	oncha = &(on->on_req.dca_charg);

	if(refer->st_alias == NULLDN)
	{
	    if (err.ERR_REFERRAL.DSE_ref_candidates)
		    oncha->cha_target = dn_cpy(err.ERR_REFERRAL.DSE_ref_candidates->cr_name);
	    else 
		    oncha->cha_target = dn_cpy (di->di_target);
	}
	else
	{
	    oncha->cha_target = dn_cpy(refer->st_alias);
	}

	if(di->di_rdn_resolved <= 0)
	{
	    oncha->cha_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    oncha->cha_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    oncha->cha_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    oncha->cha_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}

	oncha->cha_aliasderef = ((oncha->cha_aliasedrdns = di->di_aliasedRDNs) != CR_NOALIASEDRDNS);
	if((oncha->cha_reftype = di->di_reftype) == RT_UNDEFINED)
	    oncha->cha_reftype = RT_SUPERIOR;

#ifdef COMPAT_6_0
        oncha->cha_entryonly = FALSE;
#else
        oncha->cha_entryonly = refer->st_entryonly;
#endif

	oncha->cha_returnrefs = FALSE;
	oncha->cha_domaininfo = NULLPE;

	if(tk->tk_timed == FALSE)
	{
	    oncha->cha_timelimit = NULLCP;
	}
	else
	{
#ifdef CHAIN_ARGS_TIMEOUT
	    struct UTCtime	ut;
	    tm2ut(gmtime(&(tk->tk_timeout)), &(ut));
	    oncha->cha_timelimit = strdup(utct2str(&ut));
#else
	    oncha->cha_timelimit = NULLCP;
#endif
	}

	DLOG(log_dsap, LLOG_DEBUG, ("Checking history of op"));
	if(tk->tk_conn->cn_ctx == DS_CTX_X500_DAP)
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("... user originated ..."));
	    oncha->cha_originator = dn_cpy(tk->tk_conn->cn_dn);
	    oncha->cha_trace = NULLTRACEINFO;
	}
	else
	{
	    oncha->cha_originator = dn_cpy(tk->tk_dx.dx_arg.dca_charg.cha_originator);
	    oncha->cha_trace = ti_cpy(tkcha->cha_trace);
	}

	DLOG(log_dsap, LLOG_DEBUG, ("Setting trace info"));
	ti = (struct trace_info *) malloc(sizeof(struct trace_info));
	ti->ti_dsa = dn_cpy(mydsadn);
	ti->ti_target = dn_cpy(di->di_target);

	if(di->di_rdn_resolved <= 0)
	{
	    ti->ti_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    ti->ti_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    ti->ti_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    ti->ti_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}

	ti->ti_next = oncha->cha_trace;
	oncha->cha_trace = ti;

	 on->on_req.dca_dsarg = tk->tk_dx.dx_arg.dca_dsarg; 	/* struct copy */
/*
	(void) ds_arg_dup (&(tk->tk_dx.dx_arg.dca_dsarg), &(on->on_req.dca_dsarg));
*/

#ifdef COMPAT_6_0
	on->on_req.dca_dsarg.arg_sr.sra_subset = refer->st_subset;
#endif

	on->on_arg = &(on->on_req);

	DLOG(log_dsap, LLOG_DEBUG, ("Generating search subtask OP"));
	if( (ca->ca_servicecontrol.svc_options & SVC_OPT_CHAININGPROHIBIT) 
		|| (oper_chain(on) != OK))
	{
	   add_cref2poq (&tk->tk_resp.di_result.dr_res.dcr_dsres.res_sr, err.ERR_REFERRAL.DSE_ref_candidates);
	   oper_task_extract(on);
	   oper_free(on);
		
	   if (trail == NULL_ST)
		tk->refer_st = st_done(&refer);
	   else
		trail->st_next = st_done (&refer);
	} else {
		refer->st_cr = err.ERR_REFERRAL.DSE_ref_candidates;
		trail = refer;
	}
    }
    if (trail != NULL_ST)
	trail->st_next = tk->referred_st;
    tk->referred_st = tk->refer_st;
    tk->refer_st = NULL_ST;

    if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}

}

subtask_result_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct ds_search_task	**next_st;
    struct ds_search_task	* st;

    DLOG(log_dsap, LLOG_TRACE, ("subtask_result_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (result)extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote subtask result and here it is.
	*/
	next_st = &(tk->referred_st);
	for(st=tk->referred_st; st!=NULL_ST; st=(*next_st))
	{
	    if(st == on->on_subtask)
		break;

	    next_st = &(st->st_next);
	}
	if(st == NULL_ST)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_result_wakeup - subtask lost from referred list"));
	}
	else
	{
	    /*
	    *  Correlate uncorrelated search results from oper,
	    *  then merge with correlated search results of task.
	    */

	    struct ds_search_result * tk_sr = &(tk->tk_resp.di_result.dr_res.dcr_dsres.res_sr);
	    struct ds_search_result * op_sr = &(on->on_resp.di_result.dr_res.dcr_dsres.res_sr);

	    DLOG(log_dsap, LLOG_DEBUG, ("Collating a search result"));

	    st_comp_free (st);
	    (*next_st) = st->st_next;

	    correlate_search_results(op_sr);
	    if(tk_sr->srr_next == NULLSRR)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("Search result unallocated!"));
		tk_sr->srr_next = (struct ds_search_result *) calloc(1, sizeof(struct ds_search_result));
		tk_sr->srr_next->srr_correlated = TRUE;
		tk_sr->srr_next->srr_un.srr_unit = (struct ds_search_unit *) calloc(1, sizeof(struct ds_search_unit));
		tk_sr->srr_next->CSR_limitproblem = LSR_NOLIMITPROBLEM;
	    }

	    merge_search_results(tk_sr->srr_next, op_sr);
	}
	
	oper_extract(on);

	if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}
    }
}

subtask_error_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct ds_search_task	**next_st;
    struct ds_search_task	* st;

    DLOG(log_dsap, LLOG_TRACE, ("subtask_error_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (error) extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote subtask result and got a remote error.
	*  If it is a referral, then rechain the operation if appropriate
	*  otherwise dump the subtask and check the task for completion.
	*/
/*
	ds_error_free(&(tk->tk_resp.di_error.de_err));
*/
	tk->tk_error = &(on->on_resp.di_error.de_err);

	if((on->on_resp.di_error.de_err.dse_type == DSE_DSAREFERRAL)
  	|| (on->on_resp.di_error.de_err.dse_type == DSE_REFERRAL))
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("Try rechaining st"));
	    if(oper_rechain(on) == OK)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("Succeeded rechaining st"));
		return;
	    }
	    DLOG(log_dsap, LLOG_DEBUG, ("Failed rechaining st"));
	    add_cref2poq (&tk->tk_resp.di_result.dr_res.dcr_dsres.res_sr, on->on_resp.di_error.de_err.ERR_REFERRAL.DSE_ref_candidates);
	    on->on_resp.di_error.de_err.ERR_REFERRAL.DSE_ref_candidates = NULLCONTINUATIONREF;
	}

	next_st = &(tk->referred_st);
	for(st=tk->referred_st; st!=NULL_ST; st=(*next_st))
	{
	    if(st == on->on_subtask)
		break;

	    next_st = &(st->st_next);
	}
	if(st == NULL_ST)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_result_wakeup - subtask lost from referred list"));
	}
	else
	{
	    st_comp_free (st);
	    (*next_st) = st->st_next;
	}

	oper_extract(on);

	if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}
    }
}

subtask_fail_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct DSError	* err;
    struct ds_search_task	**next_st;
    struct ds_search_task	* st;

    DLOG(log_dsap, LLOG_TRACE, ("subtask_fail_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_FATAL, ("subtask_fail_wakeup: no task"));
	oper_extract(on);
	return;
    }
    else
    {
	next_st = &(tk->referred_st);
	for(st=tk->referred_st; st!=NULL_ST; st=(*next_st))
	{
	    if(st == on->on_subtask)
		break;

	    next_st = &(st->st_next);
	}
	if(st == NULL_ST)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_result_wakeup - subtask lost from referred list"));
	}
	else
	{
	    add_cref2poq (&tk->tk_resp.di_result.dr_res.dcr_dsres.res_sr, st->st_cr);
	    st_comp_free (st);
	    (*next_st) = st->st_next;
	}

	oper_extract(on);

	if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}
    }
    err = &(tk->tk_resp.di_error.de_err);
    if((err->dse_type != DSE_REFERRAL) && (err->dse_type != DSE_DSAREFERRAL))
    {
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_UNAVAILABLE;
    }
}

subtask_dsa_info_wakeup(di)
struct di_block	* di;
{
    struct task_act	* tk = di->di_task;


    DLOG(log_dsap, LLOG_TRACE, ("subtask_dsa_info_wakeup"));

    if (tk == NULLTASK) {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_dsa_info_wakeup no task"));
	return;
    }

    /*
    * Were waiting for a reference to return.
    * Check if the reference now returned is acceptable.
    * If it is return a referral and unwrap everything,
    * otherwise try another di_block for the reference.
    */

    if(di2cref(di, &(tk->tk_resp.di_error.de_err), tk->tk_conn->cn_ctx) != OK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_dsa_info_wakeup - reference not acceptable"));
	/* Remove di_block which generated unwanted referral wait */
	if(tk->tk_dsas == NULL_DI_BLOCK)
	{
	    /* No more dsas from which to request info to form referral */
	    tk->tk_resp.di_error.de_err.dse_type = DSE_SERVICEERROR;
	    tk->tk_resp.di_error.de_err.ERR_SERVICE.DSE_sv_problem = DSE_SV_INVALIDREFERENCE;
	    task_conn_extract(tk);
	    task_error(tk);
	    task_extract(tk);
	    return;
	}
	return;
    }

    task_conn_extract(tk);
    task_error(tk);
    task_extract(tk);
}


add_cref2poq (res,cref)
struct ds_search_result *res;
ContinuationRef cref;	
{
ContinuationRef cr;	

	if (res->CSR_cr == NULLCONTINUATIONREF) {
		res->CSR_cr = cref;
		return;
	}
	for (cr = res->CSR_cr; cr->cr_next != NULLCONTINUATIONREF; cr=cr->cr_next)
		;

	cr->cr_next = cref;
}

relay_dsa (on)
struct oper_act * on;
{
struct DSError  err;
struct di_block *di = NULL_DI_BLOCK;
Entry my_entry;
Attr_Sequence as, entry_find_type();
AV_Sequence avs;
struct di_block	**di_trail;
struct dn_seq	* dn_stack = NULLDNSEQ;

	if (  (on == NULLOPER)
	   || (on->on_relay == FALSE)
	   || (on->on_relay == 2)	/* relayed once before */
	   || (on->on_task == NULLTASK)
	   || (on->on_task->tk_conn == NULLCONN)
	   || (on->on_task->tk_conn->cn_ctx != DS_CTX_X500_DAP))
		return NOTOK;

	if ((my_entry = local_find_entry_aux (mydsadn,TRUE)) == NULLENTRY)
		return NOTOK;

	if (( as = entry_find_type (my_entry,at_relaydsa)) == NULLATTR)
		return NOTOK;

	di_trail = &di;

	for (avs = as->attr_value; avs != NULLAV; avs=avs->avseq_next) {
		if (avs->avseq_av.av_struct == NULL)
			continue;

		switch(get_dsa_info((DN)avs->avseq_av.av_struct, dn_stack,
		       &err, di_trail))
		{
		case DS_OK:
		    /* di_trail is a completed dsa info block */
		    DLOG(log_dsap, LLOG_DEBUG, ("In relay gdiOK:"));
#ifdef DEBUG
		    di_list_log(*di_trail);
#endif
		    (*di_trail)->di_target = NULLDN;
		    di_trail = &((*di_trail)->di_next);
		    break;

		case DS_CONTINUE:
		    /* di_trail is a deferred dsa info block */
		    DLOG(log_dsap, LLOG_DEBUG, ("In relay gdiCONT:"));
#ifdef DEBUG
		    di_list_log(*di_trail);
#endif
		    (*di_trail)->di_target = NULLDN;
		    di_trail = &((*di_trail)->di_next);
		    break;

		case DS_X500_ERROR:
		    /* Error encountered generating di_block */
		    DLOG(log_dsap, LLOG_NOTICE, ("relay - get_dsa_info (slave) returned X500 ERROR"));
		    ds_error_free(&err);
		    break;

		default:
		    LLOG(log_dsap, LLOG_EXCEPTIONS, ("dsa_info_new - get_dsa_info (master) unexpected return"));
		    break;
		}

	}

	on->on_relay = 2;	/* Don't relay twice to same DSA ! */

	if (di == NULL_DI_BLOCK)
		return NOTOK;

	on->on_dsas = di;

	return OK;

}