/*
 * @(#) socket.c  RCS: $Revision: 1.6 $ $Date: 95/03/02 12:54:08 $
 *
 * Socket operations
 *
 * getLocalAddr - no error reporting
 * getRemoteAddr - no error reporting
 * openClient - reports error
 * openServer - reports error
 */
#if defined(_POSIX_SOURCE)
#undef _POSIX_SOURCE	/* see comment below */
#endif
#include <sys/types.h>	/* pid_t */
/*
 * errno.h must not be _POSIX_SOURCE in BSDI's BSDI/386 or ENOTSOCK not defined
 */
#include <errno.h>	/* EINTR ENOTSOCK */
/*
 * These symbols are defined in <sys/types.h> but not if _POSIX_SOURCE
 *
 * Some machines can cause them to be defined by:
 * DEC/OSF1  - #define _OSF_SOURCE
 * HP-UX     - #define _HPUX_SOURCE - must still do this for other declarations
 * SunOS 4.1 - not possible, that's why we put them here.
 */
#if 0
typedef unsigned char	u_char;		/* <netinet/in.h> */
typedef unsigned short	u_short;	/* <sys/socket.h> <netinet/tcp.h> */
typedef unsigned long  	u_long;		/* <netinet/tcp.h> */
typedef unsigned int	u_int;		/* <sys/socket.h> and DEC OSF/1 */
#endif
/* #define _POSIX_SOURCE /* This isn't posix source */
#include <sys/socket.h>	/* struct sockaddr */
			/* connect accept listen bind socket */
			/* getpeername getsockname */
#include <netinet/in.h>
#include <netinet/tcp.h>/* TCP_NODELAY */
#include <netdb.h>	/* gethostbyaddr */
#include <arpa/inet.h>	/* inet_ntoa on mips and hpux */

#include <unistd.h>	/* close waitpid */
#include <stdlib.h>	/* atexit */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>	/* WNOHANG */
#include "log.h"
#include "socket.h"
#include "deslogin.h"
#include "posignal.h"
#if defined(TERM_LINK)	/* { */
/*
 * When using a dialup line to a non-slip capable terminal server, there
 * is a package which allows you to multiplex the serial line.  See
 * sunsite.unc.edu:/pub/Linux/apps/comm/term/term/term117.tar.gz for details.
 */
#include <client.h>
#endif			/* } */

/*
 * Return a non-zero socket type of the specifed fd if it is a socket
 *    0 otherwise, -1 for failure
 *
 * socket types in <sys/socket.h>: (socket types always > 0)
 *     SOCK_STREAM SOCK_DGRAM SOCK_RAW SOCK_RDM SOCK_SEQPACKET
 */
int socktype(fd) 
   int fd;
{
   int sotype = -1;
   int size = sizeof sotype;
   int res;

   res = getsockopt(fd, SOL_SOCKET, SO_TYPE, (void *) &sotype, &size);
   if (res >= 0) {
      res = sotype;
   } else if (errno == ENOTSOCK) {
      res = 0;
   }
   return res;
}

/*
 * If flag non-zero, enable TCP delays
 *
 * Returns:
 *     1 delay was previously enabled
 *     0 delay was previously disabled
 *    -1 if gotprotobyname for "tcp" failed
 *    -2 if getsockopt TCP_NODELAY failed
 *    -3 if setsockopt TCP_NODELAY failed
 */
int tcpDelay(sock, flag) 
   int sock, flag;
{
   struct protoent *pent;
   int newflag = -1, oldflag = -1;
   int newlen = sizeof newflag, oldlen = sizeof oldflag;
   int tcplevel, res;

   flag = !flag;			/* NODELAY is opposate sense */

   pent = getprotobyname("tcp");
   if (pent == (struct protoent *) 0) {	/* opens file /etc/protocols */
      return -1;
   }
   tcplevel = pent->p_proto;		/* protocol number for tcp */

   res = getsockopt(sock, tcplevel, TCP_NODELAY, (void *) &oldflag, &oldlen);
   if (res == 0) {
      oldflag = !oldflag;		/* 4 for TCP_NODELAY on, 0 for OFF */
   } else {
      if (errno == ENOPROTOOPT) {	/* never happens on hpux */
	 oldflag = 1;			/* NODELAY was disabled */
      } else {
	 return -2;
      }
   }
   res = setsockopt(sock, tcplevel, TCP_NODELAY, (void *) &flag, sizeof flag);
   if (res != 0) {
      return -3;
   }
   /*
    * To detect new state when in debugger
    */
   res = getsockopt(sock, tcplevel, TCP_NODELAY, (void *) &newflag, &newlen);
   return oldflag;
}

/*
 * Lookup the specified network address (from accept for example).
 * Returns (char *) 0 for failure.
 */
char *hostAddrToName(addr, name, namelen)
   struct sockaddr_in *addr;	/* address from accept(2) */
   char *name;			/* where to put hostname */
   unsigned namelen;		/* how many bytes are there for it */
{
   struct hostent *hp = gethostbyaddr(
      (char *) &(addr->sin_addr), sizeof addr->sin_addr, addr->sin_family);

   if (hp == (struct hostent *) 0) {
      return (char *) 0;
   }
   if (name != (char *) 0) {
      strncpy(name, hp->h_name, namelen);
   }
   return name;
}

/*
 * Internal: convert address to string and port
 *
 * Returns:
 *    -1 - if hostname not found (string contains internet address)
 *     0 - if hostname found and copied
 */
int mapAddr(iaddr, hostName, size, port)
   struct sockaddr_in *iaddr;
   char *hostName;
   unsigned size;
   unsigned *port;
{
   char *chp;
   int res = 0;

   chp = hostAddrToName(iaddr, hostName, size);
   if (chp == (char *) 0) {
      if (hostName != (char *) 0) {
	 strncpy(hostName, inet_ntoa(iaddr->sin_addr), size);
      }
      res = -1;
   }
   if (port != (unsigned *) 0) {
      *port = (unsigned) ntohs(iaddr->sin_port);
   }
   return res;
}

/*
 * Find the address for the specified host and port (AF_INET only).
 *
 * Input;  addr, size    location and size of whereto put the result
 *         port          which port number to use
 *
 * Output: addr - the address
 * Returns: The sizeof the area used by addr, or -1 if error
 */
int hostNameToAddr(addr, size, name, port)
   struct sockaddr *addr;
   unsigned size;
   char *name;
   int port;
{
   struct hostent   *hp = gethostbyname(name);
   struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
   int res = -1;

   if (hp != (struct hostent *) 0) {
      memset(addr, '\0', size);
      iaddr->sin_family = hp->h_addrtype;
      if (hp->h_addrtype == AF_INET) {
	 if ((char *) &iaddr->sin_addr + hp->h_length
	  <= (char *) iaddr + size) {
	    memcpy(&iaddr->sin_addr, hp->h_addr, hp->h_length);
	    iaddr->sin_port   = htons(port);
	 }
	 res = sizeof (struct sockaddr_in);
      }
   }
   return res;
}

/*
 * Try to find the local address of the specified socket. 
 *
 * Returns:
 *     0        - success
 *     ENOTSOCK - if not a socket
 *     errno    - if other error
 */
int getLocalAddr(sock, hostName, size, port)
   int sock;
   char *hostName;
   unsigned size;
   unsigned *port;
{
   int res;
   struct sockaddr_in iaddr;
   int addrlen;

   addrlen = sizeof iaddr;
   res = getsockname(sock, (struct sockaddr *) &iaddr, &addrlen);
   if (res < 0) {
      return errno;
   }

   res = mapAddr(&iaddr, hostName, size, port);
   return 0;
}

/*
 * Try to find the remote address of the specified socket. 
 *
 * Returns:
 *     0        - success
 *     ENOTSOCK - if not a socket
 *     errno    - if other error
 */
int getRemoteAddr(sock, hostName, size, port)
   int sock;
   char *hostName;
   unsigned size;
   unsigned *port;
{
   int res;
   struct sockaddr_in iaddr;
   int addrlen;

   addrlen = sizeof iaddr;
   res = getpeername(sock, (struct sockaddr *) &iaddr, &addrlen);
   if (res < 0) {
      return errno;
   }

   res = mapAddr(&iaddr, hostName, size, port);
   return 0;
}

/*
 * Return the port number for the specified service name or -1 if failed
 */
int getServicePort(name)
   char *name;
{
   struct servent *sent; 
   int res = -1;
   
   sent = getservbyname(name, (char *) 0);
   if (sent != (struct servent *) 0) {
      res = ntohs(sent->s_port);
   }
   return res;
}

void sockHandler(sig) 
   int sig; 
{ 
   if (debug) {
      log("%s(openServer): SIGCHLD\n", progName);
   }
}

/*
 * Listen for a TCP connection on the specified port.  Return socket to it.
 * If hostname is non-null, return upto size bytes of remote host's name.
 * If rport is non-null, set it to the remote port.
 *
 * The caller of this routine should catch SIGCHLD using POSIX sigaction call
 * (see posignal.c) so that we can reap unwanted children when they die.
 *
 * Reports Error if failure and returns -1; othewise the socket
 */
int openServer(port, hostName, size, rport, serverflag)
   int port;
   char *hostName;
   unsigned size;
   unsigned *rport;
   int serverflag;			/* 1 if server, 0 if only once */
{
   int lsock, csock = -1, caddrLen, res; 
   pid_t pid;
   struct sockaddr_in laddr, caddr;
   pid_t child = 0;
   pfv   oldHandler; 
   
   lsock = socket(AF_INET, SOCK_STREAM, 0);
   if (lsock < 0) {
      log("%s: stream socket create failed--%s\n", 
	 progName, ERRMSG);
      return -1;
   }
   memset(&laddr, '\0', sizeof laddr);
   laddr.sin_family = AF_INET;
   laddr.sin_addr.s_addr = htonl(INADDR_ANY);
   laddr.sin_port = htons(port);
   res = bind(lsock,  (void *) &laddr, sizeof laddr);
   if (res < 0) {
      log("%s: bind to port %d failed --%s\n", progName, port, ERRMSG);
      return -1;
   }
   res = listen(lsock, 2);
   if (res < 0) {
      log("%s: listen failed --%s\n", progName, ERRMSG);
      return -1;
   }

   oldHandler = posignal(SIGCHLD, sockHandler);
   if (oldHandler == (pfv) -1)  {
      if (oldHandler == (pfv) -1) {
	 log("%s(openServer): sigaction SIGCHLD failed--%s\n", 
	    progName, ERRMSG);
	 return -1;
      }
   }
   do {
      if (csock >= 0) {
	 close(csock);
      }
      /*
       * Block until we receive a connect request.
       */
      caddrLen = sizeof caddr;
      memset(&caddr, '\0', sizeof caddr);
      csock = accept(lsock, (void *) &caddr, &caddrLen);
      if (csock < 0) {
	 if (errno != EINTR) {
	    log("%s: accept call failed--%s\n", progName, ERRMSG);
	    exit(1);
	 }
	 /* SIGCHLD: Reap all children so we don't leave zombie processes. */
	 do {
	    pid = waitpid(-1, (int *) 0, WNOHANG);
	 } while (pid > 0);
      } else {
	 if (serverflag) {
	    child = fork();
	 }
      }
   } while (serverflag && child > 0);

   posignal(SIGCHLD, oldHandler);
   close(lsock);

   if (debug) {
      log("%s: accept from %s:%u\n", 
	 progName, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
   }
   if (child < 0) {
      log("%s: openServer: fork of child failed\n", progName);
      exit(1);
   }

   res = tcpDelay(csock, 1);		/* don't delay TCP transmissions */
   if (res < 0) {
      log("%s: tcpDelay returned (%d) --%s\n", progName, res, ERRMSG);
      /* OK to continue */
   }
   res = mapAddr(&caddr, hostName, size, rport);
   return csock;
}

/*
 * Establish a connection to the remote host and port.  
 *
 * Return -1 for failure (error reported)
 */
int openClient(hostName, port)
   char *hostName;
   int port;
{
   int sock, res;
#if !defined(TERM_LINK) /* { */
   struct sockaddr addr;
   int addrLen;

   addrLen = hostNameToAddr(&addr, sizeof addr, hostName, port);
   if (addrLen < 0) {
      log("%s: can't find address for host \"%s\"\n", 
	 progName, hostName, ERRMSG);
      return -1;
   }

   sock = socket(addr.sa_family, SOCK_STREAM, 0);
   if (sock < 0) {
      log("%s: stream socket create failed--%s\n", 
	 progName, ERRMSG);
      return -1;
   }
   res = connect(sock, (struct sockaddr *) &addr, addrLen);
   if (res < 0) {
      log("%s: connect to %s:%d failed--%s\n", 
	 progName, hostName, port, ERRMSG);
      return -1;
   }
#if 0		/* BSD removed this and replaced with struct linger. */
   res = setsockopt(sock, SOL_SOCKET, SO_DONTLINGER, (char *) 0, 0);
#endif
   res = tcpDelay(sock, 1);		/* don't delay TCP transmissions */
   if (res < 0) {
      log("%s: tcpDelay returned (%d) --%s\n", progName, res, ERRMSG);
      /* OK to continue */
   }
#else /* } { TERMLINK */
   sock = connect_server(0);
   if (sock < 0) {
      log("%s: connect to term server failed\n", progName);
      return -1;
   }
   res = send_command(sock, C_PORT, 0, "%s:%u", hostName, port);
   if (res < 0) {
      log("%s: send_command(%u, C_PORT, 0 \"%s:%u\") failed--%s\n", 
	 progName, sock, hostName, port, command_result);
      return -1;
   }
   res = send_command(sock, C_DUMB, 1, 0);
   if (res < 0) {
      log("%s: send_command(%u, C_DUMB) failed--%s\n", 
	 progName, sock, command_result);
      return -1;
   }
#endif /* } TERMLINK */
   return sock;
}
