/* gtd_reqst.c - Request reader for gtalk daemon
 *
 * 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.
 * 
 * Description:
 * 
 *  Manages the different types of requests the talk daemon will read.
 * One function will know how to read from each daemon type.
 * 
 * History:
 * zappo   9/14/94         Created
 *
 * $Log: gtd_reqst.c,v $
 * Revision 1.13  1995/07/16  16:18:18  zappo
 * When sending BADVERSION, make sure the message type sent back respects
 * that information
 *
 * Revision 1.12  1995/07/16  13:47:05  zappo
 * Added check to prevent the sending of a LOOK_HERE message to older
 * talk protocols which don't know about it.  This means older talk
 * programs cannot be forwarded to other machines.
 *
 * Revision 1.11  1995/04/01  17:04:51  zappo
 * Fixed rouge & for arrays, and removed references to "extended[0]"
 * which used to exist in gtalk.h
 *
 * Revision 1.10  1995/03/30  02:35:48  zappo
 * Signal handler now returns a dynamic type from autoconf
 *
 * Revision 1.9  1995/03/25  04:23:06  zappo
 * Updated copyright
 *
 * Revision 1.8  1995/03/23  00:56:34  zappo
 * Added checks when in BSD mode to ignore GTALK things.
 *
 * Revision 1.7  1995/03/16  03:26:38  zappo
 * try_here returned wrong piece of address.
 *
 * Revision 1.6  1995/03/04  14:48:21  zappo
 * Added use of syslog to report errors when daemon not run on tty
 *
 * Revision 1.5  1995/03/03  02:50:46  zappo
 * Added ability to read in extensions to talk packets.
 *
 * Revision 1.4  1995/02/25  20:53:30  zappo
 * Fixed forwarding problem where id_num and ctrl_addr were swapped at
 * read in, and not re-swapped on send.
 *
 * Revision 1.3  1995/02/12  13:58:59  zappo
 * Added oldtalk-forwardto ntalk protocol!
 *
 * Revision 1.2  1995/02/11  17:17:20  zappo
 * Made alg. more responsible when handling -bsd type flags.
 *
 * Revision 1.1  1995/02/01  03:50:04  zappo
 * Initial revision
 *
 * Tokens: ::Header:: gtalkd.h
 */
#include "etalklib.h"
#include "gtalkd.h"

#include "sitecnfg.h"

#if HAVE_SIGNAL_H == 1
#include <signal.h>
#endif
#if HAVE_SYS_WAIT_H == 1
#include <sys/wait.h>
#else
/* What should I do in this case?? */
#endif

/* These two appear in etl_msgm.c, but are not advertised.
 */
extern union ctl_msg      Control;
extern union ctl_response Response;
extern char               ControlExtension[256];


/*
 * Function: send_response, recv_control
 *
 *   Locally defined functions which send and receive responses, and
 * control message to and from talk clients.
 *
 * Returns:     static int  - success/fail
 * Parameters:  io   - Pointer to io device
 *              Ctxt - Context
 * History:
 * zappo   9/16/94         Created from et_daemn.c code
 */
static int send_response(io, Ctxt)
     struct InputDevice *io;
     struct DaemonContext *Ctxt;
{
  int result;

  result = ET_send(io,		/* talk daemon           */
		   &Response,	/* Control psuedo global */
		   response_size(Ctxt->type)); /* msg size to io        */

  if(result == Fail) {
    if(!syslogdebug)
      printf("send_response: Error sending response message\n");
    else
      syslog(LOG_WARNING, "send_response: Error sending response message.");
    return Fail;
  }

  if(verbose)
    {
      printf("Sending a Response message over %s...\n", ET_dev_name(io));
      printf("Address: ");
      print_sockaddr((struct sockaddr *)&io->raddr);
      printf("\n");
      if(Ctxt->type == OTALKD)
	Response.otalk.id_num = htonl( Response.otalk.id_num );
      else
	Response.talk.id_num = htonl( Response.talk.id_num );
      
      ETM_response_print(Ctxt->type);
    }

  return Success;
}

static int recv_control(io, Ctxt)
     struct InputDevice *io;
     struct DaemonContext *Ctxt;
{
  int result;
  char buffer[sizeof(Control) + sizeof(ControlExtension)];

  /* Because some packet sizes are dynamic, we must accept data in a */
  /* large buffer, and then move the pieces outside of this region */
  /* based on smart moves.  This is because recvfrom will discard */
  /* extra bytes in a single UDP packet if you do not give it a big */
  /* enough buffer. */
  result = ET_recv(io, buffer, sizeof(buffer));

  if(result == Fail)
    {
      if(verbose)
	printf("recv_control: Error receiving control message\n");
      else
	syslog(LOG_ERR, "recv_control: Error receiving control message");
      return Fail;
    }
  if(result < control_size(Ctxt->type))
    {
      if(verbose)
	printf("recv_control: Received type is %d, expected is %d\n", 
	       result, control_size(Ctxt->type));
      else
	syslog(LOG_ERR, "recv_control: Size of packet received differs from expected.");
      return Fail;
    }

  /* Now copy the pieces of the buffer into the control structure. */
  memcpy(&Control, buffer, control_size(Ctxt->type));

  /* And if we have extra parts, then copy those too! */
  if(result > control_size(Ctxt->type))
    {
      strncpy(ControlExtension, (char *)buffer + sizeof(CTL_MSG_GNU),
	      sizeof(ControlExtension));
      if(verbose)
	printf("Extension recieved: %s\n", ControlExtension);
    }
  else
    {
      ControlExtension[0] = 0;
    }

  if(verbose)
    {
      printf("Receiving response message on %s...\n", ET_dev_name(io));

      ETM_control_print(Ctxt->type);
    }

  /*
   * Take the response, and host to network convert it right now before
   * anyone gets confused somewhere else!
   */
  if(Ctxt->type == OTALKD)
    {
      Control.otalk.id_num = ntohl( Control.otalk.id_num );
      (*(struct sockaddr_in *)&Control.otalk.ctl_addr).sin_family =
	ntohs((*(struct sockaddr_in *)&Control.otalk.ctl_addr).sin_family);
    }
  else
    {
      Control.talk.id_num = ntohl( Control.talk.id_num );
      (*(struct sockaddr_in *)&Control.talk.ctl_addr).sin_family =
	ntohs((*(struct sockaddr_in *)&Control.talk.ctl_addr).sin_family);
    }

  return Success;
}


/*
 * Function: fill_response
 *
 *   Locally defined function which fills in response messages based
 * on the input parameters.
 *
 * Returns:     None
 * Parameters:  Ctxt   - Context
 *              type   - Type of
 *              answer - the answer to a requeset
 *              id     - Identifier of current message
 *              addr   - address requested
 *              vers   - version of response to send
 * History:
 * zappo   9/17/94    Created
 * zappo   11/13/94   Added parameter vers, type
 * zappo   2/12/95    removed dev from params
 */
static void fill_response(Ctxt, type, answer, id, addr, vers)
     struct DaemonContext *Ctxt;
     enum InputDeviceType  type;
     u_char                answer;
     u_long                id;
     struct sockaddr      *addr;
     int                   vers;
{
  if(Ctxt->type == OTALKD)
    {
      /* fill in otalk part */
      Response.otalk.type = type;
      Response.otalk.answer = answer;
      Response.otalk.id_num = htonl(id);
      if(addr)
	Response.otalk.addr = *addr;
      else
	memset(&Response.otalk.addr, 0, sizeof(Response.otalk.addr));
    }
  else
    {
      Response.talk.vers = vers;
      Response.talk.type = type;
      Response.talk.answer = answer;
      Response.talk.id_num = ntohl(id);
      if(addr)
	Response.talk.addr = *addr;
      else
	memset(&Response.talk.addr, 0, sizeof(Response.talk.addr));
      if(vers >= TALK_VERSION_GNU)
	memset(Response.gtalk.r_name, 0, sizeof(Response.gtalk.r_name));
    }
}

/*
 * Function: GTD_forwardread
 *
 *   Reads from io device the request.  Only called when the otalk
 * emulator is in forward mode.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt - Context
 *              io   - Pointer to io device
 * History:
 * zappo   2/11/95    Created
 */
void GTD_forwardread(Ctxt, io)
     struct DaemonContext *Ctxt;
     struct InputDevice   *io;
{
  if(verbose)
    printf("Read in response to forwarded message.\n");
  ET_recv(io, &Response, sizeof(CTL_RESPONSE));
}

/*
 * Function: sigchild_handle
 *
 *   Locally defined function which handles SIGCHILD.
 *
 * Returns:     static RETSIGTYPE  - 
 * Parameters:  val - Some number
 *
 * History:
 * zappo   2/12/95    Created
 */
static RETSIGTYPE sigchild_handle(val)
     int val;
{
  int status;

  /* We don't care who we wait for because we don't care about
   * the responsibilities of the child.  That worry is up to the
   * child.
   */
  wait(&status);

  if(verbose)
    {
      printf("Recieved sig-child from forked process.\n");
    }
}

/*
 * Function: FW_sendmessage
 *
 *   A Locally defined function which sends amessage to retto, and
 * waits for a responce within a forked child.  This child will close
 * other ports off (turn off read) so that the parent may continue to
 * monitor.  Only to be used for OTALK to NTALK requests.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt  - Context
 *              retto - Pointer to return address
 *              ro    - Pointer to request object
 * History:
 * zappo   2/12/95    Created 
 * zappo   2/25/95    Fixed problem of swapped addresses, and ids.
 */
static void FW_sendmessage(Ctxt, retto, ro)
     struct DaemonContext *Ctxt;
     struct InputDevice   *retto;
     struct RequestObject *ro;
{
  pid_t      kid;

  kid = fork();

  if(verbose) printf("Response to fork is %d\n", kid);

  if(kid == 0)
    {
      int ans;
      int result;

      Ctxt->talk->readme = NULL; /* close reader from normal talk socket */

      if(verbose) 
	{
	  printf("Forwarding request from/to:");
	  print_sockaddr((struct sockaddr *)&Ctxt->udp_ring->laddr);
	  printf("\n");
	}

      /* Send the converted message to udp_ring since that is the
       * device used when forwarding (since we won't be ringing anyone)
       * Go with simple talk version to make sure of compatibility.
       */
      ro->request.vers = TALK_VERSION;
      /* And point info back to us. */
      ro->request.ctl_addr = *(struct sockaddr *)&Ctxt->udp_ring->laddr;

      /* Now fix things that were swapped internally. */
      ro->request.id_num = htonl(ro->request.id_num);
      (*(struct sockaddr_in *)&ro->request.ctl_addr).sin_family =
	htons((*(struct sockaddr_in *)&Control.otalk.ctl_addr).sin_family);

      if(ET_send(Ctxt->udp_ring, (char *)&ro->request, sizeof(CTL_MSG))
	 == Fail)
	{
	  if(verbose)
	    printf("Error sending request to other daemon.\n");
	  exit(0);
	}

      Ctxt->udp_ring->readme = GTD_forwardread;
      /* this is a local host, it should be fast... */
      Ctxt->udp_ring->timeout = 2;
      
      /* Set this to verify reciept. */
      Response.talk.vers = 0;

      ans = ET_select_all(Ctxt, Ctxt->udp_ring);
      if(verbose)
	printf("OTALK: Recieved answer %d to forwarded message.\n", ans);

      if(Response.talk.vers != 1)
	if(verbose)
	  {
	    printf("Timeout waiting for response to forwarded message.\n");
	  }

      Ctxt->udp_ring->readme = NULL;
      Ctxt->udp_ring->timeout = 0;
      
      /* Now fill it back up as it should be for shipping. */
      fill_response(Ctxt, Response.talk.type,
		    Response.talk.answer,
		    ntohl(Response.talk.id_num),
		    &Response.talk.addr,
		    Response.talk.vers);      

      /* Send the prepared message back...
       */
      result = send_response(retto, Ctxt);
      
      if(result == Fail)
	{
	  if(verbose)
	    printf("Error sending response back to given address.\n");

	  result = send_response(ro->sender, Ctxt);
	  if((result == Fail) && verbose)
	    printf("Hmmm... Returned address was different.\n");
	}

      exit(0);
    }

  signal(SIGCHLD, sigchild_handle); /* setup signal handler each time */
}

/*
 * Function: GTR_read_request
 *
 *   Reads requests from talk clients and process them.  Supports all
 * three types of talk requests.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt - Context
 *              dev  - Pointer to device being read from.
 * History:
 * zappo   9/14/94         Created
 */
void GTR_read_request(Ctxt, dev)
     struct DaemonContext *Ctxt;
     struct InputDevice *dev;
{
  struct RequestObject *ro;	/* new request object            */
  struct InputDevice *retto;	/* saved value of retto field.   */
  int result;			/* results of various operations */
  int vers;			/* version of incomming reqest   */

  result = recv_control(dev, Ctxt);

  if(result == Fail)
    {
      if(verbose)
	printf("Recieving of control message failed.  No action taken.\n");
      return;
    }      

  ro = GCM_alloc(Ctxt, dev, &Control, ControlExtension);
  
  if(Ctxt->type == OTALKD)
    vers = 0;
  else
    vers = ro->request.vers;
  
  retto = ro->retto;

  /*
   * DO SUN PACKET FORWARDING
   */
  if((vers == 0) && Ctxt->sunf)
    {
      /* Used forked child to forward the request. */
      FW_sendmessage(Ctxt, retto, ro);

      GCM_free_request(ro);      

      return;
    }
  /*
   * CHECK FOR CORRCT VERSION
   */
  if(!((Ctxt->type == OTALKD) ||
       ((Ctxt->type == NTALKD) && (ro->request.vers == TALK_VERSION)) ||
       ((Ctxt->type == GTALKD) && (ro->request.vers <= TALK_VERSION_GNU))))
    {
      /* Gee!  Something better than GNU talk daemon?!  Mabee this
       * should send me mail too so I can keep up! ;)
       */
      fill_response(Ctxt, ro->request.type, BADVERSION,
		    ro->request.id_num, NULL, Ctxt->type);

      /* Now release the memory used by this rogue request. */
      GCM_free_request(ro);
    }
  /*
   * HANDLE THE REQUESTS
   */
  else
    {
      switch(ro->request.type)
	{
	case LEAVE_INVITE:
	  /* Return FAIL if it is new, if it is replaced, return SUCCESS */
	  {
	    struct RequestObject *fo;

	    /* see if we have an identical (except for ID of course) in
	     * our list.
	     */
	    fo = GCM_looksame(ro);

	    /* the answer is based on if there was anything there before hand.
	     */
	    if(fo)
	      {
		fill_response(Ctxt, LEAVE_INVITE, SUCCESS,
			      ro->request.id_num, NULL, vers);
	    
		GCM_free_request(fo); /* release old version if there */
	      }
	    else
	      {
		fill_response(Ctxt, LEAVE_INVITE, NOT_HERE,
			      ro->request.id_num, NULL, vers);
	      }
	  }
	  break;
	case LOOK_UP:
	  /* Return address if found, otherwise FAIL */
	  {
	    struct RequestObject *fo;

	    fo = GCM_lookfor(ro);

	    if(! fo)
	      {
		fill_response(Ctxt, LOOK_UP, NOT_HERE,
			      ro->request.id_num, NULL, vers);
	      }
	    else
	      {
		fill_response(Ctxt, LOOK_UP, SUCCESS,
			      fo->request.id_num, 
			      &fo->request.addr, vers);
	      }
	  }
	  GCM_free_request(ro);
	  break;
	case DELETE:
	  /* return FAIL if thre is nothing to delete. */
	  {
	    struct RequestObject *fo;

	    fo = GCM_lookid(ro);

	    if(fo)
	      {
		fill_response(Ctxt, DELETE, SUCCESS,
			      ro->request.id_num, NULL, vers);
		GCM_free_request(fo);
	      }
	    else
	      {
		fill_response(Ctxt, DELETE, NOT_HERE,
			      ro->request.id_num, NULL, vers);
	      }
	  }
	  GCM_free_request(ro);
	  break;
	case ANNOUNCE:
	  {
	    int ans;
	    struct UserObject *uo;

	    /* Look up this particular user. */
	    uo = FUSR_alloc(ro->request.r_name, ro->request.r_tty);

	    if(uo)
	      {
		/* first try the ringer method
		 * If we are not in GTALK mode skip this, if we
		 * are, and remote specified a tty, the go there instead.
		 */
		if((Ctxt->type >= GTALKD) || !ro->request.r_tty[0])
		  ans = RINGER_sendto(uo, ro, Ctxt);
		else
		  ans = FAILED;

		if(ans != SUCCESS)
		  {
		    /* So long as we don't get the TRY_HERE error, try */
		    /* the tty  method.  Reguardless of this, the      */
		    /* ringto struct will always portray the thing we  */
		    /* need to deal with for a forward                 */
		    /* Also, if the caller is not GTALKD compaitible   */
		    /* then announce to a TTY                          */
		    if((ans != TRY_HERE) ||
		       (ro->request.vers < TALK_VERSION_GNU))
		      ans = ANN_sendto(uo, ro);
		  }
		
		fill_response(Ctxt, ANNOUNCE, ans, ro->request.id_num,
			      uo->ringto?&uo->ringto->raddr:NULL,
			      vers);
		/* Save announces for REPLYs, and for delete later on. */
	      }
	    else
	      {
		fill_response(Ctxt, ANNOUNCE, NOT_HERE,
			      ro->request.id_num, NULL, vers);
	      }
	  }
	  break;
	case REPLY_QUERY:
	  {
	    struct RequestObject *fo;
	    struct sockaddr_in   sas;
	    
	    if(Ctxt->type < GTALKD)
	      {
		if(verbose)
		  printf("Ignoring REPLY_QUERY in BSD mode.\n");

		fill_response(Ctxt, REPLY_QUERY, UNKNOWN_REQUEST,
			      ro->request.id_num, NULL, vers);
	      }

	    fo = GCM_findannounce(ro);

	    if(fo)
	      {
		sas = *(struct sockaddr_in *)&fo->request.addr;
		sas.sin_port = Ctxt->talk->laddr.sin_port;

		/* the address we send back has the address of the 
		 * person who SENT the announcement.
		 */
		fill_response(Ctxt, REPLY_QUERY, SUCCESS,
			      ro->request.id_num, &sas, vers);

		strcpy(Response.gtalk.r_name, fo->request.l_name);
	      }
	    else
	      {
		fill_response(Ctxt, REPLY_QUERY, NO_CALLER,
			      ro->request.id_num, NULL, vers);
	      }
	  }
	  GCM_free_request(ro);
	  break;
	default:
	  if(verbose)
	    printf("Error: request type %d\n", ro->request.type);

	  /* Tell them we are confused. */
	  fill_response(Ctxt, ro->request.type, UNKNOWN_REQUEST,
			ro->request.id_num, NULL, vers);
	  GCM_free_request(ro);
	}
    }
  /* Send the prepared message back...
   */
  result = send_response(retto, Ctxt);
  
  if(result == Fail)
    {
      if(verbose)
	printf("Error sending response back to given address.\n");

      result = send_response(ro->sender, Ctxt);
      if((result == Fail) && verbose)
	printf("Hmmm... Returned address was different.\n");
    }

  dev->timeout = Ctxt->checkrate;

#ifndef DEBUG_CONTROL
  memset(&Control, 0, sizeof(Control));
#endif
}

