[BACK]Return to netcat.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / nc

File: [local] / src / usr.bin / nc / netcat.c (download)

Revision 1.11, Tue Sep 26 01:15:49 2000 UTC (23 years, 8 months ago) by ericj
Branch: MAIN
Changes since 1.10: +572 -831 lines


Alot of cleanups to netcat
use errx/warn instead of homegrown functions
indent properly to make the code readable
get rid of generic.h as its not used
dont declare optarg
register is depreciated
get rid of undocumented options -a and -e
throw out IP_OPTIONS code for now, its not on by default
and doesnt work properly
make usage more like other bsd commands

/* $OpenBSD: netcat.c,v 1.11 2000/09/26 01:15:49 ericj Exp $ */

/* Netcat 1.10 RELEASE 960320
 *
 *   A damn useful little "backend" utility begun 950915 or thereabouts,
 *   as *Hobbit*'s first real stab at some sockets programming.  Something that
 *   should have and indeed may have existed ten years ago, but never became a
 *   standard Unix utility.  IMHO, "nc" could take its place right next to cat,
 *   cp, rm, mv, dd, ls, and all those other cryptic and Unix-like things.
 *
 *   Read the README for the whole story, doc, applications, etc.
 *
 *   Layout:
 *	conditional includes:
 *	includes:
 *	handy defines:
 *	globals:
 *	malloced globals:
 *	cmd-flag globals:
 *	support routines:
 *	readwrite select loop:
 *	main:
 *
 *  bluesky:
 *	parse ranges of IP address as well as ports, perhaps
 *	RAW mode!
 *	backend progs to grab a pty and look like a real telnetd?!
 *	backend progs to do various encryption modes??!?!
*/


#define HAVE_BIND		/* ASSUMPTION -- seems to work everywhere! */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>		/* hostent, gethostby*, getservby* */
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

/* Random Numbers aren't too needed here */
#define SRAND srandom
#define RAND random

#define SLEAZE_PORT 31337	/* for UDP-scan RTT trick, change if ya want */
#define BIGSIZ 8192		/* big buffers */

struct host_info {
	char	name[MAXHOSTNAMELEN];	/* DNS name */
	char	addrs[8][24];		/* ascii-format IP addresses */
	struct 	in_addr iaddrs[8];	/* in_addr.s_addr: ulong */
};

struct port_info {
	char    name[64];	/* name in /etc/services */
	char    anum[8];	/* ascii-format number */
	u_short  num;		/* real host-order number */
};

/* globals: */
jmp_buf jbuf;			/* timer jump buffer*/
int     jval = 0;		/* timer crud */
int     netfd = -1;
int     ofd = 0;		/* hexdump output fd */

extern int h_errno;
/* stolen almost wholesale from bsd herror.c */
static char *h_errs[] = {
	"Error 0",				/* but we *don't* use this */
	"Unknown host",				/* 1 HOST_NOT_FOUND */
	"Host name lookup failure",		/* 2 TRY_AGAIN */
	"Unknown server error",			/* 3 NO_RECOVERY */
	"No address associated with name",	/* 4 NO_ADDRESS */
};

int     gatesidx = 0;		/* LSRR hop count */
int     gatesptr = 4;		/* initial LSRR pointer, settable */
u_short  Single = 1;		/* zero if scanning */
unsigned int insaved = 0;	/* stdin-buffer size for multi-mode */
unsigned int wrote_out = 0;	/* total stdout bytes */
unsigned int wrote_net = 0;	/* total net bytes */
static char hexnibs[20] = "0123456789abcdef  ";

/* will malloc up the following globals: */
struct timeval timer1, timer2;
struct sockaddr_in    *lclend = NULL;	/* sockaddr_in structs */
struct sockaddr_in    *remend = NULL;
struct host_info  **gates = NULL;	/* LSRR hop hinfo */
char   *optbuf = NULL;			/* LSRR or sockopts */
char   *bigbuf_in;			/* data buffers */
char   *bigbuf_net;
fd_set fds1, fds2;
struct port_info   *pinfo = NULL;	/* for getpinfo / getservby* */
unsigned char *stage = NULL;		/* hexdump line buffer */

/* global cmd flags: */
u_short  o_alla = 0;
unsigned int o_interval = 0;
u_short  o_listen = 0;
u_short  o_nflag = 0;
u_short  o_wfile = 0;
u_short  o_random = 0;
u_short  o_udpmode = 0;
u_short  o_verbose = 0;
unsigned int o_wait = 0;
u_short  o_zero = 0;

/* Function Prototype's */
void	usage	__P((int));
void	help	__P(());

/* 
 * support routines -- the bulk of this thing.  Placed in such an order that
 * we don't have to forward-declare anything: 
 */

/* 
 * catch :
 * no-brainer interrupt handler
 */
void 
catch()
{
	errno = 0;
	if (o_verbose > 1)	/* normally we don't care */
		errx(1, "Sent %i Rcvd %i", wrote_net, wrote_out);
	errx(1, " punt!");
}

/* timeout and other signal handling cruft */
void 
tmtravel()
{
	signal(SIGALRM, SIG_IGN);
	alarm(0);
	if (jval == 0)
		errx(1, "spurious timer interrupt!");
	longjmp(jbuf, jval);
}

/*
 * arm :
 * set the timer.  Zero secs arg means unarm
 */
void 
arm(num, secs)
	unsigned int num;
	unsigned int secs;
{
	if (secs == 0) {
		signal(SIGALRM, SIG_IGN);
		alarm(0);
		jval = 0;
	} else {
		signal(SIGALRM, tmtravel);
		alarm(secs);
		jval = num;
	}	
}

/*
 * findline :
 * find the next newline in a buffer; return inclusive size of that "line",
 * or the entire buffer size, so the caller knows how much to then write().
 * Not distinguishing \n vs \r\n for the nonce; it just works as is... 
 */
unsigned int 
findline(buf, siz)
	char   *buf;
	unsigned int siz;
{
	char *p;
	int x;
	if (!buf)		/* various sanity checks... */
		return (0);
	if (siz > BIGSIZ)
		return (0);
	x = siz;
	for (p = buf; x > 0; x--) {
		if (*p == '\n') {
			x = (int) (p - buf);
			x++;	/* 'sokay if it points just past the end! */
			    return (x);
		}
		p++;
	}
	    return (siz);
}

/*
 * comparehosts :
 * cross-check the host_info we have so far against new gethostby*() info,
 * and holler about mismatches.  Perhaps gratuitous, but it can't hurt to
 * point out when someone's DNS is fukt.  Returns 1 if mismatch, in case
 * someone else wants to do something about it. 
 */
int 
comparehosts(hinfo, hp)
	struct host_info   *hinfo;
	struct hostent *hp;
{
	errno = 0;
	h_errno = 0;
	if (strcasecmp(hinfo->name, hp->h_name) != 0) {
		warn("DNS fwd/rev mismatch: %s != %s", hinfo->name, hp->h_name);
		return (1);
	}
	return (0);
}

/* 
 * gethinfo:
 * resolve a host 8 ways from sunday; return a new host_info struct with its
 * info.  The argument can be a name or [ascii] IP address; it will try its
 * damndest to deal with it.  "numeric" governs whether we do any DNS at all,
 * and we also check o_verbose for what's appropriate work to do.
 */
struct host_info   *
gethinfo(name, numeric)
	char   *name;
	u_short  numeric;
{
	struct hostent *hostent;
	struct in_addr iaddr;
	struct host_info *hinfo = NULL;
	int x;

	errno = 0;
	h_errno = 0;
	if (name)
		hinfo = (struct host_info *) calloc(1, sizeof(struct host_info));

	if (!hinfo)
		errx(1, "error obtaining host information");

	strlcpy(hinfo->name, "(UNKNOWN)", sizeof(hinfo->name));
	if (inet_aton(name, &iaddr) == 0) {
		if (numeric)
			errx(1, "Can't parse %s as an IP address", name);

		/*
		 * failure to look up a name is fatal, 
		 * since we can't do anything with it.
		 */
		hostent = gethostbyname(name);
		if (!hostent)
			errx(1, "%s: forward host lookup failed: ", name);

		strlcpy(hinfo->name, hostent->h_name, MAXHOSTNAMELEN);
		for (x = 0; hostent->h_addr_list[x] && (x < 8); x++) {
			memcpy(&hinfo->iaddrs[x], hostent->h_addr_list[x],
			    sizeof(struct in_addr));
			strlcpy(hinfo->addrs[x], inet_ntoa(hinfo->iaddrs[x]),
			    sizeof(hinfo->addrs[0]));
		}
		/* Go ahead and return if we don't want to view more */
		if (!o_verbose)	
			return (hinfo);

		/*
		 * Do inverse lookups in separate loop based on our collected 
	   	 * forward addrs, since gethostby* tends to crap into the same
		 * buffer over and over.
		 */
		for (x = 0; hinfo->iaddrs[x].s_addr && (x < 8); x++) {
			hostent = gethostbyaddr((char *) &hinfo->iaddrs[x],
			    sizeof(struct in_addr), AF_INET);
			if ((!hostent) || (!hostent->h_name))
				warn("Warning: inverse host lookup failed for %s: ",
				    hinfo->addrs[x]);
			else
				(void) comparehosts(hinfo, hostent);
		}

	} else {		/* not INADDR_NONE: numeric addresses... */
		memcpy(hinfo->iaddrs, &iaddr, sizeof(struct in_addr));
		strlcpy(hinfo->addrs[0], inet_ntoa(iaddr), sizeof(hinfo->addrs));
		/* If all that's wanted is numeric IP, go ahead and leave */
		if (numeric)	
			return (hinfo);

		/* Go ahead and return if we don't want to view more */
		if (!o_verbose)	
			return (hinfo);

		hostent = gethostbyaddr((char *) &iaddr, 
					sizeof(struct in_addr), AF_INET);

		/* 
		 * numeric or not, failure to look up a PTR is 
		 * *not* considered fatal 
		 */
		if (!hostent)
			warn("%s: inverse host lookup failed: ", name);
		else {
			strlcpy(hinfo->name, hostent->h_name, MAXHOSTNAMELEN);
			hostent = gethostbyname(hinfo->name);
			if ((!hostent) || (!hostent->h_addr_list[0]))
				warn("Warning: forward host lookup failed for %s: ",
				    hinfo->name);
			else
				(void) comparehosts(hinfo, hostent);
		}
	}

	/*
	 * Whatever-all went down previously, we should now have a host_info 
	 * struct with at least one IP address in it.
	 */
	h_errno = 0;
	return (hinfo);
}

/*
 * getpinfo:
 * Same general idea as gethinfo-- look up a port in /etc/services, fill
 * in global port_info, but return the actual port *number*.  Pass ONE of:
 *	pstring to resolve stuff like "23" or "exec";
 *	pnum to reverse-resolve something that's already a number.
 * If o_nflag is on, fill in what we can but skip the getservby??? stuff.
 * Might as well have consistent behavior here, and it *is* faster.
 */
u_short 
getpinfo(pstring, pnum)
	char   *pstring;
	unsigned int pnum;
{
	struct servent *servent;
	int x;
	int y;
	char   *whichp = "tcp";
	if (o_udpmode)
		whichp = "udp";

	pinfo->name[0] = '?';/* fast preload */
	pinfo->name[1] = '\0';

	/*
	 * case 1: reverse-lookup of a number; placed first since this case
	 * is much more frequent if we're scanning.
	 */
	if (pnum) {
		/* Can't be both */
		if (pstring)
			return (0);

		x = pnum;
		if (o_nflag)	/* go faster, skip getservbyblah */
			goto gp_finish;
		y = htons(x);	/* gotta do this -- see Fig.1 below */
		servent = getservbyport(y, whichp);
		if (servent) {
			y = ntohs(servent->s_port);
			if (x != y)	/* "never happen" */
				warn("Warning: port-bynum mismatch, %d != %d", x, y);
			strlcpy(pinfo->name, servent->s_name,
			    sizeof(pinfo->name));
		}
		goto gp_finish;
	}
	/*
	 * case 2: resolve a string, but we still give preference to numbers
	 * instead of trying to resolve conflicts.  None of the entries in *my*
	 * extensive /etc/services begins with a digit, so this should "always
	 * work" unless you're at 3com and have some company-internal services
	 * defined.
	 */
	if (pstring) {
		/* Can't be both */
		if (pnum)
			return (0);

		x = atoi(pstring);
		if (x)
			return (getpinfo(NULL, x));	/* recurse for
							 * numeric-string-arg */
		if (o_nflag)	/* can't use names! */
			return (0);
		servent = getservbyname(pstring, whichp);
		if (servent) {
			strlcpy(pinfo->name, servent->s_name,
			    sizeof(pinfo->name));
			x = ntohs(servent->s_port);
			goto gp_finish;
		}		/* if servent */
	}			/* if pstring */
	return (0);		/* catches any problems so far */

gp_finish:
	/*
	 * Fall here whether or not we have a valid servent at this point, with
   	 * x containing our [host-order and therefore useful, dammit] port number.
	 */
	sprintf(pinfo->anum, "%d", x);	/* always load any numeric
						 * specs! */
	pinfo->num = (x & 0xffff);	/* u_short, remember... */
	return (pinfo->num);
}

/*
 * nextport :
 * Come up with the next port to try, be it random or whatever.  "block" is
 * a ptr to randports array, whose bytes [so far] carry these meanings:
 *	0	ignore
 * 	1	to be tested
 *	2	tested [which is set as we find them here]
 * returns a u_short random port, or 0 if all the t-b-t ones are used up. 
 */
u_short 
nextport(block)
	char   *block;
{
	unsigned int x;
	unsigned int y;

	y = 70000;			/* high safety count for rnd-tries */
	while (y > 0) {
		x = (RAND() & 0xffff);
		if (block[x] == 1) {	/* try to find a not-done one... */
			block[x] = 2;
			break;
		}
		x = 0;		
		y--;
	}
	if (x)
		return (x);

	y = 65535;		/* no random one, try linear downsearch */
	while (y > 0) {		/* if they're all used, we *must* be sure! */
		if (block[y] == 1) {
			block[y] = 2;
			break;
		}
		y--;
	}
	if (y)
		return (y);	/* at least one left */

	return (0);
}

/*
 * loadports :
 * set "to be tested" indications in BLOCK, from LO to HI.  Almost too small
 * to be a separate routine, but makes main() a little cleaner.
 */
void 
loadports(block, lo, hi)
	char   *block;
	u_short  lo;
	u_short  hi;
{
	u_short  x;

	if (!block)
		errx(1, "loadports: no block?!");
	if ((!lo) || (!hi))
		errx(1, "loadports: bogus values %d, %d", lo, hi);
	x = hi;
	while (lo <= x) {
		block[x] = 1;
		x--;
	}
}


/*
 * doconnect :
 * do all the socket stuff, and return an fd for one of
 *	an open outbound TCP connection
 * 	a UDP stub-socket thingie
 * with appropriate socket options set up if we wanted source-routing, or
 *	an unconnected TCP or UDP socket to listen on.
 * Examines various global o_blah flags to figure out what-all to do. 
 */
int 
doconnect(rad, rp, lad, lp)
	struct in_addr     *rad;
	u_short  rp;
	struct in_addr     *lad;
	u_short  lp;
{
	int nnetfd;
	int rr;
	int     x, y;
	errno = 0;

	/* grab a socket; set opts */
newskt:
	if (o_udpmode)
		nnetfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	else
		nnetfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (nnetfd < 0)
		errx(1, "Can't get socket");
	if (nnetfd == 0)	/* if stdin was closed this might *be* 0, */
		goto newskt;	/* so grab another.  See text for why... */
	x = 1;
	rr = setsockopt(nnetfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
	if (rr == -1)
		warn("nnetfd reuseaddr failed");	/* ??? */

	/* fill in all the right sockaddr crud */
	lclend->sin_family = AF_INET;

	/* fill in all the right sockaddr crud */
	lclend->sin_family = AF_INET;
	remend->sin_family = AF_INET;

	/* if lad/lp, do appropriate binding */
	if (lad)
		memcpy(&lclend->sin_addr.s_addr, lad, sizeof(struct in_addr));
	if (lp)
		lclend->sin_port = htons(lp);

	rr = 0;
	if (lad || lp) {
		x = (int) lp;
		/* try a few times for the local bind, a la ftp-data-port... */
		for (y = 4; y > 0; y--) {
			rr = bind(nnetfd, (struct sockaddr *) lclend,
			    sizeof(struct sockaddr_in));
			if (rr == 0)
				break;
			if (errno != EADDRINUSE)
				break;
			else {
				warn("retrying local %s:%d", inet_ntoa(lclend->sin_addr), lp);
				sleep(2);
				errno = 0;	/* clear from sleep */
			}
		}
	}
	if (rr)
		errx(1, "Can't grab %s:%d with bind",
		    inet_ntoa(lclend->sin_addr), lp);

	if (o_listen)
		return (nnetfd);/* thanks, that's all for today */

	memcpy(&remend->sin_addr.s_addr, rad, sizeof(struct in_addr));
	remend->sin_port = htons(rp);

	/* wrap connect inside a timer, and hit it */
	arm(1, o_wait);
	if (setjmp(jbuf) == 0) {
		rr = connect(nnetfd, (struct sockaddr *) remend, 
						sizeof(struct sockaddr));
	} else {
		rr = -1;
		errno = ETIMEDOUT;
	}
	arm(0, 0);
	if (rr == 0)
		return (nnetfd);
	close(nnetfd);
	return (-1);
}

/*
 * dolisten :
 * just like doconnect, and in fact calls a hunk of doconnect, but listens for
 * incoming and returns an open connection *from* someplace.  If we were
 * given host/port args, any connections from elsewhere are rejected.  This
 * in conjunction with local-address binding should limit things nicely.
 */
int 
dolisten(rad, rp, lad, lp)
	struct in_addr     *rad;
	u_short  rp;
	struct in_addr     *lad;
	u_short  lp;
{
	int nnetfd;
	int rr;
	struct host_info   *whozis = NULL;
	int     x;
	char   *cp;
	u_short  z;
	errno = 0;

	/*
	 * Pass everything off to doconnect, 
	 * who in o_listen mode just gets a socket 
	 */
	nnetfd = doconnect(rad, rp, lad, lp);
	if (nnetfd <= 0)
		return (-1);
	if (o_udpmode) {
		if (!lp)
			errx(1, "UDP listen needs -p arg");
	} else {
		rr = listen(nnetfd, 1);
		if (rr < 0)	
			errx(1, "error listening");
	}

	if (o_verbose) {
		x = sizeof(struct sockaddr);
		rr = getsockname(nnetfd, (struct sockaddr *) lclend, &x);
		if (rr < 0)
			warn("local getsockname failed");
		strcpy(bigbuf_net, "listening on [");	/* buffer reuse... */
		if (lclend->sin_addr.s_addr)
			strcat(bigbuf_net, inet_ntoa(lclend->sin_addr));
		else
			strcat(bigbuf_net, "any");
		strcat(bigbuf_net, "] %d ...");
		z = ntohs(lclend->sin_port);
		warn(bigbuf_net, z);
	}			/* verbose -- whew!! */
	/*
	 * UDP is a speeeeecial case -- we have to do I/O *and* get the
	 * calling party's particulars all at once, listen() and accept()
	 * don't apply. At least in the BSD universe, however, recvfrom/PEEK
	 * is enough to tell us something came in, and we can set things up so
	 * straight read/write actually does work after all.  Yow.  YMMV on
	 * strange platforms!
	 */
	if (o_udpmode) {
		x = sizeof(struct sockaddr);	/* retval for recvfrom */
		arm(2, o_wait);	/* might as well timeout this, too */
		if (setjmp(jbuf) == 0) {	/* do timeout for initial
						 * connect */
			rr = recvfrom(nnetfd, bigbuf_net, BIGSIZ, MSG_PEEK,
					(struct sockaddr *)remend, &x);
		} else
			goto dol_tmo;
		arm(0, 0);
		rr = connect(nnetfd, (struct sockaddr *)remend, 
						sizeof(struct sockaddr));
		goto whoisit;
	}
	/* fall here for TCP */
	x = sizeof(struct sockaddr);
	arm(2, o_wait);	
	if (setjmp(jbuf) == 0) {
		rr = accept(nnetfd, (struct sockaddr *) remend, &x);
	} else
		goto dol_tmo;
	arm(0, 0);
	close(nnetfd);		/* dump the old socket */
	nnetfd = rr;		/* here's our new one */

whoisit:
	if (rr < 0)
		goto dol_err;	/* bail out if any errors so far */

	/*
	 * Find out what address the connection was *to* on 
	 * our end, in case we're doing a listen-on-any on
	 * a multihomed machine. This allows one to offer 
	 * different services via different alias addresses,
	 * such as the "virtual web site" hack.
	 */
	memset(bigbuf_net, 0, 64);
	cp = &bigbuf_net[32];
	x = sizeof(struct sockaddr);
	rr = getsockname(nnetfd, (struct sockaddr *) lclend, &x);
	if (rr < 0)
		warn("post-rcv getsockname failed");
	strcpy(cp, inet_ntoa(lclend->sin_addr));

	z = ntohs(remend->sin_port);
	strcpy(bigbuf_net, inet_ntoa(remend->sin_addr));
	whozis = gethinfo(bigbuf_net, o_nflag);
	errno = 0;
	x = 0;
	if (rad)		/* xxx: fix to go down the *list* if we have
				 * one? */
		if (memcmp(rad, whozis->iaddrs, sizeof(struct sockaddr)))
			x = 1;
	if (rp) {
		if (z != rp)
			x = 1;
	}
	if (x) {
		errx(1, "invalid connection to [%s] from %s [%s] %d",
		    cp, whozis->name, whozis->addrs[0], z);
	}
	warn("connect to [%s] from %s [%s] %d",	
	    cp, whozis->name, whozis->addrs[0], z);
	return (nnetfd);

dol_tmo:
	errno = ETIMEDOUT;
dol_err:
	close(nnetfd);
	return (-1);
}

/*
 * udptest :
 * fire a couple of packets at a UDP target port, just to see if it's really
 * there.  On BSD kernels, ICMP host/port-unreachable errors get delivered to
 * our socket as ECONNREFUSED write errors.  On SV kernels, we lose; we'll have
 * to collect and analyze raw ICMP ourselves a la satan's probe_udp_ports
 * backend.  Guess where one could swipe the appropriate code from...
 * 
 * Use the time delay between writes if given, otherwise use the "tcp ping"
 * trick for getting the RTT.  [I got that idea from pluvius, and warped it.]
 * Return either the original fd, or clean up and return -1. 
 */
udptest(fd, where)
	int     fd;
	struct in_addr     *where;
{
	int rr;

	rr = write(fd, bigbuf_in, 1);
	if (rr != 1)
		warn("udptest first write failed?! errno %d", errno);
	if (o_wait)
		sleep(o_wait);
	else {
		o_udpmode = 0;
		o_wait = 5;
		rr = doconnect(where, SLEAZE_PORT, 0, 0);
		if (rr > 0)
			close(rr);
		o_wait = 0;
		o_udpmode++;
	}
	errno = 0;
	rr = write(fd, bigbuf_in, 1);
	if (rr == 1)
		return (fd);
	close(fd);
	return (-1);
}

/*
 *  oprint :
 * Hexdump bytes shoveled either way to a running logfile, in the format:
 * D offset       -  - - - --- 16 bytes --- - - -  -     # .... ascii .....
 * where "which" sets the direction indicator, D:
 * 0 -- sent to network, or ">"
 * 1 -- rcvd and printed to stdout, or "<"
 * and "buf" and "n" are data-block and length.  If the current block generates
 * a partial line, so be it; we *want* that lockstep indication of who sent
 * what when.  Adapted from dgaudet's original example -- but must be ripping
 * *fast*, since we don't want to be too disk-bound.
 */
void 
oprint(which, buf, n)
	int     which;
	char   *buf;
	int     n;
{
	int     bc;		/* in buffer count */
	int     obc;		/* current "global" offset */
	int     soc;		/* stage write count */
	unsigned char *p;	/* main buf ptr; m.b. unsigned here */
	unsigned char *op;	/* out hexdump ptr */
	unsigned char *a;	/* out asc-dump ptr */
	int x;
	unsigned int y;

	if (!ofd)
		errx(1, "oprint called with no open fd?!");
	if (n == 0)
		return;

	op = stage;
	if (which) {
		*op = '<';
		obc = wrote_out;/* use the globals! */
	} else {
		*op = '>';
		obc = wrote_net;
	}
	op++;			/* preload "direction" */
	*op = ' ';
	p = (unsigned char *) buf;
	bc = n;
	stage[59] = '#';	/* preload separator */
	stage[60] = ' ';

	while (bc) {		/* for chunk-o-data ... */
		x = 16;
		soc = 78;	/* len of whole formatted line */
		if (bc < x) {
			soc = soc - 16 + bc;	/* fiddle for however much is
						 * left */
			x = (bc * 3) + 11;	/* 2 digits + space per, after
						 * D & offset */
			op = &stage[x];
			x = 16 - bc;
			while (x) {
				*op++ = ' ';	/* preload filler spaces */
				*op++ = ' ';
				*op++ = ' ';
				x--;
			}
			x = bc;	/* re-fix current linecount */
		}		/* if bc < x */
		bc -= x;	/* fix wrt current line size */
		sprintf(&stage[2], "%8.8x ", obc);	/* xxx: still slow? */
		obc += x;	/* fix current offset */
		op = &stage[11];/* where hex starts */
		a = &stage[61];	/* where ascii starts */

		while (x) {	/* for line of dump, however long ... */
			y = (int) (*p >> 4);	/* hi half */
			*op = hexnibs[y];
			op++;
			y = (int) (*p & 0x0f);	/* lo half */
			*op = hexnibs[y];
			op++;
			*op = ' ';
			op++;
			if ((*p > 31) && (*p < 127))
				*a = *p;	/* printing */
			else
				*a = '.';	/* nonprinting, loose def */
			a++;
			p++;
			x--;
		}		/* while x */
		*a = '\n';	/* finish the line */
		x = write(ofd, stage, soc);
		if (x < 0)
			errx(1, "ofd write err");
	}
}

#ifdef TELNET
u_short  o_tn = 0;		/* global -t option */

/*
 *  atelnet :
 * Answer anything that looks like telnet negotiation with don't/won't.
 * This doesn't modify any data buffers, update the global output count,
 * or show up in a hexdump -- it just shits into the outgoing stream.
 * Idea and codebase from Mudge@l0pht.com.
 */
void 
atelnet(buf, size)
	unsigned char *buf;	/* has to be unsigned here! */
	unsigned int size;
{
	static unsigned char obuf[4];	/* tiny thing to build responses into */
	int x;
	unsigned char y;
	unsigned char *p;

	y = 0;
	p = buf;
	x = size;
	while (x > 0) {
		if (*p != 255)	/* IAC? */
			goto notiac;
		obuf[0] = 255;
		p++;
		x--;
		if ((*p == 251) || (*p == 252))	/* WILL or WONT */
			y = 254;/* -> DONT */
		if ((*p == 253) || (*p == 254))	/* DO or DONT */
			y = 252;/* -> WONT */
		if (y) {
			obuf[1] = y;
			p++;
			x--;
			obuf[2] = *p;
			(void) write(netfd, obuf, 3);
			/* 
			 * if one wanted to bump wrote_net or do
			 * a hexdump line, here's the place.
			 */ 
			y = 0;
		}
notiac:
		p++;
		x--;
	}
}
#endif /* TELNET */

/*
 *  readwrite :
 * handle stdin/stdout/network I/O.  Bwahaha!! -- the select loop from hell.
 * In this instance, return what might become our exit status.
 */
int 
readwrite(fd)
	int     fd;
{
	int rr;
	char *zp;	/* stdin buf ptr */
	char *np;	/* net-in buf ptr */
	unsigned int rzleft;
	unsigned int rnleft;
	u_short  netretry;	/* net-read retry counter */
	u_short  wretry;	/* net-write sanity counter */
	u_short  wfirst;	/* one-shot flag to skip first net read */

	/*
	 * if you don't have all this FD_* macro hair in sys/types.h, 
	 * you'll have to either find it or do your own bit-bashing:
	 * *ds1 |= (1 << fd), etc.
	 */
	if (fd > FD_SETSIZE) {
		warn("Preposterous fd value %d", fd);
		return (1);
	}
	FD_SET(fd, &fds1);
	netretry = 2;
	wfirst = 0;
	rzleft = rnleft = 0;
	if (insaved) {
		rzleft = insaved;	/* preload multi-mode fakeouts */
		zp = bigbuf_in;
		wfirst = 1;
		/* If not scanning, this is a one-off first */
		if (Single)
			insaved = 0;
		else {
			FD_CLR(0, &fds1);
			close(0);
		}
	}
	if (o_interval)
		sleep(o_interval);
	errno = 0;

	while (FD_ISSET(fd, &fds1)) {	/* i.e. till the *net* closes! */
		struct timeval *tv;

		wretry = 8200;		/* more than we'll ever hafta write */
		if (wfirst) {		/* any saved stdin buffer? */
			wfirst = 0;	/* clear flag for the duration */
			goto shovel;	/* and go handle it first */
		}
		fds2 = fds1;
		if (timer1.tv_sec > 0 || timer1.tv_usec > 0) {
			memcpy(&timer2, &timer1, sizeof(struct timeval));
			tv = &timer2;
		} else
			tv = NULL;
		rr = select(getdtablesize(), &fds2, 0, 0, tv);
		if (rr < 0) {
			if (errno != EINTR) {
				warn("Select Failure");
				close(fd);
				return (1);
			}
		}
		/* if we have a timeout AND stdin is closed AND we haven't
		 * heard anything from the net during that time, assume it's
		 * dead and close it too. */
		if (rr == 0) {
			if (!FD_ISSET(0, &fds1))
				netretry--;	/* we actually try a coupla
						 * times. */
			if (!netretry) {
				if (o_verbose > 1)	/* normally we don't
							 * care */
					warn("net timeout");
				close(fd);
				return (0);	/* not an error! */
			}
		}		/* select timeout */
		/* XXX: should we check the exception fds too?  The read fds
		 * seem to give us the right info, and none of the examples I
		 * found bothered. */
		/* Ding!!  Something arrived, go check all the incoming
		 * hoppers, net first */
		if (FD_ISSET(fd, &fds2)) {	/* net: ding! */
			rr = read(fd, bigbuf_net, BIGSIZ);
			if (rr <= 0) {
				FD_CLR(fd, &fds1);	/* net closed, we'll
							 * finish up... */
				rzleft = 0;	/* can't write anymore: broken
						 * pipe */
			} else {
				rnleft = rr;
				np = bigbuf_net;
#ifdef TELNET
				if (o_tn)
					atelnet(np, rr);	/* fake out telnet stuff */
#endif /* TELNET */
			}
		}
		/* if we're in "slowly" mode there's probably still stuff in
		 * the stdin buffer, so don't read unless we really need MORE
		 * INPUT!  MORE INPUT! */
		if (rzleft)
			goto shovel;

		if (FD_ISSET(0, &fds2)) {
			rr = read(0, bigbuf_in, BIGSIZ);
			if (rr <= 0) {
				FD_CLR(0, &fds1);	/* disable and close */
				close(0);
			} else {
				rzleft = rr;
				zp = bigbuf_in;
				if (!Single) {
					insaved = rr;
					FD_CLR(0, &fds1);
					close(0);
				}
			}
		}
shovel:
		/* sanity check.  Works because they're both unsigned... */
		if ((rzleft > 8200) || (rnleft > 8200)) {
			warn("Bogus buffers: %d, %d", rzleft, rnleft);
			rzleft = rnleft = 0;
		}
		/* net write retries sometimes happen on UDP connections */
		if (!wretry) {	/* is something hung? */
			warn("too many output retries");
			return (1);
		}
		if (rnleft) {
			rr = write(1, np, rnleft);
			if (rr > 0) {
				if (o_wfile)
					oprint(1, np, rr);
				np += rr;
				rnleft -= rr;
				wrote_out += rr;	/* global count */
			}
		}
		if (rzleft) {
			if (o_interval)
				rr = findline(zp, rzleft);
			else
				rr = rzleft;
			rr = write(fd, zp, rr);	
			if (rr > 0) {
				if (o_wfile)
					oprint(0, zp, rr);
				zp += rr;
				rzleft -= rr;
				wrote_net += rr;
			}
		}
		if (o_interval) {
			sleep(o_interval);
			errno = 0;
			continue;
		}
		if ((rzleft) || (rnleft)) {	
			wretry--;
			goto shovel;
		}
	}

	close(fd);
	return (0);
}

/* main :
   now we pull it all together... */
main(argc, argv)
	int     argc;
	char  **argv;
{
	int x, ch;
	char *cp;
	struct host_info   *gp;
	struct host_info   *whereto = NULL;
	struct host_info   *wherefrom = NULL;
	struct in_addr     *ouraddr = NULL;
	struct in_addr     *themaddr = NULL;
	u_short  o_lport = 0;
	u_short  ourport = 0;
	u_short  loport = 0;	/* for scanning stuff */
	u_short  hiport = 0;
	u_short  curport = 0;
	char   *randports = NULL;

#ifdef HAVE_BIND
	res_init();
#endif
	lclend = (struct sockaddr_in *) calloc(1, sizeof(struct sockaddr));
	remend = (struct sockaddr_in *) calloc(1, sizeof(struct sockaddr));
	bigbuf_in = calloc(1, BIGSIZ);
	bigbuf_net = calloc(1, BIGSIZ);
	pinfo= (struct port_info *) calloc(1, sizeof(struct port_info));

	errno = 0;
	gatesptr = 4;
	h_errno = 0;

	/*
	 * We want to catch a few of these signals. 
	 * Others we disgard.
	 */
	signal(SIGINT, catch);
	signal(SIGQUIT, catch);
	signal(SIGTERM, catch);
	signal(SIGURG, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);	/* important! */

	/* 
	 * If no args given at all, get 'em from stdin, construct an argv, 
	 * and hand anything left over to readwrite().
	 */
	if (argc == 1) {
		cp = argv[0];
		/* XXX - 128 ? */
		argv = (char **) calloc(1, 128 * sizeof(char *));
		argv[0] = cp;	/* leave old prog name intact */
		cp = calloc(1, BIGSIZ);
		argv[1] = cp;	/* head of new arg block */
		fprintf(stderr, "Cmd line: ");
		fflush(stderr);	/* I dont care if it's unbuffered or not! */
		insaved = read(0, cp, BIGSIZ-1); /* we're gonna fake fgets()
						 * here */
		cp[BIGSIZ-1] = '\0';
		if (insaved <= 0)
			errx(1, "wrong");
		x = findline(cp, insaved);
		if (x)
			insaved -= x;	/* remaining chunk size to be sent */
		if (insaved)	/* which might be zero... */
			memcpy(bigbuf_in, &cp[x], insaved);
		cp = strchr(argv[1], '\n');
		if (cp)
			*cp = '\0';
		cp = strchr(argv[1], '\r');	/* look for ^M too */
		if (cp)
			*cp = '\0';

		/*
		 * Find and stash pointers to remaining new "args"
		 */
		cp = argv[1];
		cp++;		/* skip past first char */
		x = 2;		/* we know argv 0 and 1 already */
		for (; *cp != '\0'; cp++) {
			if (*cp == ' ') {
				*cp = '\0';	/* smash all spaces */
				continue;
			} else {
				if (*(cp - 1) == '\0') {
					argv[x] = cp;
					x++;
				}
			}	/* if space */
		}		/* for cp */
		argc = x;
	}

	while ((ch = getopt(argc, argv, "g:G:hi:lno:p:rs:tuvw:z")) != -1) {
		switch (ch) {
		case 'G':			/* srcrt gateways pointer val */
			x = atoi(optarg);
			/* Mask of bits */
			if ((x) && (x == (x & 0x1c)))
				gatesptr = x;
			else
				errx(1, "invalid hop pointer %d, must be multiple of 4 <= 28", x);
			break;
		case 'g':			/* srcroute hop[s] */
			if (gatesidx > 8)
				errx(1, "Too many -g hops!");
			if (gates == NULL)
				gates = (struct host_info **) calloc(1, 
						sizeof(struct host_info *) * 10);
			gp = gethinfo(optarg, o_nflag);
			if (gp)
				gates[gatesidx] = gp;
			gatesidx++;
			break;
		case 'h':
			help();
			break;
		case 'i':			/* line-interval time */
			o_interval = atoi(optarg) & 0xffff;
			if (!o_interval)
				errx(1, "invalid interval time %s", optarg);
			break;
		case 'l':			/* listen mode */
			o_listen++;
			break;
		case 'n':			/* numeric-only, no DNS lookups */
			o_nflag++;
			break;
		case 'o':			/* hexdump log */
			stage = (unsigned char *) optarg;
			o_wfile++;
			break;
		case 'p':			/* local source port */
			o_lport = getpinfo(optarg, 0);
			if (o_lport == 0)
				errx(1, "invalid local port %s", optarg);
			break;
		case 'r':			/* randomize various things */
			o_random++;
			break;
		/* 
		 * Do a full lookup [since everything else goes through the same 
		 * mill], unless -n was previously specified. In fact, careful
		 * placement of -n can be useful, so we'll still pass o_nflag
		 * here instead of forcing numeric.
		 */
		case 's':			/* local source address */
			wherefrom = gethinfo(optarg, o_nflag);
			ouraddr = &wherefrom->iaddrs[0];
			break;
#ifdef TELNET
		case 't':			/* do telnet fakeout */
			o_tn++;
			break;
#endif
		case 'u':			/* use UDP */
			o_udpmode++;
			break;
		case 'v':			/* verbose */
			o_verbose++;
			break;
		case 'w':			/* wait time */
			o_wait = atoi(optarg);
			if (o_wait <= 0)
				errx(1, "invalid wait-time %s", optarg);
			timer1.tv_sec = o_wait;
			timer1.tv_usec = 0;
			break;
		case 'z':			/* little or no data xfer */
			o_zero++;
			break;
		default:
			usage(1);
		}
	}

	/* other misc initialization */
	    FD_SET(0, &fds1);	/* stdin *is* initially open */
	if (o_random) {
		SRAND(time(0));
		randports = calloc(1, 65536);   /* big flag array for ports */
	}
	if (o_wfile) {
		ofd = open(stage, O_WRONLY | O_CREAT | O_TRUNC, 0664);
		if (ofd <= 0)	/* must be > extant 0/1/2 */
			errx(1, "Can't open %s", stage);
		stage = (unsigned char *) calloc(1, 100);
	}
	/* optind is now index of first non -x arg */
	if (argv[optind])
		whereto = gethinfo(argv[optind], o_nflag);
	if (whereto && whereto->iaddrs)
		themaddr = &whereto->iaddrs[0];
	if (themaddr)
		optind++;	/* skip past valid host lookup */
	errno = 0;
	h_errno = 0;

	/*
	 * Handle listen mode here, and exit afterward.  Only does one connect;
	 * this is arguably the right thing to do.  A "persistent listen-and-fork"
	 * mode a la inetd has been thought about, but not implemented.  A tiny
 	 * wrapper script can handle such things.
	 */
	if (o_listen) {
		curport = 0;
		if (argv[optind]) {
			curport = getpinfo(argv[optind], 0);
			if (curport == 0)
				errx(1, "invalid port %s", argv[optind]);
		}
		netfd = dolisten(themaddr, curport, ouraddr, o_lport);
		if (netfd > 0) {
			x = readwrite(netfd);
			if (o_verbose > 1)
				warn("Sent %i Rcvd %i", wrote_net, wrote_out);
			exit(x);
		} else	
			errx(1, "no connection");
	}
	/* fall thru to outbound connects.  Now we're more picky about args... */
	if (!themaddr)
		errx(1, "no destination");
	if (argv[optind] == NULL)
		errx(1, "no port[s] to connect to");
	if (argv[optind + 1])
		Single = 0;
	ourport = o_lport;

	while (argv[optind]) {
		hiport = loport = 0;
		cp = strchr(argv[optind], '-');
		if (cp) {
			*cp = '\0';
			cp++;
			hiport = getpinfo(cp, 0);
			if (hiport == 0)
				errx(1, "invalid port %s", cp);
		}		/* if found a dash */
		loport = getpinfo(argv[optind], 0);
		if (loport == 0)
			errx(1, "invalid port %s", argv[optind]);
		if (hiport > loport) {	/* was it genuinely a range? */
			Single = 0;	/* multi-mode, case B */
			if (o_random) {	/* maybe populate the random array */
				loadports(randports, loport, hiport);
				curport = nextport(randports);
			} else
				curport = hiport;
		} else		/* not a range, including args like "25-25" */
			curport = loport;
	  	    /*
		     * Now start connecting to these things.  
		     * curport is already preloaded.
		     */
		    while (loport <= curport) {
			curport = getpinfo(NULL, curport);
			netfd = doconnect(themaddr, curport, ouraddr, ourport);
			if (netfd > 0)
				if (o_zero && o_udpmode)
					netfd = udptest(netfd, themaddr);
			if (netfd > 0) {
				x = 0;
				if (o_verbose) {
					warn("%s [%s] %d (%s) open",
				    		whereto->name, 
						whereto->addrs[0], curport, 
						pinfo->name);
				}
				if (!o_zero)
					x = readwrite(netfd);
			} else {
				x = 1;
				if ((Single || (o_verbose > 1)) 
				   || (errno != ECONNREFUSED)) {
					warn("%s [%s] %d (%s)",
					    whereto->name, whereto->addrs[0], 
							curport, pinfo->name);
				}
			}
			close(netfd);
			if (o_interval)
				sleep(o_interval);
			if (o_random)
				curport = nextport(randports);
			else
				curport--;
		}
		optind++;
	}

	errno = 0;
	if (o_verbose > 1)
		warn("Sent %i Rcvd %i", wrote_net, wrote_out);
	if (Single)
		exit(x);
	exit(0);
}

void
usage(doexit)
{
	fprintf(stderr, "netcat - [v1.10]\n");
	fprintf(stderr, "nc [-lnrtuvz] [-e command] [-g intermediates]\n");
	fprintf(stderr, "   [-G hopcount] [-i interval] [-o filename] [-p source port]\n");
	fprintf(stderr, "   [-s ip address] [-w timeout] [hostname] [port[s...]]\n");
	if (doexit)
		exit(1);
}

void
help()
{
	usage(0);
	fprintf(stderr, "\tCommand Summary:\n\
	 \t-g gateway	source-routing hop point[s], up to 8\n\
  	 \t-G num\t	source-routing pointer: 4, 8, 12, ...\n\
	 \t-h		this help text\n\
	 \t-i secs\t	delay interval for lines sent, ports scanned\n\
	 \t-l		listen mode, for inbound connects\n\
	 \t-n		numeric-only IP addresses, no DNS\n\
	 \t-o file\t	hex dump of traffic\n\
	 \t-r		randomize local and remote ports\n\
	 \t-s addr\t	local source address\n");
#ifdef TELNET
	 fprintf(stderr, "\t\t-t		answer TELNET negotiation\n");
#endif
	 fprintf(stderr, "\t\t-u		UDP mode\n\
	 \t-v		verbose [use twice to be more verbose]\n\
	 \t-w secs\t	timeout for connects and final net reads\n\
	 \t-z		zero-I/O mode [used for scanning]\n\
	 Port numbers can be individual or ranges: lo-hi [inclusive]\n");
	exit(1);
}