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
;
160 /* parse command line for (optional) arguments */
161 while (*++argv
&& **argv
== COMMAND
)
162 for (s
= *argv
+ 1; *s
; s
++)
196 fprintf(stderr
, "Usage: finger [-bfhilmpqsw] [login1 [login2 ...] ]\n");
202 * *argv == 0 means no names given
215 register struct person
*p
;
216 register struct passwd
*pw
;
221 if ((uf
= open(USERLOG
, 0)) < 0) {
222 fprintf(stderr
, "finger: error opening %s\n", USERLOG
);
229 while (read(uf
, (char *)&user
, sizeof user
) == sizeof user
) {
230 if (user
.ut_name
[0] == 0)
233 p
= person1
= (struct person
*) malloc(sizeof *p
);
235 p
->link
= (struct person
*) malloc(sizeof *p
);
238 bcopy(user
.ut_name
, name
, NMAX
);
240 bcopy(user
.ut_line
, p
->tty
, LMAX
);
242 bcopy(user
.ut_host
, p
->host
, HMAX
);
244 p
->loginat
= user
.ut_time
;
248 if (unquick
&& (pw
= getpwnam(name
))) {
249 p
->pwd
= pwdcopy(pw
);
251 p
->name
= p
->pwd
->pw_name
;
253 p
->name
= strcpy(malloc(strlen(name
) + 1), name
);
261 printf("No one logged on\n");
267 static void donames(argv
)
270 register struct person
*p
;
271 register struct passwd
*pw
;
275 * get names from command line and check to see if they're
279 for (; *argv
!= 0; argv
++) {
280 if (netfinger(*argv
))
283 p
= person1
= (struct person
*) malloc(sizeof *p
);
285 p
->link
= (struct person
*) malloc(sizeof *p
);
297 * if we are doing it, read /etc/passwd for the useful info
302 for (p
= person1
; p
!= 0; p
= p
->link
)
303 if (pw
= getpwnam(p
->name
))
304 p
->pwd
= pwdcopy(pw
);
305 } else while ((pw
= getpwent()) != 0) {
306 for (p
= person1
; p
!= 0; p
= p
->link
) {
309 if (strcmp(p
->name
, pw
->pw_name
) != 0 &&
310 !matchcmp(pw
->pw_gecos
, pw
->pw_name
, p
->name
))
313 p
->pwd
= pwdcopy(pw
);
317 * handle multiple login names, insert
318 * new "duplicate" entry behind
320 new = (struct person
*)
322 new->pwd
= pwdcopy(pw
);
335 /* Now get login information */
336 if ((uf
= open(USERLOG
, 0)) < 0) {
337 fprintf(stderr
, "finger: error opening %s\n", USERLOG
);
340 while (read(uf
, (char *)&user
, sizeof user
) == sizeof user
) {
341 if (*user
.ut_name
== 0)
343 for (p
= person1
; p
!= 0; p
= p
->link
) {
344 if (p
->loggedin
== 2)
346 if (strncmp(p
->pwd
? p
->pwd
->pw_name
: p
->name
,
347 user
.ut_name
, NMAX
) != 0)
349 if (p
->loggedin
== 0) {
350 bcopy(user
.ut_line
, p
->tty
, LMAX
);
352 bcopy(user
.ut_host
, p
->host
, HMAX
);
354 p
->loginat
= user
.ut_time
;
356 } else { /* p->loggedin == 1 */
358 new = (struct person
*) malloc(sizeof *new);
360 bcopy(user
.ut_line
, new->tty
, LMAX
);
362 bcopy(user
.ut_host
, new->host
, HMAX
);
364 new->loginat
= user
.ut_time
;
378 for (p
= person1
; p
!= 0; p
= p
->link
)
387 register struct person
*p
;
392 * print out what we got
398 printf("Login Name TTY Idle When Where\n");
400 printf("Login TTY Idle When Where\n");
402 printf("Login TTY When");
408 for (p
= person1
; p
!= 0; p
= p
->link
) {
418 if (p
->pwd
!= 0 && !AlreadyPrinted(p
->pwd
->pw_uid
)) {
419 AnyMail(p
->pwd
->pw_name
);
421 s
= malloc(strlen(p
->pwd
->pw_dir
) +
423 strcpy(s
, p
->pwd
->pw_dir
);
425 if ((fp
= fopen(s
, "r")) != 0) {
427 while ((c
= getc(fp
)) != EOF
) {
430 if (isprint(c
) || isspace(c
))
441 s
= malloc(strlen(p
->pwd
->pw_dir
) +
443 strcpy(s
, p
->pwd
->pw_dir
);
445 if ((fp
= fopen(s
, "r")) == 0) {
446 if (!NONOTHING
) printf("No Plan.\n");
449 while ((c
= getc(fp
)) != EOF
)
450 if (isprint(c
) || isspace(c
))
465 * Duplicate a pwd entry.
466 * Note: Only the useful things (what the program currently uses) are copied.
468 static struct passwd
*
470 register struct passwd
*pfrom
;
472 register struct passwd
*pto
;
474 pto
= (struct passwd
*) malloc(sizeof *pto
);
475 #define savestr(s) strcpy(malloc(strlen(s) + 1), s)
476 pto
->pw_name
= savestr(pfrom
->pw_name
);
477 pto
->pw_uid
= pfrom
->pw_uid
;
478 pto
->pw_gecos
= savestr(pfrom
->pw_gecos
);
479 pto
->pw_dir
= savestr(pfrom
->pw_dir
);
480 pto
->pw_shell
= savestr(pfrom
->pw_shell
);
486 * print out information on quick format giving just name, tty, login time
487 * and idle time if idle is set.
489 static void quickprint(pers
)
490 register struct person
*pers
;
492 printf("%-*.*s ", NMAX
, NMAX
, pers
->name
);
493 if (pers
->loggedin
) {
496 printf("%c%-*s %-16.16s", pers
->writable
? ' ' : '*',
497 LMAX
, pers
->tty
, ctime(&pers
->loginat
));
498 ltimeprint(" ", &pers
->idletime
, "");
500 printf(" %-*s %-16.16s", LMAX
,
501 pers
->tty
, ctime(&pers
->loginat
));
504 printf(" Not Logged In\n");
508 * print out information in short format, giving login name, full name,
509 * tty, idle time, login time, and host.
511 static void shortprint(pers
)
512 register struct person
*pers
;
517 if (pers
->pwd
== 0) {
518 printf("%-15s ???\n", pers
->name
);
521 printf("%-*s", NMAX
, pers
->pwd
->pw_name
);
525 printf(" %-20.20s", pers
->realname
);
530 if (pers
->loggedin
&& !pers
->writable
)
535 if (pers
->tty
[0] == 't' && pers
->tty
[1] == 't' &&
536 pers
->tty
[2] == 'y') {
537 if (pers
->tty
[3] == 'd' && pers
->loggedin
)
539 printf("%-2.2s ", pers
->tty
+ 3);
541 printf("%-2.2s ", pers
->tty
);
544 p
= ctime(&pers
->loginat
);
545 if (pers
->loggedin
) {
546 stimeprint(&pers
->idletime
);
547 printf(" %3.3s %-5.5s ", p
, p
+ 11);
548 } else if (pers
->loginat
== 0)
549 printf(" < . . . . >");
550 else if (tloc
- pers
->loginat
>= 180L * 24 * 60 * 60)
551 printf(" <%-6.6s, %-4.4s>", p
+ 4, p
+ 20);
553 printf(" <%-12.12s>", p
+ 4);
555 printf(" %-20.20s", pers
->host
);
561 * print out a person in long format giving all possible information.
562 * directory and shell are inhibited if unbrief is clear.
566 register struct person
*pers
;
568 if (pers
->pwd
== 0) {
569 printf("Login name: %-10s\t\t\tIn real life: ???\n",
573 printf("Login name: %-10s", pers
->pwd
->pw_name
);
574 if (pers
->loggedin
&& !pers
->writable
)
575 printf(" (messages off) ");
579 printf("In real life: %s", pers
->realname
);
581 printf("\nDirectory: %-25s", pers
->pwd
->pw_dir
);
582 if (*pers
->pwd
->pw_shell
)
583 printf("\tShell: %-s", pers
->pwd
->pw_shell
);
585 if (pers
->loggedin
) {
586 register char *ep
= ctime(&pers
->loginat
);
588 printf("\nOn since %15.15s on %s from %s",
589 &ep
[4], pers
->tty
, pers
->host
);
590 ltimeprint("\n", &pers
->idletime
, " Idle Time");
592 printf("\nOn since %15.15s on %-*s",
593 &ep
[4], LMAX
, pers
->tty
);
594 ltimeprint("\t", &pers
->idletime
, " Idle Time");
596 } else if (pers
->loginat
== 0) {
597 if (lf
>= 0) printf("\nNever logged in.");
598 } else if (tloc
- pers
->loginat
> 180L * 24 * 60 * 60) {
599 register char *ep
= ctime(&pers
->loginat
);
600 printf("\nLast login %10.10s, %4.4s on %s",
601 ep
, ep
+20, pers
->tty
);
603 printf(" from %s", pers
->host
);
605 register char *ep
= ctime(&pers
->loginat
);
606 printf("\nLast login %16.16s on %s", ep
, pers
->tty
);
608 printf(" from %s", pers
->host
);
615 * decode the information in the gecos field of /etc/passwd
619 register struct person
*pers
;
622 register char *bp
, *gp
, *lp
;
630 gp
= pers
->pwd
->pw_gecos
;
634 while (*gp
&& *gp
!= COMMA
) /* name */
635 if (*gp
== SAMENAME
) {
636 lp
= pers
->pwd
->pw_name
;
638 *bp
++ = toupper(*lp
++);
639 while (*bp
++ = *lp
++)
646 if ((len
= bp
- buffer
) > 1)
647 pers
->realname
= strcpy(malloc(len
), buffer
);
655 * find the last log in of a user by checking the LASTLOG file.
656 * the entry is indexed by the uid, so this can only be done if
657 * the uid is known (which it isn't in quick mode)
663 if ((lf
= open(LASTLOG
, 0)) < 0) {
664 if (errno
== ENOENT
) return;
665 fprintf(stderr
, "finger: %s open error\n", LASTLOG
);
671 register struct person
*pers
;
674 #define ll_line ut_line
675 #define ll_host ut_host
676 #define ll_time ut_time
681 lseek(lf
, (long)pers
->pwd
->pw_uid
* sizeof ll
, 0);
682 if ((i
= read(lf
, (char *)&ll
, sizeof ll
)) == sizeof ll
) {
683 bcopy(ll
.ll_line
, pers
->tty
, LMAX
);
685 bcopy(ll
.ll_host
, pers
->host
, HMAX
);
686 pers
->host
[HMAX
] = 0;
687 pers
->loginat
= ll
.ll_time
;
690 fprintf(stderr
, "finger: %s read error\n",
703 static void fwclose()
710 * find the idle time of a user by doing a stat on /dev/tty??,
711 * where tty?? has been gotten from USERLOG, supposedly.
715 register struct person
*pers
;
717 struct stat ttystatus
;
718 static char buffer
[20] = "/dev/";
722 strcpy(buffer
+ TTYLEN
, pers
->tty
);
723 buffer
[TTYLEN
+LMAX
] = 0;
724 if (stat(buffer
, &ttystatus
) < 0) {
725 fprintf(stderr
, "finger: Can't stat %s\n", buffer
);
729 if (t
< ttystatus
.st_atime
)
732 pers
->idletime
= t
- ttystatus
.st_atime
;
733 pers
->writable
= (ttystatus
.st_mode
& TALKABLE
) == TALKABLE
;
737 * print idle time in short format; this program always prints 4 characters;
738 * if the idle time is zero, it prints 4 blanks.
744 register struct tm
*delta
;
747 if (delta
->tm_yday
== 0)
748 if (delta
->tm_hour
== 0)
749 if (delta
->tm_min
== 0)
752 printf(" %2d", delta
->tm_min
);
754 if (delta
->tm_hour
>= 10)
755 printf("%3d:", delta
->tm_hour
);
758 delta
->tm_hour
, delta
->tm_min
);
760 printf("%3dd", delta
->tm_yday
);
764 * print idle time in long format with care being taken not to pluralize
765 * 1 minutes or 1 hours or 1 days.
766 * print "prefix" first.
769 ltimeprint(before
, dt
, after
)
771 char *before
, *after
;
773 register struct tm
*delta
;
776 if (delta
->tm_yday
== 0 && delta
->tm_hour
== 0 && delta
->tm_min
== 0 &&
779 printf("%s", before
);
780 if (delta
->tm_yday
>= 10)
781 printf("%d days", delta
->tm_yday
);
782 else if (delta
->tm_yday
> 0)
783 printf("%d day%s %d hour%s",
784 delta
->tm_yday
, delta
->tm_yday
== 1 ? "" : "s",
785 delta
->tm_hour
, delta
->tm_hour
== 1 ? "" : "s");
787 if (delta
->tm_hour
>= 10)
788 printf("%d hours", delta
->tm_hour
);
789 else if (delta
->tm_hour
> 0)
790 printf("%d hour%s %d minute%s",
791 delta
->tm_hour
, delta
->tm_hour
== 1 ? "" : "s",
792 delta
->tm_min
, delta
->tm_min
== 1 ? "" : "s");
794 if (delta
->tm_min
>= 10)
795 printf("%2d minutes", delta
->tm_min
);
796 else if (delta
->tm_min
== 0)
797 printf("%2d seconds", delta
->tm_sec
);
799 printf("%d minute%s %d second%s",
801 delta
->tm_min
== 1 ? "" : "s",
803 delta
->tm_sec
== 1 ? "" : "s");
808 matchcmp(gname
, login
, given
)
809 register char *gname
;
814 register char *bp
, *lp
;
817 if (*gname
== ASTERISK
)
822 switch (c
= *gname
++) {
824 for (lp
= login
; bp
< buffer
+ sizeof buffer
833 if (namecmp(buffer
, given
))
835 if (c
== COMMA
|| c
== 0)
840 if (bp
< buffer
+ sizeof buffer
)
847 namecmp(name1
, name2
)
848 register char *name1
, *name2
;
865 for (name2
--; isdigit(*name2
); name2
++)
870 for (name1
--; isdigit(*name1
); name1
++)
901 nwio_tcpconf_t tcpconf
;
902 nwio_tcpcl_t tcpconnopt
;
907 host
= rindex(name
, '@');
911 hp
= gethostbyname(host
);
913 static struct hostent def
;
914 static ipaddr_t defaddr
;
915 static char namebuf
[128];
917 defaddr
= inet_addr(host
);
919 printf("unknown host: %s\n", host
);
922 strcpy(namebuf
, host
);
923 def
.h_name
= namebuf
;
924 def
.h_addr
= (char *)&defaddr
;
925 def
.h_length
= sizeof (ipaddr_t
);
926 def
.h_addrtype
= AF_INET
;
930 printf("[%s] ", hp
->h_name
);
933 tcp_device
= getenv("TCP_DEVICE");
934 if (tcp_device
== NULL
)
935 tcp_device
= TCP_DEVICE
;
936 s
= open (tcp_device
, O_RDWR
);
939 fprintf(stderr
, "%s: unable to open %s (%s)\n",
940 prog_name
, tcp_device
, strerror(errno
));
943 tcpconf
.nwtc_flags
= NWTC_LP_SEL
| NWTC_SET_RA
| NWTC_SET_RP
;
944 tcpconf
.nwtc_remaddr
= *(ipaddr_t
*)hp
->h_addr
;
945 tcpconf
.nwtc_remport
= htons(TCPPORT_FINGER
);
947 result
= ioctl (s
, NWIOSTCPCONF
, &tcpconf
);
950 fprintf(stderr
, "%s\n", strerror(errno
));
954 tcpconnopt
.nwtcl_flags
= 0;
958 result
= ioctl (s
, NWIOTCPCONN
, &tcpconnopt
);
959 if (result
<0 && errno
== EAGAIN
)
961 fprintf(stderr
, "got EAGAIN error, sleeping 2s\n");
964 } while (result
<0 && errno
== EAGAIN
);
967 fprintf(stderr
, "%s\n", strerror(errno
));
971 if (large
) write(s
, "/W ", 3);
972 write(s
, name
, strlen(name
));
975 while ((c
= getc(f
)) != EOF
) {
1003 if (isprint(c
) || isspace(c
))
1016 * AnyMail - takes a username (string pointer thereto), and
1017 * prints on standard output whether there is any unread mail,
1018 * and if so, how old it is. (JCM@Shasta 15 March 80)
1020 #define preamble "/usr/spool/mail/" /* mailboxes are there */
1025 struct stat buf
; /* space for file status buffer */
1026 char *mbxdir
= preamble
; /* string with path preamble */
1027 char *mbxpath
; /* space for entire pathname */
1030 char *ctime(); /* convert longword time to ascii */
1034 mbxpath
= malloc(strlen(name
) + strlen(preamble
) + 1);
1036 strcpy(mbxpath
, mbxdir
); /* copy preamble into path name */
1037 strcat(mbxpath
, name
); /* concatenate user name to path */
1039 if (stat(mbxpath
, &buf
) == -1 || buf
.st_size
== 0) {
1040 /* Mailbox is empty or nonexistent */
1041 if (!NONOTHING
) printf("No unread mail\n");
1043 if (buf
.st_mtime
== buf
.st_atime
) {
1044 /* There is something in the mailbox, but we can't really
1045 * be sure whether it is mail held there by the user
1046 * or a (single) new message that was placed in a newly
1047 * recreated mailbox, so we punt and call it "unread mail."
1049 printf("Unread mail since ");
1050 printf(ctime(&buf
.st_mtime
));
1052 /* New mail has definitely arrived since the last time
1053 * mail was read. mtime is the time the most recent
1054 * message arrived; atime is either the time the oldest
1055 * unread message arrived, or the last time the mail
1058 printf("New mail received ");
1059 timestr
= ctime(&buf
.st_mtime
); /* time last modified */
1060 timestr
[24] = '\0'; /* suppress newline (ugh) */
1062 printf(";\n unread since ");
1063 printf(ctime(&buf
.st_atime
)); /* time last accessed */
1071 * return true iff we've already printed project/plan for this uid;
1072 * if not, enter this uid into table (so this function has a side-effect.)
1074 #define PPMAX 200 /* assume no more than 200 logged-in users */
1075 int PlanPrinted
[PPMAX
+1];
1076 int PPIndex
= 0; /* index of next unused table entry */
1084 while (i
++ < PPIndex
) {
1085 if (PlanPrinted
[i
] == uid
)
1089 PlanPrinted
[i
] = uid
;