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 */
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 */
131 /* Already defined in stdio.h */
133 #define fwopen finger_fwopen
136 int main (int argc
, char *argv
[]);
137 static void doall(void);
138 static void donames(char **args
);
139 static void print(void);
140 static void fwopen(void);
141 static void decode(struct person
*pers
);
142 static void fwclose(void);
143 static int netfinger (char *name
);
144 static int matchcmp (char *gname
, char *login
, char *given
);
145 static void quickprint (struct person
*pers
);
146 static void shortprint (struct person
*pers
);
147 static void personprint (struct person
*pers
);
148 static int AlreadyPrinted(int uid
);
149 static int AnyMail (char *name
);
150 static struct passwd
*pwdcopy(struct passwd
*pfrom
);
151 static void findidle (struct person
*pers
);
152 static int ltimeprint (char *dt
, long *before
, char *after
);
153 static void stimeprint (long *dt
);
154 static void findwhen (struct person
*pers
);
155 static int namecmp (char *name1
, char *name2
);
159 register char **argv
;
165 /* parse command line for (optional) arguments */
166 while (*++argv
&& **argv
== COMMAND
)
167 for (s
= *argv
+ 1; *s
; s
++)
201 fprintf(stderr
, "Usage: finger [-bfhilmpqsw] [login1 [login2 ...] ]\n");
207 * *argv == 0 means no names given
220 register struct person
*p
;
221 register struct passwd
*pw
;
226 if ((uf
= open(USERLOG
, 0)) < 0) {
227 fprintf(stderr
, "finger: error opening %s\n", USERLOG
);
234 while (read(uf
, (char *)&user
, sizeof user
) == sizeof user
) {
235 if (user
.ut_name
[0] == 0)
238 p
= person1
= (struct person
*) malloc(sizeof *p
);
240 p
->link
= (struct person
*) malloc(sizeof *p
);
243 bcopy(user
.ut_name
, name
, NMAX
);
245 bcopy(user
.ut_line
, p
->tty
, LMAX
);
247 bcopy(user
.ut_host
, p
->host
, HMAX
);
249 p
->loginat
= user
.ut_time
;
253 if (unquick
&& (pw
= getpwnam(name
))) {
254 p
->pwd
= pwdcopy(pw
);
256 p
->name
= p
->pwd
->pw_name
;
258 p
->name
= strcpy(malloc(strlen(name
) + 1), name
);
266 printf("No one logged on\n");
272 static void donames(argv
)
275 register struct person
*p
;
276 register struct passwd
*pw
;
280 * get names from command line and check to see if they're
284 for (; *argv
!= 0; argv
++) {
285 if (netfinger(*argv
))
288 p
= person1
= (struct person
*) malloc(sizeof *p
);
290 p
->link
= (struct person
*) malloc(sizeof *p
);
302 * if we are doing it, read /etc/passwd for the useful info
307 for (p
= person1
; p
!= 0; p
= p
->link
)
308 if (pw
= getpwnam(p
->name
))
309 p
->pwd
= pwdcopy(pw
);
310 } else while ((pw
= getpwent()) != 0) {
311 for (p
= person1
; p
!= 0; p
= p
->link
) {
314 if (strcmp(p
->name
, pw
->pw_name
) != 0 &&
315 !matchcmp(pw
->pw_gecos
, pw
->pw_name
, p
->name
))
318 p
->pwd
= pwdcopy(pw
);
322 * handle multiple login names, insert
323 * new "duplicate" entry behind
325 new = (struct person
*)
327 new->pwd
= pwdcopy(pw
);
340 /* Now get login information */
341 if ((uf
= open(USERLOG
, 0)) < 0) {
342 fprintf(stderr
, "finger: error opening %s\n", USERLOG
);
345 while (read(uf
, (char *)&user
, sizeof user
) == sizeof user
) {
346 if (*user
.ut_name
== 0)
348 for (p
= person1
; p
!= 0; p
= p
->link
) {
349 if (p
->loggedin
== 2)
351 if (strncmp(p
->pwd
? p
->pwd
->pw_name
: p
->name
,
352 user
.ut_name
, NMAX
) != 0)
354 if (p
->loggedin
== 0) {
355 bcopy(user
.ut_line
, p
->tty
, LMAX
);
357 bcopy(user
.ut_host
, p
->host
, HMAX
);
359 p
->loginat
= user
.ut_time
;
361 } else { /* p->loggedin == 1 */
363 new = (struct person
*) malloc(sizeof *new);
365 bcopy(user
.ut_line
, new->tty
, LMAX
);
367 bcopy(user
.ut_host
, new->host
, HMAX
);
369 new->loginat
= user
.ut_time
;
383 for (p
= person1
; p
!= 0; p
= p
->link
)
392 register struct person
*p
;
397 * print out what we got
403 printf("Login Name TTY Idle When Where\n");
405 printf("Login TTY Idle When Where\n");
407 printf("Login TTY When");
413 for (p
= person1
; p
!= 0; p
= p
->link
) {
423 if (p
->pwd
!= 0 && !AlreadyPrinted(p
->pwd
->pw_uid
)) {
424 AnyMail(p
->pwd
->pw_name
);
426 s
= malloc(strlen(p
->pwd
->pw_dir
) +
428 strcpy(s
, p
->pwd
->pw_dir
);
430 if ((fp
= fopen(s
, "r")) != 0) {
432 while ((c
= getc(fp
)) != EOF
) {
435 if (isprint(c
) || isspace(c
))
446 s
= malloc(strlen(p
->pwd
->pw_dir
) +
448 strcpy(s
, p
->pwd
->pw_dir
);
450 if ((fp
= fopen(s
, "r")) == 0) {
451 if (!NONOTHING
) printf("No Plan.\n");
454 while ((c
= getc(fp
)) != EOF
)
455 if (isprint(c
) || isspace(c
))
470 * Duplicate a pwd entry.
471 * Note: Only the useful things (what the program currently uses) are copied.
473 static struct passwd
*
475 register struct passwd
*pfrom
;
477 register struct passwd
*pto
;
479 pto
= (struct passwd
*) malloc(sizeof *pto
);
480 #define savestr(s) strcpy(malloc(strlen(s) + 1), s)
481 pto
->pw_name
= savestr(pfrom
->pw_name
);
482 pto
->pw_uid
= pfrom
->pw_uid
;
483 pto
->pw_gecos
= savestr(pfrom
->pw_gecos
);
484 pto
->pw_dir
= savestr(pfrom
->pw_dir
);
485 pto
->pw_shell
= savestr(pfrom
->pw_shell
);
491 * print out information on quick format giving just name, tty, login time
492 * and idle time if idle is set.
494 static void quickprint(pers
)
495 register struct person
*pers
;
497 printf("%-*.*s ", NMAX
, NMAX
, pers
->name
);
498 if (pers
->loggedin
) {
501 printf("%c%-*s %-16.16s", pers
->writable
? ' ' : '*',
502 LMAX
, pers
->tty
, ctime(&pers
->loginat
));
503 ltimeprint(" ", &pers
->idletime
, "");
505 printf(" %-*s %-16.16s", LMAX
,
506 pers
->tty
, ctime(&pers
->loginat
));
509 printf(" Not Logged In\n");
513 * print out information in short format, giving login name, full name,
514 * tty, idle time, login time, and host.
516 static void shortprint(pers
)
517 register struct person
*pers
;
522 if (pers
->pwd
== 0) {
523 printf("%-15s ???\n", pers
->name
);
526 printf("%-*s", NMAX
, pers
->pwd
->pw_name
);
530 printf(" %-20.20s", pers
->realname
);
535 if (pers
->loggedin
&& !pers
->writable
)
540 if (pers
->tty
[0] == 't' && pers
->tty
[1] == 't' &&
541 pers
->tty
[2] == 'y') {
542 if (pers
->tty
[3] == 'd' && pers
->loggedin
)
544 printf("%-2.2s ", pers
->tty
+ 3);
546 printf("%-2.2s ", pers
->tty
);
549 p
= ctime(&pers
->loginat
);
550 if (pers
->loggedin
) {
551 stimeprint(&pers
->idletime
);
552 printf(" %3.3s %-5.5s ", p
, p
+ 11);
553 } else if (pers
->loginat
== 0)
554 printf(" < . . . . >");
555 else if (tloc
- pers
->loginat
>= 180L * 24 * 60 * 60)
556 printf(" <%-6.6s, %-4.4s>", p
+ 4, p
+ 20);
558 printf(" <%-12.12s>", p
+ 4);
560 printf(" %-20.20s", pers
->host
);
566 * print out a person in long format giving all possible information.
567 * directory and shell are inhibited if unbrief is clear.
571 register struct person
*pers
;
573 if (pers
->pwd
== 0) {
574 printf("Login name: %-10s\t\t\tIn real life: ???\n",
578 printf("Login name: %-10s", pers
->pwd
->pw_name
);
579 if (pers
->loggedin
&& !pers
->writable
)
580 printf(" (messages off) ");
584 printf("In real life: %s", pers
->realname
);
586 printf("\nDirectory: %-25s", pers
->pwd
->pw_dir
);
587 if (*pers
->pwd
->pw_shell
)
588 printf("\tShell: %-s", pers
->pwd
->pw_shell
);
590 if (pers
->loggedin
) {
591 register char *ep
= ctime(&pers
->loginat
);
593 printf("\nOn since %15.15s on %s from %s",
594 &ep
[4], pers
->tty
, pers
->host
);
595 ltimeprint("\n", &pers
->idletime
, " Idle Time");
597 printf("\nOn since %15.15s on %-*s",
598 &ep
[4], LMAX
, pers
->tty
);
599 ltimeprint("\t", &pers
->idletime
, " Idle Time");
601 } else if (pers
->loginat
== 0) {
602 if (lf
>= 0) printf("\nNever logged in.");
603 } else if (tloc
- pers
->loginat
> 180L * 24 * 60 * 60) {
604 register char *ep
= ctime(&pers
->loginat
);
605 printf("\nLast login %10.10s, %4.4s on %s",
606 ep
, ep
+20, pers
->tty
);
608 printf(" from %s", pers
->host
);
610 register char *ep
= ctime(&pers
->loginat
);
611 printf("\nLast login %16.16s on %s", ep
, pers
->tty
);
613 printf(" from %s", pers
->host
);
620 * decode the information in the gecos field of /etc/passwd
624 register struct person
*pers
;
627 register char *bp
, *gp
, *lp
;
633 gp
= pers
->pwd
->pw_gecos
;
637 while (*gp
&& *gp
!= COMMA
) /* name */
638 if (*gp
== SAMENAME
) {
639 lp
= pers
->pwd
->pw_name
;
641 *bp
++ = toupper(*lp
++);
642 while (*bp
++ = *lp
++)
649 if ((len
= bp
- buffer
) > 1)
650 pers
->realname
= strcpy(malloc(len
), buffer
);
658 * find the last log in of a user by checking the LASTLOG file.
659 * the entry is indexed by the uid, so this can only be done if
660 * the uid is known (which it isn't in quick mode)
666 if ((lf
= open(LASTLOG
, 0)) < 0) {
667 if (errno
== ENOENT
) return;
668 fprintf(stderr
, "finger: %s open error\n", LASTLOG
);
674 register struct person
*pers
;
677 #define ll_line ut_line
678 #define ll_host ut_host
679 #define ll_time ut_time
684 lseek(lf
, (long)pers
->pwd
->pw_uid
* sizeof ll
, 0);
685 if ((i
= read(lf
, (char *)&ll
, sizeof ll
)) == sizeof ll
) {
686 bcopy(ll
.ll_line
, pers
->tty
, LMAX
);
688 bcopy(ll
.ll_host
, pers
->host
, HMAX
);
689 pers
->host
[HMAX
] = 0;
690 pers
->loginat
= ll
.ll_time
;
693 fprintf(stderr
, "finger: %s read error\n",
706 static void fwclose()
713 * find the idle time of a user by doing a stat on /dev/tty??,
714 * where tty?? has been gotten from USERLOG, supposedly.
718 register struct person
*pers
;
720 struct stat ttystatus
;
721 static char buffer
[20] = "/dev/";
725 strcpy(buffer
+ TTYLEN
, pers
->tty
);
726 buffer
[TTYLEN
+LMAX
] = 0;
727 if (stat(buffer
, &ttystatus
) < 0) {
728 fprintf(stderr
, "finger: Can't stat %s\n", buffer
);
732 if (t
< ttystatus
.st_atime
)
735 pers
->idletime
= t
- ttystatus
.st_atime
;
736 pers
->writable
= (ttystatus
.st_mode
& TALKABLE
) == TALKABLE
;
740 * print idle time in short format; this program always prints 4 characters;
741 * if the idle time is zero, it prints 4 blanks.
747 register struct tm
*delta
;
750 if (delta
->tm_yday
== 0)
751 if (delta
->tm_hour
== 0)
752 if (delta
->tm_min
== 0)
755 printf(" %2d", delta
->tm_min
);
757 if (delta
->tm_hour
>= 10)
758 printf("%3d:", delta
->tm_hour
);
761 delta
->tm_hour
, delta
->tm_min
);
763 printf("%3dd", delta
->tm_yday
);
767 * print idle time in long format with care being taken not to pluralize
768 * 1 minutes or 1 hours or 1 days.
769 * print "prefix" first.
772 ltimeprint(before
, dt
, after
)
774 char *before
, *after
;
776 register struct tm
*delta
;
779 if (delta
->tm_yday
== 0 && delta
->tm_hour
== 0 && delta
->tm_min
== 0 &&
782 printf("%s", before
);
783 if (delta
->tm_yday
>= 10)
784 printf("%d days", delta
->tm_yday
);
785 else if (delta
->tm_yday
> 0)
786 printf("%d day%s %d hour%s",
787 delta
->tm_yday
, delta
->tm_yday
== 1 ? "" : "s",
788 delta
->tm_hour
, delta
->tm_hour
== 1 ? "" : "s");
790 if (delta
->tm_hour
>= 10)
791 printf("%d hours", delta
->tm_hour
);
792 else if (delta
->tm_hour
> 0)
793 printf("%d hour%s %d minute%s",
794 delta
->tm_hour
, delta
->tm_hour
== 1 ? "" : "s",
795 delta
->tm_min
, delta
->tm_min
== 1 ? "" : "s");
797 if (delta
->tm_min
>= 10)
798 printf("%2d minutes", delta
->tm_min
);
799 else if (delta
->tm_min
== 0)
800 printf("%2d seconds", delta
->tm_sec
);
802 printf("%d minute%s %d second%s",
804 delta
->tm_min
== 1 ? "" : "s",
806 delta
->tm_sec
== 1 ? "" : "s");
811 matchcmp(gname
, login
, given
)
812 register char *gname
;
817 register char *bp
, *lp
;
820 if (*gname
== ASTERISK
)
825 switch (c
= *gname
++) {
827 for (lp
= login
; bp
< buffer
+ sizeof buffer
836 if (namecmp(buffer
, given
))
838 if (c
== COMMA
|| c
== 0)
843 if (bp
< buffer
+ sizeof buffer
)
850 namecmp(name1
, name2
)
851 register char *name1
, *name2
;
868 for (name2
--; isdigit(*name2
); name2
++)
873 for (name1
--; isdigit(*name1
); name1
++)
902 nwio_tcpconf_t tcpconf
;
903 nwio_tcpcl_t tcpconnopt
;
908 host
= rindex(name
, '@');
912 hp
= gethostbyname(host
);
914 static struct hostent def
;
915 static ipaddr_t defaddr
;
916 static char namebuf
[128];
918 defaddr
= inet_addr(host
);
920 printf("unknown host: %s\n", host
);
923 strcpy(namebuf
, host
);
924 def
.h_name
= namebuf
;
925 def
.h_addr
= (char *)&defaddr
;
926 def
.h_length
= sizeof (ipaddr_t
);
927 def
.h_addrtype
= AF_INET
;
931 printf("[%s] ", hp
->h_name
);
934 tcp_device
= getenv("TCP_DEVICE");
935 if (tcp_device
== NULL
)
936 tcp_device
= TCP_DEVICE
;
937 s
= open (tcp_device
, O_RDWR
);
940 fprintf(stderr
, "%s: unable to open %s (%s)\n",
941 prog_name
, tcp_device
, strerror(errno
));
944 tcpconf
.nwtc_flags
= NWTC_LP_SEL
| NWTC_SET_RA
| NWTC_SET_RP
;
945 tcpconf
.nwtc_remaddr
= *(ipaddr_t
*)hp
->h_addr
;
946 tcpconf
.nwtc_remport
= htons(TCPPORT_FINGER
);
948 result
= ioctl (s
, NWIOSTCPCONF
, &tcpconf
);
951 fprintf(stderr
, "%s\n", strerror(errno
));
955 tcpconnopt
.nwtcl_flags
= 0;
959 result
= ioctl (s
, NWIOTCPCONN
, &tcpconnopt
);
960 if (result
<0 && errno
== EAGAIN
)
962 fprintf(stderr
, "got EAGAIN error, sleeping 2s\n");
965 } while (result
<0 && errno
== EAGAIN
);
968 fprintf(stderr
, "%s\n", strerror(errno
));
972 if (large
) write(s
, "/W ", 3);
973 write(s
, name
, strlen(name
));
976 while ((c
= getc(f
)) != EOF
) {
1004 if (isprint(c
) || isspace(c
))
1017 * AnyMail - takes a username (string pointer thereto), and
1018 * prints on standard output whether there is any unread mail,
1019 * and if so, how old it is. (JCM@Shasta 15 March 80)
1021 #define preamble "/usr/spool/mail/" /* mailboxes are there */
1026 struct stat buf
; /* space for file status buffer */
1027 char *mbxdir
= preamble
; /* string with path preamble */
1028 char *mbxpath
; /* space for entire pathname */
1031 char *ctime(); /* convert longword time to ascii */
1035 mbxpath
= malloc(strlen(name
) + strlen(preamble
) + 1);
1037 strcpy(mbxpath
, mbxdir
); /* copy preamble into path name */
1038 strcat(mbxpath
, name
); /* concatenate user name to path */
1040 if (stat(mbxpath
, &buf
) == -1 || buf
.st_size
== 0) {
1041 /* Mailbox is empty or nonexistent */
1042 if (!NONOTHING
) printf("No unread mail\n");
1044 if (buf
.st_mtime
== buf
.st_atime
) {
1045 /* There is something in the mailbox, but we can't really
1046 * be sure whether it is mail held there by the user
1047 * or a (single) new message that was placed in a newly
1048 * recreated mailbox, so we punt and call it "unread mail."
1050 printf("Unread mail since ");
1051 printf(ctime(&buf
.st_mtime
));
1053 /* New mail has definitely arrived since the last time
1054 * mail was read. mtime is the time the most recent
1055 * message arrived; atime is either the time the oldest
1056 * unread message arrived, or the last time the mail
1059 printf("New mail received ");
1060 timestr
= ctime(&buf
.st_mtime
); /* time last modified */
1061 timestr
[24] = '\0'; /* suppress newline (ugh) */
1063 printf(";\n unread since ");
1064 printf(ctime(&buf
.st_atime
)); /* time last accessed */
1072 * return true iff we've already printed project/plan for this uid;
1073 * if not, enter this uid into table (so this function has a side-effect.)
1075 #define PPMAX 200 /* assume no more than 200 logged-in users */
1076 int PlanPrinted
[PPMAX
+1];
1077 int PPIndex
= 0; /* index of next unused table entry */
1085 while (i
++ < PPIndex
) {
1086 if (PlanPrinted
[i
] == uid
)
1090 PlanPrinted
[i
] = uid
;