/*
 * Copyright (C) 1994, 1995 Free Software Foundation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can either send email to this
 * program's author (see below) or write to:
 *
 *              The Free Software Foundation, Inc.
 *              675 Mass Ave.
 *              Cambridge, MA 02139, USA. 
 *
 * Please send bug reports, etc. to zappo@gnu.ai.mit.edu.
 *
 * et_proto.c
 *
 * Purpose:
 *  This file implements the talk protocol.  It is dependant on the
 * recursive nature of the select_all fn which is similar to RPC
 * calls.  This will allow us to detect timeouts and all that kind of
 * good stuff, and view it logically in a linear manor.
 * 
 * $Log: et_proto.c,v $
 * Revision 1.18  1995/07/23  18:00:08  zappo
 * When in test mode, variable "myname" is now filled in by querying with
 * the "getpw" functions.  Used to get the environment $USER which
 * doesn't exist on all systems.
 *
 * Revision 1.17  1995/07/16  16:34:23  zappo
 * Code involving degrading talk protocols is now much more robust.  In
 * fact, it works now.  Uses USER_hangup command to clean up from failed
 * attempts instead of poking information in by hand.  GET_MSG_RETRY now
 * works the way it was indended.  PROTOCOL_test now tests sun-forwarding
 * method correctly after doing a GNU daemon check.
 *
 * Revision 1.16  1995/07/15  19:48:27  zappo
 * Modified section dealing with "LOOK_HERE".  no longer terminate user
 * struct, but recycle when the userid is re-used by parent process.
 * Also added more error-checking when looking up name from address
 *
 * Revision 1.15  1995/05/25  23:12:52  zappo
 * the static/local "io" variable was not reliably cleared: fixed.
 *
 * Revision 1.14  1995/05/09  01:52:32  zappo
 * reply function did not always return FAIL to the controlling process
 * when errors occured, thus locking emacs up until a C-g was pressed.
 *
 * Revision 1.13  1995/04/08  20:05:19  zappo
 * Replaced some malloc/strcpy with strdups
 *
 * Revision 1.12  1995/04/01  17:04:06  zappo
 * Fixed a rouge slash, and added an appname before the announcement to
 * test the ability of gtalk to read this in correctly.
 *
 * Revision 1.11  1995/03/25  03:35:21  zappo
 * Update copyright.
 *
 * Revision 1.10  1995/03/23  02:58:45  zappo
 * Greatly improved the testing facility.
 *
 * Revision 1.9  1995/01/30  02:27:06  zappo
 * Fixed non-apparent host problem to prevent a core dump
 *
 * Revision 1.8  1995/01/29  14:24:24  zappo
 * Fixed some -Wall warnings
 *
 * Revision 1.7  1994/12/12  23:51:48  zappo
 * changed headers and fixed up test procedure a wee bit
 *
 * Revision 1.6  1994/12/07  23:18:55  zappo
 * Added checks to make sure that a daemon has a type before sending
 * various messages.
 *
 * Revision 1.5  1994/11/24  02:46:50  zappo
 * Include sitecnfg.h which contains site configurable parameters.
 *
 * Revision 1.4  1994/11/17  03:07:28  zappo
 * Added extra checks for announcement and reply-query check.
 *
 * Revision 1.3  1994/10/11  00:30:07  zappo
 * Improved testing for both etalk vs BSD/Sun and gtalkd testing.
 *
 * Revision 1.2  1994/09/15  02:06:26  zappo
 * Added cast to printing of sockaddress in test function
 *
 * Revision 1.1  1994/08/29  23:28:21  zappo
 * Initial revision
 *
 * History:
 * interran@uluru.Stanford.EDU (John Interrante)
 *   Spelling error in PROTOCOL_test.
 * eml
 *   Increased the number of things tested against for etalk.
 *
 * ::Header:: etalk.h 
 */
#include "etalklib.h"
#include "etalk.h"
#include "gtalk.h"		/* this contains retry limits and such */
#include "talk.h"
#include "otalk.h"
#include "etl_union.h"

#include "sitecnfg.h"

#ifdef HAVE_PWD_H
#include <pwd.h>
#endif

/* The current objects used during user aquisition
 */
static struct UserObject  *uobj = NULL;
static struct InputDevice *io = NULL;
static int                 announce_vers;
static int                 version_check = FALSE;
static char               *idlestr = "IDLE";
static char               *status = "IDLE";
static int                 testringerflag = 0;
/*
 * This macro will simply encompass a loop to try to get a UDP message.
 * for the following attach function.  I should probably replace this
 * with something kinder in the future.
 */
#define GET_MSG(fn, io) \
{ int cnt = 0; if(!uobj || !(io) || !io) { status = idlestr; return; } do \
    { if(uobj->state == USER_CLOSED) \
	{ PROTOCOL_delete_all(Ctxt, TRUE); status = idlestr; return; } \
	if(cnt >= (UDP_RETRY)) \
	{ uobj->state = USER_UNREACHABLE; USER_hangup(Ctxt, uobj->id); \
	    printf("\03Retry limit reached. Abandoning Call.\n"); \
	    PROTOCOL_delete_all(Ctxt, TRUE); \
	    (io)->readme = NULL; status = idlestr; return; } \
	if(!(fn)) \
	{ uobj->state = USER_UNREACHABLE; USER_hangup(Ctxt, uobj->id); \
	    printf("\03Error running function, no more actions taken.\n"); \
	    PROTOCOL_delete_all(Ctxt, TRUE); \
	    (io)->readme = NULL; status = idlestr; return; } \
	(io)->timeout = (UDP_LIFE); (io)->readme = no_action; cnt++; } \
    while(ET_select_all(Ctxt, io) == Fail); \
    (io)->timeout = 0; (io)->readme = NULL; }

/* similar to previous macro, but instead worries about version checks
 */
#define GET_MSG_RETRY(fn, io) \
{ int cnt = 0; if(!uobj || !(io) || !io) { status = idlestr; return; } do \
    { if(uobj->state == USER_CLOSED) \
	{ PROTOCOL_delete_all(Ctxt, TRUE); status = idlestr; return; } \
	if(cnt >= (UDP_RETRY)) \
	{ printf("\03Retry limit reached. Checking Version.\n"); \
	    version_check = TRUE; \
	    (io)->readme = NULL; break; } \
	if(!(fn)) \
	{ printf("\03Error running function, no more actions taken.\n"); \
	    PROTOCOL_delete_all(Ctxt, TRUE); \
	    (io)->readme = NULL; status = idlestr; return; } \
	(io)->timeout = (UDP_LIFE); (io)->readme = no_action; cnt++; } \
    while(ET_select_all(Ctxt, io) == Fail); \
    (io)->timeout = 0; (io)->readme = NULL; }


/*
 * Function: no_action
 *
 * Do nothing so that the readme flag gets set...
 * 
 * Parameters: Ctxt - talk context
 *             io   - the input device
 * History:
 * eml 4/6/94
 */
static void no_action(Ctxt, io)
     struct TalkContext *Ctxt;
     struct InputDevice *io;
{ /* Do nothing so README fn can be set while waiting about... */ }

/*
 * Function: display_timeout
 *
 * display a message discussing timeout till next announcement.
 * 
 * Parameters: Ctxt - context
 *             io - the io device in question
 * History:
 * eml 4/15/94
 */
void display_timeout(Ctxt, io)
     struct TalkContext *Ctxt;
     struct InputDevice *io;
{
  /* if the calling user object is blank, ignore. */
  if(!uobj) 
    {
      printf("protocol display_timeout called with no active user obect.\n");
      return;
    }
  
  printf("\03%cCalling %s: %d Announces, %d seconds till next ring.\n", 
	 TTY_NOLOG,
	 uobj->name,
	 announce_vers,
	 io->timeout);
}

/*
 * Function: PROTOCOL_connect
 *
 * If we mysteriously get a socket number, use that to connect directly
 * to some remote.
 * 
 * Parameters: Ctxt   - talk context
 *             userid - the user id of the emacs socket available
 *             sock   - the socket id on remote's machine
 *             user   - the user name
 *             node   - the remote node name
 *             tty    - the tty of remote
 * History:
 * eml 4/18/94
 */
void PROTOCOL_connect(Ctxt, userid, sock, user, node, tty)
     struct TalkContext *Ctxt;
     int userid;
     int sock;
     char *user;
     char *node;
     char *tty;
{
  struct HostObject *host;
  struct InputDevice *newtcp;
  struct sockaddr_in addr;

  /* now get the user struct from our list...
   */
  uobj = USER_find(userid);

  if(!uobj) {
    printf("Bad user id structure...\n");
    uobj = NULL;
    io = NULL;
    return;
  }

  if(uobj->state != USER_NEW)
    {
      printf("Attempt to call out on a used user struct!");
      uobj = NULL;
      io = NULL;
      return;
    }
  announce_vers = 1;

  uobj->name = strdup(user);

  uobj->local->name = (char *)malloc(strlen(user) + 8);
  sprintf(uobj->local->name, "TCP to %s", uobj->name);

  /* Ok, now just try to connect...
   */
  host = HOST_gen_host(node);

  if(!host)
    {
      printf("Cannot connect to %s because host does not exist.\n",
	     node);
      uobj->state = USER_CLOSED;
      ET_clean_dev(uobj->local);
      return;
    }

  addr = host->addr;
  addr.sin_port = htons(sock);

  newtcp = TCP_connect((struct sockaddr *)&addr);
  
  if(newtcp)
    {
      newtcp->state = CONNECTED;
      newtcp->readme = STREAM_remote_read;
      newtcp->timeout = 0;
      newtcp->timefn = 0;
	  
      uobj->local->state = CONNECTED;
      uobj->remote = newtcp;
      uobj->state = USER_CONNECTED;
	  
      uobj->remote->name = (char *)malloc(strlen(user) + 10);
      sprintf(uobj->remote->name, "TCP from %s", uobj->name);
	  
      ET_send(newtcp, Ctxt->editkeys, NUMEDITKEYS);
    }
  else
    {
      printf("\03Unable to connect to %s.\n", node);
      uobj->state = USER_CLOSED;
      ET_clean_dev(uobj->local);
    }
  uobj = NULL;
}

/*
 * Function: PROTOCOL_wait
 *
 * Wait for a connection to a given user id.
 * 
 * Parameters: Ctxt - talk context
 *             userid - the user id of the emacs socket available
 *             user - the user name
 *             node - the remote node name
 *             tty - the tty of remote
 * History:
 * eml 4/18/94
 */
void PROTOCOL_wait(Ctxt, userid, user, node, tty)
     struct TalkContext *Ctxt;
     int                 userid;
     char               *user;
     char               *node;
     char               *tty;
{
  struct InputDevice *newtcp;

  if(io || uobj)
    {
      printf("\03You may only make one outgoing call at a time!\n");
      return;
    }
  uobj = USER_find(userid);

  if(!uobj) {
    printf("Bad user id structure...\n");
    uobj = NULL;
    io = NULL;
    return;
  }

  if(uobj->state != USER_NEW)
    {
      printf("Attempt to call out on a used user struct!");
      uobj = NULL;
      io = NULL;
      return;
    }
  
  status = "Waiting for call with known address.";

  /*
   * Wait for connection from the remote...
   */
  Ctxt->remote_connect->readme  = no_action;
  Ctxt->remote_connect->timeout = RING_WAIT;
  Ctxt->remote_connect->timefn  = NULL;

  while(ET_select_all(Ctxt, Ctxt->remote_connect) == Fail)
    {
      /* first, check to see if we are even checking anymore...
       */
      if(!uobj) 
	{
	  status = idlestr;
	  return;
	}
      Ctxt->remote_connect->timeout = RING_WAIT;
    }
  
  Ctxt->remote_connect->readme  = NULL;
  Ctxt->remote_connect->timeout = 0;
  Ctxt->remote_connect->timemsg = NULL;
  /*
   * now run accept on the socket to get the new user and do the final
   * setup
   */
  newtcp = TCP_accept(Ctxt->remote_connect);
  
  newtcp->state = CONNECTED;
  newtcp->readme = STREAM_remote_read;
  newtcp->timeout = 0;
  newtcp->timefn = 0;

  uobj->local->state = CONNECTED;
  uobj->remote = newtcp;
  uobj->state = USER_CONNECTED;

  uobj->remote->name = (char *)malloc(strlen(user) + 10);
  sprintf(uobj->remote->name, "TCP from %s", uobj->name);

  ET_send(newtcp, Ctxt->editkeys, NUMEDITKEYS); 

  /* cleanup static varialbes used herin
   */
  uobj = NULL;
  io = NULL;

  status = idlestr;
}

/*
 * Function: PROTOCOL_reply
 *
 *   Ask local server who to the last person to send us an
 * announcement was, and print out that information.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt   - Context
 *
 * History:
 * zappo   11/18/94   Created
 */
void PROTOCOL_reply(Ctxt)
     struct TalkContext *Ctxt;
{
  struct sockaddr_in *addr;	/* address used for TCP creation */
  int cnt = 0; 

  if(io || uobj)
    {
      printf("\03%cFAIL\n", TTY_REPLY_USER);
      printf("\03You may only make one outgoing call request at a time!\n");
      return;
    }

  /* First, make sure we have an IO device for talking to remote daemon.
   */
  io = Ctxt->local_daemon;	/* use local daemon for remote daemon */
  
  if(io->host->type <= TALK_VERSION)
    {
      printf("\03%cFAIL\n", TTY_REPLY_USER);
      printf("\03You may only use reply if YOUR machine is GNU talk.\n");
      io = NULL;
      return;
    }

  status = "Recieving REPLY information.";

  do
    { 
      if(cnt >= (UDP_RETRY))
	{
	  printf("\03%cFAIL\n", TTY_REPLY_USER);
	  printf("\03Retry limit reached. Abandoning Call.\n");
	  io->readme = NULL; 
	  io = NULL;
	  status = idlestr; 
	  return; 
	}
      if(!DMN_Reply_Query(Ctxt, io))
	{
	  printf("\03%cFAIL\n", TTY_REPLY_USER);
	  printf("\03Error running function, no more actions taken.\n");
	  io->readme = NULL;
	  io = NULL;
	  status = idlestr;
	  return; 
	}
      io->timeout = (UDP_LIFE); 
      io->readme = no_action; cnt++; 
    }
  while(ET_select_all(Ctxt, io) == Fail);

  io->timeout = 0; 
  io->readme = NULL;

  addr = DMN_get_reply_query_response(Ctxt->local_daemon);

  if(addr == (struct sockaddr_in *)NULL)  
    {
      printf("\03%cFAIL\n", TTY_REPLY_USER);
      printf("\03No one is calling you right now.\n");
    }
  else
    {
      char *user;
      struct HostObject *host;

      user = DMN_last_response_user(io);
      host = HOST_gen_host_by_addr((struct sockaddr *)addr, sizeof(addr));

      printf("\03%c%s@%s\n", TTY_REPLY_USER, user, host->name);
    }
  io = NULL;
  status = idlestr;
}

/*
 * Function: PROTOCOL_attach
 *
 * Simply do the "talk thing" to connect to a remote user.
 * 
 * Parameters: Ctxt - talk context
 *             uid  - userobject id number.
 *             user - the user
 *             node - the node to attach to
 *             tty  - the users tty.
 * History:
 * eml 4/6/94
 */
void PROTOCOL_attach(Ctxt, userid, user, node, tty)
     struct TalkContext *Ctxt;
     int                 userid;
     char               *user;
     char               *node;
     char               *tty;
{
  struct sockaddr_in *addr;	/* address used for TCP creation */
  struct InputDevice *newtcp;	/* new TCP socket                */

  if(io || uobj)
    {
      printf("\03You may only make one outgoing call at a time!\n");
      return;
    }

  /* First, make sure we have an IO device for talking to remote daemon.
   */
  if(!node) {
    io = Ctxt->local_daemon;	/* use local daemon for remote daemon */
    node = "";
  } else {
    io = UDP_host(node);
  }
  if(!tty) tty = "";
  
  /* Do nothing if there is not daemon. */
  if(io->host && (io->host->type == -1))
    {
      printf("\03That host has no daemon.\n");
      io = NULL;
      return;
    }

  /* now get the user struct from our list...
   */
  uobj = USER_find(userid);

  if(!uobj) {
    printf("Bad user id structure...\n");
    uobj = NULL;
    io = NULL;
    return;
  }

  if(uobj->state != USER_NEW)
    {
      printf("Attempt to call out on a used user struct!");
      uobj = NULL;
      io = NULL;
      return;
    }
  announce_vers = 1;

  uobj->name = strdup(user);

  uobj->local->name = (char *)malloc(strlen(user) + 8);
  sprintf(uobj->local->name, "TCP to %s", uobj->name);

  status = "Looking for invite.";

  do
    {
      version_check = FALSE;
      /* Now, lets find out if we have been invited or not by asking that
       * remote machine...
       */
      printf("\03Sending lookup message [V %d].\n", io->host->type);

      GET_MSG_RETRY(DMN_Lookup(Ctxt, io, user, tty), io);
      if(!version_check)
	addr = DMN_get_lookup_response(io);
      else
	addr = NULL;

      if(addr == (struct sockaddr_in *)-1) version_check = TRUE;
      
      if(addr && (addr != (struct sockaddr_in *)-1))
	{
	  addr->sin_family = ntohs(addr->sin_family);
	  printf("Address found:");
	  print_sockaddr((struct sockaddr *)addr);
	  printf("\n");
	  
	  newtcp = TCP_connect((struct sockaddr *)addr);
	  
	  if(newtcp)
	    {
	      newtcp->state = CONNECTED;
	      newtcp->readme = STREAM_remote_read;
	      newtcp->timeout = 0;
	      newtcp->timefn = 0;
	      
	      uobj->local->state = CONNECTED;
	      uobj->remote = newtcp;
	      uobj->state = USER_CONNECTED;
	      
	      uobj->remote->name = (char *)malloc(strlen(user) + 10);
	      sprintf(uobj->remote->name, "TCP from %s", uobj->name);
	      
	      ET_send(newtcp, Ctxt->editkeys, NUMEDITKEYS);
	      
	      /* make sure we don't delete non-existing thingies
	       */
	      uobj = NULL;
	      io = NULL;
	      
	      status = idlestr;

	      return;
	    }
	  else
	    {
	      /*
	       * If we get crap from the daemon, just call them!!
	       */
	      printf("\03Invite information failed.  Calling out...\n");
	    }
	}
      else
	{
	  /* check for potential errors, and we may need to downgrade the
	   * supposed talk daemon we are connecting to.
	   */
	  if(version_check)
	    {
	      /* The daemon may or may not respond to this query,
	       * so slowly drop to nothing...
	       */
	      if(io->host->type == 0)
		{
		  printf("\03Host %s has no apparent talk daemon (what?)!!\n",
			 io->host->name);
		  uobj->state = USER_UNREACHABLE;
		  USER_hangup(Ctxt, uobj->id);
		  io = NULL;
		  uobj = NULL;
		  status = idlestr;
		  return;
		}
	      io->host->type--;
	      UDP_daemon_change(io);
	    }
	  /*
	   * If we do get a response, and no addr, then
	   * check version error.
	   */
	  else if(DMN_last_response_numeric(io) == BADVERSION)
	    {
	      if(io->host->type == OTALKD)
		{
		  printf("\03Host %s has no apparent talk daemon!!\n",
			 io->host->name);
		  uobj->state = USER_UNREACHABLE;
		  USER_hangup(Ctxt, uobj->id);
		  io = NULL;
		  uobj = NULL;
		  status = idlestr;
		  return;
		}
	      /* In this case, we must degrade down to the level descibed.
	       */
	      printf("\03Downgrading daemon versions...\n");
	      io->host->type = DMN_last_response_version(io);
	      UDP_daemon_change(io);
	    }
	  /* and if that is all ok, then just call out!!
	   */
	}
    } while((DMN_last_response_numeric(io) == BADVERSION) || version_check);

  /* if we have not yet connected, then we must:
   * 1) prepare TCP listener for a connection.
   * do {
   *   2) leave an invitation
   *   3) announce to terminal
   * } while we don't hear from him.
   *
   *************
   *
   * Loop on the RING-WAIT thing after doing the invite/announce thing.
   */
  status = "Making outgoing call.";

  if(verbose)
    printf("Sending invitation message.\n");

  GET_MSG(DMN_LeaveInvite(Ctxt, user, tty), Ctxt->local_daemon);
  if(DMN_get_invite_response(Ctxt->local_daemon) == Fail) 
    {
      printf("\03Error leaving invitation for %s is [%s]\n", 
	     user, DMN_last_response(Ctxt->local_daemon));
      PROTOCOL_delete_all(Ctxt, TRUE);
      status = idlestr;
      return;
    }
  printf("\03Announcing...\n");
  GET_MSG(DMN_Announce(Ctxt, io, user, tty), io);
  if(DMN_get_announce_response(io) == Fail) 
    {
      /* In this case, just tell parent where they should be looking, */
      /* and let adjust their address/pointers, and they can then     */
      /* re-assign the current user-object.                           */
      if(DMN_last_response_numeric(io) == TRY_HERE)
	{
	  struct HostObject *host;

	  host = HOST_gen_host_by_addr((struct sockaddr *)DMN_last_addr(io),
				       sizeof(struct sockaddr));

	  if(!host)
	    {
	      printf("\03Address: ");
	      print_sockaddr((struct sockaddr *)DMN_last_addr(Ctxt->local_daemon));
	      printf("\n\03Talk Daemon responded LOOK_HERE but address is invalid.\n");
		     
	    }
	  else
	    {
	      printf("\03%c%d %s\n", TTY_LOOK_HERE, uobj->id, host->name);
	    }

	  /* reguardless of look_here, the daemon won't let us connect
	   * so shutdown now.  DO NOT TERMINATE USER OBJECT.  This
	   * will be (maybe) recycled by the controlling process
	   */
	  PROTOCOL_delete_all(Ctxt, FALSE);
	  uobj = NULL;		/* reset user object  */
	  io   = NULL;		/* cancel io object   */
	  ET_reset_ids();	/* reset callout ids  */
	  ET_end_recursion();	/* now cancel any lazy calls */
	  status = idlestr;
	  return;
	}
      else
	{
	  printf("\03Response to announcement to %s is [%s]\n",
		 user, DMN_last_response(io));
	  PROTOCOL_delete_all(Ctxt, TRUE);
	  status = idlestr;
	  return;
	}
    }
  
  /*
   * now wait for the connect.  If connect occurs during this time,
   * reads will be buffered... I hope anyway... so prepare rc for cnct
   */
  Ctxt->remote_connect->readme  = no_action;
  Ctxt->remote_connect->timeout = RING_WAIT;
  Ctxt->remote_connect->timefn  = NULL;
  Ctxt->remote_connect->timemsg = display_timeout;

  while(ET_select_all(Ctxt, Ctxt->remote_connect) == Fail)
    {
      /*
       * Check for a behind the back cancel of this opperation.
       */
      if(!uobj || !io) 
	{
	  status = idlestr;
	  return;
	}
      /* buffer connects until we finish doing out UDP thing 
       * by simply removing it from the Q of Input devices to read from.
       */
      Ctxt->remote_connect->readme  = NULL;
      Ctxt->remote_connect->timeout = 0;
      /*
       * There was no connect, so delete the old ones, and put in some
       * new ones...
       */ 
      PROTOCOL_delete_all(Ctxt, 0);
      /*
       * setup for receiving again.
       */
      Ctxt->remote_connect->readme  = no_action;
      Ctxt->remote_connect->timeout = RING_WAIT;
      /*
       * Now, reannounce, and leave a new copy of the invitation...
       */
      if(verbose)
	printf("Sending invite message.\n");

      GET_MSG(DMN_LeaveInvite(Ctxt, user, tty), Ctxt->local_daemon);
      if(DMN_get_invite_response(Ctxt->local_daemon) == Fail)
	{
	  printf("\03Error leaving invitation for %s is  [%s]\n", 
		 user, DMN_last_response(Ctxt->local_daemon));
	  PROTOCOL_delete_all(Ctxt, TRUE);
	  status = idlestr;
	  return;
	}

      printf("\03Announcing again [%d]...\n", announce_vers++);

      GET_MSG(DMN_Announce(Ctxt, io, user, tty), io);
      if(DMN_get_announce_response(io) == Fail)
	{
	  printf("\03Response to announcement to %s is [%s]\n",
		 user, DMN_last_response(io));
	  PROTOCOL_delete_all(Ctxt, TRUE);
	  status = idlestr;
	  return;
	}
  
    }
  /* there shouldn't be any more remote buffer connects until we
   * finish doing out UDP thing by simply removing it from the Q of
   * Input devices to read from.
   */
  Ctxt->remote_connect->readme  = NULL;
  Ctxt->remote_connect->timeout = 0;
  Ctxt->remote_connect->timemsg = NULL;
  /*
   * we have a connection, so delete the announces and invites, and
   * put in some new ones...
   */ 
  PROTOCOL_delete_all(Ctxt, 0);
  /*
   * now run accept on the socket to get the new user and do the final
   * setup
   */
  newtcp = TCP_accept(Ctxt->remote_connect);
  
  newtcp->state = CONNECTED;
  newtcp->readme = STREAM_remote_read;
  newtcp->timeout = 0;
  newtcp->timefn = 0;

  uobj->local->state = CONNECTED;
  uobj->remote = newtcp;
  uobj->state = USER_CONNECTED;

  uobj->remote->name = (char *)malloc(strlen(user) + 10);
  sprintf(uobj->remote->name, "TCP from %s", uobj->name);

  ET_send(newtcp, Ctxt->editkeys, NUMEDITKEYS); 

  /* cleanup static varialbes used herin
   */
  ET_reset_ids();
  io = NULL;
  uobj = NULL;
}

/*
 * Function: PROTOCOL_delete_all
 *
 * Delete the last announcement, and the last invitation.
 * 
 * Parameters: uobj - user object of current pass
 *
 * History:
 * eml 4/15/94
 */
void PROTOCOL_delete_all(Ctxt, terminate)
     struct TalkContext *Ctxt;
     int                 terminate;
{
  if(!io && !uobj) return;

  /* don't try this unless we have a daemon type. */
  if(io->host && (io->host->type == -1))
    return;

  status = "Deleting Announcements.";
  
  /*
   * We cannot use the nifty macro or we get a nasty little recursive 
   * problem.
   */
  if(verbose)
    printf("Sending delete announce message.\n");

  {
    int cnt = 0; 
    do {
      if(cnt >= (UDP_RETRY))
	{
	  printf("\03Retry limit reached. Can't delete announce.\n"); 
	  ET_clean_dev(uobj->local);
	  uobj->state = USER_CLOSED;
	  io->readme = NULL;
	  break;
	}
      else
	{
	  if(!DMN_Delete(Ctxt, io, DMN_announce))
	    { 
	      printf("\03Error running delete invite function.\n");
	      close(uobj->local->fd);
	      ET_clean_dev(uobj->local);
	      uobj->state = USER_CLOSED;
	      io->readme = NULL;
	      break;
	    }
	  io->timeout = (UDP_LIFE);
	  io->readme = no_action;
	  cnt++; 
	}
    }
    while(ET_select_all(Ctxt, io) == Fail);
    io->timeout = 0; 
    io->readme = NULL;
  }
  if(DMN_get_delete_response(io) == Fail)
    {
      printf("\03Delete announce error  %s\n", DMN_last_response(io));
    }
  if(verbose)
    printf("Sending delete invite message.\n");

  {
    int cnt = 0; 
    do {
      if(cnt >= (UDP_RETRY))
	{
	  printf("\03Retry limit reached. Can't delete invite.\n"); 
	  ET_clean_dev(uobj->local);
	  uobj->state = USER_CLOSED;
	  Ctxt->local_daemon->readme = NULL;
	  break;
	}
      else
	{
	  if(!DMN_Delete(Ctxt, Ctxt->local_daemon, DMN_invite))
	    { 
	      printf("\03Error running delete invite function.\n");
	      ET_clean_dev(uobj->local);
	      uobj->state = USER_CLOSED;
	      Ctxt->local_daemon->readme = NULL;
	      break;
	    }
	  Ctxt->local_daemon->timeout = (UDP_LIFE);
	  Ctxt->local_daemon->readme = no_action; 
	  cnt++; 
	}
    }
    while(ET_select_all(Ctxt, Ctxt->local_daemon) == Fail);
    Ctxt->local_daemon->timeout = 0; 
    Ctxt->local_daemon->readme = NULL;
  }
  if(DMN_get_delete_response(Ctxt->local_daemon) == Fail)
    {
      printf("\03Delete invite error  %s\n", 
	     DMN_last_response(Ctxt->local_daemon));
    }

  Ctxt->remote_connect->readme = NULL;
  Ctxt->remote_connect->timeout = 0;

  if(terminate)
    {
      if(verbose)
	printf("Executing terminate call information...\n");

      io = NULL;
      if(uobj)
	uobj->state = USER_CLOSED;
      if(uobj && uobj->local)
	{
	  ET_clean_dev(uobj->local);
	}
      uobj = NULL;
      ET_reset_ids();
      ET_end_recursion();	/* now cancel any lazy calls */
    }

  status = idlestr;
}


/*
 * Function: PROTOCOL_status
 *
 *   Return a string point to a description of the current status of
 * any given operation which may be happening.
 *
 * Returns:     char * - 
 * Parameters:  Ctxt - Context
 *
 * History:
 * zappo   11/19/94   Created
 */
char *PROTOCOL_status(Ctxt)
     struct TalkContext *Ctxt;
{
  return status;
}

/*
 * Function: PROTOCOL_abort
 *
 * Abort any currently active call.
 * 
 * Parameters: Ctxt - the current talk context
 *
 * History:
 * eml 4/19/94
 */
void PROTOCOL_abort(Ctxt)
     struct TalkContext *Ctxt;
{
  if(!io && !uobj)
    {
      printf("No active calls waiting...\n");
      return;
    }

  status = "Aborting ongoing call.";

  /* Basically, null out any active call structures.
   */
  if(io)
    {
      io->readme = NULL;
      io = NULL;
    }
  if(uobj)
    {
      if(verbose)
	printf("Call to %s UID %d deleted.\n",
	       uobj->name?uobj->name:"Unknown", uobj->id);

      ET_clean_dev(uobj->local);
      uobj->state = USER_CLOSED;
      uobj = NULL;
    }
  if(Ctxt->remote_connect->timemsg)
    {
      Ctxt->remote_connect->timemsg = NULL;
      Ctxt->remote_connect->timeout = 1; /* to free a waitfor */
      Ctxt->remote_connect->readme = NULL;
    }

  status = idlestr;
}


/*
 * Function: PROTOCOL_ringerread, PROTOCOL_test
 *
 * Test the local talk daemon by looking up USER.  Don't bother
 * reading the message in since we'll just quit in a minute anyway.
 *
 * The ringread function is used solely for reading/printing the
 * contents of a ringer message passed in during a -t test.
 *
 * Parameters:  Ctxt - talk context
 *
 * History:
 * eml	May 27, 1994  Created
 * eml  Aug 10, 1994  Spelling, OTALKD and NTALKD not OTALK and NTALK
 * eml  Aug 19, 1994  Added much more comprehensive test of the
 *                    whole protocol exchange between daemon and server.
 * zappo   9/14/94    Added cast to sockaddr printing function.
 * zappo   9/24/94    Improved error checking for no-daemon conditions.
 * zappo   10/1/94    Added check when daemon forgets our invite
 * zappo   11/16/94   Added Announcement and reply-query check
 * zappo   12/12/94   Fixed up some printing.
 * zappo   3/15/95    Added PROTOCOL_ringread for use with ringer
 *                    testing functions, and added numbers before each
 *                    testing seequence.
 */
static void PROTOCOL_ringerread(Ctxt, dev)
     struct TalkContext *Ctxt;
     struct InputDevice *dev;
{
  GNU_RING_CONTROL    rc;
  GNU_RING_RESPONSE   rr;
  struct InputDevice *newudp;

  printf("3.2) ringerread: Recieved a ringer message.\n");
  if(ET_recv(dev, &rc, sizeof(rc)) == Fail)
    {
      printf("3.2) Failed to read in ringer message from daemon!\n");
      return;
    }

  /* Fix byte swapping in the family so we can use this address. */
  ((struct sockaddr_in *)&rc.addr)->sin_family = 
    ntohs(((struct sockaddr_in *)&rc.addr)->sin_family);

  /* We could also use dev->lraddr, but this is more stable. In     */
  /* addition, this particular address will always be recycled too. */
  newudp = UDP_byaddr(dev, (struct sockaddr_in *)&rc.addr);
  newudp->name = "daemon_ringport";
 
  printf("3.2.2) ringerread: Daemon ringport at: ");
  print_sockaddr((struct sockaddr *)&rc.addr);
  printf("\n");
  
  /* Always print this: This will let us see the full message in the log. */
  if(verbose) RING_print_control(&rc);

  /* Now formulate a response ... */
  rr.vers = TALK_VERSION_GNU;
  rr.type = rc.type;
  rr.answer = SUCCESS;
  
  if(verbose) RING_print_response(&rr);

  if(ET_send(newudp, &rr, sizeof(rr)) == Fail)
    printf("3.2.2) ringerread: Failed to send response back to daemon!\n");
  else
    printf("3.2.2) ringerread: Sent response back to daemon.\n");

  /* if(verbose) RING_print_response(&rr); */
  printf("3.2.2) ringerread: Person calling is %s\n", rc.name);

  testringerflag = 1;

  return;  
}

int PROTOCOL_test(Ctxt)
     struct TalkContext *Ctxt;
{
  int myflag = 1;
  Ctxt->myname = "testing";

  printf("1) Checking for daemon: Sending test lookup message [V %d].\n", 
	 Ctxt->local_daemon->host->type);
  DMN_Lookup(Ctxt, Ctxt->local_daemon, "test", "");

  Ctxt->tty->readme = NULL;	/* turn off command line from intrusion */
  Ctxt->local_daemon->timeout = (UDP_LIFE); 
  Ctxt->local_daemon->readme = no_action;

  if(ET_select_all(Ctxt, Ctxt->local_daemon))
    {
      /* Read in the text sent back, and make sure it's ok. */
      DMN_get_lookup_response(Ctxt->local_daemon);
      if(DMN_last_response_numeric(Ctxt->local_daemon) != SUCCESS)
	myflag = 1;
      else
	myflag = 0;
    }
  else
    myflag = 0;
  
  if(myflag)
    {
      printf("1) Brief daemon check successful!\n");
    }
  else
    {
      printf("1) Daemon check failed!\n");
      if(Ctxt->local_daemon->host->type != 0)
	{
	  printf("1) You may need to install ntalk or reconfigure for otalk!\n");
	  printf("1) Try configure --enable-OTALK_ONLY\n");
	  printf("1) OR putting the line:\n");
	  printf("1) 0 %s OTALKD\n", Ctxt->local_daemon->host->name);
	  printf("1) into the file %s to force use of OTALKD protocol.\n",
		 LOCAL_RC);
	}
      else
	{
	  printf("1) You may need to install ntalk or reconfigure for ntalk!\n");
	  printf("1) Try putting the line:\n");
	  printf("1) 0 %s NTALKD\n", Ctxt->local_daemon->host->name);
	  printf("1) into the file %s to force use of NTALKD protocol.\n",
		 LOCAL_RC);
	}
      printf("\n1) Read the etalk info file under `Setup Problems' for help.\n");
      return Fail;
    }

  {
    uid_t          my_id;
    struct passwd *pw;

    my_id = getuid();
    pw = getpwuid(my_id);
    
    if(!pw)
      {
	printf("You don't exist! Can't fill in user name.\n");
      }

    /* Some systems it's static, others it's malloced.  Since etalk is
     * about to exit anyway, ignore this problem.
     */
    Ctxt->myname = pw->pw_name;
  }

  printf("2) Initiating protocol test.\n");

  printf("2.1) Sending announcement message...\n");
  Ctxt->myappname = "etalk_running_a_test_procedure";
  DMN_Announce(Ctxt, Ctxt->local_daemon, Ctxt->myname, "");

  Ctxt->local_daemon->timeout = (UDP_LIFE); 
  Ctxt->local_daemon->readme = no_action;

  if(ET_select_all(Ctxt, Ctxt->local_daemon))
    {
      int r = DMN_get_announce_response(Ctxt->local_daemon);

      printf("2.1) Received response %s to announcement!\n",
	     DMN_last_response(Ctxt->local_daemon));
      if(r == Fail)
	{
	  printf("2.1) Announcement in protocol test failed.  See info file.\n");
	  exit(1);
	}
    }
  else
    {
      printf("2.1) Announcement in protocol test failed.  See info file.\n");
      exit(1);
    }

  printf("2.2) Sending invitation...\n");
  DMN_LeaveInvite(Ctxt, Ctxt->myname, "");

  Ctxt->local_daemon->timeout = (UDP_LIFE); 
  Ctxt->local_daemon->readme = no_action;

  if(ET_select_all(Ctxt, Ctxt->local_daemon))
    {
      int r = DMN_get_invite_response(Ctxt->local_daemon);

      printf("2.2) Received response %s to invitation!\n",
	     DMN_last_response(Ctxt->local_daemon));
      if(r == Fail)
	{
	  printf("2.2) Invitation in protocol test failed.  See info file.\n");
	  exit(1);
	}
    }
  else
    {
      printf("2.2) Invitation in protocol test failed.  See info file.\n");
      exit(1);
    }

  if(Ctxt->local_daemon->host->type >= TALK_VERSION_GNU)
    {
      printf("2.2.1) Sending reply query message for previous announce...\n");
      DMN_Reply_Query(Ctxt, Ctxt->local_daemon);

      Ctxt->local_daemon->timeout = (UDP_LIFE); 
      Ctxt->local_daemon->readme = no_action;

      if(ET_select_all(Ctxt, Ctxt->local_daemon))
	{
	  struct sockaddr_in *r;

	  r = DMN_get_reply_query_response(Ctxt->local_daemon);

	  if(!r || r == (struct sockaddr_in *)-1)
	    {
	      printf("2.2.1) Talk daemon forgot about our announcement! Daemon error!\n");
	      exit(1);
	    }
	  else
	    {
	      /* convert address out of network order. */
	      r->sin_family = ntohs(r->sin_family);

	      printf("2.2.1) Received response %s to reply request!\n",
		     DMN_last_response(Ctxt->local_daemon));
	      if(DMN_last_response_numeric(Ctxt->local_daemon) != SUCCESS)
		{
		  printf("2.2.1) Reply lookup in  protocol test failed.  See info file.\n");
		  exit(1);
		}
	      printf("2.2.1) Daemon has sent us   :");
	      print_sockaddr((struct sockaddr *)r);
	      printf("\n");
	    }
	}
      else
	{
	  printf("Reply lookup in protocol test failed.  See info file.\n");
	  exit(1);
	}
    }

  printf("2.3) Sending lookup query message for previous invite...\n");

  DMN_Lookup(Ctxt, Ctxt->local_daemon, Ctxt->myname, "");

  Ctxt->local_daemon->timeout = (UDP_LIFE); 
  Ctxt->local_daemon->readme = no_action;

  if(ET_select_all(Ctxt, Ctxt->local_daemon))
    {
      struct sockaddr_in *r;

      r = DMN_get_lookup_response(Ctxt->local_daemon);

      if(!r || r == (struct sockaddr_in *)-1)
	{
	  printf("2.3) Talk daemon forgot about our invitation! Daemon error!\n");
	  exit(1);
	}
      else
	{
	  /* convert address out of network order. */
	  r->sin_family = ntohs(r->sin_family);

	  printf("2.3) Received response %s to lookup request!\n",
		 DMN_last_response(Ctxt->local_daemon));
	  if(DMN_last_response_numeric(Ctxt->local_daemon) != SUCCESS)
	    {
	      printf("2.3) Lookup in  protocol test failed.  See info file.\n");
	      exit(1);
	    }
	  printf("2.3) Our remote connect socket is:");
	  print_sockaddr((struct sockaddr *)&Ctxt->remote_connect->raddr);
	  printf("\n     Daemon thinks it is         :");
	  print_sockaddr((struct sockaddr *)r);
	  printf("\n");

	  if((r->sin_family != Ctxt->remote_connect->raddr.sin_family) ||
	     (r->sin_port != Ctxt->remote_connect->raddr.sin_port) ||
	     (r->sin_addr.s_addr != Ctxt->remote_connect->raddr.sin_addr.s_addr))
	    {
	      printf("2.3) Response from daemon garbled our address!\n");
	      exit(0);
	    }
	  else
	    {
	      printf("2.3) Address to our socket transferred correctly.\n");
	    }
	}
    }
  else
    {
      printf("2.3) Lookup in protocol test failed.  See info file.\n");
      exit(1);
    }

  printf("2.4) Sending deletion message for previous invite...\n");
  DMN_Delete(Ctxt, Ctxt->local_daemon, DMN_invite);

  Ctxt->local_daemon->timeout = (UDP_LIFE); 
  Ctxt->local_daemon->readme = no_action;

  if(ET_select_all(Ctxt, Ctxt->local_daemon))
    {
      int r = DMN_get_delete_response(Ctxt->local_daemon);

      printf("2.4) Received response %s to delete request!\n",
	     DMN_last_response(Ctxt->local_daemon));
      if(r == Fail)
	{
	  printf("2.4) Deletion (invite) in protocol test failed.  See info file.\n");
	  exit(1);
	}
    }
  else
    {
      printf("2.4) Deletion (announce) in protocol test failed.  See info file.\n");
      exit(1);
    }

  printf("2.5) Sending deletion message for previous announce...\n");
  DMN_Delete(Ctxt, Ctxt->local_daemon, DMN_announce);

  Ctxt->local_daemon->timeout = (UDP_LIFE); 
  Ctxt->local_daemon->readme = no_action;

  if(ET_select_all(Ctxt, Ctxt->local_daemon))
    {
      int r = DMN_get_delete_response(Ctxt->local_daemon);

      printf("2.5) Received response %s to delete request!\n",
	     DMN_last_response(Ctxt->local_daemon));
      if(r == Fail)
	{
	  printf("2.5) Deletion (announce) in protocol test failed.  See info file.\n");
	  exit(1);
	}
    }
  else
    {
      printf("2.5) Deletion (announce) in protocol test failed.  See info file.\n");
      exit(1);
    }
  printf("2) Protocol test successful.\n");

  /* Now lets test the ringer facility (if available) */
  if(Ctxt->local_daemon->host->type >= TALK_VERSION_GNU)
    {
      char buff[80];

      printf("3) Ringer test.\n");

      /* First, just make sure we can get called back... */
      if(RING_activate(&testringerflag, Ctxt->udp_ring, PROTOCOL_ringerread)
	 == Fail)
	{
	  printf("3.1) Ringer file activation failed.\n");
	  return Fail;
	}
      printf("3.1) Ringer file successfully engaged.\n");

      printf("3.2) Sending announcement message to activate ringer.\n");

      DMN_Announce(Ctxt, Ctxt->local_daemon, Ctxt->myname, "");

      Ctxt->local_daemon->timeout = (UDP_LIFE); 
      Ctxt->local_daemon->readme = no_action;

      if(ET_select_all(Ctxt, Ctxt->local_daemon))
	{
	  int r = DMN_get_announce_response(Ctxt->local_daemon);
	  
	  printf("3.2) Received response %s to announcement!\n",
		 DMN_last_response(Ctxt->local_daemon));
	  if(r == Fail)
	    {
	      printf("3.2) Announcement in protocol test failed.  See info file.\n");
	      exit(1);
	    }
	}
      else
	{
	  printf("3.2) Announcement in protocol test failed.  See info file.\n");
	  exit(1);
	}

      printf("3.2) Response to message 0k.  Testflag = %d\n", testringerflag);

      printf("3.3) Deleting that pesky announce we just did.\n");
      DMN_Delete(Ctxt, Ctxt->local_daemon, DMN_announce);

      Ctxt->local_daemon->timeout = (UDP_LIFE); 
      Ctxt->local_daemon->readme = no_action;

      if(ET_select_all(Ctxt, Ctxt->local_daemon))
	{
	  int r = DMN_get_delete_response(Ctxt->local_daemon);
	  
	  printf("3.3) Received response %s to delete request!\n",
		 DMN_last_response(Ctxt->local_daemon));
	  if(r == Fail)
	    {
	      printf("3.3) Deletion (announce) in protocol test failed.  See info file.\n");
	      exit(1);
	    }
	}
      else
	{
	  printf("3.3) Deletion (announce) in protocol test failed.  See info file.\n");
	  exit(1);
	}

      printf("3.4) Testing ringer file forwarding via daemon.\n");

      /* first, we must create a recognizable bogus address */
      {
	char *env;
	FILE *f;			/* ringer file. */

	/* Open for replacing. 
	 */
	env = getenv("HOME");
	
	if(env == NULL)
	  {
	    printf("You do not have the HOME environment variable set!\n");
	    return Fail;
	  }

	strcpy(buff, env);
	strcat(buff, "/");
	strcat(buff, RINGER_RC);

	f = fopen(buff, "w");

	if(f == NULL)
	  {
	    printf("Failure to open Ringer File [%s].\n", buff);
	    return Fail;
	  }

	/* Write an identifiable address here! */
	fprintf(f, "%d %d %ld", 1, htons(2), htonl(420000));
	 

	printf("3.4) Stowing address: 1 2 420000 into ringer file.\n");
	  
	fclose(f);
      }

      /* Now send an announcement! */
      printf("3.5) Sending announcement message to activate ringer.\n");

      DMN_Announce(Ctxt, Ctxt->local_daemon, Ctxt->myname, "");

      Ctxt->local_daemon->timeout = (UDP_LIFE); 
      Ctxt->local_daemon->readme = no_action;

      if(ET_select_all(Ctxt, Ctxt->local_daemon))
	{
	  int r = DMN_get_announce_response(Ctxt->local_daemon);
	  
	  printf("3.5) Received response %s to announcement!\n",
		 DMN_last_response(Ctxt->local_daemon));
	  if(r == Fail)
	    {
	      if(DMN_last_response_numeric(Ctxt->local_daemon) == TRY_HERE)
		{
		  struct HostObject *host;
		  
		  printf("3.5) Recieved TRY_HERE: Note, only last number matters.\n");
		  printf("3.5) Daemon responded with: ");
		  print_sockaddr((struct sockaddr *)DMN_last_addr(Ctxt->local_daemon));
		  printf("\n");
		  if(DMN_last_addr(Ctxt->local_daemon)->sin_addr.s_addr !=
		     420000)
		    {
		      printf("3.5) Transfer of address was not successful!\n");
		      exit(1);
		    }
		}
	      else
		{
		  printf("3.5) Failed to get TRY_HERE failure response, got %d instead.\n",
			 DMN_last_response_numeric(Ctxt->local_daemon));
		  printf("3.6) Deleting the ringer file.\n");
		  if(RING_deactivate() == Fail)
		    {
		      printf("3.6) Deactivation failed!\n");
		      exit(1);
		    }
		  else
		    printf("3.6) Deactivation (deletion of file) successful.\n");

		  exit(1);
		}
	    }
	  else 
	    {
	      printf("3.5) Announcement in protocol test was unsespectedly Successful\n");
	      printf("3.6) Deleting the ringer file.\n");
	      if(RING_deactivate() == Fail)
		{
		  printf("3.6) Deactivation failed!\n");
		  exit(1);
		}
	      else
		printf("3.6) Deactivation (deletion of file) successful.\n");

	      exit(1);
	    }
	}
      else
	{
	  printf("3.5) Announcement in protocol test failed.  See info file.\n");
	  printf("3.6) Deleting the ringer file.\n");
	  if(RING_deactivate() == Fail)
	    {
	      printf("3.6) Deactivation failed!\n");
	      exit(1);
	    }
	  else
	    printf("3.6) Deactivation (deletion of file) successful.\n");

	  exit(1);
	}

      printf("3.6) Deleting the ringer file.\n");
      if(RING_deactivate() == Fail)
	{
	  printf("3.6) Deactivation failed!\n");
	  exit(1);
	}
      else
	printf("3.6) Deactivation (deletion of file) successful.\n");

      printf("3.7) Deleting that pesky announce we just did.\n");
      DMN_Delete(Ctxt, Ctxt->local_daemon, DMN_announce);

      Ctxt->local_daemon->timeout = (UDP_LIFE); 
      Ctxt->local_daemon->readme = no_action;

      if(ET_select_all(Ctxt, Ctxt->local_daemon))
	{
	  int r = DMN_get_delete_response(Ctxt->local_daemon);
	  
	  printf("3.7) Received response %s to delete request!\n",
		 DMN_last_response(Ctxt->local_daemon));
	  if(r == Fail)
	    {
	      printf("3.7) Deletion (announce) in protocol test failed.  See info file.\n");
	      exit(1);
	    }
	}
      else
	{
	  printf("3.7) Deletion (announce) in protocol test failed.  See info file.\n");
	  exit(1);
	}

      printf("3) Ringer test successful!\n");

#define I_FEEL_BRAVE
#ifdef I_FEEL_BRAVE
      printf("4) Reverting back to 1, and using OTALK protocol to test forwarding.\n");

      Ctxt->local_daemon->host->type = OTALKD;

      UDP_daemon_change(Ctxt->local_daemon);

      PROTOCOL_test(Ctxt);
#endif /* Actually, this didn't work. Hmmm! */
    }

  return Success;
}
