2 * Copyright (c) 1980 Regents of the University of California.
3 * All rights reserved. The Berkeley software License Agreement
4 * specifies the terms and conditions for redistribution.
8 static char sccsid
[] = "@(#)finger.c 1.1 87/12/21 SMI"; /* from 5.8 3/13/86 */
12 * This is a finger program. It prints out useful information about users
13 * by digging it up from various system files.
15 * There are three output formats, all of which give login name, teletype
16 * line number, and login time. The short output format is reminiscent
17 * of finger on ITS, and gives one line of information per user containing
18 * in addition to the minimum basic requirements (MBR), the full name of
19 * the user, his idle time and location. The
20 * quick style output is UNIX who-like, giving only name, teletype and
21 * login time. Finally, the long style output give the same information
22 * as the short (in more legible format), the home directory and shell
23 * of the user, and, if it exits, a copy of the file .plan in the users
24 * home directory. Finger may be called with or without a list of people
25 * to finger -- if no list is given, all the people currently logged in
28 * The program is validly called by one of the following:
30 * finger {short form list of users}
31 * finger -l {long form list of users}
32 * finger -b {briefer long form list of users}
33 * finger -q {quick list of users}
34 * finger -i {quick list of users with idle times}
35 * finger namelist {long format list of specified users}
36 * finger -s namelist {short format list of specified users}
37 * finger -w namelist {narrow short format list of specified users}
39 * where 'namelist' is a list of users login names.
40 * The other options can all be given after one '-', or each can have its
41 * own '-'. The -f option disables the printing of headers for short and
42 * quick outputs. The -b option briefens long format outputs. The -p
43 * option turns off plans for long format outputs.
46 #include <sys/types.h>
57 #include <sys/ioctl.h>
59 #include <net/gen/in.h>
60 #include <net/gen/inet.h>
61 #include <net/gen/netdb.h>
62 #include <net/gen/socket.h>
63 #include <net/gen/tcp.h>
64 #include <net/gen/tcp_hdr.h>
65 #include <net/gen/tcp_io.h>
67 #include <net/netlib.h>
69 #define NONOTHING 1 /* don't say "No plan", or "No mail" */
73 #define ASTERISK '*' /* ignore this in real name */
74 #define COMMA ',' /* separator in pw_gecos field */
75 #define COMMAND '-' /* command line flag char */
76 #define SAMENAME '&' /* repeat login name in real name */
77 #define TALKABLE 0220 /* tty is writable if this mode */
80 #define NMAX sizeof(user.ut_name)
81 #define LMAX sizeof(user.ut_line)
82 #define HMAX sizeof(user.ut_host)
84 struct person
{ /* one for each person fingered */
85 char *name
; /* name */
86 char tty
[LMAX
+1]; /* null terminated tty line */
87 char host
[HMAX
+1]; /* null terminated remote host name */
88 long loginat
; /* time of (last) login */
89 long idletime
; /* how long idle (if logged in) */
90 char *realname
; /* pointer to full name */
91 struct passwd
*pwd
; /* structure of /etc/passwd stuff */
92 char loggedin
; /* person is logged in */
93 char writable
; /* tty is writable */
94 char original
; /* this is not a duplicate entry */
95 struct person
*link
; /* link to next person */
96 char *where
; /* terminal location */
97 char hostt
[HMAX
+1]; /* login host */
100 #include <minix/paths.h>
102 char LASTLOG
[] = _PATH_LASTLOG
; /* last login info */
103 char USERLOG
[] = _PATH_UTMP
; /* who is logged in */
104 char PLAN
[] = "/.plan"; /* what plan file is */
105 char PROJ
[] = "/.project"; /* what project file */
107 int unbrief
= 1; /* -b option default */
108 int header
= 1; /* -f option default */
109 int hack
= 1; /* -h option default */
110 int idle
= 0; /* -i option default */
111 int large
= 0; /* -l option default */
112 int match
= 1; /* -m option default */
113 int plan
= 1; /* -p option default */
114 int unquick
= 1; /* -q option default */
115 int small
= 0; /* -s option default */
116 int wide
= 1; /* -w option default */
119 int lf
; /* LASTLOG file descriptor */
120 struct person
*person1
; /* list of people */
121 long tloc
; /* current time */
130 int main (int argc
, char *argv
[]);
131 static void doall(void);
132 static void donames(char **args
);
133 static void print(void);
134 static void fwopen(void);
135 static void decode(struct person
*pers
);
136 static void fwclose(void);
137 static int netfinger (char *name
);
138 static int matchcmp (char *gname
, char *login
, char *given
);
139 static void quickprint (struct person
*pers
);
140 static void shortprint (struct person
*pers
);
141 static void personprint (struct person
*pers
);
142 static int AlreadyPrinted(int uid
);
143 static int AnyMail (char *name
);
144 static struct passwd
*pwdcopy(struct passwd
*pfrom
);
145 static void findidle (struct person
*pers
);
146 static int ltimeprint (char *dt
, long *before
, char *after
);
147 static void stimeprint (long *dt
);
148 static void findwhen (struct person
*pers
);
149 static int namecmp (char *name1
, char *name2
);
153 register char **argv
;
159 /* parse command line for (optional) arguments */
160 while (*++argv
&& **argv
== COMMAND
)
161 for (s
= *argv
+ 1; *s
; s
++)
195 fprintf(stderr
, "Usage: finger [-bfhilmpqsw] [login1 [login2 ...] ]\n");
201 * *argv == 0 means no names given
214 register struct person
*p
;
215 register struct passwd
*pw
;
220 if ((uf
= open(USERLOG
, 0)) < 0) {
221 fprintf(stderr
, "finger: error opening %s\n", USERLOG
);
228 while (read(uf
, (char *)&user
, sizeof user
) == sizeof user
) {
229 if (user
.ut_name
[0] == 0)
232 p
= person1
= (struct person
*) malloc(sizeof *p
);
234 p
->link
= (struct person
*) malloc(sizeof *p
);
237 bcopy(user
.ut_name
, name
, NMAX
);
239 bcopy(user
.ut_line
, p
->tty
, LMAX
);
241 bcopy(user
.ut_host
, p
->host
, HMAX
);
243 p
->loginat
= user
.ut_time
;
247 if (unquick
&& (pw
= getpwnam(name
))) {
248 p
->pwd
= pwdcopy(pw
);
250 p
->name
= p
->pwd
->pw_name
;
252 p
->name
= strcpy(malloc(strlen(name
) + 1), name
);
260 printf("No one logged on\n");
266 static void donames(argv
)
269 register struct person
*p
;
270 register struct passwd
*pw
;
274 * get names from command line and check to see if they're
278 for (; *argv
!= 0; argv
++) {
279 if (netfinger(*argv
))
282 p
= person1
= (struct person
*) malloc(sizeof *p
);
284 p
->link
= (struct person
*) malloc(sizeof *p
);
296 * if we are doing it, read /etc/passwd for the useful info
301 for (p
= person1
; p
!= 0; p
= p
->link
)
302 if (pw
= getpwnam(p
->name
))
303 p
->pwd
= pwdcopy(pw
);
304 } else while ((pw
= getpwent()) != 0) {
305 for (p
= person1
; p
!= 0; p
= p
->link
) {
308 if (strcmp(p
->name
, pw
->pw_name
) != 0 &&
309 !matchcmp(pw
->pw_gecos
, pw
->pw_name
, p
->name
))
312 p
->pwd
= pwdcopy(pw
);
316 * handle multiple login names, insert
317 * new "duplicate" entry behind
319 new = (struct person
*)
321 new->pwd
= pwdcopy(pw
);
334 /* Now get login information */
335 if ((uf
= open(USERLOG
, 0)) < 0) {
336 fprintf(stderr
, "finger: error opening %s\n", USERLOG
);
339 while (read(uf
, (char *)&user
, sizeof user
) == sizeof user
) {
340 if (*user
.ut_name
== 0)
342 for (p
= person1
; p
!= 0; p
= p
->link
) {
343 if (p
->loggedin
== 2)
345 if (strncmp(p
->pwd
? p
->pwd
->pw_name
: p
->name
,
346 user
.ut_name
, NMAX
) != 0)
348 if (p
->loggedin
== 0) {
349 bcopy(user
.ut_line
, p
->tty
, LMAX
);
351 bcopy(user
.ut_host
, p
->host
, HMAX
);
353 p
->loginat
= user
.ut_time
;
355 } else { /* p->loggedin == 1 */
357 new = (struct person
*) malloc(sizeof *new);
359 bcopy(user
.ut_line
, new->tty
, LMAX
);
361 bcopy(user
.ut_host
, new->host
, HMAX
);
363 new->loginat
= user
.ut_time
;
377 for (p
= person1
; p
!= 0; p
= p
->link
)
386 register struct person
*p
;
391 * print out what we got
397 printf("Login Name TTY Idle When Where\n");
399 printf("Login TTY Idle When Where\n");
401 printf("Login TTY When");
407 for (p
= person1
; p
!= 0; p
= p
->link
) {
417 if (p
->pwd
!= 0 && !AlreadyPrinted(p
->pwd
->pw_uid
)) {
418 AnyMail(p
->pwd
->pw_name
);
420 s
= malloc(strlen(p
->pwd
->pw_dir
) +
422 strcpy(s
, p
->pwd
->pw_dir
);
424 if ((fp
= fopen(s
, "r")) != 0) {
426 while ((c
= getc(fp
)) != EOF
) {
429 if (isprint(c
) || isspace(c
))
440 s
= malloc(strlen(p
->pwd
->pw_dir
) +
442 strcpy(s
, p
->pwd
->pw_dir
);
444 if ((fp
= fopen(s
, "r")) == 0) {
445 if (!NONOTHING
) printf("No Plan.\n");
448 while ((c
= getc(fp
)) != EOF
)
449 if (isprint(c
) || isspace(c
))
464 * Duplicate a pwd entry.
465 * Note: Only the useful things (what the program currently uses) are copied.
467 static struct passwd
*
469 register struct passwd
*pfrom
;
471 register struct passwd
*pto
;
473 pto
= (struct passwd
*) malloc(sizeof *pto
);
474 #define savestr(s) strcpy(malloc(strlen(s) + 1), s)
475 pto
->pw_name
= savestr(pfrom
->pw_name
);
476 pto
->pw_uid
= pfrom
->pw_uid
;
477 pto
->pw_gecos
= savestr(pfrom
->pw_gecos
);
478 pto
->pw_dir
= savestr(pfrom
->pw_dir
);
479 pto
->pw_shell
= savestr(pfrom
->pw_shell
);
485 * print out information on quick format giving just name, tty, login time
486 * and idle time if idle is set.
488 static void quickprint(pers
)
489 register struct person
*pers
;
491 printf("%-*.*s ", NMAX
, NMAX
, pers
->name
);
492 if (pers
->loggedin
) {
495 printf("%c%-*s %-16.16s", pers
->writable
? ' ' : '*',
496 LMAX
, pers
->tty
, ctime(&pers
->loginat
));
497 ltimeprint(" ", &pers
->idletime
, "");
499 printf(" %-*s %-16.16s", LMAX
,
500 pers
->tty
, ctime(&pers
->loginat
));
503 printf(" Not Logged In\n");
507 * print out information in short format, giving login name, full name,
508 * tty, idle time, login time, and host.
510 static void shortprint(pers
)
511 register struct person
*pers
;
516 if (pers
->pwd
== 0) {
517 printf("%-15s ???\n", pers
->name
);
520 printf("%-*s", NMAX
, pers
->pwd
->pw_name
);
524 printf(" %-20.20s", pers
->realname
);
529 if (pers
->loggedin
&& !pers
->writable
)
534 if (pers
->tty
[0] == 't' && pers
->tty
[1] == 't' &&
535 pers
->tty
[2] == 'y') {
536 if (pers
->tty
[3] == 'd' && pers
->loggedin
)
538 printf("%-2.2s ", pers
->tty
+ 3);
540 printf("%-2.2s ", pers
->tty
);
543 p
= ctime(&pers
->loginat
);
544 if (pers
->loggedin
) {
545 stimeprint(&pers
->idletime
);
546 printf(" %3.3s %-5.5s ", p
, p
+ 11);
547 } else if (pers
->loginat
== 0)
548 printf(" < . . . . >");
549 else if (tloc
- pers
->loginat
>= 180L * 24 * 60 * 60)
550 printf(" <%-6.6s, %-4.4s>", p
+ 4, p
+ 20);
552 printf(" <%-12.12s>", p
+ 4);
554 printf(" %-20.20s", pers
->host
);
560 * print out a person in long format giving all possible information.
561 * directory and shell are inhibited if unbrief is clear.
565 register struct person
*pers
;
567 if (pers
->pwd
== 0) {
568 printf("Login name: %-10s\t\t\tIn real life: ???\n",
572 printf("Login name: %-10s", pers
->pwd
->pw_name
);
573 if (pers
->loggedin
&& !pers
->writable
)
574 printf(" (messages off) ");
578 printf("In real life: %s", pers
->realname
);
580 printf("\nDirectory: %-25s", pers
->pwd
->pw_dir
);
581 if (*pers
->pwd
->pw_shell
)
582 printf("\tShell: %-s", pers
->pwd
->pw_shell
);
584 if (pers
->loggedin
) {
585 register char *ep
= ctime(&pers
->loginat
);
587 printf("\nOn since %15.15s on %s from %s",
588 &ep
[4], pers
->tty
, pers
->host
);
589 ltimeprint("\n", &pers
->idletime
, " Idle Time");
591 printf("\nOn since %15.15s on %-*s",
592 &ep
[4], LMAX
, pers
->tty
);
593 ltimeprint("\t", &pers
->idletime
, " Idle Time");
595 } else if (pers
->loginat
== 0) {
596 if (lf
>= 0) printf("\nNever logged in.");
597 } else if (tloc
- pers
->loginat
> 180L * 24 * 60 * 60) {
598 register char *ep
= ctime(&pers
->loginat
);
599 printf("\nLast login %10.10s, %4.4s on %s",
600 ep
, ep
+20, pers
->tty
);
602 printf(" from %s", pers
->host
);
604 register char *ep
= ctime(&pers
->loginat
);
605 printf("\nLast login %16.16s on %s", ep
, pers
->tty
);
607 printf(" from %s", pers
->host
);
614 * decode the information in the gecos field of /etc/passwd
618 register struct person
*pers
;
621 register char *bp
, *gp
, *lp
;
627 gp
= pers
->pwd
->pw_gecos
;
631 while (*gp
&& *gp
!= COMMA
) /* name */
632 if (*gp
== SAMENAME
) {
633 lp
= pers
->pwd
->pw_name
;
635 *bp
++ = toupper(*lp
++);
636 while (*bp
++ = *lp
++)
643 if ((len
= bp
- buffer
) > 1)
644 pers
->realname
= strcpy(malloc(len
), buffer
);
652 * find the last log in of a user by checking the LASTLOG file.
653 * the entry is indexed by the uid, so this can only be done if
654 * the uid is known (which it isn't in quick mode)
660 if ((lf
= open(LASTLOG
, 0)) < 0) {
661 if (errno
== ENOENT
) return;
662 fprintf(stderr
, "finger: %s open error\n", LASTLOG
);
668 register struct person
*pers
;
671 #define ll_line ut_line
672 #define ll_host ut_host
673 #define ll_time ut_time
678 lseek(lf
, (long)pers
->pwd
->pw_uid
* sizeof ll
, 0);
679 if ((i
= read(lf
, (char *)&ll
, sizeof ll
)) == sizeof ll
) {
680 bcopy(ll
.ll_line
, pers
->tty
, LMAX
);
682 bcopy(ll
.ll_host
, pers
->host
, HMAX
);
683 pers
->host
[HMAX
] = 0;
684 pers
->loginat
= ll
.ll_time
;
687 fprintf(stderr
, "finger: %s read error\n",
700 static void fwclose()
707 * find the idle time of a user by doing a stat on /dev/tty??,
708 * where tty?? has been gotten from USERLOG, supposedly.
712 register struct person
*pers
;
714 struct stat ttystatus
;
715 static char buffer
[20] = "/dev/";
719 strcpy(buffer
+ TTYLEN
, pers
->tty
);
720 buffer
[TTYLEN
+LMAX
] = 0;
721 if (stat(buffer
, &ttystatus
) < 0) {
722 fprintf(stderr
, "finger: Can't stat %s\n", buffer
);
726 if (t
< ttystatus
.st_atime
)
729 pers
->idletime
= t
- ttystatus
.st_atime
;
730 pers
->writable
= (ttystatus
.st_mode
& TALKABLE
) == TALKABLE
;
734 * print idle time in short format; this program always prints 4 characters;
735 * if the idle time is zero, it prints 4 blanks.
741 register struct tm
*delta
;
744 if (delta
->tm_yday
== 0)
745 if (delta
->tm_hour
== 0)
746 if (delta
->tm_min
== 0)
749 printf(" %2d", delta
->tm_min
);
751 if (delta
->tm_hour
>= 10)
752 printf("%3d:", delta
->tm_hour
);
755 delta
->tm_hour
, delta
->tm_min
);
757 printf("%3dd", delta
->tm_yday
);
761 * print idle time in long format with care being taken not to pluralize
762 * 1 minutes or 1 hours or 1 days.
763 * print "prefix" first.
766 ltimeprint(before
, dt
, after
)
768 char *before
, *after
;
770 register struct tm
*delta
;
773 if (delta
->tm_yday
== 0 && delta
->tm_hour
== 0 && delta
->tm_min
== 0 &&
776 printf("%s", before
);
777 if (delta
->tm_yday
>= 10)
778 printf("%d days", delta
->tm_yday
);
779 else if (delta
->tm_yday
> 0)
780 printf("%d day%s %d hour%s",
781 delta
->tm_yday
, delta
->tm_yday
== 1 ? "" : "s",
782 delta
->tm_hour
, delta
->tm_hour
== 1 ? "" : "s");
784 if (delta
->tm_hour
>= 10)
785 printf("%d hours", delta
->tm_hour
);
786 else if (delta
->tm_hour
> 0)
787 printf("%d hour%s %d minute%s",
788 delta
->tm_hour
, delta
->tm_hour
== 1 ? "" : "s",
789 delta
->tm_min
, delta
->tm_min
== 1 ? "" : "s");
791 if (delta
->tm_min
>= 10)
792 printf("%2d minutes", delta
->tm_min
);
793 else if (delta
->tm_min
== 0)
794 printf("%2d seconds", delta
->tm_sec
);
796 printf("%d minute%s %d second%s",
798 delta
->tm_min
== 1 ? "" : "s",
800 delta
->tm_sec
== 1 ? "" : "s");
805 matchcmp(gname
, login
, given
)
806 register char *gname
;
811 register char *bp
, *lp
;
814 if (*gname
== ASTERISK
)
819 switch (c
= *gname
++) {
821 for (lp
= login
; bp
< buffer
+ sizeof buffer
830 if (namecmp(buffer
, given
))
832 if (c
== COMMA
|| c
== 0)
837 if (bp
< buffer
+ sizeof buffer
)
844 namecmp(name1
, name2
)
845 register char *name1
, *name2
;
862 for (name2
--; isdigit(*name2
); name2
++)
867 for (name1
--; isdigit(*name1
); name1
++)
896 nwio_tcpconf_t tcpconf
;
897 nwio_tcpcl_t tcpconnopt
;
902 host
= rindex(name
, '@');
906 hp
= gethostbyname(host
);
908 static struct hostent def
;
909 static ipaddr_t defaddr
;
910 static char namebuf
[128];
912 defaddr
= inet_addr(host
);
914 printf("unknown host: %s\n", host
);
917 strcpy(namebuf
, host
);
918 def
.h_name
= namebuf
;
919 def
.h_addr
= (char *)&defaddr
;
920 def
.h_length
= sizeof (ipaddr_t
);
921 def
.h_addrtype
= AF_INET
;
925 printf("[%s] ", hp
->h_name
);
928 tcp_device
= getenv("TCP_DEVICE");
929 if (tcp_device
== NULL
)
930 tcp_device
= TCP_DEVICE
;
931 s
= open (tcp_device
, O_RDWR
);
934 fprintf(stderr
, "%s: unable to open %s (%s)\n",
935 prog_name
, tcp_device
, strerror(errno
));
938 tcpconf
.nwtc_flags
= NWTC_LP_SEL
| NWTC_SET_RA
| NWTC_SET_RP
;
939 tcpconf
.nwtc_remaddr
= *(ipaddr_t
*)hp
->h_addr
;
940 tcpconf
.nwtc_remport
= htons(TCPPORT_FINGER
);
942 result
= ioctl (s
, NWIOSTCPCONF
, &tcpconf
);
945 fprintf(stderr
, "%s\n", strerror(errno
));
949 tcpconnopt
.nwtcl_flags
= 0;
953 result
= ioctl (s
, NWIOTCPCONN
, &tcpconnopt
);
954 if (result
<0 && errno
== EAGAIN
)
956 fprintf(stderr
, "got EAGAIN error, sleeping 2s\n");
959 } while (result
<0 && errno
== EAGAIN
);
962 fprintf(stderr
, "%s\n", strerror(errno
));
966 if (large
) write(s
, "/W ", 3);
967 write(s
, name
, strlen(name
));
970 while ((c
= getc(f
)) != EOF
) {
998 if (isprint(c
) || isspace(c
))
1011 * AnyMail - takes a username (string pointer thereto), and
1012 * prints on standard output whether there is any unread mail,
1013 * and if so, how old it is. (JCM@Shasta 15 March 80)
1015 #define preamble "/usr/spool/mail/" /* mailboxes are there */
1020 struct stat buf
; /* space for file status buffer */
1021 char *mbxdir
= preamble
; /* string with path preamble */
1022 char *mbxpath
; /* space for entire pathname */
1025 char *ctime(); /* convert longword time to ascii */
1029 mbxpath
= malloc(strlen(name
) + strlen(preamble
) + 1);
1031 strcpy(mbxpath
, mbxdir
); /* copy preamble into path name */
1032 strcat(mbxpath
, name
); /* concatenate user name to path */
1034 if (stat(mbxpath
, &buf
) == -1 || buf
.st_size
== 0) {
1035 /* Mailbox is empty or nonexistent */
1036 if (!NONOTHING
) printf("No unread mail\n");
1038 if (buf
.st_mtime
== buf
.st_atime
) {
1039 /* There is something in the mailbox, but we can't really
1040 * be sure whether it is mail held there by the user
1041 * or a (single) new message that was placed in a newly
1042 * recreated mailbox, so we punt and call it "unread mail."
1044 printf("Unread mail since ");
1045 printf(ctime(&buf
.st_mtime
));
1047 /* New mail has definitely arrived since the last time
1048 * mail was read. mtime is the time the most recent
1049 * message arrived; atime is either the time the oldest
1050 * unread message arrived, or the last time the mail
1053 printf("New mail received ");
1054 timestr
= ctime(&buf
.st_mtime
); /* time last modified */
1055 timestr
[24] = '\0'; /* suppress newline (ugh) */
1057 printf(";\n unread since ");
1058 printf(ctime(&buf
.st_atime
)); /* time last accessed */
1066 * return true iff we've already printed project/plan for this uid;
1067 * if not, enter this uid into table (so this function has a side-effect.)
1069 #define PPMAX 200 /* assume no more than 200 logged-in users */
1070 int PlanPrinted
[PPMAX
+1];
1071 int PPIndex
= 0; /* index of next unused table entry */
1079 while (i
++ < PPIndex
) {
1080 if (PlanPrinted
[i
] == uid
)
1084 PlanPrinted
[i
] = uid
;