static char *sccsid
= "@(#)finger.c 4.1 (Berkeley) %G%";
/* This is a finger program. It prints out useful information about users
* by digging it up from various system files. It is not very portable
* because the most useful parts of the information (the full user name,
* office, and phone numbers) are all stored in the VAX-unused gecos field
* of /etc/passwd, which, unfortunately, other UNIXes use for other things.
* There are three output formats, all of which give login name, teletype
* line number, and login time. The short output format is reminiscent
* of finger on ITS, and gives one line of information per user containing
* in addition to the minimum basic requirements (MBR), the full name of
* the user, his idle time and office location and phone number. The
* quick style output is UNIX who-like, giving only name, teletype and
* login time. Finally, the long style output give the same information
* as the short (in more legible format), the home directory and shell
* of the user, and, if it exits, a copy of the file .plan in the users
* home directory. Finger may be called with or without a list of people
* to finger -- if no list is given, all the people currently logged in
* The program is validly called by one of the following:
* finger {short form list of users}
* finger -l {long form list of users}
* finger -b {briefer long form list of users}
* finger -q {quick list of users}
* finger -i {quick list of users with idle times}
* finger namelist {long format list of specified users}
* finger -s namelist {short format list of specified users}
* finger -w namelist {narrow short format list of specified users}
* where 'namelist' is a list of users login names.
* The other options can all be given after one '-', or each can have its
* own '-'. The -f option disables the printing of headers for short and
* quick outputs. The -b option briefens long format outputs. The -p
* option turns off plans for long format outputs.
struct utmp utmp
; /* for sizeof */
#define NMAX sizeof(utmp.ut_name)
#define LMAX sizeof(utmp.ut_line)
#define ASTERISK '*' /* ignore this in real name */
#define BLANK ' ' /* blank character (i.e. space) */
#define CAPITALIZE 0137& /* capitalize character macro */
#define COMMA ',' /* separator in pw_gecos field */
#define COMMAND '-' /* command line flag char */
#define CORY 'C' /* cory hall office */
#define EVANS 'E' /* evans hall office */
#define LINEBREAK 012 /* line feed */
#define NULLSTR "" /* the null string, opposed to NULL */
#define SAMENAME '&' /* repeat login name in real name */
#define TALKABLE 0222 /* tty is writeable if 222 mode */
struct person
{ /* one for each person fingered */
char name
[NMAX
+1]; /* login name */
char tty
[LMAX
+1]; /* NULL terminated tty line */
long loginat
; /* time of login (possibly last) */
long idletime
; /* how long idle (if logged in) */
short int loggedin
; /* flag for being logged in */
short int writeable
; /* flag for tty being writeable */
char *realname
; /* pointer to full name */
char *office
; /* pointer to office name */
char *officephone
; /* pointer to office phone no. */
char *homephone
; /* pointer to home phone no. */
char *random
; /* for any random stuff in pw_gecos */
struct passwd
*pwd
; /* structure of /etc/passwd stuff */
struct person
*link
; /* link to next person */
struct passwd
*NILPWD
= 0;
struct person
*NILPERS
= 0;
int persize
= sizeof( struct person
);
int pwdsize
= sizeof( struct passwd
);
char LASTLOG
[] = "/usr/adm/lastlog"; /* last login info */
char USERLOG
[] = "/etc/utmp"; /* who is logged in */
char outbuf
[BUFSIZ
]; /* output buffer */
int unbrief
= 1; /* -b option default */
int header
= 1; /* -f option default */
int hack
= 1; /* -h option default */
int idle
= 0; /* -i option default */
int large
= 0; /* -l option default */
int match
= 1; /* -m option default */
int plan
= 1; /* -p option default */
int unquick
= 1; /* -q option default */
int small
= 0; /* -s option default */
int wide
= 1; /* -w option default */
long tloc
; /* current time */
FILE *fp
, *fopen(); /* for plans */
struct passwd
*getpwent(); /* read /etc/passwd */
struct person
*person1
, *p
, *pend
; /* people */
struct passwd
*pw
; /* temporary */
struct utmp user
; /* ditto */
char *PLAN
= "/.plan"; /* what plan file is */
char *PROJ
= "/.project"; /* what project file */
int PLANLEN
= strlen( PLAN
);
int PROJLEN
= strlen( PROJ
);
setbuf( stdout
, outbuf
); /* buffer output */
/* parse command line for (optional) arguments */
if( strcmp( *argv
, "sh" ) ) {
while( i
++ < argc
&& (*++argv
)[0] == COMMAND
) {
for( s
= argv
[0] + 1; *s
!= NULL
; s
++ ) {
fprintf( stderr
, "finger: Usage -- 'finger [-bfhilmpqsw] [login1 [login2 ...] ]'\n" );
/* i > argc means no login names given so get them by reading USERLOG */
if( (i
> argc
) || fngrlogin
) {
if( ( uf
= open(USERLOG
, 0) ) >= 0 ) {
while( user
.ut_name
[0] == NULL
) {
if( read( uf
, (char *) &user
, usize
) != usize
) {
printf( "\nNo one logged on\n" );
person1
= (struct person
*) malloc( persize
);
for( j
= 0; j
< NMAX
; j
++ ) {
person1
->tty
[j
] = user
.ut_line
[j
];
person1
->name
[j
] = user
.ut_name
[j
];
person1
->name
[NMAX
] = NULL
;
person1
->tty
[NMAX
] = NULL
;
person1
->loginat
= user
.ut_time
;
while( read( uf
, (char *) &user
, usize
) == usize
) {
if( user
.ut_name
[0] == NULL
) continue;
p
->link
= (struct person
*) malloc( persize
);
for( j
= 0; j
< NMAX
; j
++ ) {
p
->tty
[j
] = user
.ut_line
[j
];
p
->name
[j
] = user
.ut_name
[j
];
p
->loginat
= user
.ut_time
;
fprintf( stderr
, "finger: error opening %s\n", USERLOG
);
/* if we are doing it, read /etc/passwd for the useful info */
while( ( (pw
= getpwent()) != NILPWD
) && ( i
> 0 ) ) {
if( strcmp( p
->name
, pw
->pw_name
) == 0 ) {
p
->pwd
= (struct passwd
*) malloc( pwdsize
);
/* get names from command line and check to see if they're logged in */
unshort
= ( small
== 1 ? 0 : 1 );
person1
= (struct person
*) malloc( persize
);
strcpy( person1
->name
, (argv
++)[ 0 ] );
p
->link
= (struct person
*) malloc( persize
);
strcpy( p
->name
, (argv
++)[ 0 ] );
/* if we are doing it, read /etc/passwd for the useful info */
while( ( pw
= getpwent() ) != NILPWD
) {
if( strcmp( p
->name
, pw
->pw_name
) == 0 ||
matchcmp( pw
->pw_gecos
, pw
->pw_name
, p
->name
) ) {
p
->pwd
= (struct passwd
*) malloc( pwdsize
);
else { /* handle multiple logins -- append new
"duplicate" entry to end of list */
pend
->link
= (struct person
*) malloc(persize
);
strcpy( pend
->name
, p
->name
);
pend
->pwd
= (struct passwd
*) malloc(pwdsize
);
pwdcopy( pend
->pwd
, pw
);
} while( ++i
< orgnumnames
);
/* Now get login information */
if( ( uf
= open(USERLOG
, 0) ) >= 0 ) {
while( read( uf
, (char *) &user
, usize
) == usize
) {
if( user
.ut_name
[0] == NULL
) continue;
i
= ( strcmp( p
->name
, user
.ut_name
) ? 0 : NMAX
);
( pw
->pw_name
[i
] == user
.ut_name
[i
]) ) {
if( pw
->pw_name
[i
] == NULL
) {
pend
->link
= (struct person
*) malloc(persize
);
strcpy( pend
->name
, p
->name
);
for( j
= 0; j
< NMAX
; j
++ ) {
pend
->tty
[j
] = user
.ut_line
[j
];
pend
->tty
[ NMAX
] = NULL
;
pend
->loginat
= user
.ut_time
;
pend
->pwd
= (struct passwd
*) malloc(pwdsize
);
pwdcopy( pend
->pwd
, pw
);
for( j
= 0; j
< NMAX
; j
++ ) {
p
->tty
[j
] = user
.ut_line
[j
];
p
->loginat
= user
.ut_time
;
fprintf( stderr
, "finger: error opening %s\n", USERLOG
);
/* print out what we got */
"Login Name TTY Idle When Office\n" );
"Login TTY Idle When Office\n" );
printf( "Login TTY When" );
s
= malloc(strlen((p
->pwd
)->pw_dir
) + PROJLEN
+ 1 );
strcpy( s
, (p
->pwd
)->pw_dir
);
if( ( fp
= fopen( s
, "r") ) != NULL
) {
while( ( c
= getc(fp
) ) != EOF
) {
s
= malloc( strlen( (p
->pwd
)->pw_dir
) + PLANLEN
+ 1 );
strcpy( s
, (p
->pwd
)->pw_dir
);
if( ( fp
= fopen( s
, "r") ) == NULL
) {
while( ( c
= getc(fp
) ) != EOF
) {
if( p
->link
!= NILPERS
) {
/* given a pointer to a pwd (pfrom) copy it to another one, allocating
* space for all the stuff in it. Note: Only the useful (what the
* program currently uses) things are copied.
pwdcopy( pto
, pfrom
) /* copy relevant fields only */
struct passwd
*pto
, *pfrom
;
pto
->pw_name
= malloc( strlen( pfrom
->pw_name
) + 1 );
strcpy( pto
->pw_name
, pfrom
->pw_name
);
pto
->pw_uid
= pfrom
->pw_uid
;
pto
->pw_gecos
= malloc( strlen( pfrom
->pw_gecos
) + 1 );
strcpy( pto
->pw_gecos
, pfrom
->pw_gecos
);
pto
->pw_dir
= malloc( strlen( pfrom
->pw_dir
) + 1 );
strcpy( pto
->pw_dir
, pfrom
->pw_dir
);
pto
->pw_shell
= malloc( strlen( pfrom
->pw_shell
) + 1 );
strcpy( pto
->pw_shell
, pfrom
->pw_shell
);
/* print out information on quick format giving just name, tty, login time
* and idle time if idle is set.
printf( "%-*.*s", NMAX
, NMAX
, pers
->name
);
printf( " %-*.*s %-16.16s", LMAX
, LMAX
,
pers
->tty
, ctime( &pers
->loginat
) );
printf( "*%-*.*s %-16.16s", LMAX
, LMAX
,
pers
->tty
, ctime( &pers
->loginat
) );
idleprinted
= ltimeprint( &pers
->idletime
);
printf( " %-*.*s %-16.16s", LMAX
, LMAX
,
pers
->tty
, ctime( &pers
->loginat
) );
printf( " Not Logged In" );
/* print out information in short format, giving login name, full name,
* tty, idle time, login time, office location and phone.
struct passwd
*pwdt
= pers
->pwd
;
int i
, len
, offset
, dialup
;
printf( "%-*.*s", NMAX
, NMAX
, pers
->name
);
printf( "%-*.*s", NMAX
, NMAX
, pwdt
->pw_name
);
if( strlen( pers
->realname
) > 0 ) {
printf( " %-20.20s", pers
->realname
);
if( strlen( pers
->tty
) > 0 ) {
strcpy( buf
, pers
->tty
);
if( (buf
[0] == 't') && (buf
[1] == 't') && (buf
[2] == 'y') ) {
for( i
= 0; i
< 2; i
++ ) {
buf
[i
] = buf
[i
+ offset
];
if( (buf
[0] == 'd') && pers
->loggedin
) {
printf( "%-2.2s ", buf
);
strcpy( buf
, ctime( &pers
->loginat
) );
stimeprint( &pers
->idletime
);
for( i
= 4; i
< 19; i
++ ) {
buf
[i
] = buf
[i
+ offset
];
printf( " %-9.9s ", buf
);
for( i
= 0; i
<22; i
++ ) {
buf
[i
] = buf
[i
+ offset
];
printf( "<%-12.12s>", buf
);
len
= strlen( pers
->homephone
);
if( dialup
&& (len
> 0) ) {
for( i
= 1; i
<= 21 - len
; i
++ ) {
printf( "%s", pers
->homephone
);
if( strlen( pers
->office
) > 0 ) {
printf( " %-11.11s", pers
->office
);
if( strlen( pers
->officephone
) > 0 ) {
printf( " %8.8s", pers
->officephone
);
printf( " %8.8s", pers
->homephone
);
if( strlen( pers
->officephone
) > 0 ) {
printf( " %8.8s", pers
->officephone
);
printf( " %8.8s", pers
->homephone
);
printf( " %12.12s", pers
->homephone
);
/* print out a person in long format giving all possible information.
* directory and shell are inhibited if unbrief is clear.
struct passwd
*pwdt
= pers
->pwd
;
printf( "Login name: %-10s", pers
->name
);
printf( "In real life: ???\n");
printf( "Login name: %-10s", pwdt
->pw_name
);
printf( " (messages off) " );
if( strlen( pers
->realname
) > 0 ) {
printf( "In real life: %-s", pers
->realname
);
if( strlen( pers
->office
) > 0 ) {
printf( "\nOffice: %-.11s", pers
->office
);
if( strlen( pers
->officephone
) > 0 ) {
printf( ", %s", pers
->officephone
);
if( strlen( pers
->homephone
) > 0 ) {
printf( " Home phone: %s", pers
->homephone
);
if( strlen( pers
->random
) > 0 ) {
printf( " %s", pers
->random
);
if( strlen( pers
->homephone
) > 0 ) {
printf(" Home phone: %s",pers
->homephone
);
if( strlen( pers
->random
) > 0 ) {
printf( " %s", pers
->random
);
if( strlen( pers
->officephone
) > 0 ) {
printf( "\nPhone: %s", pers
->officephone
);
if( strlen( pers
->homephone
) > 0 ) {
printf( "\n, %s", pers
->homephone
);
if( strlen( pers
->random
) > 0 ) {
printf( ", %s", pers
->random
);
if( strlen( pers
->random
) > 0 ) {
printf( "\n, %s", pers
->random
);
if( strlen( pers
->homephone
) > 0 ) {
printf( "\nPhone: %s", pers
->homephone
);
if( strlen( pers
->random
) > 0 ) {
printf( ", %s", pers
->random
);
if( strlen( pers
->random
) > 0 ) {
printf( "\n%s", pers
->random
);
printf( "Directory: %-25s", pwdt
->pw_dir
);
if( strlen( pwdt
->pw_shell
) > 0 ) {
printf( " Shell: %-s", pwdt
->pw_shell
);
register char *ep
= ctime( &pers
->loginat
);
printf("\nOn since %15.15s on %-*.*s ", &ep
[4], LMAX
, LMAX
, pers
->tty
);
idleprinted
= ltimeprint( &pers
->idletime
);
register char *ep
= ctime( &pers
->loginat
);
printf("\nLast login %16.16s on %.*s", ep
, LMAX
, pers
->tty
);
* very hacky section of code to format phone numbers. filled with
* magic constants like 4, 7 and 10.
for( i
= 0; i
<= 3; i
++ ) {
return( strsave( &fonebuf
[0] ) );
for( i
= 0; i
<= 2; i
++ ) {
for( i
= 0; i
<= 3; i
++ ) {
return( strsave( &fonebuf
[0] ) );
for( i
= 0; i
<= 2; i
++ ) {
for( i
= 0; i
<= 2; i
++ ) {
for( i
= 0; i
<= 3; i
++ ) {
return( strsave( &fonebuf
[0] ) );
fprintf( stderr
, "finger: error in phone numbering\n" );
/* decode the information in the gecos field of /etc/passwd
* another hacky section of code, but given the format the stuff is in...
struct passwd
*pwdt
= pers
->pwd
;
char buffer
[ 40 ], *bp
, *gp
, *lp
;
pers
->realname
= NULLSTR
;
pers
->officephone
= NULLSTR
;
pers
->homephone
= NULLSTR
;
while( (*gp
!= NULL
) && (*gp
!= COMMA
) ) { /* name */
*bp
++ = CAPITALIZE(*lp
++);
pers
->realname
= malloc( strlen( &buffer
[0] ) + 1 );
strcpy( pers
->realname
, &buffer
[0] );
if( *gp
++ == COMMA
) { /* office, supposedly */
while( (*gp
!= NULL
) && (*gp
!= COMMA
) ) {
alldigits
= alldigits
&& ('0' <= *bp
) && (*bp
<= '9');
len
= strlen( &buffer
[0] );
if( buffer
[ len
- 1 ] == CORY
) {
strcpy( &buffer
[ len
- 1 ], " Cory" );
pers
->office
= malloc( len
+ 5 );
strcpy( pers
->office
, &buffer
[0] );
if( buffer
[ len
- 1 ] == EVANS
) {
strcpy( &buffer
[ len
- 1 ], " Evans" );
pers
->office
= malloc( len
+ 6 );
strcpy( pers
->office
, &buffer
[0] );
if( buffer
[ len
- 1 ] == 'L' ) {
strcpy( &buffer
[ len
- 1 ], " LBL" );
pers
->office
= malloc( len
+ 4 );
strcpy( pers
->office
, &buffer
[0] );
pers
->officephone
= phone(&buffer
[0], len
);
if( (len
== 7) || (len
== 10) ) {
pers
->homephone
= phone(&buffer
[0],len
);
pers
->random
= malloc( len
+ 1 );
strcpy( pers
->random
, &buffer
[0] );
if( *gp
++ == COMMA
) { /* office phone, theoretically */
while( (*gp
!= NULL
) && (*gp
!= COMMA
) ) {
alldigits
= alldigits
&& ('0' <= *bp
) && (*bp
<= '9');
len
= strlen( &buffer
[0] );
if( (len
== 7) || (len
== 10) ) {
pers
->homephone
= phone( &buffer
[0], len
);
pers
->random
= malloc( len
+ 1 );
strcpy( pers
->random
, &buffer
[0] );
pers
->officephone
= phone( &buffer
[0], len
);
pers
->random
= malloc( len
+ 1 );
strcpy( pers
->random
, &buffer
[0] );
if( *gp
++ == COMMA
) { /* home phone?? */
while( (*gp
!= NULL
) && (*gp
!= COMMA
) ) {
alldigits
= alldigits
&& ('0' <= *bp
) &&
len
= strlen( &buffer
[0] );
if( alldigits
&& ( (len
== 7) || (len
== 10) ) ) {
if( *pers
->homephone
!= NULL
) {
pers
->officephone
= pers
->homephone
;
pers
->homephone
= phone( &buffer
[0], len
);
pers
->random
= malloc( strlen( &buffer
[0] ) + 1 );
strcpy( pers
->random
, &buffer
[0] );
if( pers
->loggedin
== 0 ) {
/* find the last log in of a user by checking the LASTLOG file.
* the entry is indexed by the uid, so this can only be done if
* the uid is known (which it isn't in quick mode)
if( ( lf
= open(LASTLOG
, 0) ) >= 0 ) {
fprintf( stderr
, "finger: lastlog open error\n" );
struct passwd
*pwdt
= pers
->pwd
;
lseek( lf
, pwdt
->pw_uid
*llsize
, 0 );
if( read( lf
, (char *) &ll
, llsize
) == llsize
) {
for( i
= 0; i
< LMAX
; i
++ ) {
pers
->tty
[ i
] = ll
.ll_line
[ i
];
pers
->tty
[ LMAX
] = NULL
;
pers
->loginat
= ll
.ll_time
;
fprintf( stderr
, "finger: lastlog read error\n" );
/* find the idle time of a user by doing a stat on /dev/histty,
* where histty has been gotten from USERLOG, supposedly.
struct passwd
*pwdt
= pers
->pwd
;
int TTYLEN
= strlen( TTY
);
strcpy( &buffer
[0], TTY
);
buffer
[ TTYLEN
+ i
] = pers
->tty
[ i
];
if( stat( &buffer
[0], &ttystatus
) >= 0 ) {
if( tloc
< ttystatus
.st_atime
) {
pers
->idletime
= tloc
- ttystatus
.st_atime
;
if( (ttystatus
.st_mode
& TALKABLE
) == TALKABLE
) {
fprintf( stderr
, "finger: error STATing %s\n", &buffer
[0] );
/* print idle time in short format; this program always prints 4 characters;
* if the idle time is zero, it prints 4 blanks.
if( delta
->tm_yday
== 0 ) {
if( delta
->tm_hour
== 0 ) {
if( delta
->tm_min
>= 10 ) {
printf( " %2.2d ", delta
->tm_min
);
if( delta
->tm_min
== 0 ) {
printf( " %1.1d ", delta
->tm_min
);
if( delta
->tm_hour
>= 10 ) {
printf( "%3.3d:", delta
->tm_hour
);
printf( "%1.1d:%02.2d", delta
->tm_hour
, delta
->tm_min
);
printf( "%3dd", delta
->tm_yday
);
/* print idle time in long format with care being taken not to pluralize
* 1 minutes or 1 hours or 1 days.
if( delta
->tm_yday
== 0 ) {
if( delta
->tm_hour
== 0 ) {
if( delta
->tm_min
>= 10 ) {
printf( "%2d minutes", delta
->tm_min
);
if( delta
->tm_min
== 0 ) {
if( delta
->tm_sec
> 10 ) {
printf( "%2d seconds", delta
->tm_sec
);
if( delta
->tm_min
== 1 ) {
if( delta
->tm_sec
== 1 ) {
printf( "%1d minute %1d second",
delta
->tm_min
, delta
->tm_sec
);
printf( "%1d minute %d seconds",
delta
->tm_min
, delta
->tm_sec
);
if( delta
->tm_sec
== 1 ) {
printf( "%1d minutes %1d second",
delta
->tm_min
, delta
->tm_sec
);
printf( "%1d minutes %d seconds",
delta
->tm_min
, delta
->tm_sec
);
if( delta
->tm_hour
>= 10 ) {
printf( "%2d hours", delta
->tm_hour
);
if( delta
->tm_hour
== 1 ) {
if( delta
->tm_min
== 1 ) {
printf( "%1d hour %1d minute",
delta
->tm_hour
, delta
->tm_min
);
printf( "%1d hour %2d minutes",
delta
->tm_hour
, delta
->tm_min
);
if( delta
->tm_min
== 1 ) {
printf( "%1d hours %1d minute",
delta
->tm_hour
, delta
->tm_min
);
printf( "%1d hours %2d minutes",
delta
->tm_hour
, delta
->tm_min
);
if( delta
->tm_yday
>= 10 ) {
printf( "%2d days", delta
->tm_yday
);
if( delta
->tm_yday
== 1 ) {
if( delta
->tm_hour
== 1 ) {
printf( "%1d day %1d hour",
delta
->tm_yday
, delta
->tm_hour
);
printf( "%1d day %2d hours",
delta
->tm_yday
, delta
->tm_hour
);
if( delta
->tm_hour
== 1 ) {
printf( "%1d days %1d hour",
delta
->tm_yday
, delta
->tm_hour
);
printf( "%1d days %2d hours",
delta
->tm_yday
, delta
->tm_hour
);
matchcmp( gname
, login
, given
)
if( namecmp( login
, given
) ) {
if( *gname
== ASTERISK
) {
unfound
= (*gname
!= COMMA
) && (*gname
!= NULL
);
if( (*gname
== COMMA
) || (*gname
== NULL
) ) {
if( namecmp( buffer
, given
) ) {
if( namecmp( buffer
, given
) ) {
if( (('A' <= c1
) && (c1
<= 'Z')) || (('a' <= c1
) && (c1
<= 'z')) ) {
if( (('A' <= c2
) && (c2
<= 'Z')) || (('a' <= c2
) && (c2
<= 'z')) ) {
if( (('A'<=c1
) && (c1
<='Z')) || (('a'<=c1
) && (c1
<='z')) ) {
if( (('A'<=c2
) && (c2
<='Z')) || (('a'<=c2
) && (c2
<='z')) ) {
while( ('0' <= *name2
) && (*name2
<= '9') ) {
while( ('0' <= *name1
) && (*name1
<= '9') ) {
p
= malloc( strlen( s
) + 1 );