/*
 *	This is a simple netstat(8). Currently the following options are
 *	implemented:
 *
 *	-f [inet|unix]	-- show inet/unix sockets
 *	-a		-- show all sockets, even if they are listening
 *			   TCP sockets which are not shown by default
 *
 *	(w) 1993,1994, Kay Roemer.
 */

#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "../include/socket.h"
#include "../include/net.h"

#define UNIX_DEVICE	"/dev/unix"
#define INET_DEVICE	"/dev/inet"

struct unix_info {
	short		proto;	 /* protcol numer, always 0 */
	short		flags;	 /* socket flags, SO_* */
	short		type;	 /* socket type, SOCK_DGRAM or SOCK_STREAM */
	short		state;	 /* socket state, SS_* */
	short		qlen;	 /* bytes in read buffer */
	short		addrlen; /* addrlen, 0 if no address */
	struct sockaddr_un addr; /* addr, only meaningful if addrlen > 0 */
};

struct inet_info {
	short		proto;		/* protocol, IPPROTO_* */
	long		sendq;		/* bytes in send queue */
	long		recvq;		/* bytes in recv queue */
	short		state;		/* state */
	struct sockaddr_in laddr;	/* local address */
	struct sockaddr_in faddr;	/* foreign address */
};

extern int errno;

static short show_unix_opt = 0, show_usage_opt = 0,
	show_inet_opt = 1, show_all_opt = 0;

/*
 *	UNIX domain stuff.
 */

static char *
un_decode_state (state)
	short state;
{
	switch (state) {
	case SS_VIRGIN:
		return "VIRGIN";

	case SS_ISUNCONNECTED:
		return "UNCONNECTED";

	case SS_ISCONNECTING:
		return "CONNECTING";
			
	case SS_ISCONNECTED:
		return "CONNECTED";
			
	case SS_ISDISCONNECTING:
		return "DISCONNECTING";
		
	case SS_ISDISCONNECTED:
		return "DISCONNECTED";

	default:
		return "UNKNOWN";
	}
}

static char *
un_decode_type (type)
	short type;
{
	switch (type) {
	case SOCK_DGRAM:
		return "DGRAM";
		
	case SOCK_STREAM:
		return "STREAM";
		
	case SOCK_RDM:
		return "RDM";
		
	case SOCK_SEQPACKET:
		return "SEQPACKET";
		
	case SOCK_RAW:
		return "RAW";
		
	default:
		return "UNKNOWN";
	}
}

static char *
un_decode_flags (flags)
	short flags;
{
	static char strflags[20];

	strcpy (strflags, "");
	if (flags & SO_ACCEPTCON)
		strcat (strflags, "ACC ");
	if (flags & SO_CANTSNDMORE)
		strcat (strflags, "SND ");
	if (flags & SO_CANTRCVMORE)
		strcat (strflags, "RCV ");
	if (flags & SO_RCVATMARK)
		strcat (strflags, "MRK ");

	return strflags;
}

static void
show_unix (void)
{
	struct unix_info info;
	char *proto;
	int fd, r;

	fd = open (UNIX_DEVICE, O_RDONLY);
	if (fd < 0) {
		fprintf (stderr, "cannot open device %s: %s\n",
			UNIX_DEVICE, strerror (errno));
		return;
	}
	puts ("Unix domain communication endpoints:");
	puts ("Proto   Flags            Type      State         Recv-Q Address");
	for (;;) {
		r = read (fd, &info, sizeof (info));
		if (!r) break;
		if (r < 0) {
			fprintf (stderr, "cannot read next entry from %s: %s\n",
				UNIX_DEVICE, strerror (errno));
			break;
		}
		if (r != sizeof (info)) {
			break;
		}

		switch (info.proto) {
		case 0:
			proto = "unix";
			break;

		default:
			proto = "unknown";
			break;
		}

		printf ("%-7s %-16s %-9s %-13s %6u %s\n",
			proto,
			un_decode_flags (info.flags),
			un_decode_type (info.type),
			un_decode_state (info.state),
			info.qlen,
			info.addrlen ? info.addr.sun_path : "");
	}
	close (fd);
}

/*
 * INET domain stuff.
 */

static char *
in_decode_state (state)
	short state;
{
	switch (state) {
	case TCPS_CLOSED:
		return "CLOSED";

	case TCPS_LISTEN:
		return "LISTEN";

	case TCPS_SYNSENT:
		return "SYNSENT";

	case TCPS_SYNRCVD:
		return "SYNRCVD";

	case TCPS_ESTABLISHED:
		return "ESTABLISHED";

	case TCPS_FINWAIT1:
		return "FINWAIT1";

	case TCPS_FINWAIT2:
		return "FINWAIT2";

	case TCPS_CLOSEWAIT:
		return "CLOSEWAIT";

	case TCPS_LASTACK:
		return "LASTACK";

	case TCPS_CLOSING:
		return "CLOSING";

	case TCPS_TIMEWAIT:
		return "TIMEWAIT";

	default:
		return "UNKNOWN";
	}
}

static char *
in_decode_addr (sin, proto)
	struct sockaddr_in *sin;
	char *proto;
{
	char buf[40];
	struct servent *sent;

	if (sin->sin_port == 0)
		return "*:*";

	sent = getservbyport (sin->sin_port, proto);
	if (sent) {
		sprintf (buf, "%s:%s",
			sin->sin_addr.s_addr == INADDR_ANY
				? "*" : inet_ntoa (sin->sin_addr),
			sent->s_name);
	} else {
		sprintf (buf, "%s:%d",
			sin->sin_addr.s_addr == INADDR_ANY
				? "*" : inet_ntoa (sin->sin_addr),
			sin->sin_port);
	}
	return strdup (buf);
}

static char *
in_decode_proto (proto)
	short proto;
{
	switch (proto) {
	case IPPROTO_UDP:
		return "UDP";

	case IPPROTO_TCP:
		return "TCP";

	default:
		return "UNKNOWN";
	}
}

static void
show_udp (void)
{
	struct inet_info info;
	char *laddr, *faddr;
	int fd, r;

	fd = open (INET_DEVICE, O_RDONLY);
	if (fd < 0) {
		fprintf (stderr, "cannot open device %s: %s\n",
			INET_DEVICE, strerror (errno));
		return;
	}
	puts ("Active UDP connections");
	puts ("Proto  Recv-Q Send-Q Local-Address        "
	      "Foreign-Address      State");
	for (;;) {
		r = read (fd, &info, sizeof (info));
		if (!r) break;
		if (r < 0) {
			fprintf (stderr, "cannot read next entry from %s: %s",
				INET_DEVICE, strerror (errno));
			break;
		}
		if (r != sizeof (info)) {
			break;
		}
		if (info.proto != IPPROTO_UDP)
			continue;

		laddr = in_decode_addr (&info.laddr, "udp");
		faddr = in_decode_addr (&info.faddr, "udp");

		printf ("%-6s %6lu %6lu %-20s %-20s %-15s\n",
			in_decode_proto (info.proto),
			info.recvq, info.sendq,
			laddr, faddr,
			in_decode_state (info.state));

		free (laddr);
		free (faddr);
	}
	close (fd);
}

static void
show_tcp (void)
{
	struct inet_info info;
	char *laddr, *faddr;
	int fd, r;

	fd = open (INET_DEVICE, O_RDONLY);
	if (fd < 0) {
		fprintf (stderr, "cannot open device %s: %s\n",
			INET_DEVICE, strerror (errno));
		return;
	}
	puts ("Active TCP connections");
	puts ("Proto  Recv-Q Send-Q Local-Address        "
	      "Foreign-Address      State");
	for (;;) {
		r = read (fd, &info, sizeof (info));
		if (!r) break;
		if (r < 0) {
			fprintf (stderr, "cannot read next entry from %s: %s",
				INET_DEVICE, strerror (errno));
			break;
		}
		if (r != sizeof (info)) {
			break;
		}
		if (info.proto != IPPROTO_TCP)
			continue;

		if (!show_all_opt && info.state == TCPS_LISTEN)
			continue;


		laddr = in_decode_addr (&info.laddr, "tcp");
		faddr = in_decode_addr (&info.faddr, "tcp");

		printf ("%-6s %6lu %6lu %-20s %-20s %-15s\n",
			in_decode_proto (info.proto),
			info.recvq, info.sendq,
			laddr, faddr,
			in_decode_state (info.state));

		free (laddr);
		free (faddr);
	}
	close (fd);
}


void
usage (void)
{
	puts ("Usage: netstat [options]");
	puts ("Options:");
	puts ("\t [-a] [-f [unix|inet]]");
}

int
main (argc, argv)
	int argc;
	char *argv[];
{
	extern char *optarg;
	int c;

	while ((c = getopt (argc, argv, "af:")) != EOF) {
		switch (c) {
		case 'f':
			if (!strcmp (optarg, "unix")) {
				show_unix_opt = 1;
			} else if (!strcmp (optarg, "inet")) {
				show_inet_opt = 1;
			} else {
				show_usage_opt = 1;
			}
			break;

		case 'a':
			show_all_opt = 1;
			break;

		case '?':
			show_usage_opt = 1;
			break;
		}
	}
	if (show_usage_opt) {
		usage ();
		return 0;
	}
	if (show_inet_opt) {
		show_tcp ();
		show_udp ();
	}
	if (show_unix_opt) {	
		show_unix ();
	}
	return 0;
}
