/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #if ((defined(__unix__) || defined(unix)) && !defined(USG)) #include #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 && numnext){ 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(decidednext){ 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); } }