/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#pragma ident	"@(#)myrcmd.c 1.14 99/10/25 SMI"	/* SVr4.0 1.6	*/

/*
 * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * 		PROPRIETARY NOTICE (Combined)
 *
 * This source code is unpublished proprietary information
 * constituting, or derived under license from AT&T's UNIX(r) System V.
 * In addition, portions of such source code were derived from Berkeley
 * 4.3 BSD under license from the Regents of the University of
 * California.
 *
 *
 *
 * 		Copyright Notice
 *
 * Notice of copyright on this source code product does not indicate
 * publication.
 *
 * 	Copyright (c) 1986,1987,1988.1989,1998-1999 by Sun Microsystems, Inc.
 * 	Copyright (c) 1983,1984,1985,1986,1987,1988,1989  AT&T.
 *		All rights reserved.
 *
 */

#include <myrcmd.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/file.h>
#include <signal.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <netdb.h>
#include <fcntl.h>
#include <libintl.h>

#include <memutils.h>

#define	index(s, c)		strchr(s, c)
char	*strchr();

char	*inet_ntoa();

char myrcmd_stderr[1024];

int
myrcmd(char **ahost, unsigned short rport, char *locuser, char *remuser,
    char *cmd)
{
	uint_t loclen, remlen, cmdlen;
	int s, timo, retval;
	int tries = 0;
	pid_t pid;
	struct sockaddr_in sin;
	char c;
	int lport;
	int saverr;
	struct hostent *hp;
	sigset_t oldmask;
	sigset_t newmask;
	struct sigaction oldaction;
	struct sigaction newaction;
	static struct hostent numhp;
	static char numhostname[32];	/* big enough for "255.255.255.255" */
	struct in_addr numaddr;
	struct in_addr *numaddrlist[2];

	myrcmd_stderr[0] = '\0';	/* empty error string */
	pid = getpid();
	hp = gethostbyname(*ahost);
	if (hp == 0) {
		char *straddr;

		bzero((char *)numaddrlist, sizeof (numaddrlist));
		if ((numaddr.s_addr = inet_addr(*ahost)) == (in_addr_t)-1) {
			(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
			    gettext("%s: unknown host\n"), *ahost);
			return (MYRCMD_NOHOST);
		} else {
			bzero((char *)&numhp, sizeof (numhp));
			bzero(numhostname, sizeof (numhostname));

			if ((straddr = inet_ntoa(numaddr)) == (char *)0) {
				(void) snprintf(myrcmd_stderr,
				    sizeof (myrcmd_stderr),
				    gettext("%s: unknown host\n"), *ahost);
				return (MYRCMD_NOHOST);
			}
			(void) strncpy(numhostname, straddr,
			    sizeof (numhostname));
			numhostname[sizeof (numhostname) - 1] = '\0';
			numhp.h_name = numhostname;
			numhp.h_addrtype = AF_INET;
			numhp.h_length = sizeof (numaddr);
			numaddrlist[0] = &numaddr;
			numaddrlist[1] = NULL;
			numhp.h_addr_list = (char **)numaddrlist;
			hp = &numhp;
		}
	}
	*ahost = hp->h_name;

	/* This provides a bounds-test for the bcopy()s below. */
	if ((unsigned)(hp->h_length) > sizeof (sin.sin_addr)) {
		(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
		    gettext("rcmd: address size: %d larger than limit %d\n"),
		    hp->h_length, sizeof (sin.sin_addr));
		return (MYRCMD_EBAD);
	}

	/* ignore SIGPIPE */
	bzero((char *)&newaction, sizeof (newaction));
	newaction.sa_handler = SIG_IGN;
	newaction.sa_flags = SA_ONSTACK;
	(void) sigaction(SIGPIPE, &newaction, &oldaction);

	/* block SIGURG */
	bzero((char *)&newmask, sizeof (newmask));
	(void) sigaddset(&newmask, SIGURG);
	(void) sigprocmask(SIG_BLOCK, &newmask, &oldmask);
again:
	timo = 1;
	/*
	 * Use 0 as lport means that rresvport() will bind to a port in
	 * the anonymous priviledged port range.
	 */
	lport = 0;
	for (;;) {
		s = rresvport(&lport);
		if (s < 0) {
			int err;

			if (errno == EAGAIN) {
				(void) snprintf(myrcmd_stderr,
				    sizeof (myrcmd_stderr),
				    gettext("socket: All ports in use\n"));
				err = MYRCMD_ENOPORT;
			} else {
				saverr = errno;
				(void) snprintf(myrcmd_stderr,
				    sizeof (myrcmd_stderr),
				    gettext("rcmd: socket: %s\n"),
				    strerror(saverr));
				err = MYRCMD_ENOSOCK;
			}
			/* restore original SIGPIPE handler */
			(void) sigaction(SIGPIPE, &oldaction,
			    (struct sigaction *)0);

			/* restore original signal mask */
			(void) sigprocmask(SIG_SETMASK, &oldmask,
			    (sigset_t *)0);
			return (err);
		}
		/* Can't fail, according to fcntl(2) */
		(void) fcntl(s, F_SETOWN, pid);
		sin.sin_family = hp->h_addrtype;
		bcopy(hp->h_addr_list[0], (caddr_t)&sin.sin_addr, hp->h_length);
		sin.sin_port = rport;
		if (connect(s, (struct sockaddr *)&sin, sizeof (sin)) >= 0)
			break;
		saverr = errno;
		(void) close(s);
		if (saverr == EADDRINUSE) {
			continue;
		}
		if (saverr == ECONNREFUSED && timo <= 16) {
			sleep(timo);
			timo *= 2;
			continue;
		}
		if (hp->h_addr_list[1] != NULL) {
			saverr = errno;

			fprintf(stderr,
			    gettext("connect to address %s: "),
			    inet_ntoa(sin.sin_addr));
			errno = saverr;
			perror(0);
			hp->h_addr_list++;
			bcopy(hp->h_addr_list[0], (caddr_t)&sin.sin_addr,
			    hp->h_length);
			fprintf(stderr, gettext("Trying %s...\n"),
				inet_ntoa(sin.sin_addr));
			continue;
		}
		(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
		    "%s: %s\n", hp->h_name, strerror(saverr));
		/* restore original SIGPIPE handler */
		(void) sigaction(SIGPIPE, &oldaction,
		    (struct sigaction *)0);

		/* restore original signal mask */
		(void) sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0);
		return (MYRCMD_ENOCONNECT);
	}
	if (write(s, "", 1) < 0) {
		(void) close(s);
		return (MYRCMD_ENOCONNECT);
	}

	loclen = strlen(locuser) + 1;
	remlen = strlen(remuser) + 1;
	cmdlen = strlen(cmd) + 1;

	if (((retval = write(s, locuser, loclen)) != loclen) ||
	    ((retval = write(s, remuser, remlen)) != remlen) ||
	    ((retval = write(s, cmd, cmdlen)) != cmdlen)) {
		if (retval == -1)
			(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
			    "write: %s\n", strerror(errno));
		else
			(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
			    gettext("write unexpectedly truncated\n"));
		goto bad;
	}
	retval = read(s, &c, 1);
	if (retval != 1) {
		if (retval == 0) {
			/*
			 * Solaris 2.0 bug alert.  Sometimes, if the
			 * tapehost is a Solaris 2.0 system, the connection
			 * will be dropped at this point.  Let's try again,
			 * three times, before we throw in the towel.
			 */
			if (++tries < 3) {
				(void) close(s);
				goto again;
			}
			(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
			    gettext("Protocol error, %s closed connection\n"),
			    *ahost);
		} else if (retval < 0) {
			(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
			    "%s: %s\n", *ahost, strerror(errno));
		} else {
			(void) snprintf(myrcmd_stderr, sizeof (myrcmd_stderr),
			    gettext("Protocol error, %s sent %d bytes\n"),
			    *ahost, retval);
		}
		goto bad;
	}
	if (c != 0) {
		char *cp = myrcmd_stderr;
		char *ecp = &myrcmd_stderr[sizeof (myrcmd_stderr) - 1];

		while (read(s, &c, 1) == 1) {
			*cp++ = c;
			if (c == '\n' || cp >= ecp)
				break;
		}
		*cp = '\0';
		goto bad;
	}
	/* restore original SIGPIPE handler */
	(void) sigaction(SIGPIPE, &oldaction, (struct sigaction *)0);

	/* restore original signal mask */
	(void) sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0);
	return (s);
bad:
	(void) close(s);
	/* restore original SIGPIPE handler */
	(void) sigaction(SIGPIPE, &oldaction, (struct sigaction *)0);

	/* restore original signal mask */
	(void) sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0);
	return (MYRCMD_EBAD);
}
