/********************************************************************
* wilkinson
* 3.19VMS
* 1995/05/25 14:00
* gopher_root1:[gopher.g2.vms2_13.object]Sockets.c,v
* Exp
*
* Paul Lindner, University of Minnesota CIS.
*
* Copyright 1991, 1992 by the Regents of the University of Minnesota
* see the file "Copyright" in the distribution for conditions of use.
*********************************************************************
* MODULE: Sockets.c
* Socket functions
*********************************************************************
* Revision History:
* Sockets.c,v
* Revision 3.19VMS 1995/05/25 14:00    wilkinson
* Consolodate VMS/Unix source code for server as well as client
*
* Revision 3.19  1994/07/31  05:09:47  lindner
* Add option to log only IP
*
* Revision 3.18  1994/04/25  03:46:03  lindner
* Fix for NETLIB and CMUIP
*
* Revision 3.17  1994/04/25  03:36:59  lindner
* Modifications for Debug() and mismatched NULL arguments, added Debugmsg
*
* Revision 3.16  1994/04/08  20:05:53  lindner
* gcc -Wall fixes
*
* Revision 3.15  1994/03/31  21:03:35  lindner
* Use better, more descriptive return codes for socket routines
*
* Revision 3.14  1994/03/17  04:45:39  lindner
* Add needed stdio.h
*
* Revision 3.13  1994/03/17  04:44:39  lindner
* Add needed stdio.h
*
* Revision 3.12  1994/03/17  04:36:01  lindner
* Move socket specific server code here, rearrange include files
*
* Revision 3.11  1994/03/08  15:56:20  lindner
* gcc -Wall fixes
*
* Revision 3.10  1994/01/12  22:23:50  lindner
* Fixes for Data General
*
* Revision 3.9  1993/12/27  16:16:03  lindner
* Fix for sign on integer..
*
* Revision 3.8  1993/10/19  20:49:02  lindner
* Fix for NETLIB
*
* Revision 3.7  1993/10/11  17:26:01  lindner
* Fix for cmuip/netlib
*
* Revision 3.6  1993/09/21  01:51:34  lindner
* Moved netnames fcn..
*
* Revision 3.5  1993/08/09  20:17:10  lindner
* Fixes for CMULIB and NETLIB for VMS
*
* Revision 3.4  1993/08/05  03:23:37  lindner
* Changes for CMUIP and NETLIB
*
* Revision 3.3  1993/07/29  20:01:02  lindner
* Removed dead variables
*
* Revision 3.2  1993/07/27  05:30:27  lindner
* Mondo Debug overhaul from Mitra
*
* Revision 3.1  1993/07/07  19:27:25  lindner
* Socket functions
*
*
*
*********************************************************************/

/* Generic stuff */

#include "boolean.h"
#include "compatible.h"
#include "Debug.h"

#ifdef unix
#  include <sys/param.h>
#endif

#include "Sockets.h"
#include "Malloc.h"
#include <stdio.h>

#ifdef VMS_SERVER
#include <prvdef.h>
char * vms_errno_string();
#include <errno.h>
#include <ssdef.h>
#endif

/*
* This turns the linger output off
*/

void
SOCKlinger(sockfd, onoff)
 int sockfd;
 boolean onoff;
{
#if defined(SO_LINGER) && !defined(NO_LINGER)
   struct linger linger;

   linger.l_onoff = onoff;
   linger.l_linger = 0;
   if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *)&linger,
                  sizeof (linger)) < 0)
#ifndef VMS_SERVER
        perror("server: can't turn off linger sockopt"),exit(-1);
#else
        LOGGopher(-99,"fatal: can't turn off linger sockopt, %s",
                                               vms_errno_string());
#endif
#endif
}



/*
* This function returns a socket file descriptor bound to the given port
*/

#if !defined(CMUIP) && !defined(NETLIB)
int
SOCKbind_to_port(port)
 int port;
{
   struct sockaddr_in serv_addr;
   int reuseaddr = 1;
   int sockfd;
#ifdef VMS_SERVER
   union prvdef prvadr;
   unsigned long int ON = -1;

   if (port<1024) {
       bzero((char *) &prvadr, sizeof(prvadr));
       prvadr.prv$v_sysprv = 1;
       if (SS$_NORMAL != (vaxc$errno = SYS$SETPRV (ON, &prvadr, 0, 0))) {
             LOGGopher(-1,"Can't insure  SYSPRV for bind to port %d, %s",
                               port, STRerror(vaxc$errno));
       }
   }
#endif


   if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
#ifndef VMS_SERVER
        perror("server: can't open stream socket"), exit(-1);
#else
        LOGGopher(-99,"fatal: can't open stream socket, %s",
                                               vms_errno_string());
#endif

   /** Bind our local address so that the client can send to us **/

   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family                = AF_INET;
   serv_addr.sin_addr.s_addr   = htonl(INADDR_ANY);
   serv_addr.sin_port          = htons(port);

   if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseaddr,
                  sizeof(reuseaddr)) < 0)
#ifndef VMS_SERVER
         perror("server: can't set REUSEADDR!"),exit(-1);
#else
         LOGGopher(-99,"fatal: can't set socket REUSEADDR, %s",
                                       vms_errno_string());
#endif

   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <0)
#ifndef VMS_SERVER
        perror("server: can't bind local address"),exit(-1);
#else
        LOGGopher(-99, "fatal: can't bind local address, %s",
                               vms_errno_string());
#endif

   SOCKlinger(sockfd, FALSE);
#ifdef VMS_SERVER
   bzero((char *) &prvadr, sizeof(prvadr));
   prvadr.prv$v_sysprv = 1;
   if (SS$_NORMAL != (vaxc$errno = SYS$SETPRV (DEBUG, &prvadr, 0, 0))) {
         LOGGopher(-1,"Can't discard PRIVS, %s", STRerror(vaxc$errno));
   }
#endif
   return(sockfd);
}



/*
* This routine finds out the hostname of the server machine.
* It uses a couple of methods to find the fully qualified
* Domain name
*
* If gdchost is non NULL then use that paramater instead of
* the code in here.
*/

char *
SOCKgetDNSname(backupdomain, gdchost)
 char *backupdomain;
 char *gdchost;
{
    static char DNSname[MAXHOSTNAMELEN];
    struct hostent *hp;

    if (*gdchost != '\0')
         return(gdchost);

    DNSname[0] = '\0';
    /* Work out our fully-qualified name, for later use */

    if (gethostname(DNSname, MAXHOSTNAMELEN) != 0) {
#ifndef VMS_SERVER
         fprintf(stderr, "Cannot determine the name of this host\n");
         exit(-1);
#else
         LOGGopher(-99,"fatal: Cannot determine the name of this host");
#endif
    }

    /* Now, use gethostbyname to (hopefully) do a nameserver lookup */
    hp = gethostbyname( DNSname);

    /*
     ** If we got something, and the name is longer than hostname, then
     ** assume that it must the the fully-qualified hostname
     */
    if ( hp!=NULL && strlen(hp->h_name) > strlen(DNSname) )
         strncpy( DNSname, hp->h_name, MAXHOSTNAMELEN );
    else
         strcat(DNSname, backupdomain);

    return(DNSname);
}


/*
* Tries to figure out what the currently connected port is.
*
* If it's a socket then it will return the port of the socket,
* if it isn't a socket then it returns -1.
*/

int
SOCKgetPort(fd)
 int fd;
{
    struct sockaddr_in serv_addr;

    int length = sizeof(serv_addr);

    /** Try to figure out the port we're running on. **/

    if (getsockname(fd, (struct sockaddr *) &serv_addr,&length) == 0)
         return(ntohs(serv_addr.sin_port));
    else
         return(-1);

}

#endif /* not CMUIP nor NETLIB */



/* SOCKconnect performs a connection to socket 'service' on host
* 'host'.  Host can be a hostname or ip-address.  If 'host' is null, the
* local host is assumed.   The parameter full_hostname will, on return,
* contain the expanded hostname (if possible).  Note that full_hostname is a
* pointer to a char *, and is allocated by connect_to_gopher()
*
* Errors: ErrSocket* defined in Sockets.h
*
*/

int
SOCKconnect(hostname, port)
 char *hostname;
 int port;
{
#if !defined(CMUIP) && !defined(NETLIB)
    struct sockaddr_in Server;
    struct hostent *HostPtr;
    int sockfd = 0;
    unsigned int ERRinet = -1;

#ifdef _CRAY
    ERRinet = 0xFFFFFFFF;  /* -1 doesn't sign extend on 64 bit machines */
#endif

    /*** Find the hostname address ***/

    if (hostname == NULL || *hostname == '\0')
         return(ErrSocketNullHost);

#ifdef DGUX
    Server.sin_addr = inet_addr(hostname);
    if (Server.sin_addr.s_addr == ERRinet)
#else
    if ((Server.sin_addr.s_addr = inet_addr(hostname)) == ERRinet)
#endif
    {
         if ((HostPtr = gethostbyname(hostname)) != NULL) {
              bzero((char *) &Server, sizeof(Server));
              bcopy(HostPtr->h_addr, (char *) &Server.sin_addr, HostPtr->h_length);
              Server.sin_family = HostPtr->h_addrtype;
         } else
              return (ErrSocketGetHost);
    } else
         Server.sin_family = AF_INET;

    Server.sin_port = (unsigned short) htons(port);

    /*** Open the socket ***/

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
         return (ErrSocketCall);

#ifndef UCX
    setsockopt(sockfd, SOL_SOCKET, ~SO_LINGER, 0, 0);
#endif

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0);
    setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, 0, 0);

    /*** Connect ***/

    if (connect(sockfd, (struct sockaddr *) &Server, sizeof(Server)) < 0) {
         closenet(sockfd);
         return (ErrSocketConnect);
    }

    return(sockfd);

#else /* !NETLIB && !CMUIP */
#ifdef NETLIB
    int status;
    static int iSock = 0;
    struct {
         long len;
         char *adr;
    } host_desc;

#define NET_K_TCP 1
    status = NET_ASSIGN (&iSock);
    if ((status & 1) == 0)
         return (ErrSocketCall);
    status = NET_BIND (&iSock, NET_K_TCP);
    if ((status & 1) == 0)
         return (ErrSocketCall);
    host_desc.adr = hostname;
    host_desc.len = strlen (host_desc.adr);
    status = TCP_CONNECT (&iSock, &host_desc, port);
    if ((status & 1) == 0) {
         NET_DEASSIGN (&iSock);
         if (status == SS$_REJECT || status == SS$_TIMEOUT) {
              if (status == SS$_REJECT)
                   errno = ECONNREFUSED;
              else
                   errno = ETIMEDOUT;

              return (ErrSocketConnect);
         }
         if (status == SS$_ENDOFFILE)
              return (ErrSocketGetHost);
         return (ErrSocketGetService);
    }
    return (iSock);
#else /* ifdef NETLIB:  assume CMUIP */
    short channel;
    int status;
    struct {
         short status;
         short size;
         long xxx;
    } cmu_iosb;
    static struct {long l; char *a;} ip_dev = {12, "INET$DEVICE:"};
    globalvalue NET$_CREF;           /* Connection refused */
    globalvalue NET$_FTO;            /* Function timedout */
    globalvalue NET$_DSNAMERR;       /* Domain server name error */
    status = SYS$ASSIGN (&ip_dev, &channel, 0, 0);
    if ((status & 1) == 0)
         return (ErrSocketCall);
    status = SYS$QIOW (0, channel, IO$_CREATE, &cmu_iosb, 0, 0,
         hostname, port, 0, 1, 0, 0);
    if ((status & 1) == 0 || (cmu_iosb.status & 1) == 0) {
         SYS$DASSGN (channel);
         if (cmu_iosb.status == SS$_ABORT || cmu_iosb.xxx == NET$_FTO) {
              if (cmu_iosb.xxx == NET$_CREF)
                   errno = ECONNREFUSED;
              else
                   errno = ETIMEDOUT;

              return(ErrSocketConnect);
         }
         if (cmu_iosb.xxx == NET$_DSNAMERR)
              return (ErrSocketGetHost);
    }
    return (channel);
#endif
#endif

}




/*
*
*/

#if !defined(CMUIP) && !defined(NETLIB)         /* temp - MLH */
int
SOCKlisten(We)
 struct sockaddr_in *  We;
{
    int             sockfd = 0;
    struct hostent *HostPtr;
    int             len = sizeof(struct sockaddr);
    char            name[100];


    sockfd = SOCKbind_to_port(sockfd);
    if (listen(sockfd, 5) || getsockname(sockfd, (struct sockaddr *) We, &len)) {
         closenet(sockfd);
         return(ErrSocketGetHost);
    }

    gethostname(name, 100);
    if ((HostPtr = gethostbyname(name)))
         bcopy(HostPtr->h_addr, (char *) &We->sin_addr, HostPtr->h_length);
    return(sockfd);
}


/* SOCKaccept accepts a connection form some socket. *
* Errors: ErrSocket* defined in Sockets.h
*/

int
SOCKaccept(s, we)
 int s;
 struct sockaddr_in we;
{
    int            sockfd    = 0;
    int            len       = sizeof(struct sockaddr);
    unsigned short tem;

    tem = ntohs(we.sin_port);

    Debugmsg("Here we go...\n");

    if ((sockfd = accept(s, (struct sockaddr *) &we, &len)) < 0) {
         return ErrSocketConnect;
    }
    close(s); /* Der Mohr hat seine Schuldigkeit getan */

    return(sockfd);
}



/*
* SOCKnetnames -- return the network, subnet, and host names of
* our peer process for the Internet domain.
*
*      Parameters:     "sock" is our socket
*                      "host_name"
*                      is filled in by this routine with the
*                      corresponding ASCII names of our peer.
*
*                      if there doesn't exist a hostname in DNS etal,
*                      the IP# will be inserted for the host_name
*
*                      "ipnum" is filled in with the ascii IP#
*/

void
SOCKnetnames(sockfd, host_name, ipnum)
 int  sockfd;
 char *host_name;
 char *ipnum;
{
    struct sockaddr_in      sa;
    int                     length;
    struct hostent          *hp;

    length = sizeof(sa);
    if (getpeername(sockfd, (struct sockaddr *)&sa, &length))
         /** May fail if sockfd has been closed **/
#ifdef VMS_SERVER
    {
         LOGGopher(-2, "getpeername() failure: %s", vms_errno_string());
         if (ipnum != NULL)
           strcpy(ipnum,"Unknown");
         if (host_name != NULL)
           strcpy(host_name,"Unknown");
         return;
     }

#else
         return;
#endif

    if (ipnum != NULL)
         strcpy(ipnum, inet_ntoa(sa.sin_addr));

    if (host_name != NULL)
         strcpy(host_name, inet_ntoa(sa.sin_addr));

#ifdef LOG_IP_ONLY
    hp = NULL;
#else
    hp = gethostbyaddr((char *) &sa.sin_addr,sizeof (sa.sin_addr), AF_INET);
#endif

    if (hp != NULL && host_name != NULL)
         (void) strcpy(host_name, hp->h_name);

}

#endif  /** CMUIP etal **/

#if defined(VMS_SERVER) && defined(MULTINET)
/*
* This turns the keepalive option on
*      TGV recommended setting KEEPALIVE on the server, just in case
*          we somehow got a socket locked in a CLOSE_WAIT state (shouldn't
*          happen since we set REUSEADDR *before* we bind, but... it's
*          sure a lot better than rebooting your VAX/AXP node to get the
*          port back from a %MULTINET-F-EADDRINUSE error condition).
*/
void
SOCKkeepalive(sockfd, onoff)
 int sockfd;
 boolean onoff;
{
#if defined(SO_KEEPALIVE) && !defined(NO_KEEPALIVE)
   int keepalive = onoff;
   if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive,
                  sizeof (keepalive)) < 0)
        LOGGopher(-1,"warning: can't turn %s keepalive sockopt, %s",
                               onoff?"on":"off", vms_errno_string());
#endif
}
#endif