/*
 * tcplist.c - make list of tcp connections, making use of Ident services
 *             if available. 
 *             
 *             tcplist attempts to be efficient about Ident queries, using
 *             the following techniques:
 *             - all connections to a particular host are grouped together.
 *             - connections to the Ident service on different hosts are 
 *               made simultaneously, where available file descriptors allow,
 *               using non-blocking I/O.
 *             - if the Ident service on a host is not available, no further
 *               requests will be made to that host.
 *             - if the Ident service supports multiple queries per service
 *               connection, tcplist will take advantage of this. 
 * 
 * Author: John DiMarco, University of Toronto, CDF
 *         jdd@cdf.toronto.edu
 */

#ifndef lint
static char rcsid[] = "$Id: tcplist.c,v 1.17 1997/04/24 18:54:37 jdd Exp $";
#include "patchlevel.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/file.h>

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

#if ((defined(__unix__) || defined(unix)) && !defined(USG))
#include <sys/param.h>
#endif

#if (defined(BSD) && (BSD >= 199103))
#define getnumfd() getdtablesize()
#else
#define getnumfd() ulimit(4,0l)
#endif /* defined(BSD)... */

#include "utils.h"		
#include "tcplist.h"

extern struct hostlist *make_hostlist();

void identhosts();
void doidenthosts();
struct hostent *gethostbyaddr();
struct servent *getservbyport();

/* Output formats */
#ifndef TERSEFORMAT
#define TERSEFORMAT   "%s@localhost:%s  %s@%s:%s\n"
#endif /* TERSEFORMAT */
#ifndef TABULARFORMAT
#define TABULARFORMAT "%-9s%-9s%-9s%-44s%-9s\n"
#endif /* TABULARFORMAT */

/* Ident port */
#ifndef PORT
#define PORT 113
#endif /* PORT */

/* Ident query timeout */
#ifndef TIMEOUT
#define TIMEOUT 15
#endif /* TIMEOUT */

/* identhosts status codes */
#define UNKNOWN -1
#define BAD 0
#define GOOD 1

/* NOIDENT file */
#ifndef NOIDENT
#define NOIDENT "/dev/null"
#endif /* NOIDENT */

#define NOIDSIZ 512	/* no more than NOIDSIZ hosts to skip Ident id for */
#define INBUFSIZ 80
u_long noid[NOIDSIZ];   /* NOIDENT table */
int ni=0; 		/* index into noid */
char *noidfile = NULL;

#include "tcplist.h"

struct hostlist *hlist = NUL(struct hostlist *);

struct hostlist *make_hostlist();

char *progname;
int d;
long time_out;

int hostresolv = TRUE;
int servresolv = TRUE;
int servershow = FALSE;
int printheader = FALSE;
int verbose = FALSE;

void usage()
{
	(void)fprintf(stderr, 
		"Usage: %s [-n][-N][-s][-T][-f filename][-t timeout]\n", 
			progname);
	(void)exit(2);
}

struct file *filetable;
int nfiles;

int ulongcmp(i, j)
const void *i, *j;
{
	return((int)(*((u_long *)i)-*((u_long *)j)));
}

/*
 * main - parse arguments and handle options
 */
main(argc, argv)
int argc;
char *argv[];
{
	int c, i;
	int errflg = 0;
	extern int optind;
	extern char *optarg;
	long addr;
	u_long localaddr;
	char *format = TERSEFORMAT;

	time_out=(long)TIMEOUT;
	progname = argv[0];
	while ((c = getopt(argc, argv, "dnNsTf:t:vV")) != EOF)
		switch (c) {
		case 'd':
			++d;
			break;
		case 'n':
			hostresolv = FALSE;
			break;
		case 'N':
			servresolv = FALSE;
			break;
		case 's':
			servershow = TRUE;
			break;
		case 'T':
			format=TABULARFORMAT;
			printheader = TRUE;
			break;
		case 't':
			time_out = atol(optarg);
			break;
		case 'f':
			noidfile = optarg;
			break;
		case 'V':
			printf("%s\n", patchlevel); 
			exit(0);
		case 'v':
			verbose = TRUE;
			break;
		default:
			errflg++;
			break;
		}
	if (errflg) {
		usage();
	}

	/* read in the hosts not to Ident-query */
	{
		FILE *fp;
		char buf[INBUFSIZ];

		if(NULL==noidfile) noidfile=NOIDENT;
		if(NULL!=(fp=fopen(noidfile, "r"))) {
			while(NULL!=fgets(buf, INBUFSIZ, fp)){
				struct hostent *h;
				if(buf[0]) buf[strlen(buf)-1]='\0'; 
					else continue; 
				if('#'==buf[0]) continue; /* skip comments */
				if(-1==(noid[ni]=inet_addr(buf))){
					if(NULL==(h=gethostbyname(buf))){
						Warning(
						  "line %d: unknown host %s",
							ni+1, buf);
						ni--;
					} else {
						memcpy((char *)&noid[ni], 
							h->h_addr_list[0], 
							h->h_length);
					}
				} 
				ni++;
#ifdef DEBUG
				if(ni){
					struct in_addr debuga;
					debuga.s_addr=noid[ni-1];
					dfprintf(1, stderr, 
						"noid[%d] is %s\n", ni-1, 
						    inet_ntoa(debuga));
				}
#endif /* DEBUG */
			}
			(void)fclose(fp);
		}
	}
	qsort((char *)noid, ni, sizeof(u_long), ulongcmp);

	if(verbose) printf("Fetching open connections.\n");
	hlist=make_hostlist(&localaddr, servershow);
	identhosts(hlist); /* do Ident stuff */

	if(printheader){
		printf(TABULARFORMAT, "LOCAL", "LOCAL",
			"REMOTE", "REMOTE", "REMOTE");
		printf(TABULARFORMAT, "USER", "PORT",
			" USER", " HOST", " PORT");
		printf(TABULARFORMAT, "-----", "-----",
			"------", "------", "------");
	}

	{
		/* print out results */

		struct connection *cn;
		struct hostlist *hl;
		char *remotehost, *remoteport, *localport, *remoteuser;
	
		for(hl=hlist;NULL!=hl;hl=hl->next){
			for(cn=hl->conn;NULL!=cn;cn=cn->next){
				/* for each connection, print line */

				remotehost=s(InAddrToString(hl->addr,
						hostresolv));
				localport=s(PNumToString(cn->local,
					       localaddr, servresolv));
				remoteport=s(PNumToString(cn->remote,
						hl->addr, servresolv));
				remoteuser=cn->ident;
				if(NULL==remoteuser) remoteuser="?";
				printf(format, cn->user, 
					localport, remoteuser,
					remotehost, remoteport);
			}
		}
	}
	exit(0);
}

/* 
 * identhosts(): use the Ident service, where available, to fill in ident 
 *               field of the connection lists in hostlist. This takes 
 *               advantage, where available, of daemons (eg. pidentd) that can
 *               answer several queries per connection, which is a performance
 *               win, although not strictly Ident compliant. 
 *
 *               Restrictions: long identstrings may be lost, since identhosts
 *                             is lazy and only does one read. 
 */
void identhosts(hosts)
struct hostlist *hosts;
{
	int numdescs; 
	int num = 0;
	struct hostlist *ih, *sav, *undone;

	numdescs = sysconf(_SC_OPEN_MAX) - 10;
	ih=undone=hosts;

	/* do identhosts in batches of MAX_FILE_DESCRIPTORS - 10, to 
           eliminate risk of running out of file descriptors. */
 
	while(NULL!=ih){
		for(;NULL!=ih && num<numdescs;ih=ih->next){
			if(NULL==bsearch((char *)&(ih->addr), (char *)noid, ni,
					sizeof(u_long), ulongcmp)){
				ih->identstatus = UNKNOWN;
				num++;
			} else {
				ih->identstatus = BAD;
			}
		}
		if(NULL!=ih){
			sav=ih->next;
			ih->next=NULL;
			doidenthosts(undone);
			num=0;
			ih->next=undone=sav;
		}
	}
	doidenthosts(undone);
}

int doread(fd, vp, si)
int fd, si;
void *vp;
{
	/* read from fd until EOF, newline, or buffer full */
	int n=1, ct;
	char *cp;

	ct=(int)si;
	cp=(char *)vp;
	while(n && ct>0){
		if(0>(n=(int)read(fd,cp,ct))) return(n);
		cp += n;
		ct -= n;
		if(n && '\n'==*(cp-1)) break;
	}
	return(cp-(char *)vp);
}

int dowrite(fd, vp, si)
int fd, si;
void *vp;
{
	int ct=(int)si, n;
	char *cp = (char *)vp;

	/* write to fd until error or buffer empty */
	while(ct>0){
		if(0>=(n=(int)write(fd,cp,ct))) return(n);
		cp += n;
		ct -= n;
	}
	return(cp-(char *)vp);
}

void doidenthosts(hosts)
struct hostlist *hosts;
{
	long maxfd;
	struct connection *ic;
	struct hostlist *ih;
	int r;
	int port=htons(PORT);
	fd_set wds;
	int num=0, decided=0;
	struct timeval timeout;
#ifdef DEBUG
	struct in_addr debuga;
#endif

	if(verbose) printf("Connecting to remote hosts.\n");

	if(0>(maxfd=getnumfd())){
		perror("getnumfd");
		exit(2);
	}
	
	/* waltz through the connections in the hostlist, setting idents  */
	/* to NULL */

	for(ih=hosts;NULL!=ih;ih=ih->next){
		for(ic=ih->conn;NULL!=ic;ic=ic->next){
			ic->ident = NULL; 
		}
		if(UNKNOWN==ih->identstatus) num++;
	}
#ifdef DEBUG
	dfprintf(1, stderr, "Doidenthosts: num=%d\n", num);
#endif

	/* get sockets */
	for(ih=hosts;NULL!=ih;ih=ih->next){
		if(ih->identstatus==BAD) continue; /* skip known-BAD hosts */
		if(0>(ih->sock=socket(AF_INET,SOCK_STREAM, 0))){
			perror("socket");
		}
		/* turn on non-blocking I/O */
		(void)fcntl(ih->sock, F_SETFL, FNDELAY);
	}

	/* start connects on all of them */
	for(ih=hosts;NULL!=ih;ih=ih->next){
		struct sockaddr_in sa;
	
#ifdef DEBUG
		debuga.s_addr = ih->addr;
#endif /* DEBUG */
		if(BAD==ih->identstatus){
			/* 
			 * this host has been found on the noident list. Don't
			 * bother with connect. 
			 */
#ifdef DEBUG
			dfprintf(2, stderr, "Skipping %s\n",
					inet_ntoa(debuga));
#endif /* DEBUG */
		} else {
#ifdef DEBUG
			dfprintf(2, stderr, "Init connection to %s\n",
					inet_ntoa(debuga));
#endif /* DEBUG */
			sa.sin_family = AF_INET;
			sa.sin_port = port;
			sa.sin_addr.s_addr = ih->addr;

			(void)connect(ih->sock, (struct sockaddr *)&sa, 
					sizeof(sa));
		}
	}

	while(decided<num){
		timeout.tv_sec = time_out;
		timeout.tv_usec = 0;

		/* make write descriptors list for a select, to  */
		/* determine when the connects finish.*/
		FD_ZERO(&wds);
		for(ih=hosts;NULL!=ih;ih=ih->next){
			if(INADDR_ANY==ih->addr) continue;
			if(UNKNOWN==ih->identstatus){
#ifdef DEBUG
				debuga.s_addr = ih->addr;
				dfprintf(2, stderr, "Want to query %s\n", 
					inet_ntoa(debuga));
#endif /* DEBUG */
				FD_SET(ih->sock,&wds);
			}
		}

#ifdef DEBUG
		dfprintf(2, stderr, "Selecting... (timeout %d)\n", time_out);
#endif /* DEBUG */
		if(0>(r=select((int)maxfd, NUL(fd_set *), &wds, NUL(fd_set *), 
			&timeout))){
			perror("select");
			exit(2);
		}

		if(r==0){
			/* we've timed out. Write off all remaining sockets */
			for(ih=hosts;NULL!=ih;ih=ih->next){
				if(UNKNOWN==ih->identstatus){
#ifdef DEBUG
					debuga.s_addr = ih->addr;
					dfprintf(2, stderr, 
						"Giving up on %s\n", 
						inet_ntoa(debuga));
#endif /* DEBUG */
					ih->identstatus = BAD;
				}
			}
			break;
		}

		for(ih=hosts;NULL!=ih;ih=ih->next){
			if(UNKNOWN==ih->identstatus){
				if(FD_ISSET(ih->sock,&wds)){
					/* connection has completed */
					struct sockaddr_in sa;

					sa.sin_family = AF_INET;
					sa.sin_port = port;
					sa.sin_addr.s_addr = ih->addr;

					if(-1==connect(ih->sock, 
							(struct sockaddr *)&sa, 
							sizeof(sa))){
						if(EISCONN==errno){
#ifdef DEBUG
					debuga.s_addr = ih->addr;
					dfprintf(2, stderr, 
					"Connection succeeded for %s\n",
					inet_ntoa(debuga));
#endif /* DEBUG */
							ih->identstatus=GOOD;
						} else {
							ih->identstatus=BAD;
#ifdef DEBUG
					debuga.s_addr = ih->addr;
					dfprintf(2, stderr, 
					"Connection failed for %s\n",
					inet_ntoa(debuga));
#endif /* DEBUG */
						}
						decided++;
					}
					r--;
					if(r<1) break;
				}
			}
		}
#ifdef DEBUG
		dfprintf(2, stderr, "%d of %d decided\n", decided, num);
#endif /* DEBUG */
	}
	
#ifdef DEBUG
	dfprintf(2, stderr, "Results:\n");
#endif
	for(ih=hosts;NULL!=ih;ih=ih->next){
		/* turn off nonblocking I/O */
		if(GOOD==ih->identstatus) (void)fcntl(ih->sock, F_SETFL, 0); 

#ifdef DEBUG
                debuga.s_addr = ih->addr;

		switch(ih->identstatus){
		case GOOD:
			dfprintf(2, stderr, "%s good\n",inet_ntoa(debuga));
			break;
		case BAD:
			dfprintf(2, stderr, "%s bad\n",inet_ntoa(debuga));
			break;
		case UNKNOWN:
                        dfprintf(2, stderr, "%s unknown\n",inet_ntoa(debuga));
			break;
		default:
			dfprintf(2, stderr, "%s strange status %d\n",
				inet_ntoa(debuga), ih->identstatus);
			break;	
		} 
#endif
	}

	if(verbose) printf("Querying remote hosts.\n");

	/* now, for each ok host, send an appropriate request off to the 
           Ident server */
	for(ih=hosts;NULL!=ih;ih=ih->next){
		char ubuff[1024], osbuff[128];
		unsigned short remote, local;
		for(ic=ih->conn;NULL!=ic;ic=ic->next){
			char wbuff[80];
			char rbuff[1024];
			int n;

			if(GOOD!=ih->identstatus) continue;

			(void)sprintf(wbuff, "%hu,%hu\n", 
				ic->remote, ic->local);

			if(0>=dowrite(ih->sock, wbuff, strlen(wbuff))){
				(void)close(ih->sock);
				ih->identstatus=BAD;
				break;
			}
			if(0>(n=doread(ih->sock, rbuff, sizeof(rbuff)))){
				(void)close(ih->sock);
				ih->identstatus=BAD;
				break;
			} else if(0==n){
				/* other side may have closed conn, reopen */
                                /* and try again. */
				struct sockaddr_in sa;
#ifdef DEBUG
				dfprintf(3, stderr, "reopening\n");
#endif

				/* close the socket */
				(void)close(ih->sock);

				/* get a new socket */
				if(0>(ih->sock=socket(AF_INET,
					SOCK_STREAM, 0))){
					ih->identstatus=BAD;
					break;
				}

				/* connect */
				sa.sin_family = AF_INET;
       		         	sa.sin_port = port;
       		         	sa.sin_addr.s_addr = ih->addr;
				if(0>connect(ih->sock, (struct sockaddr *)&sa,
					sizeof(sa))){
					ih->identstatus=BAD;
					break;
				}

				if(0>dowrite(ih->sock, wbuff, strlen(wbuff))){
					(void)close(ih->sock);
					ih->identstatus=BAD;
					break;
				}
				if(0>(n=doread(ih->sock, rbuff, 
						sizeof(rbuff)))){
					(void)close(ih->sock);
					ih->identstatus=BAD;
					break;
				}
				if(0==n){
					/* still eof! give up. */
					(void)close(ih->sock);
					ih->identstatus=BAD;
					break;
				}
			} 
			rbuff[n]='\0';
#ifdef DEBUG
			debuga.s_addr = ih->addr;
			dfprintf(1, stderr, "%s: %s", 
				inet_ntoa(debuga), rbuff);
#endif
			/* Check the response */
			if(4>sscanf(rbuff, "%hu , %hu : USERID : %s : %s", 
				  &remote, &local, osbuff, ubuff)){
				/* bad response. */
#ifdef DEBUG
				dfprintf(2, stderr, "bad response %s\n", rbuff);
#endif
				continue;
			}
			if(ic->local!=local || ic->remote!=remote){
				/* Port number mismatch. */
#ifdef DEBUG
				dfprintf(2, stderr, 
				  "port mismatch %s: %hu, %hu with %hu, %hu\n",
					 rbuff, remote, local, ic->remote, 
				   ic->local);
#endif
				continue;
			}

			/* It's okay. Trash trailing newline or lf, set ident 
			   field */
			{
				int sl=strlen(ubuff)-1;
				if(ubuff[sl]=='\n' || ubuff[sl]=='\r'){
					ubuff[sl]='\0';
				} 
			} 
			ic->ident = s(ubuff);
#ifdef DEBUG
			debuga.s_addr = ih->addr;
			dfprintf(2, stderr, 
				"Host %s remote %hu local %hu user %s\n",
				inet_ntoa(debuga), ic->remote, ic->local, 
				ic->ident);
#endif
		}
		/* we're done with this host */
		(void)close(ih->sock);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1