/* * 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 #include #include #include #include #include #include #include #include #include /* * 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 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; }