/*
* 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