Metropoli BBS
VIEWER: finger.c MODE: TEXT (ASCII)
/*
 *  finger.c -- show the owner of a login id or the login id of a person
 *
 *  v2.12  94.02.15  by Curt Sampson
 *  Copyright 1991-1994 by Fluor Daniel, Inc.
 *
 *  Usage: finger [server/]
 *	   finger [server/] [user ...] ...
 *
 *  See the manual page for complete usage details.
 *
 *  This program is free software; you may redistribute it and/or
 *  modify it under the terms of the GNU General Public Licence
 *  as published by the Free Software Foundation; either version 1
 *  or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; with even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public Licence for more details.
 *
 *  You should have received a copy of the GNU General Public Licence
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 * 
 *  This program was compiled under Borland's Turbo C++ 3.00 with the
 *  Novell NetWare C Interface--DOS libraries. If you make changes to
 *  this program, please send me a copy. If they seem relevant to the
 *  community at large, I'll integrate them into the standard release.
 *
 *  Curt Sampson		email: a09878@giant.rsoft.bc.ca
 *  Fluor Daniel
 *  1444 Alberni Street		Tel: 604 691 5458
 *  Vancouver, B.C. CANADA	Fax: 604 687 6130
 *  V6G 2Z4
 *
 *  Revision History:
 *  91.10.30  cjs  Initial version (1.0).
 *  92.06.19  cjs  Rewritten (2.0).
 *  92.06.25  cjs  Added last login time field.
 *  92.07.02  cjs  Now check LOGIN_CONTROL before MISC_LOGIN_INFO for last
 *		   login time.
 *  93.09.17  cjs  Prints out object-id of user as well (to help find the
 *		   Novell mail directory); "department" is now called "org"
 *		   and the number is printed as well; checks mail directory
 *		   for plan and profile files if it can't find them in the
 *		   user directory. (v2.02)
 *  93.09.20  cjs  (2.02) Fixed it so that it displays "never logged in"
 *		   instead of "Sun  0 00:00:00 2000" when the user has never
 *		   logged in.
 *  93.10.21  cjs  (2.10) Added finger.ini file for locations of home dirs,
 *		   moved .plan and .project out of /lib up to root of home dir.
 *  93.10.25  cjs  (2.11) Now looks at S_SERVER environment variable to
 *		   determine the server to finger on if no server is specified.
 *                 Also prints out month/day of login if >5 days ago.
 *  93.10.28  cjs  2.11 release.
 *  93.11.16  cjs  Fixed it so that it doesn't warn about volumes not found
 *		   on servers to which one is not attached.
 *  93.12.01  cjs  Moved plan and project out of /lib up to root of home dir,
 *		   but for real this time. :-)
 *  94.01.17  cjs  Now prints an asterisk before the user-id in the -a listing
 *		   and after the id in other listings if the user-id is
 *		   security-equivalant to SUPERVISOR.
 *  94.02.15  cjs  (2.12) Fixed bug in display of network address; removed
 *		   code to print out only last two digits of network address
 *		   if first four digits were zero (a relic of the ARCNet days).
 */

char id[]="@(#)finger.c 2.12 94.02.15 by Curt Sampson (FDW Vancouver)";
char copyright[]="Copyright 1991-1994 by Fluor Daniel, Inc.";


#include <dos.h>
#include <cslib/error.h>
#include <cslib/getopt.h>
#include <netware/nit.h>
#include <netware/ntt.h>
#include <netware/niterror.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
 

/*
 *  useful types
 */

typedef enum { False = 0, True } Boolean;

struct arg {
    enum { Server, User } type;
    char *name;
    struct arg *next;
};

struct userDirStruct {		/* info for the user directories */
    char *server;		/* server the dir is on */
    char *path;			/* path to get to the dir */
    WORD conn;			/* connection no. of the server */
    BYTE dir;			/* dir handle on that server for that path */
};

#define OBJECT_NAME_LENGTH	48
#define OBJECT_PROPERTY_LENGTH	16
#define OBJECT_VALUE_LENGTH	128 

#define INITFILE "finger.ini"	/* location of init file */
#define CLIST_SIZE 1000		/* maximum number of users on a server */

#define MAXNAMES 64		/* maximum number of individuals one can */
				/* finger on a server at once */

#define MAX_USER_DIRS 64	/* max. no of places to search for user dir */
				/* (anybody with more than 64 user dirs     */
				/* is just silly, anyway.)		    */


/*
 *  Global vars
 */

struct userDirStruct userDir[MAX_USER_DIRS]; /* paths to searh for user dirs */
 

/************************************************************************/
/*									*/
/*  Utility subroutines							*/
/*									*/
/************************************************************************/


/*
 *  containsWord(s, w) -- return true if a word w is contained in
 *			  string s (non case-sensitive comparison)
 *			  (Note that this will match prefixes as well, e.g.,
 *			  the word "jeff" will match "jeffrey" in the string;
 *			  this is intentional.)
 */

Boolean containsWord(char *s, char *w)
{
    int si = 0, wi = 0,		/* indices into strings s and w */
        sl, wl;			/* lengths of the two strings */
    char lcw[128];		/* lowercase version of w */

    sl = strlen(s);
    wl = strlen(w);

    /* if w is longer than s, w can't be in s */
    if ( wl > sl )
	return False;

    /* make a lower-case copy of string w in advance--more efficient */
    for ( wi=0; wi < wl; wi++ )
	lcw[wi] = tolower(w[wi]);
    lcw[wi] = '\000';

    wi = 0;
    while ( si < sl )  {
	if ( tolower(s[si++]) == lcw[wi] )  {
	    wi++;		/* matches, keep trying rest of word */
	} else {
	    wi = 0;		/* reset */
	    do			/* skip to next word */
		si++;
	    while ( ! (s[si] == ' ' || s[si] == '\t' || s[si] == '\000') );
	    while ( s[si] == ' ' || s[si] == '\t' )
		si++;
	}
	if ( wi == wl )
	    return True;
    }
    return False;
}


/*
 *  itomonth() -- returns a string with the name of the month given as an
 *		  integer (1 = Jan, 2 = Feb, etc.)  Returns a null string
 *		  if the integer is not a valid month.  The string is
 *		  static and is overwritten with each call.
 */

char *itomonth(int imonth)
{
    static char month[4];
    
    switch ( imonth )  {
      case  1 : strcpy(month, "Jan"); break;
      case  2 : strcpy(month, "Feb"); break;
      case  3 : strcpy(month, "Mar"); break;
      case  4 : strcpy(month, "Apr"); break;
      case  5 : strcpy(month, "May"); break;
      case  6 : strcpy(month, "Jun"); break;
      case  7 : strcpy(month, "Jul"); break;
      case  8 : strcpy(month, "Aug"); break;
      case  9 : strcpy(month, "Sep"); break;
      case 10 : strcpy(month, "Oct"); break;
      case 11 : strcpy(month, "Nov"); break;
      case 12 : strcpy(month, "Dec"); break;
      default : month[0] = '\0';	break;
    }

    return month;
}


/*
 *  itowday() -- returns a string with the name of the weekday given as an
 *		 integer (0 = Sun, 1 = Mon, etc.).  Returns a null string
 *		 if the integer is not a valid weekday.  The string is
 *		 static and is overwritten with each call.
 */

char *itowday(int iwday)
{
    static char wday[4];

    switch ( iwday )  {
      case 0 : strcpy(wday, "Sun"); break;
      case 1 : strcpy(wday, "Mon"); break;
      case 2 : strcpy(wday, "Tue"); break;
      case 3 : strcpy(wday, "Wed"); break;
      case 4 : strcpy(wday, "Thu"); break;
      case 5 : strcpy(wday, "Fri"); break;
      case 6 : strcpy(wday, "Sat"); break;
      case 7 : strcpy(wday, "Sun"); break;
      default: wday[0] = '\0'; break;
    }

    return wday;
}


/*
 * messagesDisabled() -- return False if messages enabled (caston), True
 *			 if messages disabled (castoff) for the connection
 *			 on preferred server.
 */

Boolean messagesDisabled(WORD connection)
{
    BYTE status;

    SendBroadcastMessage("", &connection, &status, 1);

    return( status == 0xFF ? True : False );
}

/*
 * isSupervisorEquivalant() -- return True if the user is security-equivalant
 *			       to SUPERVISOR, False if not. (If the caller is
 *			       not also security-equivalant to SUPERVISOR this
 *			       will always return False.)
 */

Boolean isSupervisorEquivalant(char *name, WORD type)
{
    if ( IsBinderyObjectInSet(name, type, "SECURITY_EQUALS", "SUPERVISOR",
								    (WORD) 1) )
	return False;
    else
	return True;
}


/*
 * getAddress() -- return a pointer to a string contatining the internet
 *			address of a connection on the current preferred
 *			server in a nice, printable hex form.
 */

char *getAddress(int connection)
{
    BYTE net[4], node[6];
    static char output[22];
    WORD dummyw;
    
    output[0] = '\000';

    if ( GetInternetAddress(connection, net, node, &dummyw) == 0 )  {
	sprintf(output, "%08lx", LongSwap(*((long *) net)) );
	sprintf(output + 8, ":%08lx%04x", LongSwap(*((long *) node)),
						IntSwap(*((int *) (node+4))));
    } else {
	sprintf(output, "(unknown)");
    }

    return(output);
}


/*
 * getHomeDir(user) -- find and return the home directory of a user; NULL
 *		       if it doesn't seem to exist.  Conn is the connection
 *		       no. of the server the user is on.
 */

char *getHomeDir(WORD conn, char *user)
{
    char name[9];		/* name of dir is user name trunc to 8 chars */
    static char path[256];	/* buffer for path we construct & return */
    int i;

    WORD prefConn;
    long seq;
    NWDIR_ENTRY dummy;
    BYTE dh;
    

    /* initalisations */
    strncpy(name, user, 8);	/* truncate user name */
    name[8] = '\0';
    prefConn = GetPreferredConnectionID();

    /* go though our list of handles, trying to stat the dir */
    for ( i = 0; i < MAX_USER_DIRS && userDir[i].dir != 0; i++ )  {
	if ( (conn == userDir[i].conn) )  {
	    if ( conn != prefConn )  {
		SetPreferredConnectionID(conn);
		prefConn = conn;
	    }
	    if ( ! AllocTemporaryDirectoryHandle(userDir[i].dir, name, '_',
								&dh, 0) )  {
		DeallocateDirectoryHandle(dh);
		strcpy(path, userDir[i].path);
		if ( path[strlen(path)-1] != '/' )
		    strcat(path, "/");
		strcat(path, name);
		strlwr(path);		/* lowercase the whole thing */
		return path;
	    }
	}
    }

    return NULL;	/* didn't find it--sigh */
}


/*
 * getOrg(objectID) --  find the department of a user by checking for
 *			membership in a group beginning with a three
 *			digit number and an underline.  Return a
 *			pointer to an ASCII representation of that
 *			department.
 *
 *			Kinda site-specific, I know.
 */

char *getOrg(long objectID)
{
    char objectName[OBJECT_NAME_LENGTH];  /* for the objectID we are passed */
    WORD objectType;
    static char groupName[OBJECT_NAME_LENGTH]; /* and for the groups we check */
    WORD groupType;
    BYTE objectValue[OBJECT_VALUE_LENGTH];
    long *setItem = (long *) objectValue;			/* a pointer to our array of groups */
    int segno = 1;
    BYTE moreseg = 1;
    BYTE propflags;
    int i;


    /* get name and type for next call */
    (void) GetBinderyObjectName(objectID, objectName, &objectType);

    /*
     * find the first group that starts with ###_ (# = an integer),
     * what follows will be the department name
     */
     
    /* go though all the segments */
    while ( moreseg )  {
	/* grab the list of groups from each segment */
	if ( ReadPropertyValue(objectName, objectType, "GROUPS_I'M_IN",
		    segno++, objectValue, &moreseg, &propflags) )  {
	    return NULL;	/* error */
	}
	if ( ! (propflags & BF_SET) )
	    return NULL;	/* it's not a set--something very wrong here */
	/* loop though all ids in a set */
	for ( i=0; i<32 && setItem[i]; i++ )  {
	    if ( GetBinderyObjectName(LongSwap(setItem[i]),
						    groupName, &groupType) )
		continue;	/* error, just go on to next */
	    /* if group name starts with ###_, it's our group */
	    if ( isdigit(groupName[0]) && isdigit(groupName[1]) &&
		    isdigit(groupName[2]) && (groupName[3] == '_') )  {
		groupName[3] = ' ';
		return(groupName);
	    }
	    	
	}
	    
    }
    return NULL;	/* went though them all, found nothing */
}


/************************************************************************/
/*									*/
/*  Main subroutines							*/
/*									*/
/************************************************************************/


/*
 *  readInitFile() -- read the finger.ini file (in the dir we started in)
 *		      myName contains argv[0]
 */

void readInitFile(char *myName)
{
    WORD conn;
    BYTE dirH;
    
    FILE *f;
    char s[256];
    char *p, *p2;
    int i, line;
    

    /* strip our name off the path given in argv[0] and add init file name */
    strcpy(s, myName);
#pragma option -w-pia
    if ( p = strrchr(s, '\\') )
#pragma option -w.pia
	strcpy(p+1, INITFILE);		/* put it on the end of the path */
    else
	strcpy(s, INITFILE);		/* no path? try current dir

    /* token check for fuck-up (can't see how it could happen under DOS) */
    if ( strlen(s) > 255 )
	error(3, "Filename path for init file too long--call programmer!");

    /* open the file or give up if we can't find it */
    if ( ! (f = fopen(s, "r")) )
	return;

    /* ditch first line ("[USERDIRS]", one hopes!) */
    fgets(s, 255, f);

    /* and read the servers/dirs, 1 per line */
    for ( i = 0, line = 2; i < MAX_USER_DIRS && fgets(s, 255, f); i++, line++ )  {
	/* sort of a check of the format */
	if ( ! (p = strchr(s, '/'))  ||
	     ! (p2 = strchr(p, ':')) ||
	     ! strchr(p2, '/')        )
	{
	    fprintf(stderr, "finger: error in format of line %d of %s\n",
								line, INITFILE);
	    i--; continue; /* modifying loop var--naughty! */
	}		/* which leaves p at the / between server and volume */
	*(p++) = '\0';		/* split server (s) and volume/path (p) */
	p[strlen(p)-1] = '\0';	/* remove newline */
	/* check to see the volume/path exists */
	if ( ! GetConnectionID(s, &conn) )
	    SetPreferredConnectionID(conn);
	else
	    conn = 0;
	if ( conn && ! AllocTemporaryDirectoryHandle(0, p, '_', &dirH, 0) )  {
	    /* store the thing in our list */
#pragma option -w-pia
	    if ( ! ((userDir[i].server = strdup(s)) &&
		    (userDir[i].path = strdup(p))) )
#pragma option -w.pia
		error(1, "out of memory while reading init file");
	    userDir[i].conn = conn;
	    userDir[i].dir = dirH;
	} else {
	    /* warn if we're on that server and couldn't find that dir */
	    if ( conn )  {
		fprintf(stderr,
		    "finger: warning: can't find %s/%s (from finger.ini)\n",
		    s, p);
		i--; /* (continue;) modifying loop var--naughty! */
	    }
	}
    }

    /* put an end marker on the arrays, if necessary */
    if ( i < MAX_USER_DIRS )  {
	userDir[i].server = userDir[i].path = NULL;
	userDir[i].conn = userDir[i].dir = 0;
    }	

    /* clean up and get out */
    fclose(f);
    return;
}


/*
 *  printFingerInfo(objectID) -- print out finger information on user objectID
 *				 on the server on connection connID.  Print out
 *				 the .plan file if printPlan is true.
 *
 *  returns -1 on error, 0 if ok
 */

int printFingerInfo(WORD connID, long objectID, Boolean printPlan)
{
    char objectName[OBJECT_NAME_LENGTH];
    char objectName2[OBJECT_NAME_LENGTH+1];
    WORD objectType;
    char objectValue[OBJECT_VALUE_LENGTH];
    char server[49];
    char *department;
    WORD clist[CLIST_SIZE];		/* connection list */
    BYTE logintime[7];
    char *homedir;
    FILE *f;
    char filename[128];
    
    int c, found;
    WORD i;
    BYTE dummyb;


    /*
     * get name and type, we'll need them later
     */
     
    (void) GetBinderyObjectName(objectID, objectName, &objectType);
    
    /*
     * get object name and user name and print them out
     * (append asterisk to object name if su'd)
     */
     
    strcpy(objectName2, objectName);
    if ( isSupervisorEquivalant(objectName, objectType) )
	strcat(objectName2, "*");
    printf("\nLogin name: %-19s ", objectName2);
    if ( ! ReadPropertyValue(objectName, OT_USER, "IDENTIFICATION", 1,
				(BYTE *) objectValue, &dummyb, &dummyb) )
	printf("In real life: %s\n", objectValue);
    else
	printf("\n");

    /*
     * print out the object-id of the user and the server the ID is on
     */

    printf("Object ID:  %8lX            ", objectID);
    
    GetFileServerName(connID, server);
    printf("Server: %-23.23s\n", server);

    /*
     * print out office and phone number (well, eventually)
     */
     
    department = getOrg(objectID);
    if ( department )
	printf("Org: %-26.26s ", department);

    /*
     * print out home directory information
     */
     
    homedir = getHomeDir(connID, objectName);
    if ( homedir )
	printf("Directory: %s\n", homedir);
    else if ( department )
	printf("\n");


    /*
     * print out login information
     */
     
    (void) GetObjectConnectionNumbers(objectName, objectType, (WORD *) &c,
							 clist, CLIST_SIZE);
    if ( c != 0 )  {
	/*
	 *  if we have connections, print info on them
	 */
	for ( i=0; i<c; i++ )  {
	    (void) GetConnectionInformation(clist[i], objectName, &objectType,
					&objectID, logintime);
	    printf("On since %s %d %02d:%02d:%02d, connection %d",
		itomonth(logintime[1]), (int) logintime[2], (int) logintime[3],
		(int) logintime[4], (int) logintime[5], (int) clist[i]);
	    /* print workstation address */
	    printf(", addr %s", getAddress(clist[i]));
	    /* check message (caston/castoff) status */
	    printf("%s\n", messagesDisabled(clist[i]) ? "  (castoff)"
						      : "" );
	}
    } else {
	/*
	 *  No connections, print last login time, which is the format of
	 *  GetFileServerDateAndTime in bytes 56-62 of LOGIN_CONTROL.
	 */
	if ( ReadPropertyValue(objectName, OT_USER, "LOGIN_CONTROL",
		    1, (BYTE *) objectValue, &dummyb, &dummyb) == 0 )  {
	    if ( objectValue[57] )
		printf("Last login:%s %s %d %02d:%02d:%02d %d%02d\n",
		    itowday(objectValue[61]), itomonth(objectValue[57]),
		    (int) objectValue[58], (int) objectValue[59],
		    (int) objectValue[60], (int) objectValue[61],
		    objectValue[56] < 80 ? 20 : 19, objectValue[56]);
	    else
		printf("Never logged in.\n");
	} else {
	    printf("Last login: unknown.\n");
	}
    }


    /*
     * print out .project and .plan files
     */

/* avoid complaints on "if ( f=fopen() )" assignments */
#pragma option -w-pia

    /* map the user's mail dir to the temporary drive handle [: */
    /* if it doesn't map that's ok; fopen will just fail later */
    strcpy(filename, "SYS:/MAIL/");
    ltoa(objectID, filename + strlen(filename), 16);
    AllocTemporaryDirectoryHandle(NULL, filename, '[', &dummyb, &dummyb);
     
    found = 0;
    if ( homedir )  {		/* check for .project in the home dir */
	strcpy(filename, homedir);
	strcat(filename, "/project");
	if ( f = fopen(filename, "rt") )  {
	    found = 1;
	    printf("Project: ");
	    c = getc(f);
	    while ( (c != '\n') && (c != EOF) )  {
		putchar((char) c);
		c = getc(f);
	    }
	    fclose(f);
	    putchar('\n');
	}
    }
    
    if ( ! found ) {	/* if no user dir or not in user dir, try mail dir */
	if ( f = fopen("[:project", "rt") )  {
	    printf("Project: ");
	    c = getc(f);
	    while ( (c != '\n') && (c != EOF) )  {
		putchar((char) c);
		c = getc(f);
	    }
	    fclose(f);
	    putchar('\n');
	}
    }

    if ( printPlan )  {
	found = 0;
	if ( homedir )  {
	    strcpy(filename, homedir);
	    strcat(filename, "/PLAN");
	    if ( f = fopen(filename, "rt") )  {
		found = 1;
		printf("Plan:\n");
		while ( (c = getc(f)) != EOF )  {
		    putchar((char) c);
		}
		fclose(f);
		if ( c != '\n' )
		    putchar('\n');
	    }
	} 
	
	if ( ! found )  {
	    if ( f = fopen("[:plan", "rt") )  {
		printf("Plan:\n");
		while ( (c = getc(f)) != EOF )  {
		    putchar((char) c);
		}
		fclose(f);
		if ( c != '\n' )
		    putchar('\n');
	    }
	}
    }

#pragma option -w.pia
    
    /*&*/
    return 0;
}


/*
 *  list all attached servers
 *  returns 0 on success, 1 on failure of some sort
 */

int listAttachedServers()
{
    char name[49];
    WORD type;
    FILE_SERV_INFO serverInfo;
    SERVER_MISC_INFO miscInfo;
    BYTE date[7];
    
    int i;

    printf("File-Server Name          Ver  Users/Max  Date/Time    User \n");
/*	    FILE-SERVER               3.11   35/250  Jul 13 14:45  GUEST */

    /* loop though connection IDs, print out info for those which are in use */
    for ( i = 1; i <= 8; i++ )  {
	if ( IsConnectionIDInUse(i) )  {

	    SetPreferredConnectionID(i);
	    
	    GetFileServerName(i, name);
	    printf("%-25.25s ", name);
	    
	    if ( GetServerInformation(sizeof(serverInfo), &serverInfo) == 0 )  {
		printf("%1d.%02d ", (int) serverInfo.netwareVersion,
				    (int) serverInfo.netwareSubVersion);
		printf(" %3d/%-3d  ", (int) serverInfo.connectionsInUse,
				    (int) serverInfo.maxConnectionsSupported);
	    } else {
		printf("               ");
	    }

	    GetFileServerDateAndTime(date);
	    printf("%s %02d %2d:%02d ", itomonth(date[1]),
	    	  (int) date[2], (int) date[3], (int) date[4]);

	    if ( GetConnectionInformation(GetConnectionNumber(), name,
				&type, (long *) 0, (BYTE *) 0) == 0 )  {
		printf("%c%-17s",
			    isSupervisorEquivalant(name, type) ? '*' : ' ',
			    name);
	    }
	    	  
	    printf("\n");
	}
    } printf("\n");

    return 0;
}


/*
 *  display a list of the users on a given server, one user per line
 *  returns 0 if successful, -1 if not connected to server
 */

int fingerServer(const char *serverName, Boolean printHeader)
{
    WORD connectionID;
    char serverName2[49];
    
    long objectID;
    char objectName[OBJECT_NAME_LENGTH];
    char objectValue[OBJECT_VALUE_LENGTH];
    WORD objectType;
    BYTE loginTime[7];
    int  loginYear;
    struct date curDate;
    long daysSince;
    char day[4];
    char *department;
    FILE_SERV_INFO serverInfo;

    int i;
    BYTE dummyb;


    /*
     *  get the current date
     */
    getdate(&curDate);


    /*
     *  send our requests to the requested server
     */

    strncpy(serverName2, serverName, 48);
    if ( GetConnectionID(serverName2, &connectionID) )
	return -1;			/* not connected to that server */

    SetPreferredConnectionID(connectionID);
    
    
    /*
     *  print header line
     */
     
    if ( printHeader )  {
	strupr(serverName2);		/* to look better in header */
	printf("%-23.23s Name            Conn    When      Address\n",
								serverName2);
    }

    /*
     *  now go though each connection, getting the user info
     */
     
    (void) GetServerInformation(sizeof(serverInfo), &serverInfo);
    for ( i = 1; i <= serverInfo.maxConnectionsSupported; i++ )  {
	(void) GetConnectionInformation((WORD) i, objectName, &objectType,
				    &objectID, loginTime);
	if ( objectID )  {
	
	    /* get info on the object that has the connection */
	    objectValue[0] = '\0'; /* Novell bug: name unchanged if not set in bindery */
	    ReadPropertyValue(objectName, objectType, "IDENTIFICATION",
			    (WORD) 1, (BYTE *) objectValue, &dummyb, &dummyb);
			    
	    /* print login name (followed by asterisk if su'd) and full name */
	    strlwr(objectName);
	    if ( isSupervisorEquivalant(objectName, objectType) )
		strcat(objectName, "*");
	    printf("%-13.13s %-25.25s ", objectName, objectValue);
	    
	    /* print a '*' before con. no. if messages are disabled (castoff) */
	    printf("%c", messagesDisabled(i) ? '*' : ' ');
	    
	    /* print connection number */
	    printf("%-3d ", i);
	    
	    /* figure out how many days before now the object logged in */
	    if ( loginTime[0] < 80 )
		loginYear = 2000 + loginTime[0];
	    else
		loginYear = 1900 + loginTime[0];
	    daysSince  = 365 * (curDate.da_year - loginYear);
	    daysSince +=  31 * (curDate.da_mon - loginTime[1]);
	    daysSince +=       (curDate.da_day - loginTime[2]);

	    /* and print when it logged in */
	    if ( daysSince > 5 )  {	/* > 5 days, print month/day */
		switch ( loginTime[1] )  {	/* turn month to ASCII */
		  case 1: strcpy(day, "Jan"); break;
		  case 2: strcpy(day, "Feb"); break;
		  case 3: strcpy(day, "Mar"); break;
		  case 4: strcpy(day, "Apr"); break;
		  case 5: strcpy(day, "May"); break;
		  case 6: strcpy(day, "Jun"); break;
		  case 7: strcpy(day, "Jul"); break;
		  case 8: strcpy(day, "Aug"); break;
		  case 9: strcpy(day, "Sep"); break;
		  case 10: strcpy(day, "Oct"); break;
		  case 11: strcpy(day, "Nov"); break;
		  case 12: strcpy(day, "Dec"); break;
		}
		printf("  %s %2.2d ", day, (int) loginTime[2]);
	    } else {			/* < 7 days, print weekday/time */
		/* convert day to ascii */
		switch ( loginTime[6] )  {	/* turn day to ASCII */
		  case 0: strcpy(day, "Sun"); break;
		  case 1: strcpy(day, "Mon"); break;
		  case 2: strcpy(day, "Tue"); break;
		  case 3: strcpy(day, "Wed"); break;
		  case 4: strcpy(day, "Thu"); break;
		  case 5: strcpy(day, "Fri"); break;
		  case 6: strcpy(day, "Sat"); break;
		}
		printf("%s %2.2d:%2.2d", day, (int) loginTime[3],
							(int) loginTime[4]);
	    }

	    /* print address */
	    printf("  %s\n", getAddress(i));
	}
    }

    return 0;
}


/*
 *  fingerUsers() -- finger individual users
 *		     iniConn is the initial connection to start on
 *		     if printPlan is false, the .plan file won't be printed
 */

void fingerUsers(WORD iniConn, struct arg *arglist, Boolean printPlan)
{
    struct arg *a, *a2;		/* pointer in our arg list */
    long objectID[MAXNAMES];	/* list of object IDs to finger */
    int objectIdx = 0;		/* index into objectID */
				/* (points just past last object in array) */
    WORD connectionID;
    char objectName[OBJECT_NAME_LENGTH];
    char objectValue[OBJECT_VALUE_LENGTH];
    
    int i;
    long lastObjID;


    SetPreferredConnectionID(connectionID = iniConn);
    a = arglist;
    while ( a != 0 )  {

	if ( a->type == Server )  {

	    /*
	     *  finger current list of users, if any
	     */

	    if ( objectIdx > 0 )  {
		for ( i=0; i < objectIdx; i++ )
		    printFingerInfo(connectionID, objectID[i], printPlan);
		objectIdx = 0;
	    }
	    
	    /*
	     *  set new preferred server
	     */

	    if ( GetConnectionID(a->name, &connectionID) )  {
		warn(1, "finger: not connected to server %s", a->name);
		/* skip past all users on that server */
		if ( ! a->next )
		    return;
		for ( a=a->next; (a->type == User); a=a->next )
		    if ( ! a->next )
			return;
		continue;
	    } else {
		SetPreferredConnectionID(connectionID);
	    }
	    
	} else {

	    /*
	     *  look though all objects on this server for the user names
	     *  we have
	     */

	    lastObjID = -1;
	    while ( True )  {
		i = ScanBinderyObject("*", OT_USER, &lastObjID, objectName,
								    0, 0, 0, 0);
						    
		if ( i == SUCCESSFUL )  {	/* check object for match */
		    (void) ReadPropertyValue(objectName, OT_USER, "IDENTIFICATION",
					(WORD) 1, (BYTE *) objectValue, 0, 0);
		    /* loop though all user args for this server */
		    for ( a2 = a; a2 && (a2->type == User); a2 = a2->next )  {
			if ( containsWord(objectName, a2->name) ||
			     containsWord(objectValue, a2->name) )
			    objectID[objectIdx++] = lastObjID;
			if ( objectIdx >= MAXNAMES )  {
			    warn(1, "finger: name table overflow");
			    return;
			}
		    }
		}
		else if ( i == NO_SUCH_OBJECT )  {	/* last object, exit */
		    break;
		} else {			/* bindery error */
		    warn(1, "finger: error reading bindery: cod 0x%02x", i);
		}

	    }
	    /* since we just went though all the names up to the next
	       server, skip past them now */
	    while ( a->next && (a->next->type == User) )
		a = a->next;
	}
    
	a = a->next;
    }

    /*
     *  and print out the final list of users
     */
    if ( objectIdx > 0 )  {
	for ( i=0; i < objectIdx; i++ )
	    printFingerInfo(connectionID, objectID[i], printPlan);
	objectIdx = 0;
    }
	    
}


/************************************************************************/
/*									*/
/*  Main program							*/
/*									*/
/************************************************************************/

main(int argc, char *argv[])
{
    int errflag = 0;
    struct arg *arglist, *a;
    Boolean usersInArgs;
    Boolean printHeader = True;
    Boolean printPlan = True;
    
    WORD oldPrefID, connectionID;
    char serverName[49];

    int i;
    char *s;


    /*
     *  Save preferred connection ID
     */
     
    oldPrefID = GetPreferredConnectionID();
    

    /*
     *  Set preferred server
     */

    if ( getenv("S_SERVER") &&
			! GetConnectionID(getenv("S_SERVER"), &connectionID) )
	SetPreferredConnectionID(connectionID);
    else
	connectionID = 0;


    /*
     *  Parse options
     */

    while ( (i = getopt(argc, argv, "afp")) != -1 )
	switch ( i )  {
	  case 'a' :	/* list attached servers */
	    return listAttachedServers();
	    /* not reached */
	  case 'f' :	/* don't print header line */
	    printHeader = False;
	    break;
	  case 'p' :	/* don't print .plan file */
	    printPlan = False;
	    break;  
	  case '?' :
	    errflag++;
	}

    if ( errflag )
	error(2, "Usage: finger [server/] [user ...] ...\n"
		 "       finger [server/] [user ...] ...\n"
		 "       finger -a\n");
		 
		 
    /*
     *  Build argument list
     */

    if ( optind >= argc )  {
    
	arglist = 0;
	
    } else {

	/* allocate a new argument on our list */
	arglist = (struct arg *) malloc(sizeof(struct arg));
	if ( ! arglist )
	    error(2, "finger: out of memory");
	arglist->next = 0;

	a = arglist; i = optind; goto start_loop;
	
	continue_loop:
	    /* allocate a new argument on our list */
	    a->next = (struct arg *) malloc(sizeof(struct arg));
	    if ( ! a->next )
		error(2, "finger: out of memory");
	    a = a->next; a->next = 0;
	    
	start_loop:
	    /* check for a server */
	    for ( s=argv[i]; *s != '\0'; s++ )	/* convert \ to / */
		if ( *s == '\\' )
		    *s = '/';
	    if ( strchr(argv[i], '/') )  {		/* yep, server name */
		a->type = Server;
		if ( argv[i][strlen(argv[i])-1] == '/' )  {
		    argv[i][strlen(argv[i])-1] = '\0';	/* remove terminating / */
		    a->name = argv[i];
		} else {
		    a->name = argv[i];	/* store server name */
		    /* we have to allocate another struct arg for the name part */
		    a->next = (struct arg *) malloc(sizeof(struct arg));
		    if ( ! a->next )
			error(2, "finger: out of memory");
		    a->next->next = 0;
		    a->next->type = User;
		    a->next->name = (char *) (strchr(argv[i], '/') + 1);
		    /* and split the server name and user name into two strings */
		    *(strchr(argv[i], '/')) = '\0';
		    a = a->next;
		}
	    } else {				/* else it's just a user name */
		a->type = User;
		a->name = argv[i];
	    }

	    /* check loop conditions and loop again if necessary */

	    i++;
	    if ( i < argc ) 
	goto continue_loop;
    }


    /*
     *  Load up the init file
     */

    readInitFile(argv[0]);
    
     
    /*
     *  If there are no arguments, just finger the preferred server.
     */

    if ( ! arglist )  {
	if ( ! connectionID )
	    connectionID = GetPrimaryConnectionID();
	if ( ! connectionID )
	    connectionID = GetDefaultConnectionID();
	GetFileServerName(connectionID, serverName);
	if ( serverName[0] != '\0' )  {
	    fingerServer(serverName, printHeader);
	    return 0;
	} else {
	    error(1, "finger: Can't find preferred file server.");
	}
    }


    /*
     *  Check for users in list. If there are none, list all users logged
     *  in to the specified servers. If there are users given, finger the
     *  users in long form instead.
     */

    usersInArgs = False;
    for ( a=arglist; a; a = a->next )  {
	if ( a->type == User )  {
	    usersInArgs = True;
	    break;
	}
    }

    if ( ! usersInArgs )  {
	/*
	 *  go though all servers and print the list of users on each
	 */
	for ( a=arglist; a; a = a->next )  {
	    if ( fingerServer(a->name, printHeader) )
		fprintf(stderr, "finger: Not attached to server %s\n\n", a->name);
	    else
		printf("\n");
	}
    } else {
	fingerUsers(connectionID, arglist, printPlan);
    }


    /*
     *  Restore saved prefered connection ID and leave
     */
     
    SetPreferredConnectionID(oldPrefID);
    return 0;
}

[ RETURN TO DIRECTORY ]