/*
 * 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.
 *
 * etl_udp.c
 *
 * Purpose:
 *   This file contains UDP allocation and the like.  It only manages
 * UDP in relation to talk.  The static variable 'host_port'
 * represents the udp port hosting within this instantiation of etalk.
 * Other functions create addresses used in sends.
 *
 * $Log: etl_udp.c,v $
 * Revision 1.8  1995/07/16  15:33:21  zappo
 * (UDP_daemon_change) Had strings associated with OTALK/NTALK services
 * backwards.  This caused all sorts of havoc!
 *
 * Revision 1.7  1995/03/25  04:16:38  zappo
 * Updated copyright
 *
 * Revision 1.6  1995/03/04  14:47:53  zappo
 * Added use of syslog to report errors when linked agains the daemon
 *
 * Revision 1.5  1995/02/01  03:42:17  zappo
 * Forced all devs to have laddr filled it automatically.
 *
 * Revision 1.4  1995/01/29  14:26:14  zappo
 * Added an error messages, and fixed some -Wall warnings
 *
 * Revision 1.3  1995/01/14  17:44:28  zappo
 * Added reset-ports ability.  This allows for 1 main port, and secondary
 * ports opened for other tasks not related to the talk daemon.
 *
 * Revision 1.2  1994/11/15  04:10:24  zappo
 * Added gtalk service lookup of service ids, and linked to enabling use
 * of GTALK_SERVICE.  Also added function UDP_servent for use in gtalk
 * daemon.
 *
 * Revision 1.1  1994/08/29  23:43:23  zappo
 * Initial revision
 *
 * ::Header:: ./etalklib.h
 */

#include "etalklib.h"

/*
 * this is the local port representing our attachment.
 */
static struct sockaddr_in  host_port;
static int                 udp_sd = 0;



/*
 * Function: UDP_reset_ports
 *
 *   Resets the locally file descriptor of the UDP descriptor.  In
 * TALK, you may only have one UDP port, and that may only be active
 * in one call at a time.  To make use of such things as ringers, one
 * must be able to have to individual ports.  To make an extra port,
 * the ringer port will be the non-active port, and be created first,
 * with this reset function called in between.
 *
 *   This smaks me as a hack, but must do for now.
 *
 * Returns:     Nothing
 * Parameters:  None
 *
 * History:
 * zappo   11/28/94   Created
 */
void UDP_reset_ports()
{
  udp_sd = 0;
}


/*
 * Function: receive_port()
 *
 * Returns the sockaddr of our port.  Used for filling in talk request
 * messages.
 * 
 * Parameters:
 *
 * History:
 * eml 4/1/94
 */
struct sockaddr_in *UDP_receive_port()
{
  return &host_port;
}

/*
 * Function: setup_localport
 *
 * Local function which allocats a single port for local use in UDP,
 * plus sets up the stuff we need for sending our address.
 *
 * Parameters:
 *
 * History:
 * eml 4/1/94
 */
void UDP_setup_localport()
{
  int t;			/* temp variable for addr size. */

  if(udp_sd) return;

  /* 
   * This is not in net-order because it is used in BIND
   */
  host_port.sin_family = AF_INET;
  host_port.sin_port = 0;
  host_port.sin_addr.s_addr = INADDR_ANY;  
  /*
   * allocate ourselves a socket to use
   */
  if((udp_sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
      if(!syslogdebug)
	perror("UDP_setup_localport: Socket for datagram failure");
      else
	syslog(LOG_ERR, "UDP_setup_localport: Socket for datagram failure");
      exit(1);
    }
  /*
   * now bind up the datagram socket to ourselves
   */
  if(bind(udp_sd, (struct sockaddr*)&host_port, sizeof(host_port)) < 0) 
    {
      if(!syslogdebug)
	perror("UDP_setup_localport: bind for datagram failure.");
      else
	syslog(LOG_ERR, "UDP_setup_localport: bind for datagram failure.");
      exit(1);
    }
  /*
   * and do the get name thing.
   */
  t = sizeof(host_port);
  if(getsockname(udp_sd, (struct sockaddr *)&host_port, &t) != 0) 
    {
      if(!syslogdebug)
	perror("UDP_setup_localport: getsockname");
      else
	syslog(LOG_ERR, "UDP_setup_localport: getsockname");
      exit(1);
    }
  /* 
   * Finally, take this static variable used in our for
   * our port (always the same) and set it up for copies into
   * daemon messages!
   */
  host_port.sin_family = htons(AF_INET);
  /* Look to see if this step is needed. */
  host_port.sin_addr = HOST_gen_local_host()->addr.sin_addr;
}

/*
 * Function: UDP_host
 *
 * Create an inputdevice with the address field set to this machine.
 * 
 * Returns: struct InputDevice * -
 * Parameters:  machine - character string of remote machine name.
 *
 * History:
 * eml     4/1/94
 * zappo   11/14/94   Added gtalk to service names options, and
 *                    IFDEFED it so  that it will default to just
 *                    using NTALK
 */
struct InputDevice *UDP_host(machine)
     char *machine;
{
  struct HostObject  *host;	/* my host object        */
  struct InputDevice *new;	/* new input device      */
  struct sockaddr_in  hisdg;	/* target port address   */
  char               *servicename; /* service name used  */
  struct servent     *sp;
  char                serv_found = 0;

  memset(&hisdg, 0, sizeof(hisdg));
  /*
   * Get the host stuff now.
   */
  host = HOST_gen_host(machine);

  /*
   * sin port already set, and in network order, now lets
   * bind up to the service.  To use ntalk, both myself, and
   * remote needs to be of NTALK or above.  In the very first
   * case, both hosts are the same, but that's ok, since we need
   * to broadcast that addr struct with targeted TALK messages.
   */
  do {
    switch(host->type)
      {
      case OTALKD:
	/* && (HOST_gen_local_host()->type > OTALKD))
	 * Removed for same reasons as above.
	 */
	servicename = "talk";
	break;
      case GTALKD:
#ifdef GTALK_SERVICE
	servicename = "gtalk";
#else
	servicename = "ntalk";
#endif
	break;
      case NTALKD:
      default:
	servicename = "ntalk";
	break;
      }
    
    if((sp = getservbyname(servicename, "udp")) == NULL) 
      {
	if(!syslogdebug)
	  fprintf(stderr, "getservbyname - Error finding service\n");
	else
	  syslog(LOG_ERR, "getservbyname - Error finding service");
	serv_found = 0;
	if(!host->type)
	  {
	    if(!syslogdebug)
	      fprintf(stderr, "No more daemon types to check for! Exiting.\n");
	    else
	      syslog(LOG_ERR, "No more daemon types to check for! Exiting.");
	    exit(1);
	  }
	/* Since the types are kept as ints, when we drop below
	 * OTALK we get -1, and break from loop.
	 */
	host->type -= 1;
      }
    else
      serv_found = 1;

  } while(! serv_found );

  hisdg            = host->addr; /* his host address */
  hisdg.sin_family = AF_INET;	/* he is using INET  */
  hisdg.sin_port   = sp->s_port; /* the port to use  */
  /*
   * now that we have all that, lets save it.
   */
  new = ET_gen_iodev(IO_UDP, udp_sd,  &hisdg);

  if(!new->host)
    new->host = host;

  /* setup this address to remember our local socket numbers, etc */
  new->laddr = host_port;

  new->state = IDLE;

  if(verbose)
    printf("Servent port %d looked up for %s.\n", ntohs(sp->s_port),
	   ET_dev_name(new));

  return new;
}

/*
 * Function: UDP_daemon_change
 *
 * If the daemon is changing for a port, we need a new socket pointer.
 * therefore, read new type from host, and use that.
 * 
 * Parameters:  io - Pointer to io
 *
 * History:
 * eml      4/25/94
 * zappo   11/14/94   Added GTALK service, and ifdefed it out on
 *                    default with configure option.
 * zappo   7/16/95    NTALK/OTALK strings were reversed for some
 *                    reason... fixed that.
 */
int UDP_daemon_change(io)
     struct InputDevice *io;
{
  struct sockaddr_in  hisdg;	/* target port address   */
  char               *servicename; /* service name used  */
  struct servent     *sp;

  memset(&hisdg, 0, sizeof(hisdg));

  /* Set the new port in address by taking the host, and reading in
   * a new service name.
   */
  switch(io->host->type)
    {
    case OTALKD:
      /* && (HOST_gen_local_host()->type > OTALKD))
       * Removed for same reasons as above.
       */
      servicename = "talk";
      break;
    case GTALKD:
#ifdef GTALK_SERVICE
      servicename = "gtalk";
#else
      servicename = "ntalk";
#endif
      break;
    case NTALKD:
    default:
      servicename = "ntalk";
      break;
    }
  
  if((sp = getservbyname(servicename, "udp")) == NULL) 
    {
      if(!syslogdebug)
	perror("UDP_daemon_change: getservbyname: Error finding service");
      else
	syslog(LOG_ERR, "UDP_daemon_change: getservbyname: Error finding service");
      return Fail;
    }

  hisdg            = io->host->addr; /* his host address */
  hisdg.sin_family = AF_INET;	/* he is using INET  */
  hisdg.sin_port   = sp->s_port; /* the port to use  */
  /*
   * now that we have all that, lets save it.
   */
  io->raddr = hisdg;

  if(verbose)
    printf("Servent port %d looked up for %s during change.\n",
	   ntohs(sp->s_port), ET_dev_name(io));

  return Success;
}

/*
 * Function: UDP_byaddr
 *
 *   Create a new IO device based on UDP socket DEV, and with output
 * address set to ADDR.  Daemon needs this to reply back to ports
 * which may not correctly address their messages.
 *
 * Returns:     struct InputDevice * - 
 * Parameters:  dev  - Pointer to device
 *              addr - Pointer to address
 * History:
 * zappo   9/17/94    Created
 */
struct InputDevice *UDP_byaddr(dev, addr)
     struct InputDevice *dev;
     struct sockaddr_in *addr;
{
  if(dev->type == IO_UDP)
    {
      /* We have what we need in ADDR, so save it against the
       * the IO device which read the sucker.
       */
      return ET_gen_iodev(IO_UDP, dev->fd, addr);
    }
  else
    {
      if(verbose)
	printf("Recieved NUL pointer in UDP_byaddr.\n");
      return NULL;
    }
}

/*
 * Function: UDP_stdin
 *
 *   Takes STDIN (io descriptor 1) and returns a device treating it as
 * a UDP port.  This port can then be used for read/writting talk
 * messages recieved when inetd spawns off this child.
 *
 * Returns:     struct InputDevice* - 
 * Parameters:  None
 *
 * History:
 * zappo   11/13/94   Created
 */
struct InputDevice *UDP_stdin()
{
#define stdin_descriptor 0

  struct sockaddr_in  host_port;
  struct InputDevice *new;
  int                 t;

  /*
   * do the get name thing.
   */
  t = sizeof(host_port);
  if(getsockname(stdin_descriptor, (struct sockaddr *)&host_port, &t) != 0) 
    {
      if(!syslogdebug)
	perror("UDP_stdin: getsockname");
      else
	syslog(LOG_ERR, "UDP_stdin: getsockname failed.");
      exit(1);
    }
  /* 
   * Finally, take this static variable used for our port (always the
   * same) and set it up for copies into daemon messages! 
   */
  host_port.sin_family = htons(AF_INET);

  host_port.sin_addr = HOST_gen_local_host()->addr.sin_addr;
  /*
   * now that we have all that, lets save it.
   */
  new = ET_gen_iodev(IO_UDP, stdin_descriptor, NULL);

  new->laddr = host_port;
  
  new->host = HOST_gen_local_host();

  new->state = IDLE;

  if(verbose)
    {
      printf("Local port defined at: ");
      print_swapped_sockaddr((struct sockaddr *)&host_port);
      printf("\n");
    }

  return new;
  
}

/*
 * Function: UDP_servent
 *
 * This creates a input device which can be a servent based on the
 * servant name passed in.
 *
 * Returns:     struct InputDevice * - The input device
 * Parameters:  servent_name - Name of the service
 *
 * History:
 * eml     9/2/1994        Used
 */
struct InputDevice *UDP_servent( servent_name )
     const char *servent_name;
{
  struct sockaddr_in  host_port;
  struct InputDevice *new;
  struct servent     *sp;
  int                 udp_sd;
  int                 t;

  if((sp = getservbyname(servent_name, "udp")) == NULL) 
    {
      if(!syslogdebug)
	{
	  fprintf(stderr, "Service %s could not be found!\n", servent_name);
	  fprintf(stderr, 
		  "This service needs to be added to the file /etc/services\n");
	}
      else
	{
	  syslog(LOG_ERR, "Service %s could not be found. Check /etc/services.", servent_name);
	}
      return NULL;
    }
  /* 
   * This is not in net-order because it is used in BIND.
   */
  host_port.sin_family = AF_INET;
  host_port.sin_port = sp->s_port;
  host_port.sin_addr.s_addr = INADDR_ANY;
  /*
   * allocate ourselves a socket to use
   */
  if((udp_sd = socket(AF_INET,SOCK_DGRAM,0)) < 0) 
    {
      if(!syslogdebug)
	perror("UDP_servent: Socket for datagram failure");
      else
	syslog(LOG_ERR, "UDP_servent: Socket for datagram failure");
      exit(1);
    }
  /*
   * now bind up the datagram socket to ourselves
   */
  if(bind(udp_sd, (struct sockaddr*)&host_port, sizeof(host_port)) < 0) 
    {
      if(!syslogdebug)
	{
	  fprintf(stderr, "UDP_servent: Port %d ", ntohs(sp->s_port));
	  perror("bind for datagram");
	}
      else
	syslog(LOG_ERR, "UDP_servent: Port %d bind failed.", ntohs(sp->s_port));
      return (struct InputDevice *)-1;
    }
  /*
   * and do the get name thing.
   */
  t = sizeof(host_port);
  if(getsockname(udp_sd, (struct sockaddr *)&host_port, &t) != 0) 
    {
      if(!syslogdebug)
	perror("UDP_servent: getsockname");
      else
	syslog(LOG_ERR, "UDP_servent: getsockname: failed.");
      exit(1);
    }

  /* 
   * Finally, take this static variable used in our for
   * our port (always the same) and set it up for copies into
   * daemon messages!
   */
  host_port.sin_family = htons(AF_INET);
  /* Look to see if this step is needed. */
  host_port.sin_addr = HOST_gen_local_host()->addr.sin_addr;
  /*
   * now that we have all that, lets save it.
   */
  new = ET_gen_iodev(IO_UDP, udp_sd, NULL);

  new->laddr = host_port;
  
  new->host = HOST_gen_local_host();

  new->state = IDLE;

  if(verbose)
    {
      printf("Local port defined at: ");
      print_swapped_sockaddr((struct sockaddr *)&host_port);
      printf("\n");
    }

  return new;
}
