/*
 * Copyright (c) 1993 W. Richard Stevens.  All rights reserved.
 * Permission to use or modify this software and its documentation only for
 * educational purposes and without fee is hereby granted, provided that
 * the above copyright notice appear in all copies.  The author makes no
 * representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 */

#include	"sock.h"

char	*host;		/* hostname or dotted-decimal string */
char	*port;

			/* DefinE global variables */
int		bindport;			/* 0 or TCP or UDP port number to bind */
							/* set by -b or -l options */
int		broadcast;			/* SO_BROADCAST */
int		cbreak;				/* set terminal to cbreak mode */
int		chunkwrite;			/* write in small chunks; not all-at-once */
int		client = 1;			/* acting as client is the default */
int		connectudp = 1;		/* connect UDP client */
int		crlf;				/* convert newline to CR/LF & vice versa */
int		debug;				/* SO_DEBUG */
int		dofork;				/* concurrent server, do a fork() */
int		dontroute;			/* SO_DONTROUTE */
char	foreignip[32];		/* foreign IP address, dotted-decimal string */
int		foreignport;		/* foreign port number */
int		halfclose;			/* TCP half close option */
int		ignorewerr;			/* true if write() errors should be ignored */
int		iptos = -1;			/* IP_TOS opton */
int		ipttl = -1;			/* IP_TTL opton */
char	joinip[32];			/* multicast IP address, dotted-decimal string */
int		keepalive;			/* SO_KEEPALIVE */
long	linger = -1;		/* 0 or positive turns on option */
int		listenq = 5;		/* listen queue for TCP Server */
char	localip[32];		/* local IP address, dotted-decimal string */
int		maxseg;				/* TCP_MAXSEG */
int		mcastttl;			/* multicast TTL */
int		msgpeek;			/* MSG_PEEK */
int		nodelay;			/* TCP_NODELAY (Nagle algorithm) */
int		nbuf = 1024;		/* number of buffers to write (sink mode) */
int		onesbcast;			/* set IP_ONESBCAST for 255.255.255.255 bcasts */
int		pauseclose;			/* #ms to sleep after recv FIN, before close */
int		pauseinit;			/* #ms to sleep before first read */
int		pauselisten;		/* #ms to sleep after listen() */
int		pauserw;			/* #ms to sleep before each read or write */
int		reuseaddr;			/* SO_REUSEADDR */
int		reuseport;			/* SO_REUSEPORT */
int		readlen = 1024;		/* default read length for socket */
int		writelen = 1024;	/* default write length for socket */
int		recvdstaddr;		/* IP_RECVDSTADDR option */
int		rcvbuflen;			/* size for SO_RCVBUF */
int		sndbuflen;			/* size for SO_SNDBUF */
long	rcvtimeo;			/* SO_RCVTIMEO */
long	sndtimeo;			/* SO_SNDTIMEO */
int		sroute_cnt;			/* count of #IP addresses in route */
char   *rbuf;				/* pointer that is malloc'ed */
char   *wbuf;				/* pointer that is malloc'ed */
int		server;				/* to act as server requires -s option */
int		sigio;				/* send SIGIO */
int		sourcesink;			/* source/sink mode */
int		udp;				/* use UDP instead of TCP */
int		urgwrite;			/* write urgent byte after this write */
int		verbose;			/* each -v increments this by 1 */
int		usewritev;			/* use writev() instead of write() */

struct sockaddr_in	cliaddr, servaddr;

static void	usage(const char *);

int
main(int argc, char *argv[])
{
	int		c, fd;
	char	*ptr;

	if (argc < 2)
		usage("");

	opterr = 0;		/* don't want getopt() writing to stderr */
	while ( (c = getopt(argc, argv, "2b:cf:g:hij:kl:n:op:q:r:st:uvw:x:y:ABCDEFG:H:IJ:KL:NO:P:Q:R:S:TU:VWX:YZ")) != -1) {
		switch (c) {
#ifdef	IP_ONESBCAST
		case '2':			/* use 255.255.255.255 as broadcast address */
			onesbcast = 1;
			break;
#endif

		case 'b':
			bindport = atoi(optarg);
			break;

		case 'c':			/* convert newline to CR/LF & vice versa */
			crlf = 1;
			break;

		case 'f':			/* foreign IP address and port#: a.b.c.d.p */
			if ( (ptr = strrchr(optarg, '.')) == NULL)
				usage("invalid -f option");

			*ptr++ = 0;					/* null replaces final period */
			foreignport = atoi(ptr);	/* port number */
			strcpy(foreignip, optarg);	/* save dotted-decimal IP */
			break;

		case 'g':			/* loose source route */
			sroute_doopt(0, optarg);
			break;

		case 'h':			/* TCP half-close option */
			halfclose = 1;
			break;

		case 'i':			/* source/sink option */
			sourcesink = 1;
			break;

#ifdef	IP_ADD_MEMBERSHIP
		case 'j':			/* join multicast group a.b.c.d */
			strcpy(joinip, optarg);	/* save dotted-decimal IP */
			break;
#endif

		case 'k':			/* chunk-write option */
			chunkwrite = 1;
			break;

		case 'l':			/* local IP address and port#: a.b.c.d.p */
			if ( (ptr = strrchr(optarg, '.')) == NULL)
				usage("invalid -l option");

			*ptr++ = 0;					/* null replaces final period */
			bindport = atoi(ptr);		/* port number */
			strcpy(localip, optarg);	/* save dotted-decimal IP */
			break;

		case 'n':			/* number of buffers to write */
			nbuf = atol(optarg);
			break;

		case 'o':			/* do not connect UDP client */
			connectudp = 0;
			break;

		case 'p':			/* pause before each read or write */
			pauserw = atoi(optarg);
			break;

		case 'q':			/* listen queue for TCP server */
			listenq = atoi(optarg);
			break;

		case 'r':			/* read() length */
			readlen = atoi(optarg);
			break;

		case 's':			/* server */
			server = 1;
			client = 0;
			break;

#ifdef	IP_MULTICAST_TTL
		case 't':			/* IP_MULTICAST_TTL */
			mcastttl = atoi(optarg);
			break;
#endif

		case 'u':			/* use UDP instead of TCP */
			udp = 1;
			break;

		case 'v':			/* output what's going on */
			verbose++;
			break;

		case 'w':			/* write() length */
			writelen = atoi(optarg);
			break;

		case 'x':			/* SO_RCVTIMEO socket option */
			rcvtimeo = atol(optarg);
			break;

		case 'y':			/* SO_SNDTIMEO socket option */
			sndtimeo = atol(optarg);
			break;

		case 'A':			/* SO_REUSEADDR socket option */
			reuseaddr = 1;
			break;

		case 'B':			/* SO_BROADCAST socket option */
			broadcast = 1;
			break;

		case 'C':			/* set standard input to cbreak mode */
			cbreak = 1;
			break;

		case 'D':			/* SO_DEBUG socket option */
			debug = 1;
			break;

		case 'E':			/* IP_RECVDSTADDR socket option */
			recvdstaddr = 1;
			break;

		case 'F':			/* concurrent server, do a fork() */
			dofork = 1;
			break;

		case 'G':			/* strict source route */
			sroute_doopt(1, optarg);
			break;

#ifdef	IP_TOS
		case 'H':			/* IP_TOS socket option */
			iptos = atoi(optarg);
			break;
#endif

		case 'I':			/* SIGIO signal */
			sigio = 1;
			break;

#ifdef	IP_TTL
		case 'J':			/* IP_TTL socket option */
			ipttl = atoi(optarg);
			break;
#endif

		case 'K':			/* SO_KEEPALIVE socket option */
			keepalive = 1;
			break;

		case 'L':			/* SO_LINGER socket option */
			linger = atol(optarg);
			break;

		case 'N':			/* SO_NODELAY socket option */
			nodelay = 1;
			break;

		case 'O':			/* pause before listen(), before first accept() */
			pauselisten = atoi(optarg);
			break;

		case 'P':			/* pause before first read() */
			pauseinit = atoi(optarg);
			break;

		case 'Q':			/* pause after receiving FIN, but before close() */
			pauseclose = atoi(optarg);
			break;

		case 'R':			/* SO_RCVBUF socket option */
			rcvbuflen = atoi(optarg);
			break;

		case 'S':			/* SO_SNDBUF socket option */
			sndbuflen = atoi(optarg);
			break;

#ifdef	SO_REUSEPORT
		case 'T':			/* SO_REUSEPORT socket option */
			reuseport = 1;
			break;
#endif

		case 'U':			/* when to write urgent byte */
			urgwrite = atoi(optarg);
			break;

		case 'V':			/* use writev() instead of write() */
			usewritev = 1;
			chunkwrite = 1;	/* implies this option too */
			break;

		case 'W':			/* ignore write errors */
			ignorewerr = 1;
			break;

		case 'X':			/* TCP maximum segment size option */
			maxseg = atoi(optarg);
			break;

		case 'Y':			/* SO_DONTROUTE socket option */
			dontroute = 1;
			break;

		case 'Z':			/* MSG_PEEK option */
			msgpeek = MSG_PEEK;
			break;

		case '?':
			usage("unrecognized option");
		}
	}

		/* check for options that don't make sense */
	if (udp && halfclose)
		usage("can't specify -h and -u");
	if (udp && debug)
		usage("can't specify -D and -u");
	if (udp && linger >= 0)
		usage("can't specify -L and -u");
	if (udp && nodelay)
		usage("can't specify -N and -u");
#ifdef	notdef
	if (udp == 0 && broadcast)
		usage("can't specify -B with TCP");
#endif
	if (udp == 0 && foreignip[0] != 0)
		usage("can't specify -f with TCP");

	if (client) {
		if (optind != argc-2)
			usage("missing <hostname> and/or <port>");
		host = argv[optind];
		port = argv[optind+1];

	} else {
			/* If server specifies host and port, then local address is
			   bound to the "host" argument, instead of being wildcarded. */
		if (optind == argc-2) {
			host = argv[optind];
			port = argv[optind+1];
		} else if (optind == argc-1) {
			host = NULL;
			port = argv[optind];
		} else
			usage("missing <port>");
	}

	if (client)
		fd = cliopen(host, port);
	else
		fd = servopen(host, port);

	if (sourcesink) {		/* ignore stdin/stdout */
		if (client) {
			if (udp)
				source_udp(fd);
			else
				source_tcp(fd);
		} else {
			if (udp)
				sink_udp(fd);
			else
				sink_tcp(fd);
		}

	} else {				/* copy stdin/stdout to/from socket */
		if (udp)
			loop_udp(fd);
		else
			loop_tcp(fd);
	}

	exit(0);
}

static void
usage(const char *msg)
{
	err_msg(
"usage: sock [ options ] <host> <port>              (for client; default)\n"
"       sock [ options ] -s [ <IPaddr> ] <port>     (for server)\n"
"       sock [ options ] -i <host> <port>           (for \"source\" client)\n"
"       sock [ options ] -i -s [ <IPaddr> ] <port>  (for \"sink\" server)\n"
"options: -b n  bind n as client's local port number\n"
"         -c    convert newline to CR/LF & vice versa\n"
"         -f a.b.c.d.p  foreign IP address = a.b.c.d, foreign port # = p\n"
"         -g a.b.c.d  loose source route\n"
"         -h    issue TCP half close on standard input EOF\n"
"         -i    \"source\" data to socket, \"sink\" data from socket (w/-s)\n"
#ifdef	IP_ADD_MEMBERSHIP
"         -j a.b.c.d  join multicast group\n"
#endif
"         -k    write or writev in chunks\n"
"         -l a.b.c.d.p  client's local IP address = a.b.c.d, local port # = p\n"
"         -n n  # buffers to write for \"source\" client (default 1024)\n"
"         -o    do NOT connect UDP client\n"
"         -p n  # ms to pause before each read or write (source/sink)\n"
"         -q n  size of listen queue for TCP server (default 5)\n"
"         -r n  # bytes per read() for \"sink\" server (default 1024)\n"
"         -s    operate as server instead of client\n"
#ifdef	IP_MULTICAST_TTL
"         -t n  set multicast ttl\n"
#endif
"         -u    use UDP instead of TCP\n"
"         -v    verbose\n"
"         -w n  # bytes per write() for \"source\" client (default 1024)\n"
"         -x n  # ms for SO_RCVTIMEO (receive timeout)\n"
"         -y n  # ms for SO_SNDTIMEO (send timeout)\n"
"         -A    SO_REUSEADDR option\n"
"         -B    SO_BROADCAST option\n"
"         -C    set terminal to cbreak mode\n"
"         -D    SO_DEBUG option\n"
"         -E    IP_RECVDSTADDR option\n"
"         -F    fork after connection accepted (TCP concurrent server)\n"
"         -G a.b.c.d  strict source route\n"
#ifdef	IP_TOS
"         -H n  IP_TOS option (16=min del, 8=max thru, 4=max rel, 2=min$)\n"
#endif
"         -I    SIGIO signal\n"
#ifdef	IP_TTL
"         -J n  IP_TTL option\n"
#endif
"         -K    SO_KEEPALIVE option\n"
"         -L n  SO_LINGER option, n = linger time\n"
"         -N    TCP_NODELAY option\n"
"         -O n  # ms to pause after listen, but before first accept\n"
"         -P n  # ms to pause before first read or write (source/sink)\n"
"         -Q n  # ms to pause after receiving FIN, but before close\n"
"         -R n  SO_RCVBUF option\n"
"         -S n  SO_SNDBUF option\n"
#ifdef	SO_REUSEPORT
"         -T    SO_REUSEPORT option\n"
#endif
"         -U n  enter urgent mode before write number n (source only)\n"
"         -V    use writev() instead of write(); enables -k too\n"
"         -W    ignore write errors for sink client\n"
"         -X n  TCP_MAXSEG option (set MSS)\n"
"         -Y    SO_DONTROUTE option\n"
"         -Z    MSG_PEEK\n"
#ifdef	IP_ONESBCAST
"         -2    IP_ONESBCAST option (255.255.255.255 for broadcast\n"
#endif
);

	if (msg[0] != 0)
		err_quit("%s", msg);
	exit(1);
}
