/*
 *	Network Queueing System (NQS)
 *  This version of NQS is Copyright (C) 1992  John Roman
 *
 *  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 1, 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, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
*  PROJECT:     Network Queueing System
*  AUTHOR:      John Roman
*
*  Modification history:
*
*       Version Who     When            Description
*       -------+-------+---------------+-------------------------
*       V01.10  JRR                     Initial version.
*	V01.20	JRR	05-Dec-1991	Completed fixes for remote deletes.
*       V01.30  JRR     16-Jan-1992	Added support for RS6000.
*       V01.4   JRR     12-Feb-1992	Fixed references to static routines.
*       V01.5   JRR     12-Feb-1992	Fixed timezone stuff for SGI.
*					and changed it back.
*       V01.6   JRR     07-Apr-1992     Added CERN enhancements.
*	V01.7	JRR	26-May-1992	Fixed tm for ibm.
*					Added support for test environment.
*	V01.8	JRR	17-Jun-1992	Added header.
*	V01.9	JRR	05-Nov-1992	Adding support for HPUX.
*	V01.10	JRR
*	V01.11	JRR	23-Feb-1993	Added Boeing enhancement for Mids.
*					Added support for DECOSF.
*	V01.12	JRR	12-Aug-1993	Removed non-networked code.
*	V01.13	JRR	03-Dec-1993	Close socket when errors.
*	V01.14	JRR	28-Feb-1994	Added support for SOLARIS.
*/
/*++ establish.c - Network Queueing System
 *
 * $Source: /usr2/jrroma/nqs/nqs-3.35.6/lib/RCS/establish.c,v $
 *
 * DESCRIPTION:
 *
 *	Establish a network connection with a remote NQS network
 *	daemon. Give our caller a SIGPIPE handler.
 *
 *
 *	Author:
 *	-------
 *	Brent A. Kingsbury, Robert W. Sandstrom.
 *	Sterling Software Incorporated.
 *	April 21, 1986.
 *
 *
 * STANDARDS VIOLATIONS:
 *   None.
 *
 * REVISION HISTORY: ($Revision: 1.14 $ $Date: 1994/03/30 20:32:27 $ $State: Exp $)
 * $Log: establish.c,v $
 * Revision 1.14  1994/03/30  20:32:27  jrroma
 * Version 3.35.6
 *
 * Revision 1.13  94/02/24  21:28:44  jrroma
 * Version 3.35.3
 * 
 * Revision 1.12  93/09/10  13:55:09  jrroma
 * Version 3.35
 * 
 * Revision 1.11  93/07/13  21:31:01  jrroma
 * Version 3.34
 * 
 * Revision 1.10  92/12/22  15:45:57  jrroma
 * Version 3.30
 * 
 * Revision 1.9  92/11/06  11:28:56  jrroma
 * Added support for HPUX.
 * 
 * Revision 1.8  92/06/18  13:23:43  jrroma
 * Added gnu header
 * 
 * Revision 1.7  92/06/18  09:41:38  jrroma
 * Version 3.21
 * 
 * Revision 1.6  92/05/06  10:09:46  jrroma
 * Version 3.20
 * 
 * Revision 1.5  92/02/12  16:11:11  jrroma
 * *** empty log message ***
 * 
 * Revision 1.4  92/02/12  11:24:26  jrroma
 * Fix reference to static routine brokenconnection.
 * 
 * Revision 1.3  92/01/16  15:35:43  jrroma
 * Added support for RS6000.
 * 
 * Revision 1.2  91/12/05  16:48:39  jrroma
 * Completed fixes for remote deletes.
 * 
 * Revision 1.1  91/10/29  10:03:28  jrroma
 * Initial revision
 * 
 *
 */
#include "nqs.h"			/* NQS types and definitions */
					/* + sys/types.h + nmap.h + quolim.h */
#include "informcc.h"			/* Information completion codes */
#include "transactcc.h"			/* Transaction completion codes */
#include "netpacket.h"			/* Network packet types */
#include <netdb.h>			/* Network database */
#include <signal.h>			/* For SIGPIPE */
#include <errno.h>			/* System call failure errnos */
#include <sys/socket.h>			/* Socket stuff */
#include <netinet/in.h>			/* Internet stuff */
#if	HPUX | SGI | SOLARIS | SYS52 | IBMRS | DECOSF | LINUX
#include <time.h>
extern char *tzname [2];
extern void tzset();			/* Set local timezone */
#else
#if	BSD43 | ULTRIX
#include <sys/time.h>
extern char *timezone();
#else
BAD SYSTEM TYPE
#endif
#endif


#ifndef __CEXTRACT__
#if __STDC__

static void brokenconnection ( void );
char *nmap_get_nam ( Mid_t mid );

#else /* __STDC__ */

static void brokenconnection (/* void */);
char *nmap_get_nam (/* Mid_t mid */);

#endif /* __STDC__ */
#endif /* __CEXTRACT__ */
/*
 *	External variables.
 */
extern int DEBUG;			/* Debug flag */
#if	NET_ORDER
#else
extern unsigned short htons();		/* A macro if NET_ORDER, */
					/* a routine otherwise */
#endif

/*** establish
 *
 *
 *	int establish():
 *
 *	Establish a network connection with a remote NQS network
 *	daemon.
 *
 *	Returns:
 *		>= 0: if successful; the value returned is the
 *		      "file" descriptor associated with the connection.
 *
 *		-1:   if the connection failed, and we should NOT retry.
 *
 *		-2:   if the connection failed, and we should retry
 *			after some seconds or minutes.
 */
int establish (packettype, targetmid, cuid, cusername, transactcc)
int packettype;
Mid_t targetmid;
uid_t cuid;
char *cusername;
long *transactcc;
{
	
#if	BSD43 | ULTRIX
	struct timeval tv;
	struct timezone tz;
#else
#if	HPUX | SGI | SOLARIS | SYS52 | IBMRS | DECOSF | LINUX
#else
BAD SYSTEM TYPE
#endif
#endif
	Mid_t hostmid;			/* Local host machine-id */
	char *targetname;		/* Hostname of target machine */
	long elapsed;			/* Seconds since 1970 */
	char *tzn;			/* Time zone name */
	struct tm *tp;			/* Time structure pointer */
	char netpassword [MAX_NETPASSWORD+1];
					/* Network password */
	struct sockaddr_in rsin;	/* Remote Internet socket address */
	struct sockaddr_in lsin;	/* Local Internet socket address */
	struct servent *servent;	/* NQS service entry */
	struct hostent *hostent;	/* Host entry for target */
	struct passwd *passwd;		/* Client's password */
	int socket_descr;		/* Socket descriptor */
	char packet [MAX_PACKET];	/* Network message packet */
	int packetsize;			/* Size of network packet */
	short tryport;			/* Try to bind to this port */
	short retry;			/* Boolean retry flag */
	int timeout;			/* Seconds before next connect() */
	int integers;			/* # of 32 bit integers in packet */
	int strings;			/* # of strings in packet */

	/*
	 *  Get the principal name of the target machine.
	 */
	targetname = nmap_get_nam (targetmid);
	if (targetname == (char *) 0) {
		/*
		 *  Cannot determine hostname for mid.
		 */
		*transactcc = TCML_NETDBERR;
		return (-1);
	}
	if (localmid (&hostmid) != 0) {
		/*
		 *  Our attempt at determining the machine-id of the local
		 *  host was unsuccessful.
		 */
		*transactcc = TCML_SELMIDUNKN;
		return (-1);
	}
#if	TEST
	servent = getservbyname ("xqs", (char *) 0);
#else
	servent = getservbyname ("nqs", (char *) 0);
#endif
	if (servent == (struct servent *) 0) return (-1);
	/*
	 *  Determine the address of the host that we are trying
	 *  to reach.
	 */
	hostent = gethostbyname (targetname);
	if (hostent == (struct hostent *) 0) {
		*transactcc = TCML_NETDBERR;
		return (-1);
	}
	/*
	 * Verify the existence of the client we are claiming to be.
	 */
	if (DEBUG) {
		fprintf(stderr, "Doing the fetchpwuid for cuid %d.\n", cuid);
	}
	if ((passwd = fetchpwuid (cuid)) == (struct passwd *) 0) {
		if (DEBUG) {
			fprintf(stderr, "fetchpwuid not return structure.\n");
		}
		*transactcc = TCML_NOACCAUTH;
		return (-1);
	}
	if (strcmp (passwd->pw_name, cusername)) {
		if (DEBUG) {
		    fprintf(stderr, "Password name = %s, cusername = %s\n",
			passwd->pw_name, cusername);
		}
		*transactcc = TCML_NOACCAUTH;
		return (-1);
	}
#if	HPUX | SGI | SOLARIS | SYS52 | IBMRS | DECOSF | LINUX
	tzset();			/* Get local timezone */
	time (&elapsed);		/* Fill in elapsed */
	tp = localtime (&elapsed);	/* Convert to struct tm */
	tzn = tzname [tp->tm_isdst];	/* Tm_isdst is 0 or 1 */
#else
#if	BSD43 | ULTRIX
	gettimeofday (&tv, &tz);	/* Elapsed secs in tv, zone in tz */
	tp = localtime (&tv.tv_sec);	/* Convert to struct tm */
	tzn = timezone (tz.tz_minuteswest, tp->tm_isdst);
#else
BAD SYSTEM TYPE
#endif
#endif
	if (tzn == (char *) 0) tzn = "";
	/*
	 *  Obtain the network password to access the remote NQS machine.
	 */
	netpassword [0] = '\0';		/* NQS password access is not yet */
					/* implemented */
	/*
	 * Clobber whatever SIGPIPE handler our caller has,
	 * and put in one that always returns errno == EPIPE.
	 */
	signal (SIGPIPE, brokenconnection);
	/*
	 *  Append the necessary trailer information to the network
	 *  transaction packet, that is common to ALL NQS network
	 *  transaction packets.
	 */
	interw32u ((unsigned long) NPK_MAGIC1);
	interw32i ((long) packettype);	/* Write packet transaction type */
	interwstr (netpassword);	/* Attach network password to packet */
	interw32u (hostmid);		/* Attach "from-mid" to packet */
	interw32u (targetmid);		/* Attach "to-mid" to packet */
	interw32i ((long) cuid);	/* Attach client's uid to packet */
	interwstr (cusername);		/* Attach client's username to packet */
#if	HPUX | SGI | SOLARIS | SYS52 | IBMRS | DECOSF | LINUX
	/* interw32i ((long) timezone);*/ /* Write timezone difference from */
					/* GMT in seconds */
	interw32i ((long) 0); 		/* Write timezone difference from */
#else
#if	BSD43 | ULTRIX
	interw32i ((long) tz.tz_minuteswest * 60L);
					/* Write our timezone difference */
					/* from GMT in seconds */
#else
BAD SYSTEM TYPE
#endif
#endif
	interwstr (tzn);		/* Write our timezone name */
	/*
	 *  Now that we have defined the network transaction packet
	 *  contents, format the packet.
	 */
	if ((packetsize = interfmt (packet)) == -1) {
		/*
		 *  The packet contents create a packet that is too
		 *  large.  The packet cannot be sent.
		 */
		*transactcc = TCML_INTERNERR;
		return (-1);
	}
	/*
	 *  Store the address of the host we are trying to reach
	 *  in the remote socket address structure.
	 */
	bytecopy ((char *) &rsin.sin_addr.s_addr, (char *) hostent->h_addr,
		hostent->h_length);
	rsin.sin_family = hostent->h_addrtype;
	rsin.sin_port = servent->s_port;
	
	
	/*
	 *  Store the address of the local socket we will be using
	 *  in the local socket address structure.
	 */
	tryport = IPPORT_RESERVED -1;
	timeout = 1;
	lsin.sin_family = AF_INET;
	lsin.sin_addr.s_addr = 0;
	/*
	 *  Try to connect to the remote machine.
	 *  If it is likely that an immediate retry would succeed, go
	 *  ahead and do the retry right now, rather than returning -2.
	 */
	do {
		retry = 0;		/* Adopt an optimistic attitude */
		socket_descr = socket (AF_INET, SOCK_STREAM, 0);
		if (socket_descr == -1) {
			*transactcc = errnototcm ();
			if (errno == ENOBUFS) return (-2);
			else return (-1);
		}
		/*
		 *  Try to bind this socket to a reserved port.
		 *  Without a reserved port, the NQS netserver on a
		 *  remote machine would suspect that we were trying
		 *  to pull a fast one.
		 */
		lsin.sin_port = htons (tryport);
		while (bind (socket_descr, &lsin, sizeof (lsin)) < 0) {
			/*
			 *  The bind() failed.
			 *  Try another port.
			 */
			if (errno != EADDRINUSE && errno != EADDRNOTAVAIL) {
				close (socket_descr);
				*transactcc = errnototcm ();
				return (-1);
			}
			tryport--;
			if (tryport <= IPPORT_RESERVED/2) {
				close (socket_descr);
				*transactcc = TCML_NOPORTAVAI;
				return (-2);
			}
			lsin.sin_port = htons(tryport);
		}
		/*
		 *  Connect to the NQS network daemon on the remote machine.
		 */
		if (connect (socket_descr, (char *) &rsin,
			     sizeof (rsin)) == -1) {
			/*
			 *  We were unable to connect to the remote NQS
			 *  network daemon.
			 */
			switch (errno) {
			case EADDRINUSE:
				close (socket_descr);
				tryport--;
				retry = 1;	/* Retry */
				break;
			case ECONNREFUSED:
				if (timeout <= 16) {
					close (socket_descr);
					sleep (timeout);
					timeout *= 2;
					retry = 1;	/* Retry */
				}
				else {
					close (socket_descr);
					*transactcc = TCMP_NONETDAE;
					return (-1);
				}
				break;
			case ETIMEDOUT:
				close (socket_descr);
				*transactcc = TCML_ETIMEDOUT;
				return (-1);
			default:
				close (socket_descr);
				*transactcc = TCMP_NOESTABLSH;
				return (-1);
			}
		}
	} while (retry);
	if (write (socket_descr, packet, packetsize) != packetsize) {
		close (socket_descr);
		*transactcc = TCMP_CONNBROKEN;
		return (-1);
	}
	/*
	 * Before returning, check that the server liked us.
	 */
	setsockfd (socket_descr);
	switch (interread (getsockch)) {
	case 0:
		break;
	case -1:
		close (socket_descr);
		*transactcc = TCMP_CONNBROKEN;
		return (-1);
	case -2:
		close (socket_descr);
		*transactcc = TCMP_PROTOFAIL;
		return (-1);
	}
	integers = intern32i();			
	if (integers < 1) {
		close (socket_descr);
		*transactcc = TCMP_PROTOFAIL;
		return (-1);
	}
	*transactcc = interr32i (1);
	switch (*transactcc & XCI_FULREA_MASK) {
	/*
	 * Note that code outside the switch passes the TCM to our caller.
	 */
	    case TCMP_CONTINUE:
		return (socket_descr);
	    case TCMP_ERRORRETRY:
		/*
		 * We might have remote servers send strings
		 * as well as TCM's.
		 */
		strings = internstr();
		if (strings < 1) {
		}
		close (socket_descr);
		return (-1);
	    case TCMP_FATALABORT:
		strings = internstr();
		if (strings < 1) {
		}
		close (socket_descr);
		return (-1);
	    case TCMP_CLIMIDUNKN:
	    case TCMP_MAXNETCONN:
	    case TCMP_MIDCONFLCT:
	    case TCMP_NETPASSWD:
	    case TCMP_NOACCAUTH:
	    case TCMP_NOMOREPROC:
	    case TCMP_NONSECPORT:
	    case TCMP_PROTOFAIL:
	    case TCMP_UNAFAILURE:
		close (socket_descr);
		return (-1);
	    default:
		close (socket_descr);
		return (-1);
	}
}


/*** brokenconnection
 *
 *	void brokenconnection():
 *	Allow writes on broken connections to return -1 
 *	instead of exiting.
 */
static void brokenconnection ()
{
	signal (SIGPIPE, brokenconnection);
	errno = EPIPE;
}
