/* multifinger.c, 3 February, 1997, D. Glenn Arthur Jr. */ #include #define VERSION "version 0.6T (for Linux at houseoftea.org), 19970203.1615, uses /usr/bin/finger, skips access3" /*====================================================================*/ /* */ /* multifinger.c Gather info from 'finger' responses from */ /* multiple machines. */ /* */ /* This program fingers a user at access.digex.net. It displays all */ /* the usual stuff that 'finger' displays, except that it collects */ /* the "Last login", "On since", and "Never logged in" lines from all */ /* four access machines (access1.digex.net, access2.digex.net, */ /* access3.digex.net, and digex4.digex.net). */ /* */ /* The program has three parts: */ /* */ /* 1) a parent which forks and waits for its child to start babbling, */ /* then decides what parts of the result to display, */ /* 2) a child which redirects its output to a pipe back to the parent */ /* then forks itself once for each access machine, and */ /* 3) a grandchild which execs 'finger' to get the info from one */ /* machine. */ /* */ /* Flaws: This program is slow. It has to run 'finger' four times */ /* over the local network. It also reads and discards three copies */ /* of the target user's .plan file. Another problem is that it does */ /* not detect "connection refused" or timeouts on any of the 'finger' */ /* commands it executes. */ /* */ /* Improvements: Allow multiple arguments on the command line to */ /* finger more than one user at once. Allow the nomal options to the */ /* standard 'finger' command to have the same effects here. Handle */ /* refused connections, timeouts, and the like. Move functions that */ /* ought to be library routines to their own modules. Set up a table */ /* of which domains/hosts are set up with multiple host machines like */ /* Digex is, so that fingering a user at such a site automagically */ /* does the right thing. (Table should be user-configurable.) WRITE */ /* A 'MAN' PAGE. */ /* */ /* Bugs: If there is a match on the "real name" field as well as on */ /* username, remote 'finger' doesn't honor the -m option, so data for */ /* users other than the one specified come back, thus confusing this */ /* program. Also, if a user is logged on more than once on one host */ /* (i.e. using 'screen'), the program is similarly confused. */ /* */ /* Version 0.1 DGA 19950406 Announced on digex.general */ /* Modified DGA 19950407 Added comments, internal documentation */ /* Modified DGA 19950408 Added wordwrap() for help message */ /* Modified DGA 19950411 Reorganized, split into functions. */ /* Modified DGA 19950417 Changed NUMHOSTS to 5. */ /* Version 0.4a DGA 19950711 Temp. "IGNORETHREE" fix. */ /* Modified DGA 19951214 Added email address to help message */ /* Modified DGA 19951218 Made /usr/ucb/finger explicit. */ /* Modified DGA 19961011 Removed "IGNORETHREE". */ /* Modified DGA 19970902 Reinstalled "IGNORETHREE". */ /* Modified DGA 19970902 Added some clues for 'ps' to chew on */ /* Version 0.6T DGA 19980203 Uses /usr/bin/finger for Teahouse vrsn */ /* */ /*====================================================================*/ /* Glenn's standard debugging macro */ #define DEBUG if ( debug ) printf #define debug 0 /* Subscripts for the argument of pipe() */ #define W_END 1 #define R_END 0 /* The standard files */ #define STDIN 0 #define STDOUT 1 #define STDERR 2 /*====================================================================*/ /* CONFIGURATION CONSTANTS */ /*====================================================================*/ #define BUFFLEN 80 #define NUMHOSTS 5 #define HELP_MESSAGE "\ This program takes one argument, a username, and fingers that user \ at access.digex.net, but unlike the normal 'finger' command it gathers \ the login time ('On since' or 'Last login' times) from _each_ of the \ five access machines. \ \n********\n\ Notes: Will not yet correctly handle multiple hits in /etc/passwd \ i.e. situations where the username matches more than one user's \ 'realname' field.\ \n\nTemporarily ignoring access3, 'cause it seems to have been dead \ a while and messes things up.\ \n\nEmail bug reports, suggestions, patches, praise to \ glenn@access.digex.net\ \n" /* GLOBAL VARIABLES */ char argzero[80]; /* Keeps argv[0] in a globally */ /* accessible location */ char childname[6] = "child"; /*====================================================================*/ /* */ /* fdgets() Like gets() but reads from a pipe */ /* */ /* Takes three arguments: the file descriptor of the "read end" of */ /* pipe you wanna read, the buff to stuff, and the maximum number of */ /* bytes to transfer. Works much like gets() -- copies bytes from */ /* the pipe to the buffer until it's got N bytes or it hits a newline */ /* or end of file happens. Returns the number of bytes transferred, */ /* or -1 to signal end of file (closed pipe). Ought to work on */ /* ordinary files as well, but I haven't tried that. */ /* */ /* Improvements: This function really ought to read into a buffer */ /* instead of doing all those single-character read() calls, for the */ /* sake of efficiency. Even if the OS buffers read() for us, it's */ /* still that many more function calls. */ /* */ /* Created DGA 19950404 */ /* Commented DGA 19950411 */ /* */ /*====================================================================*/ int fdgets ( fd , s , length ) int fd ; /* File descriptor from which to read */ char *s; /* Buffer into which to put read data */ int length; /* Maximum number of bytes to read */ { int count; /* Holds # bytes read by call to */ /* read() */ int sofar; /* Running count of # bytes read by */ /* this invocation of this function */ char c[2]; /* Temporary buffer to hold single */ /* characters read from fd */ /* Read a byte at a time from (fd) until a termination condition */ /* (newline, EOF, or max # bytes read) happens. I guess this */ /* really ought to be a single for() statement with no body, just */ /* to be manly ... Let's see, that'd be: */ /* "for ( sofar = 0 ; (count = read(fd,c,1) != 0) && */ /* (c[0] != '\n') && (sofar < length-1) ; s[sofar++] */ /* = c[0] );" */ /* but I think I split it up for debugging purposes or something. */ sofar = 0; while ( ( count = read(fd, c, 1) != 0 ) && ( c[0] != '\n' ) && ( sofar < length-1 ) ) { s[sofar++] = c[0]; } /* At this point, (sofar) bytes have been read, so s[sofar-1] is */ /* the last byte read. Stick a null on the end no matter what */ /* made us stop. */ s[sofar] = '\0'; /* Figure out why we stopped, and return the EOF indicator (-1) */ /* or the number of bytes read, as appropriate. */ if ( count == 0 ) /* Last 1-byte read failed. */ return ( -1 ); /* Signal EOF to caller. */ else /* Note EOF yet. */ return ( sofar ); /* Return # bytes actually read. */ } /*===== end of fdgets() ==============================================*/ /*====================================================================*/ /* */ /* wordwrap() displays text word-wrapped */ /* */ /* Takes a loooong string and wraps it to the specified number of */ /* columns, breaking at whitespace. Prepends a prefix to each line */ /* of output. I should sit down and write a more versatile set of */ /* related functions. */ /* */ /* Created 19950408 DGA */ /* */ /*====================================================================*/ void wordwrap ( longstring , columns , prefix ) char *longstring; /* Input text */ int columns; /* Number of columns to wrap to */ char *prefix; /* String to prepend to output lines */ { int left, right, lf; char linebuff[256]; left = 0; while ( left + columns < strlen(longstring) ) { /* Start at the right margin and work backwards looking for a space */ for ( right = left + columns ; longstring[right] != ' ' ; right-- ); /* Copy what's between our left & right markers to (linebuff) */ strncpy ( linebuff , &longstring[left] , right-left ); /* Check for linefeeds -- we need to handle them specially */ for ( lf = 0 ; (lf < columns) && (linebuff[lf] != '\n') ; lf++ ); /* No line feeds -- terminate (linebuff) and adjust the left marker */ if ( lf < columns ) { linebuff[lf] = '\0'; left = left + lf + 1; } /* Found a line feed -- replace it with a null in (linebuff) and */ /* move the left marker to the character after it in the input */ /* buffer. The next pass through the loop will pick up with the */ /* character after the linefeed being the start of a complete line. */ else { linebuff[right-left] = '\0'; left = right + 1; } /* Whatever we've got in (linebuff) now gets printed, with (prefix) */ /* prepended first. */ printf ( "%s%s\n" , prefix , linebuff ); } /* When we get out of the while() loop, print out whatever's left */ /* in the input string. */ printf ( "%s%s\n" , prefix , &longstring[left] ); } /*===== end of wordwrap() ============================================*/ /*====================================================================*/ /* */ /* grandchild() Exec's 'finger' after having had its */ /* output redirected to a pipe. */ /* */ /* This function is called (NUMHOSTS) times by child(). Before this, */ /* child() has redirected stdout to a pipe back to main(). All this */ /* function does is use sprintf() to combine its arguments into a */ /* command string and call execlp() to invoke 'finger' on one host. */ /* */ /* Created 19950411 DGA Split this off into a separate function. */ /* */ /*====================================================================*/ int grandchild ( user , hostsuffix ) char *user; int hostsuffix; { char argbuff[80]; #ifdef IGNORETHREE /* This section forces the program to ignore access3. As this */ /* section is being added, access3 has been unresponsive for a */ /* while and I'm not ready to rewrite things so that a dead host */ /* doesn't screw things up at the moment. */ /* 11 OCTOBER 1996 -- access3 seems to be back up! */ /* 25 MARCH 1997 -- access2 is hosed, so this code is being */ /* reactivated, modified to refer to access2. */ /* 2 SEPTEMBER 1997 - access3 has been dead a while, so ... */ if (hostsuffix == 3) { printf ( "Last login: (access3 not checked).\n" ); exit(-1); } #endif /* printf ( "This is grandchild number %d.\n" , hostsuffix ); */ sprintf( argbuff, "%s@access%d.digex.net", user, hostsuffix ); execlp ( "/usr/bin/finger" , "((mfinger))" , argbuff , NULL ); /* Note that execlp(), if successful, _does_not_return_, so this */ /* point should be an implied exit(). Just in case execlp() does */ /* return, here's an error message. Might as well re-use argbuff */ /* for this, since it's there. */ sprintf ( argbuff , "%s (child process): attempt to exec 'finger' failed.\n" , argzero ); write ( STDERR , argbuff , strlen(argbuff) ); exit(-1); } /*====================================================================*/ /* */ /* child() Redirect stdout to previously opened pipe and */ /* fork() (NUMHOSTS) copies of grandchild() to run */ /* 'finger' on each host. */ /* */ /* This function redirects its stdout into the pipe opened in main(), */ /* then forks and waits (NUMHOSTS) times to run a copy of */ /* grandchild() on each host. */ /* */ /* Created 19950411 DGA Split off into separate function. */ /* */ /*====================================================================*/ int child( username , pipe_end , pname ) char *username; int pipe_end; char *pname; { int suffix; int grandchild_pid; strncpy ( pname , "(mfinger)" , strlen(pname) ); #ifdef DEBUG sleep(20); /* Give time to look up PID for debugger */ DEBUG ( "This is the child.\n" ); #endif /* Redirect stdout so that grandchildren's output gets sent back */ /* to main(). */ if ( dup2 ( pipe_end , STDOUT ) != STDOUT ) { printf ( "%s: problem redirecting output.\n" , argzero ); exit(-1); } /* For each host, fork a copy of grandchild() to run 'finger' on */ /* it. */ for ( suffix = 1 ; suffix <= NUMHOSTS ; suffix++ ) { if ( ( grandchild_pid = fork () ) == 0 ) grandchild ( username , suffix ); /* Note that grandchild() _does_not_return_, so this point is an */ /* implied exit() if the fork succeeded and this is the grandchild */ /* fork process. */ /* If this is not the grandchild process (fork() had a nonzero */ /* return), either fork() failed ... */ else { if ( grandchild_pid == -1 ) { printf ( "%s: fork() problem.\n" , argzero ); exit(-1); } /* ... or it succeeded and this is not the grandchild, so we need */ /* to wait for the grandchild to terminate before forking another, */ /* so that the output from the grandchildren isn't interleaved. */ else wait ( NULL ); } } /* In any case, we've done all that this child of main() should do, */ /* so let's terminate the process. */ exit(0); } /*==== End of child() ==================================================*/ /*====================================================================*/ /* */ /* main() 'multifinger' -- Finger a user on all four Digex */ /* "access" hosts at once, showing last-login */ /* times for each host and the rest of the */ /* 'finger' output only once. */ /* */ /* Apart from checking the command line for -v or -h or the wrong */ /* number of arguments, what this does is: */ /* 1) Create a pipe. */ /* 2) Fork -- the child will redirect it's stdout and fork again. */ /* 3) Wait for output from child and selectively display it. */ /* For details on #3, see comments inside final while() loop. */ /* */ /* Created DGA 19950406 */ /* Modified DGA 19950411 Split off child() and grandchild() */ /* */ /*====================================================================*/ int main ( argc , argv ) int argc; char **argv; { char username[10]; /* Holds copy of username to try to finger */ int child_pid; /* Holds return value of fork() */ int state, responses; /* Used to determine what parts of the */ /* output from the grandchild processes */ /* to display, and what to throw away. */ char buffer[81]; /* Holds a line of output from granchild */ int apipe[2]; /* Pipe through which grandchildren's */ /* output comes back. */ /* Check the command line. This section is fairly intuitive (and */ /* rather brute-force.) */ if ( argc != 2 ) { printf ( "usage: %s username\n" , argv[0] ); printf ( " -or- %s -h (for more info)\n" , argv[0] ); printf ( " -or- %s -v (for version)\n" , argv[0] ); exit(0); } if ( strncmp ( argv[1] , "-v" , 2 ) == 0 ) { printf ( "%s: %s\n" , argv[0] , VERSION ); exit(1); } if ( strncmp ( argv[1] , "-h" , 2 ) == 0 ) { wordwrap ( HELP_MESSAGE , 67-strlen(argv[0]) , sprintf ( buffer , "%s: " , argv[0]) ); exit(1); } DEBUG ( "DEBUG -- Got past checking command line.\n" ); /* Set things up -- set globals, copy things where they need to be, */ /* create the pipe. */ strncpy ( argzero , argv[0] , 80 ); strncpy ( username , argv[1] , 9 ); if ( pipe ( apipe ) != 0 ) { printf ( "%s: could not open pipe.\n" , argv[0] ); exit(-1); } /* Call fork(). If fork() returns zero, do child(), else continue */ /* with main(). */ if ( ( child_pid = fork () ) == 0 ) child ( username , apipe[W_END] , argv[0] ); else { /* fork() returned nonzero, so this is the parent process. First, */ /* make sure there really _is_ a child process ... */ if ( child_pid == -1 ) { /* If fork() returned -1, that means it failed. */ printf ( "%s: fork() failed.\n" , argv[0] ); exit(-1); } DEBUG ( "This is the parent. The child is %d.\n" , child_pid ); DEBUG ( "debug -- about to hit while() loop in parent.\n" ); /* Close the parent's copy of the writing end of the pipe, so that */ /* when the child process terminates (closing its copy of that end */ /* of the pipe), we'll get an end-of-file. */ close ( apipe[W_END] ); /* Now all that's left is to read the responses we get through the */ /* pipe and decide which parts to display. So what follows is a */ /* while() loop to read until the pipe closes, with the body being */ /* a state machine that handles each of the three phases of the */ /* output -- printing everything that comes before the last login */ /* time, printing the login times, and printing everything that */ /* comes after the last login time. */ state = 1; while ( fdgets ( apipe[R_END] , buffer , BUFFLEN ) != -1 ) { switch ( state ) { /* State One -- We're looking at the output of the first 'finger' */ /* command (access1). We've not yet gotten to the Last login / On */ /* since / Never logged in. Until we get that, just display what */ /* comes back from the grandchildren. When we _do_ get that line, */ /* prefix it with the name of the machine (access1), display it, */ /* and go to state two. */ case 1: /* DEBUG ( "debug: state=1 " ); */ if ((strncmp(buffer,"On since",8)==0)|| (strncmp(buffer,"Never lo",8)==0)|| (strncmp(buffer,"Last log",8)==0)) { state = 2; responses = 1; } if ( state == 1 ) puts ( buffer ); else printf ( "access%d: %s\n" , responses , buffer ); break; /* State Two -- We've already started displaying the login times. */ /* We're currently looking at the responses from 'finger' at host */ /* two, three, or four, and we're looking for that login time, */ /* displaying that and ignoring all else. Each time we get one, */ /* increment (responses), the counter of how many hosts we've */ /* gotten our answers from. Once (responses) gets to NUMHOSTS, we */ /* know we've gotten all the login times we're going to get, so we */ /* can go on to state three. */ case 2: /* DEBUG ( "debug: state=2 " ); */ if ((strncmp(buffer,"On since",8)==0)|| (strncmp(buffer,"Never lo",8)==0)|| (strncmp(buffer,"Last log",8)==0)) { responses++; printf ( "access%d: %s\n" , responses, buffer ); if ( responses == NUMHOSTS ) state = 3; } break; /* State Two -- We're looking at the output of the first 'finger' */ /* State Three -- We've printed the top section (from the first */ /* host), all the login times (from all four hosts), and now we're */ /* ready to print .project and .plan, so since we're getting the */ /* response from the last host, we just display everything we get */ /* from here until EOF on the pipe. */ case 3: /* DEBUG ( "debug: state=3 "); */ puts ( buffer ); break; } } DEBUG ( "debug -- past final while() loop in parent.\n" ); } } /* END OF main() */