/*
	There was some discussion a little while back about a "ping" for SOCKS.
	The obvious thing here seems to be to use something that uses a common
	TCP service to get an echo, rather than ICMP.  Luckily, such a thing
	already existed...

	Naturally, this depends on the target host not blocking the service on
	the appropriate port (in this case "time").  And this version is
	primarily for checking "Is it alive?" rather than gathering statistics
	on the average response time of several echo requests.  And it requires
	an ANSI C compiler (GCC 2 is sufficient).

	For SunOS 4, I use "gcc newping.c -o newping -DSOCKS -lsocks -lresolv"

	This version of newping was written by Adam Zell <zell@public.btr.com>,
	and was published in the September/October 1993 edition of Sys Admin.

	It uses the "time" TCP port to verify that a host is up, rather than
	using ICMP.  It is thus usable through a firewall that blocks ICMP.

	Requires an ANSI C compiler.

	Utterly trivial modifications made for SOCKS by
	Bryan Curnutt <bryan@Stoner.COM>.
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>

#ifndef INADDR_NONE
#	define INADDR_NONE	(-1)
#endif

#define SVC_PORT	"time"
#define DEF_TOUT	20 /* default timeout */
#define DEF_PORT	37 /* default port */

#define DEBUG		0x1 /* option flags */
#define VERBOSE		0x2

#ifdef SOCKS
#	define connect(sck,adr,len) Rconnect(sck,adr,len)
#endif /* SOCKS */

#define is_opt(x)	(opts & (x))
#define add_opt(x)	(opts |= (x))

typedef enum {		/* use enum instead of multiple #defines */
	OK_RESPONSE		= 0,
	TOUT_CONNECT	= 1,
	TOUT_RESPONSE	= 2,
	REF_CONNECT		= 3,
	BAD_NET			= 4,
	BAD_HOST		= 5,
	BAD_USAGE		= 6,
	UNKNOWN_ERR		= 255
} errtype;

static int hoststring(const char *);

static void die(errtype, const char *, const char *);
static void pexit(errtype);

static void noconnect(int); /* signal handlers */
static void noresponse(int);

/* all globals are volatile */
static volatile int timeout = DEF_TOUT, totsecs = 1, sckt = -1, opts;
static volatile const char *hostname;

int
main(int argc, char *argv[]) {
	struct sockaddr_in phost;
	struct sigaction phan;
	int res;
	const struct protoent *pent = getprotobyname("tcp");
	const char *hname, *pname = strrchr(argv[0], '/');

	if (pname) /* isolate name of program */
		pname++;
	else
		pname = argv[0];
	
	argc--;
	argv++;

	if (pent == NULL) /* tcp not available */
		die (UNKNOWN_ERR, pname, "");

	if (argv[0] && argv[0][0] == '-') { /* process options */
		const char *p = argv[0] + 1;

		if (*p == '\0') /* check for newping - */
			die(BAD_USAGE, pname, "");
		for (; *p; p++) {
			if (*p == 'd')
				add_opt(DEBUG);
			else if (*p == 'v')
				add_opt(VERBOSE);
			else die(BAD_USAGE, pname, "");
		}
		argc--;
		argv++;
	}

	if (argc == 0 || argc > 2)
		die(BAD_USAGE, pname, "");

	hostname = hname = argv[0]; /* get the host name */

	if (argv[1]) { /* if non-NULL, convert to decimal */
		timeout = atoi(argv[1]);
		if (timeout <= 0)
			die(BAD_USAGE, pname, "");
	}

#if defined(SOCKS)
	SOCKSinit(pname);
#endif /* SOCKS */

	if (hoststring(hname)) { /* is it a string? */
		const struct hostent *host = gethostbyname(hname);

		if (host) { /* found it */
			phost.sin_family = host->h_addrtype;
			memcpy(&phost.sin_addr.s_addr, host->h_addr, host->h_length);
		}
		else
			die(TOUT_CONNECT, pname, hname);
	}
	else { /* is in form 000.00.00.00 */
		unsigned long ipaddr = inet_addr(hname);

		if (ipaddr != INADDR_NONE) { /* found it */
			phost.sin_family = AF_INET;
			phost.sin_addr.s_addr = ipaddr;
		}
		else
			die(TOUT_CONNECT, pname, hname);
	}

        if (is_opt(DEBUG)) {
                u_long u = phost.sin_addr.s_addr;
                printf("The IP address for '%s' is: %u.%u.%u.%u\n", hname,
                        (u >> 24) & 0XFF, (u >> 16) & 0XFF,
                        (u >> 8)  & 0XFF, u & 0XFF);
        }
	
	phost.sin_port = DEF_PORT;

	if (is_opt(DEBUG))
		printf("Service %s recognized as port #%u\n",
			SVC_PORT, phost.sin_port);
	
	sckt = socket(PF_INET, SOCK_STREAM, pent->p_proto);
	if (sckt < 0) { /* socket() returns -1 on failure */
		perror("socket");
		pexit(UNKNOWN_ERR);
	}

	if (is_opt(DEBUG))
		printf("Socket open.  Descriptor Number %d=n", sckt);

	phan.sa_handler = &noconnect; /* set up sig handler using sigaction() */
	sigemptyset(&phan.sa_mask);  /* just block alarm signal */
	phan.sa_flags = 0;
/* if defined, SA_RESTART automatically restarts interrupted system calls */
#ifdef SA_RESTART
	phan.sa_flags = SA_RESTART;
#endif /* SA_RESTART */
	sigaction(SIGALRM, &phan, NULL);
/* >>> YDL, 94/02/16 */
/*
	alarm(1);
*/
	alarm(3);
/* <<< YDL, 94/02/16 */

	res = connect(sckt, (struct sockaddr *) &phost, sizeof(phost));
	while (res < 0) { /* connect returns -1 on failure */
		if (errno != EINTR && errno != EISCONN)
			perror("connect");
		switch(errno) {
			case EINTR: /* we were interrupted...try again */
			case EISCONN:
				close(sckt);
				sckt = socket(PF_INET, SOCK_STREAM, pent->p_proto);
				if (sckt < 0) {
					perror("socket");
					pexit(UNKNOWN_ERR);
				}
				break;
			case ECONNRESET:
			case ECONNREFUSED:
				pexit(REF_CONNECT);
				break;
			case EHOSTUNREACH:
				pexit(BAD_HOST);
				break;
			case ENETRESET:
			case ENETDOWN:
			case ENETUNREACH:
				pexit(BAD_NET);
				break;
			default:
				pexit(UNKNOWN_ERR);
				break;
		}
		res = connect(sckt, (struct sockaddr *)&phost, sizeof(phost));
	}

	if (is_opt(DEBUG) && is_opt(VERBOSE))
		printf("Connect made (returned with %d)\n", res);
	
	phan.sa_handler = &noresponse; /* register 2nd phase handler */
	sigaction(SIGALRM, &phan, NULL);

	do {
		char buf[32];

		res = recv(sckt, buf, sizeof(buf), 0); /* recv returns -1 on failure */
		if (res < 0 && errno != EINTR) { /* if EINTR, try again */
			perror("recv");
			switch(errno) {
				case ECONNABORTED:
				case ENOTCONN:
					pexit(TOUT_CONNECT);
					break;
				case ECONNRESET:
				case ENETRESET:
					pexit(BAD_NET);
					break;
				default:
					pexit(UNKNOWN_ERR);
					break;
			}
		}
	} while (res < 0);

	if (is_opt(DEBUG) && is_opt(VERBOSE))
		printf("Received something.  Len = %d  Total Elapsed Time: %d\n",
			res, totsecs);
	
	phan.sa_handler = SIG_IGN; /* just ignore signal now */
	sigaction(SIGALRM, &phan, NULL);
	printf("%s is alive (%d)\n", hname, totsecs);
	pexit(OK_RESPONSE);
	return 0;
}

/* is string in form of mach.net.com or 123.45.67? */
static int
hoststring(const char *p) {
	for (; *p; p++) {
		if (!isdigit(*p) && *p != '.')
			break;
	}
	return (*p != '\0');
}

/* limited error output */
static void
die(errtype which, const char *p, const char *q) {
	if (which == BAD_USAGE)
		fprintf(stderr, "usage: %s [-dv] host [timeout]\n", p);
	else if (which == TOUT_CONNECT)
		fprintf(stderr, "%s: unknown host '%s'\n", p, q);
	else
		fprintf(stderr, "%s: unknown protocol TCP/IP\n", p);
	exit(which);
}

static void
pexit(errtype which) {
	if (sckt > -1) /* close socket if necessary */
		close(sckt);
	exit (which);
}

static void
noconnect(int which) {
	alarm(1);
	totsecs++;
	if (is_opt(DEBUG) || is_opt(VERBOSE))
		printf("No connection after %d seconds!\n", totsecs - 1);
	if (totsecs > timeout) {
		fprintf(stderr, "'%s' not acknowledging connect attempt\n", hostname);
		pexit(TOUT_CONNECT);
	}
}

static void
noresponse(int which) {
	alarm(1);
	totsecs++;
	if (is_opt(DEBUG) || is_opt(VERBOSE))
		printf("No connection after %d seconds!\n", totsecs - 1);
	if (totsecs > timeout) {
		fprintf(stderr, "'%s' not acknowledging connect attempt\n", hostname);
		pexit(TOUT_RESPONSE);
	}
}
/* end of file */
