1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * Copyright (c) 1982, 1986, 1988 32 * The Regents of the University of California 33 * All Rights Reserved 34 * 35 * Portions of this document are derived from 36 * software developed by the University of California, Berkeley, and its 37 * contributors. 38 */ 39 40 #pragma ident "%Z%%M% %I% %E% SMI" 41 42 /* 43 * This is a finger program. It prints out useful information about users 44 * by digging it up from various system files. 45 * 46 * There are three output formats, all of which give login name, teletype 47 * line number, and login time. The short output format is reminiscent 48 * of finger on ITS, and gives one line of information per user containing 49 * in addition to the minimum basic requirements (MBR), the user's full name, 50 * idle time and location. 51 * The quick style output is UNIX who-like, giving only name, teletype and 52 * login time. Finally, the long style output give the same information 53 * as the short (in more legible format), the home directory and shell 54 * of the user, and, if it exits, a copy of the file .plan in the users 55 * home directory. Finger may be called with or without a list of people 56 * to finger -- if no list is given, all the people currently logged in 57 * are fingered. 58 * 59 * The program is validly called by one of the following: 60 * 61 * finger {short form list of users} 62 * finger -l {long form list of users} 63 * finger -b {briefer long form list of users} 64 * finger -q {quick list of users} 65 * finger -i {quick list of users with idle times} 66 * finger -m {matches arguments against only username} 67 * finger -f {suppress header in non-long form} 68 * finger -p {suppress printing of .plan file} 69 * finger -h {suppress printing of .project file} 70 * finger -i {forces "idle" output format} 71 * finger namelist {long format list of specified users} 72 * finger -s namelist {short format list of specified users} 73 * finger -w namelist {narrow short format list of specified users} 74 * 75 * where 'namelist' is a list of users login names. 76 * The other options can all be given after one '-', or each can have its 77 * own '-'. The -f option disables the printing of headers for short and 78 * quick outputs. The -b option briefens long format outputs. The -p 79 * option turns off plans for long format outputs. 80 */ 81 82 #include <sys/types.h> 83 #include <sys/stat.h> 84 #include <utmpx.h> 85 #include <sys/signal.h> 86 #include <pwd.h> 87 #include <stdio.h> 88 #include <lastlog.h> 89 #include <ctype.h> 90 #include <sys/time.h> 91 #include <time.h> 92 #include <sys/socket.h> 93 #include <netinet/in.h> 94 #include <netdb.h> 95 #include <locale.h> 96 #include <sys/select.h> 97 #include <stdlib.h> 98 #include <strings.h> 99 #include <fcntl.h> 100 #include <curses.h> 101 #include <unctrl.h> 102 #include <maillock.h> 103 #include <deflt.h> 104 #include <unistd.h> 105 #include <arpa/inet.h> 106 #include <macros.h> 107 108 static char gecos_ignore_c = '*'; /* ignore this in real name */ 109 static char gecos_sep_c = ','; /* separator in pw_gecos field */ 110 static char gecos_samename = '&'; /* repeat login name in real name */ 111 112 #define TALKABLE 0220 /* tty is writable if this mode */ 113 114 #define NMAX sizeof (((struct utmpx *)0)->ut_name) 115 #define LMAX sizeof (((struct utmpx *)0)->ut_line) 116 #define HMAX sizeof (((struct utmpx *)0)->ut_host) 117 118 struct person { /* one for each person fingered */ 119 char *name; /* name */ 120 char tty[LMAX+1]; /* null terminated tty line */ 121 char host[HMAX+1]; /* null terminated remote host name */ 122 char *ttyloc; /* location of tty line, if any */ 123 time_t loginat; /* time of (last) login */ 124 time_t idletime; /* how long idle (if logged in) */ 125 char *realname; /* pointer to full name */ 126 struct passwd *pwd; /* structure of /etc/passwd stuff */ 127 char loggedin; /* person is logged in */ 128 char writable; /* tty is writable */ 129 char original; /* this is not a duplicate entry */ 130 struct person *link; /* link to next person */ 131 }; 132 133 char LASTLOG[] = "/var/adm/lastlog"; /* last login info */ 134 char PLAN[] = "/.plan"; /* what plan file is */ 135 char PROJ[] = "/.project"; /* what project file */ 136 137 int unbrief = 1; /* -b option default */ 138 int header = 1; /* -f option default */ 139 int hack = 1; /* -h option default */ 140 int idle = 0; /* -i option default */ 141 int large = 0; /* -l option default */ 142 int match = 1; /* -m option default */ 143 int plan = 1; /* -p option default */ 144 int unquick = 1; /* -q option default */ 145 int small = 0; /* -s option default */ 146 int wide = 1; /* -w option default */ 147 148 /* 149 * RFC 1288 says that system administrators should have the option of 150 * separately allowing ASCII characters less than 32 or greater than 151 * 126. The termpass variable keeps track of this. 152 */ 153 char defaultfile[] = "/etc/default/finger"; 154 char passvar[] = "PASS="; 155 int termpass = 0; /* default is ASCII only */ 156 char *termopts[] = { 157 #define TERM_LOW 0 158 "low", 159 #define TERM_HIGH 1 160 "high", 161 (char *)NULL 162 }; 163 #define TS_LOW (1 << TERM_LOW) /* print characters less than 32 */ 164 #define TS_HIGH (1 << TERM_HIGH) /* print characters greater than 126 */ 165 166 167 int unshort; 168 FILE *lf; /* LASTLOG file pointer */ 169 struct person *person1; /* list of people */ 170 time_t tloc; /* current time */ 171 172 char usagestr[] = "Usage: " 173 "finger [-bfhilmpqsw] [name1 [name2 ...] ]\n"; 174 175 int AlreadyPrinted(uid_t uid); 176 void AnyMail(char *name); 177 void catfile(char *s, mode_t mode, int trunc_at_nl); 178 void decode(struct person *pers); 179 void doall(void); 180 void donames(char **argv); 181 void findidle(struct person *pers); 182 void findwhen(struct person *pers); 183 void fwclose(void); 184 void fwopen(void); 185 void initscreening(void); 186 void ltimeprint(char *before, time_t *dt, char *after); 187 int matchcmp(char *gname, char *login, char *given); 188 int namecmp(char *name1, char *name2); 189 int netfinger(char *name); 190 void personprint(struct person *pers); 191 void print(void); 192 struct passwd *pwdcopy(const struct passwd *pfrom); 193 void quickprint(struct person *pers); 194 void shortprint(struct person *pers); 195 void stimeprint(time_t *dt); 196 197 198 int 199 main(int argc, char **argv) 200 { 201 int c; 202 203 (void) setlocale(LC_ALL, ""); 204 /* parse command line for (optional) arguments */ 205 while ((c = getopt(argc, argv, "bfhilmpqsw")) != EOF) 206 switch (c) { 207 case 'b': 208 unbrief = 0; 209 break; 210 case 'f': 211 header = 0; 212 break; 213 case 'h': 214 hack = 0; 215 break; 216 case 'i': 217 idle = 1; 218 unquick = 0; 219 break; 220 case 'l': 221 large = 1; 222 break; 223 case 'm': 224 match = 0; 225 break; 226 case 'p': 227 plan = 0; 228 break; 229 case 'q': 230 unquick = 0; 231 break; 232 case 's': 233 small = 1; 234 break; 235 case 'w': 236 wide = 0; 237 break; 238 default: 239 (void) fprintf(stderr, usagestr); 240 exit(1); 241 } 242 if (unquick || idle) 243 tloc = time(NULL); 244 245 /* find out what filtering on .plan/.project files we should do */ 246 initscreening(); 247 248 /* 249 * optind == argc means no names given 250 */ 251 if (optind == argc) 252 doall(); 253 else 254 donames(&argv[optind]); 255 if (person1) 256 print(); 257 return (0); 258 /* NOTREACHED */ 259 } 260 261 void 262 doall(void) 263 { 264 struct person *p; 265 struct passwd *pw; 266 struct utmpx *u; 267 char name[NMAX + 1]; 268 269 unshort = large; 270 setutxent(); 271 if (unquick) { 272 setpwent(); 273 fwopen(); 274 } 275 while (u = getutxent()) { 276 if (u->ut_name[0] == 0 || 277 nonuserx(*u) || 278 u->ut_type != USER_PROCESS) 279 continue; 280 if (person1 == 0) 281 p = person1 = malloc(sizeof (*p)); 282 else { 283 p->link = malloc(sizeof (*p)); 284 p = p->link; 285 } 286 bcopy(u->ut_name, name, NMAX); 287 name[NMAX] = 0; 288 bcopy(u->ut_line, p->tty, LMAX); 289 p->tty[LMAX] = 0; 290 bcopy(u->ut_host, p->host, HMAX); 291 p->host[HMAX] = 0; 292 p->loginat = u->ut_tv.tv_sec; 293 p->pwd = 0; 294 p->loggedin = 1; 295 if (unquick && (pw = getpwnam(name))) { 296 p->pwd = pwdcopy(pw); 297 decode(p); 298 p->name = p->pwd->pw_name; 299 } else 300 p->name = strdup(name); 301 p->ttyloc = NULL; 302 } 303 if (unquick) { 304 fwclose(); 305 endpwent(); 306 } 307 endutxent(); 308 if (person1 == 0) { 309 (void) printf("No one logged on\n"); 310 return; 311 } 312 p->link = 0; 313 } 314 315 void 316 donames(char **argv) 317 { 318 struct person *p; 319 struct passwd *pw; 320 struct utmpx *u; 321 322 /* 323 * get names from command line and check to see if they're 324 * logged in 325 */ 326 unshort = !small; 327 for (; *argv != 0; argv++) { 328 if (netfinger(*argv)) 329 continue; 330 if (person1 == 0) 331 p = person1 = malloc(sizeof (*p)); 332 else { 333 p->link = malloc(sizeof (*p)); 334 p = p->link; 335 } 336 p->name = *argv; 337 p->loggedin = 0; 338 p->original = 1; 339 p->pwd = 0; 340 } 341 if (person1 == 0) 342 return; 343 p->link = 0; 344 /* 345 * if we are doing it, read /etc/passwd for the useful info 346 */ 347 if (unquick) { 348 setpwent(); 349 if (!match) { 350 for (p = person1; p != 0; p = p->link) { 351 if (pw = getpwnam(p->name)) 352 p->pwd = pwdcopy(pw); 353 } 354 } else { 355 while ((pw = getpwent()) != 0) { 356 for (p = person1; p != 0; p = p->link) { 357 if (!p->original) 358 continue; 359 if (strcmp(p->name, pw->pw_name) != 0 && 360 !matchcmp(pw->pw_gecos, pw->pw_name, 361 p->name)) { 362 continue; 363 } 364 if (p->pwd == 0) { 365 p->pwd = pwdcopy(pw); 366 } else { 367 struct person *new; 368 /* 369 * Handle multiple login names. 370 * Insert new "duplicate" entry 371 * behind. 372 */ 373 new = malloc(sizeof (*new)); 374 new->pwd = pwdcopy(pw); 375 new->name = p->name; 376 new->original = 1; 377 new->loggedin = 0; 378 new->ttyloc = NULL; 379 new->link = p->link; 380 p->original = 0; 381 p->link = new; 382 p = new; 383 } 384 } 385 } 386 } 387 endpwent(); 388 } 389 /* Now get login information */ 390 setutxent(); 391 while (u = getutxent()) { 392 if (u->ut_name[0] == 0 || u->ut_type != USER_PROCESS) 393 continue; 394 for (p = person1; p != 0; p = p->link) { 395 p->ttyloc = NULL; 396 if (p->loggedin == 2) 397 continue; 398 if (strncmp(p->pwd ? p->pwd->pw_name : p->name, 399 u->ut_name, NMAX) != 0) 400 continue; 401 if (p->loggedin == 0) { 402 bcopy(u->ut_line, p->tty, LMAX); 403 p->tty[LMAX] = 0; 404 bcopy(u->ut_host, p->host, HMAX); 405 p->host[HMAX] = 0; 406 p->loginat = u->ut_tv.tv_sec; 407 p->loggedin = 1; 408 } else { /* p->loggedin == 1 */ 409 struct person *new; 410 new = malloc(sizeof (*new)); 411 new->name = p->name; 412 bcopy(u->ut_line, new->tty, LMAX); 413 new->tty[LMAX] = 0; 414 bcopy(u->ut_host, new->host, HMAX); 415 new->host[HMAX] = 0; 416 new->loginat = u->ut_tv.tv_sec; 417 new->pwd = p->pwd; 418 new->loggedin = 1; 419 new->original = 0; 420 new->link = p->link; 421 p->loggedin = 2; 422 p->link = new; 423 p = new; 424 } 425 } 426 } 427 endutxent(); 428 if (unquick) { 429 fwopen(); 430 for (p = person1; p != 0; p = p->link) 431 decode(p); 432 fwclose(); 433 } 434 } 435 436 void 437 print(void) 438 { 439 struct person *p; 440 char *s; 441 442 /* 443 * print out what we got 444 */ 445 if (header) { 446 if (unquick) { 447 if (!unshort) { 448 if (wide) { 449 (void) printf("Login " 450 "Name TTY " 451 "Idle When Where\n"); 452 } else { 453 (void) printf("Login TTY Idle " 454 "When Where\n"); 455 } 456 } 457 } else { 458 (void) printf("Login TTY When"); 459 if (idle) 460 (void) printf(" Idle"); 461 (void) putchar('\n'); 462 } 463 } 464 for (p = person1; p != 0; p = p->link) { 465 if (!unquick) { 466 quickprint(p); 467 continue; 468 } 469 if (!unshort) { 470 shortprint(p); 471 continue; 472 } 473 personprint(p); 474 if (p->pwd != 0 && !AlreadyPrinted(p->pwd->pw_uid)) { 475 AnyMail(p->pwd->pw_name); 476 if (hack) { 477 struct stat sbuf; 478 479 s = malloc(strlen(p->pwd->pw_dir) + 480 sizeof (PROJ)); 481 if (s) { 482 (void) strcpy(s, p->pwd->pw_dir); 483 (void) strcat(s, PROJ); 484 if (stat(s, &sbuf) != -1 && 485 (S_ISREG(sbuf.st_mode) || 486 S_ISFIFO(sbuf.st_mode)) && 487 (sbuf.st_mode & S_IROTH)) { 488 (void) printf("Project: "); 489 catfile(s, sbuf.st_mode, 1); 490 (void) putchar('\n'); 491 } 492 free(s); 493 } 494 } 495 if (plan) { 496 struct stat sbuf; 497 498 s = malloc(strlen(p->pwd->pw_dir) + 499 sizeof (PLAN)); 500 if (s) { 501 (void) strcpy(s, p->pwd->pw_dir); 502 (void) strcat(s, PLAN); 503 if (stat(s, &sbuf) == -1 || 504 (!S_ISREG(sbuf.st_mode) && 505 !S_ISFIFO(sbuf.st_mode)) || 506 ((sbuf.st_mode & S_IROTH) == 0)) 507 (void) printf("No Plan.\n"); 508 else { 509 (void) printf("Plan:\n"); 510 catfile(s, sbuf.st_mode, 0); 511 } 512 free(s); 513 } 514 } 515 } 516 if (p->link != 0) 517 (void) putchar('\n'); 518 } 519 } 520 521 /* 522 * Duplicate a pwd entry. 523 * Note: Only the useful things (what the program currently uses) are copied. 524 */ 525 struct passwd * 526 pwdcopy(const struct passwd *pfrom) 527 { 528 struct passwd *pto; 529 530 pto = malloc(sizeof (*pto)); 531 pto->pw_name = strdup(pfrom->pw_name); 532 pto->pw_uid = pfrom->pw_uid; 533 pto->pw_gecos = strdup(pfrom->pw_gecos); 534 pto->pw_dir = strdup(pfrom->pw_dir); 535 pto->pw_shell = strdup(pfrom->pw_shell); 536 return (pto); 537 } 538 539 /* 540 * print out information on quick format giving just name, tty, login time 541 * and idle time if idle is set. 542 */ 543 void 544 quickprint(struct person *pers) 545 { 546 (void) printf("%-8.8s ", pers->name); 547 if (pers->loggedin) { 548 if (idle) { 549 findidle(pers); 550 (void) printf("%c%-12s %-16.16s", 551 pers->writable ? ' ' : '*', 552 pers->tty, ctime(&pers->loginat)); 553 ltimeprint(" ", &pers->idletime, ""); 554 } else { 555 (void) printf(" %-12s %-16.16s", 556 pers->tty, ctime(&pers->loginat)); 557 } 558 (void) putchar('\n'); 559 } else { 560 (void) printf(" Not Logged In\n"); 561 } 562 } 563 564 /* 565 * print out information in short format, giving login name, full name, 566 * tty, idle time, login time, and host. 567 */ 568 void 569 shortprint(struct person *pers) 570 { 571 char *p; 572 573 if (pers->pwd == 0) { 574 (void) printf("%-15s ???\n", pers->name); 575 return; 576 } 577 (void) printf("%-8s", pers->pwd->pw_name); 578 if (wide) { 579 if (pers->realname) { 580 (void) printf(" %-20.20s", pers->realname); 581 } else { 582 (void) printf(" ??? "); 583 } 584 } 585 (void) putchar(' '); 586 if (pers->loggedin && !pers->writable) { 587 (void) putchar('*'); 588 } else { 589 (void) putchar(' '); 590 } 591 if (*pers->tty) { 592 (void) printf("%-11.11s ", pers->tty); 593 } else { 594 (void) printf(" "); /* 12 spaces */ 595 } 596 p = ctime(&pers->loginat); 597 if (pers->loggedin) { 598 stimeprint(&pers->idletime); 599 (void) printf(" %3.3s %-5.5s ", p, p + 11); 600 } else if (pers->loginat == 0) { 601 (void) printf(" < . . . . >"); 602 } else if (tloc - pers->loginat >= 180 * 24 * 60 * 60) { 603 (void) printf(" <%-6.6s, %-4.4s>", p + 4, p + 20); 604 } else { 605 (void) printf(" <%-12.12s>", p + 4); 606 } 607 if (*pers->host) { 608 (void) printf(" %-20.20s", pers->host); 609 } else { 610 if (pers->ttyloc != NULL) 611 (void) printf(" %-20.20s", pers->ttyloc); 612 } 613 (void) putchar('\n'); 614 } 615 616 617 /* 618 * print out a person in long format giving all possible information. 619 * directory and shell are inhibited if unbrief is clear. 620 */ 621 void 622 personprint(struct person *pers) 623 { 624 if (pers->pwd == 0) { 625 (void) printf("Login name: %-10s\t\t\tIn real life: ???\n", 626 pers->name); 627 return; 628 } 629 (void) printf("Login name: %-10s", pers->pwd->pw_name); 630 if (pers->loggedin && !pers->writable) { 631 (void) printf(" (messages off) "); 632 } else { 633 (void) printf(" "); 634 } 635 if (pers->realname) { 636 (void) printf("In real life: %s", pers->realname); 637 } 638 if (unbrief) { 639 (void) printf("\nDirectory: %-25s", pers->pwd->pw_dir); 640 if (*pers->pwd->pw_shell) 641 (void) printf("\tShell: %-s", pers->pwd->pw_shell); 642 } 643 if (pers->loggedin) { 644 char *ep = ctime(&pers->loginat); 645 if (*pers->host) { 646 (void) printf("\nOn since %15.15s on %s from %s", 647 &ep[4], pers->tty, pers->host); 648 ltimeprint("\n", &pers->idletime, " Idle Time"); 649 } else { 650 (void) printf("\nOn since %15.15s on %-12s", 651 &ep[4], pers->tty); 652 ltimeprint("\n", &pers->idletime, " Idle Time"); 653 } 654 } else if (pers->loginat == 0) { 655 (void) printf("\nNever logged in."); 656 } else if (tloc - pers->loginat > 180 * 24 * 60 * 60) { 657 char *ep = ctime(&pers->loginat); 658 (void) printf("\nLast login %10.10s, %4.4s on %s", 659 ep, ep+20, pers->tty); 660 if (*pers->host) { 661 (void) printf(" from %s", pers->host); 662 } 663 } else { 664 char *ep = ctime(&pers->loginat); 665 (void) printf("\nLast login %16.16s on %s", ep, pers->tty); 666 if (*pers->host) { 667 (void) printf(" from %s", pers->host); 668 } 669 } 670 (void) putchar('\n'); 671 } 672 673 674 /* 675 * decode the information in the gecos field of /etc/passwd 676 */ 677 void 678 decode(struct person *pers) 679 { 680 char buffer[256]; 681 char *bp, *gp, *lp; 682 683 pers->realname = 0; 684 if (pers->pwd == 0) 685 return; 686 gp = pers->pwd->pw_gecos; 687 bp = buffer; 688 689 if (gecos_ignore_c != '\0' && 690 *gp == gecos_ignore_c) { 691 gp++; 692 } 693 while (*gp != '\0' && 694 *gp != gecos_sep_c) { /* name */ 695 if (*gp == gecos_samename) { 696 lp = pers->pwd->pw_name; 697 if (islower(*lp)) 698 *bp++ = toupper(*lp++); 699 while (*bp++ = *lp++) 700 ; 701 bp--; 702 gp++; 703 } else { 704 *bp++ = *gp++; 705 } 706 } 707 *bp++ = 0; 708 if (bp > (buffer + 1)) 709 pers->realname = strdup(buffer); 710 if (pers->loggedin) 711 findidle(pers); 712 else 713 findwhen(pers); 714 } 715 716 /* 717 * find the last log in of a user by checking the LASTLOG file. 718 * the entry is indexed by the uid, so this can only be done if 719 * the uid is known (which it isn't in quick mode) 720 */ 721 void 722 fwopen(void) 723 { 724 if ((lf = fopen(LASTLOG, "r")) == (FILE *)NULL) 725 (void) fprintf(stderr, "finger: %s open error\n", LASTLOG); 726 } 727 728 void 729 findwhen(struct person *pers) 730 { 731 struct lastlog ll; 732 733 if (lf != (FILE *)NULL) { 734 if (fseeko(lf, (off_t)pers->pwd->pw_uid * (off_t)sizeof (ll), 735 SEEK_SET) == 0) { 736 if (fread((char *)&ll, sizeof (ll), 1, lf) == 1) { 737 int l_max, h_max; 738 739 l_max = min(LMAX, sizeof (ll.ll_line)); 740 h_max = min(HMAX, sizeof (ll.ll_host)); 741 742 bcopy(ll.ll_line, pers->tty, l_max); 743 pers->tty[l_max] = '\0'; 744 bcopy(ll.ll_host, pers->host, h_max); 745 pers->host[h_max] = '\0'; 746 pers->loginat = ll.ll_time; 747 } else { 748 if (ferror(lf)) 749 (void) fprintf(stderr, 750 "finger: %s read error\n", LASTLOG); 751 pers->tty[0] = 0; 752 pers->host[0] = 0; 753 pers->loginat = 0L; 754 } 755 } else { 756 (void) fprintf(stderr, "finger: %s fseeko error\n", 757 LASTLOG); 758 } 759 } else { 760 pers->tty[0] = 0; 761 pers->host[0] = 0; 762 pers->loginat = 0L; 763 } 764 } 765 766 void 767 fwclose(void) 768 { 769 if (lf != (FILE *)0) 770 (void) fclose(lf); 771 } 772 773 /* 774 * find the idle time of a user by doing a stat on /dev/tty??, 775 * where tty?? has been gotten from UTMPX_FILE, supposedly. 776 */ 777 void 778 findidle(struct person *pers) 779 { 780 struct stat ttystatus; 781 #ifdef sun 782 struct stat inputdevstatus; 783 #endif 784 #define TTYLEN (sizeof ("/dev/") - 1) 785 static char buffer[TTYLEN + LMAX + 1] = "/dev/"; 786 time_t t; 787 time_t lastinputtime; 788 789 (void) strcpy(buffer + TTYLEN, pers->tty); 790 buffer[TTYLEN+LMAX] = 0; 791 if (stat(buffer, &ttystatus) < 0) { 792 (void) fprintf(stderr, "finger: Can't stat %s\n", buffer); 793 exit(4); 794 } 795 lastinputtime = ttystatus.st_atime; 796 #ifdef sun 797 if (strcmp(pers->tty, "console") == 0) { 798 /* 799 * On the console, the user may be running a window system; if 800 * so, their activity will show up in the last-access times of 801 * "/dev/kbd" and "/dev/mouse", so take the minimum of the idle 802 * times on those two devices and "/dev/console" and treat that 803 * as the idle time. 804 */ 805 if (stat("/dev/kbd", &inputdevstatus) == 0) { 806 if (lastinputtime < inputdevstatus.st_atime) 807 lastinputtime = inputdevstatus.st_atime; 808 } 809 if (stat("/dev/mouse", &inputdevstatus) == 0) { 810 if (lastinputtime < inputdevstatus.st_atime) 811 lastinputtime = inputdevstatus.st_atime; 812 } 813 } 814 #endif 815 t = time(NULL); 816 if (t < lastinputtime) 817 pers->idletime = (time_t)0; 818 else 819 pers->idletime = t - lastinputtime; 820 pers->writable = (ttystatus.st_mode & TALKABLE) == TALKABLE; 821 } 822 823 /* 824 * print idle time in short format; this program always prints 4 characters; 825 * if the idle time is zero, it prints 4 blanks. 826 */ 827 void 828 stimeprint(time_t *dt) 829 { 830 struct tm *delta; 831 832 delta = gmtime(dt); 833 if (delta->tm_yday == 0) 834 if (delta->tm_hour == 0) 835 if (delta->tm_min == 0) 836 (void) printf(" "); 837 else 838 (void) printf(" %2d", delta->tm_min); 839 else 840 if (delta->tm_hour >= 10) 841 (void) printf("%3d:", delta->tm_hour); 842 else 843 (void) printf("%1d:%02d", 844 delta->tm_hour, delta->tm_min); 845 else 846 (void) printf("%3dd", delta->tm_yday); 847 } 848 849 /* 850 * print idle time in long format with care being taken not to pluralize 851 * 1 minutes or 1 hours or 1 days. 852 * print "prefix" first. 853 */ 854 void 855 ltimeprint(char *before, time_t *dt, char *after) 856 { 857 struct tm *delta; 858 859 delta = gmtime(dt); 860 if (delta->tm_yday == 0 && delta->tm_hour == 0 && delta->tm_min == 0 && 861 delta->tm_sec <= 10) 862 return; 863 (void) printf("%s", before); 864 if (delta->tm_yday >= 10) 865 (void) printf("%d days", delta->tm_yday); 866 else if (delta->tm_yday > 0) 867 (void) printf("%d day%s %d hour%s", 868 delta->tm_yday, delta->tm_yday == 1 ? "" : "s", 869 delta->tm_hour, delta->tm_hour == 1 ? "" : "s"); 870 else 871 if (delta->tm_hour >= 10) 872 (void) printf("%d hours", delta->tm_hour); 873 else if (delta->tm_hour > 0) 874 (void) printf("%d hour%s %d minute%s", 875 delta->tm_hour, delta->tm_hour == 1 ? "" : "s", 876 delta->tm_min, delta->tm_min == 1 ? "" : "s"); 877 else 878 if (delta->tm_min >= 10) 879 (void) printf("%2d minutes", delta->tm_min); 880 else if (delta->tm_min == 0) 881 (void) printf("%2d seconds", delta->tm_sec); 882 else 883 (void) printf("%d minute%s %d second%s", 884 delta->tm_min, 885 delta->tm_min == 1 ? "" : "s", 886 delta->tm_sec, 887 delta->tm_sec == 1 ? "" : "s"); 888 (void) printf("%s", after); 889 } 890 891 /* 892 * The grammar of the pw_gecos field is sufficiently complex that the 893 * best way to parse it is by using an explicit finite-state machine, 894 * in which a table defines the rules of interpretation. 895 * 896 * Some special rules are necessary to handle the fact that names 897 * may contain certain punctuation characters. At this writing, 898 * the possible punctuation characters are '.', '-', and '_'. 899 * 900 * Other rules are needed to account for characters that require special 901 * processing when they appear in the pw_gecos field. At present, there 902 * are three such characters, with these default values and effects: 903 * 904 * gecos_ignore_c '*' This character is ignored. 905 * gecos_sep_c ',' Delimits displayed and nondisplayed contents. 906 * gecos_samename '&' Copies the login name into the output. 907 * 908 * As the program examines each successive character in the returned 909 * pw_gecos value, it fetches (from the table) the FSM rule applicable 910 * for that character in the current machine state, and thus determines 911 * the next state. 912 * 913 * The possible states are: 914 * S0 start 915 * S1 in a word 916 * S2 not in a word 917 * S3 copy login name into output 918 * S4 end of GECOS field 919 * 920 * Here follows a depiction of the state transitions. 921 * 922 * 923 * gecos_ignore_c OR isspace OR any other character 924 * +--+ 925 * | | 926 * | V 927 * +-----+ 928 * NULL OR | S0 | isalpha OR isdigit 929 * +---------------|start|------------------------+ 930 * | gecos_sep_c +-----+ | isalpha OR isdigit 931 * | | | | +---------------------+ 932 * | | | | | OR '.' '-' '_' | 933 * | | |isspace | | | 934 * | | +-------+ V V | 935 * | | | +-----------+ | 936 * | | | | S1 |<--+ | 937 * | | | | in a word | | isalpha OR | 938 * | | | +-----------+ | isdigit OR | 939 * | | | | | | | | '.' '-' '_' | 940 * | | +----- ---------------+ | | +-----+ | 941 * | | | | | | | 942 * | | | | gecos_ignore_c | | | 943 * | | | | isspace | | | 944 * | | | | ispunct/other | | | 945 * | | | | any other char | | | 946 * | | | | +---------------+ | | 947 * | | | | | |NULL OR gecos_sep_c | 948 * | | | | | +------------------+ | 949 * | gecos_samename| | V V | | 950 * | +-------------+ | +---------------+ | | 951 * | | | | S2 | isspace OR '.' '-' '_' | | 952 * | | gecos_samename | | not in a word |<---------------------+ | | 953 * | | +---------------+ +---------------+ OR gecos_ignore_c | | | 954 * | | | | ^ | | OR ispunct OR other | | | 955 * | | | | | | | | | | 956 * | | | gecos_samename | | | +-----------------------+ | | 957 * | | | +---------------------+ | | | | 958 * | | | | | | | | 959 * | | | | gecos_ignore_c| | NULL OR gecos_sep_c | | 960 * | | | | gecos_samename| +-----------------------+ | | 961 * | | | | ispunct/other | | | | 962 * | V V V isspace | | | | 963 * | +-----------------+ any other char| | | | 964 * | | S3 |---------------+ isalpha OR isdigit OR | | | 965 * | |insert login name|------------------------------------------ ----- ---+ 966 * | +-----------------+ '.' '-' '_' | | 967 * | | NULL OR gecos_sep_c | | 968 * | +------------------------------------------+ | | 969 * | | | | 970 * | V V V 971 * | +------------+ 972 * | NULL OR gecos_sep_c | S4 | 973 * +-------------------------------------------------------->|end of gecos|<--+ 974 * +------------+ | 975 * | all | 976 * +-----+ 977 * 978 * 979 * The transitions from the above diagram are summarized in 980 * the following table of target states, which is implemented 981 * in code as the gecos_fsm array. 982 * 983 * Input: 984 * +--gecos_ignore_c 985 * | +--gecos_sep_c 986 * | | +--gecos_samename 987 * | | | +--isalpha 988 * | | | | +--isdigit 989 * | | | | | +--isspace 990 * | | | | | | +--punctuation possible in name 991 * | | | | | | | +--other punctuation 992 * | | | | | | | | +--NULL character 993 * | | | | | | | | | +--any other character 994 * | | | | | | | | | | 995 * V V V V V V V V V V 996 * From: --------------------------------------------------- 997 * S0 | S0 | S4 | S3 | S1 | S1 | S0 | S1 | S2 | S4 | S0 | 998 * S1 | S2 | S4 | S3 | S1 | S1 | S2 | S1 | S2 | S4 | S2 | 999 * S2 | S2 | S4 | S3 | S1 | S1 | S2 | S2 | S2 | S4 | S2 | 1000 * S3 | S2 | S4 | S2 | S1 | S1 | S2 | S1 | S2 | S4 | S2 | 1001 * S4 | S4 | S4 | S4 | S4 | S4 | S4 | S4 | S4 | S4 | S4 | 1002 * 1003 */ 1004 1005 /* 1006 * Data types and structures for scanning the pw_gecos field. 1007 */ 1008 typedef enum gecos_state { 1009 S0, /* start */ 1010 S1, /* in a word */ 1011 S2, /* not in a word */ 1012 S3, /* copy login */ 1013 S4 /* end of gecos */ 1014 } gecos_state_t; 1015 1016 #define GFSM_ROWS 5 1017 #define GFSM_COLS 10 1018 1019 gecos_state_t gecos_fsm[GFSM_ROWS][GFSM_COLS] = { 1020 {S0, S4, S3, S1, S1, S0, S1, S2, S4, S0}, /* S0 */ 1021 {S2, S4, S3, S1, S1, S2, S1, S2, S4, S2}, /* S1 */ 1022 {S2, S4, S3, S1, S1, S2, S2, S2, S4, S2}, /* S2 */ 1023 {S2, S4, S2, S1, S1, S2, S1, S2, S4, S2}, /* S3 */ 1024 {S4, S4, S4, S4, S4, S4, S4, S4, S4, S4} /* S4 */ 1025 }; 1026 1027 /* 1028 * Scan the pw_gecos field according to defined state table; 1029 * return the next state according the the rules. 1030 */ 1031 gecos_state_t 1032 gecos_scan_state(gecos_state_t instate, char ch) 1033 { 1034 if (ch == gecos_ignore_c) { 1035 return (gecos_fsm[instate][0]); 1036 } else if (ch == gecos_sep_c) { 1037 return (gecos_fsm[instate][1]); 1038 } else if (ch == gecos_samename) { 1039 return (gecos_fsm[instate][2]); 1040 } else if (isalpha(ch)) { 1041 return (gecos_fsm[instate][3]); 1042 } else if (isdigit(ch)) { 1043 return (gecos_fsm[instate][4]); 1044 } else if (isspace(ch)) { 1045 return (gecos_fsm[instate][5]); 1046 } else if (ch == '.' || ch == '-' || ch == '_') { 1047 return (gecos_fsm[instate][6]); 1048 } else if (ispunct(ch)) { 1049 return (gecos_fsm[instate][7]); 1050 } else if (ch == '\0') { 1051 return (gecos_fsm[instate][8]); 1052 } 1053 return (gecos_fsm[instate][9]); 1054 } 1055 1056 1057 /* 1058 * Compare the given argument, which is taken to be a username, with 1059 * the login name and with strings in the the pw_gecos field. 1060 */ 1061 int 1062 matchcmp(char *gname, char *login, char *given) 1063 { 1064 char buffer[100]; 1065 char *bp, *lp, *gp; 1066 1067 gecos_state_t kstate = S0; 1068 gecos_state_t kstate_next = S0; 1069 1070 if (*gname == '\0' && *given == '\0') 1071 return (1); 1072 1073 bp = buffer; 1074 gp = gname; 1075 1076 do { 1077 kstate_next = gecos_scan_state(kstate, *gp); 1078 1079 switch (kstate_next) { 1080 1081 case S0: 1082 gp++; 1083 break; 1084 case S1: 1085 if (bp < buffer + sizeof (buffer)) { 1086 *bp++ = *gp++; 1087 } 1088 break; 1089 case S2: 1090 if (kstate == S1 || kstate == S3) { 1091 *bp++ = ' '; 1092 } 1093 gp++; 1094 break; 1095 case S3: 1096 lp = login; 1097 do { 1098 *bp++ = *lp++; 1099 } while (*bp != '\0' && bp < buffer + sizeof (buffer)); 1100 bp--; 1101 break; 1102 case S4: 1103 *bp++ = '\0'; 1104 break; 1105 default: 1106 *bp++ = '\0'; 1107 break; 1108 } 1109 kstate = kstate_next; 1110 1111 } while ((bp < buffer + sizeof (buffer)) && kstate != S4); 1112 1113 gp = strtok(buffer, " "); 1114 1115 while (gp != NULL) { 1116 if (namecmp(gp, given) > 0) { 1117 return (1); 1118 } 1119 gp = strtok(NULL, " "); 1120 } 1121 return (0); 1122 } 1123 1124 /* 1125 * Perform the character-by-character comparison. 1126 * It is intended that "finger foo" should match "foo2", but an argument 1127 * consisting entirely of digits should not be matched too broadly. 1128 * Also, we do not want "finger foo123" to match "Mr. Foo" in the gecos. 1129 */ 1130 int 1131 namecmp(char *name1, char *name2) 1132 { 1133 char c1, c2; 1134 boolean_t alphaseen = B_FALSE; 1135 boolean_t digitseen = B_FALSE; 1136 1137 for (;;) { 1138 c1 = *name1++; 1139 if (isalpha(c1)) 1140 alphaseen = B_TRUE; 1141 if (isdigit(c1)) 1142 digitseen = B_TRUE; 1143 if (isupper(c1)) 1144 c1 = tolower(c1); 1145 1146 c2 = *name2++; 1147 if (isupper(c2)) 1148 c2 = tolower(c2); 1149 1150 if (c1 != c2) 1151 break; 1152 if (c1 == '\0') 1153 return (1); 1154 } 1155 if (!c1) { 1156 for (name2--; isdigit(*name2); name2++) 1157 ; 1158 if (*name2 == '\0' && digitseen) { 1159 return (1); 1160 } 1161 } else if (!c2) { 1162 for (name1--; isdigit(*name1); name1++) 1163 ; 1164 if (*name1 == '\0' && alphaseen) { 1165 return (1); 1166 } 1167 } 1168 return (0); 1169 } 1170 1171 1172 int 1173 netfinger(char *name) 1174 { 1175 char *host; 1176 struct hostent *hp; 1177 struct sockaddr_in6 sin6; 1178 struct in6_addr ipv6addr; 1179 struct in_addr ipv4addr; 1180 int s; 1181 FILE *f; 1182 int c; 1183 int lastc; 1184 char abuf[INET6_ADDRSTRLEN]; 1185 int error_num; 1186 1187 if (name == NULL) 1188 return (0); 1189 host = strrchr(name, '@'); 1190 if (host == NULL) 1191 return (0); 1192 *host++ = 0; 1193 1194 if ((hp = getipnodebyname(host, AF_INET6, AI_ALL | AI_ADDRCONFIG | 1195 AI_V4MAPPED, &error_num)) == NULL) { 1196 if (error_num == TRY_AGAIN) { 1197 (void) fprintf(stderr, 1198 "unknown host: %s (try again later)\n", host); 1199 } else { 1200 (void) fprintf(stderr, "unknown host: %s\n", host); 1201 } 1202 return (1); 1203 } 1204 1205 /* 1206 * If hp->h_name is a IPv4-mapped IPv6 literal, we'll convert it to 1207 * IPv4 literal address. 1208 */ 1209 if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) && 1210 IN6_IS_ADDR_V4MAPPED(&ipv6addr)) { 1211 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr); 1212 (void) printf("[%s] ", inet_ntop(AF_INET, &ipv4addr, abuf, 1213 sizeof (abuf))); 1214 } else { 1215 (void) printf("[%s] ", hp->h_name); 1216 } 1217 bzero(&sin6, sizeof (sin6)); 1218 sin6.sin6_family = hp->h_addrtype; 1219 bcopy(hp->h_addr_list[0], (char *)&sin6.sin6_addr, hp->h_length); 1220 sin6.sin6_port = htons(IPPORT_FINGER); 1221 s = socket(sin6.sin6_family, SOCK_STREAM, 0); 1222 if (s < 0) { 1223 (void) fflush(stdout); 1224 perror("socket"); 1225 freehostent(hp); 1226 return (1); 1227 } 1228 while (connect(s, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) { 1229 1230 if (hp && hp->h_addr_list[1]) { 1231 1232 hp->h_addr_list++; 1233 bcopy(hp->h_addr_list[0], 1234 (caddr_t)&sin6.sin6_addr, hp->h_length); 1235 (void) close(s); 1236 s = socket(sin6.sin6_family, SOCK_STREAM, 0); 1237 if (s < 0) { 1238 (void) fflush(stdout); 1239 perror("socket"); 1240 freehostent(hp); 1241 return (0); 1242 } 1243 continue; 1244 } 1245 1246 (void) fflush(stdout); 1247 perror("connect"); 1248 (void) close(s); 1249 freehostent(hp); 1250 return (1); 1251 } 1252 freehostent(hp); 1253 hp = NULL; 1254 1255 (void) printf("\n"); 1256 if (large) 1257 (void) write(s, "/W ", 3); 1258 (void) write(s, name, strlen(name)); 1259 (void) write(s, "\r\n", 2); 1260 f = fdopen(s, "r"); 1261 1262 lastc = '\n'; 1263 while ((c = getc(f)) != EOF) { 1264 /* map CRLF -> newline */ 1265 if ((lastc == '\r') && (c != '\n')) 1266 /* print out saved CR */ 1267 (void) putchar('\r'); 1268 lastc = c; 1269 if (c == '\r') 1270 continue; 1271 (void) putchar(c); 1272 } 1273 1274 if (lastc != '\n') 1275 (void) putchar('\n'); 1276 (void) fclose(f); 1277 return (1); 1278 } 1279 1280 /* 1281 * AnyMail - takes a username (string pointer thereto), and 1282 * prints on standard output whether there is any unread mail, 1283 * and if so, how old it is. (JCM@Shasta 15 March 80) 1284 */ 1285 void 1286 AnyMail(char *name) 1287 { 1288 struct stat buf; /* space for file status buffer */ 1289 char *mbxdir = MAILDIR; /* string with path preamble */ 1290 char *mbxpath; /* space for entire pathname */ 1291 1292 char *timestr; 1293 1294 mbxpath = malloc(strlen(name) + strlen(MAILDIR) + 1); 1295 if (mbxpath == (char *)NULL) 1296 return; 1297 1298 (void) strcpy(mbxpath, mbxdir); /* copy preamble into path name */ 1299 (void) strcat(mbxpath, name); /* concatenate user name to path */ 1300 1301 if (stat(mbxpath, &buf) == -1 || buf.st_size == 0) { 1302 /* Mailbox is empty or nonexistent */ 1303 (void) printf("No unread mail\n"); 1304 } else { 1305 if (buf.st_mtime < buf.st_atime) { 1306 /* 1307 * No new mail since the last time the user read it. 1308 */ 1309 (void) printf("Mail last read "); 1310 (void) printf("%s", ctime(&buf.st_atime)); 1311 } else if (buf.st_mtime > buf.st_atime) { 1312 /* 1313 * New mail has definitely arrived since the last time 1314 * mail was read. mtime is the time the most recent 1315 * message arrived; atime is either the time the oldest 1316 * unread message arrived, or the last time the mail 1317 * was read. 1318 */ 1319 (void) printf("New mail received "); 1320 timestr = ctime(&buf.st_mtime); /* time last modified */ 1321 timestr[24] = '\0'; /* suppress newline (ugh) */ 1322 (void) printf("%s", timestr); 1323 (void) printf(";\n unread since "); 1324 (void) printf("%s", ctime(&buf.st_atime)); 1325 } else { 1326 /* 1327 * There is something in mailbox, but we can't really 1328 * be sure whether it is mail held there by the user 1329 * or a (single) new message that was placed in a newly 1330 * recreated mailbox, so punt and call it "unread mail." 1331 */ 1332 (void) printf("Unread mail since "); 1333 (void) printf("%s", ctime(&buf.st_mtime)); 1334 } 1335 } 1336 free(mbxpath); 1337 } 1338 1339 /* 1340 * return true iff we've already printed project/plan for this uid; 1341 * if not, enter this uid into table (so this function has a side-effect.) 1342 */ 1343 #define PPMAX 4096 /* assume no more than 4096 logged-in users */ 1344 uid_t PlanPrinted[PPMAX+1]; 1345 int PPIndex = 0; /* index of next unused table entry */ 1346 1347 int 1348 AlreadyPrinted(uid_t uid) 1349 { 1350 int i = 0; 1351 1352 while (i++ < PPIndex) { 1353 if (PlanPrinted[i] == uid) 1354 return (1); 1355 } 1356 if (i < PPMAX) { 1357 PlanPrinted[i] = uid; 1358 PPIndex++; 1359 } 1360 return (0); 1361 } 1362 1363 #define FIFOREADTIMEOUT (60) /* read timeout on select */ 1364 /* BEGIN CSTYLED */ 1365 #define PRINT_CHAR(c) \ 1366 ( \ 1367 ((termpass & TS_HIGH) && ((int)c) > 126) \ 1368 || \ 1369 (isascii((int)c) && \ 1370 (isprint((int)c) || isspace((int)c)) \ 1371 ) \ 1372 || \ 1373 ((termpass & TS_LOW) && ((int)c) < 32) \ 1374 ) 1375 /* END CSTYLED */ 1376 1377 1378 void 1379 catfile(char *s, mode_t mode, int trunc_at_nl) 1380 { 1381 if (S_ISFIFO(mode)) { 1382 int fd; 1383 1384 fd = open(s, O_RDONLY | O_NONBLOCK); 1385 if (fd != -1) { 1386 fd_set readfds, exceptfds; 1387 struct timeval tv; 1388 1389 FD_ZERO(&readfds); 1390 FD_ZERO(&exceptfds); 1391 FD_SET(fd, &readfds); 1392 FD_SET(fd, &exceptfds); 1393 1394 timerclear(&tv); 1395 tv.tv_sec = FIFOREADTIMEOUT; 1396 1397 (void) fflush(stdout); 1398 while (select(fd + 1, &readfds, (fd_set *) 0, 1399 &exceptfds, &tv) != -1) { 1400 unsigned char buf[BUFSIZ]; 1401 int nread; 1402 1403 nread = read(fd, buf, sizeof (buf)); 1404 if (nread > 0) { 1405 unsigned char *p; 1406 1407 FD_SET(fd, &readfds); 1408 FD_SET(fd, &exceptfds); 1409 for (p = buf; p < buf + nread; p++) { 1410 if (trunc_at_nl && *p == '\n') 1411 goto out; 1412 if (PRINT_CHAR(*p)) 1413 (void) putchar((int)*p); 1414 else if (isascii(*p)) 1415 (void) fputs(unctrl(*p), 1416 stdout); 1417 } 1418 } else 1419 break; 1420 } 1421 out: 1422 (void) close(fd); 1423 } 1424 } else { 1425 int c; 1426 FILE *fp; 1427 1428 fp = fopen(s, "r"); 1429 if (fp) { 1430 while ((c = getc(fp)) != EOF) { 1431 if (trunc_at_nl && c == '\n') 1432 break; 1433 if (PRINT_CHAR(c)) 1434 (void) putchar((int)c); 1435 else 1436 if (isascii(c)) 1437 (void) fputs(unctrl(c), stdout); 1438 } 1439 (void) fclose(fp); 1440 } 1441 } 1442 } 1443 1444 1445 void 1446 initscreening(void) 1447 { 1448 char *options, *value; 1449 1450 if (defopen(defaultfile) == 0) { 1451 char *cp; 1452 int flags; 1453 1454 /* 1455 * ignore case 1456 */ 1457 flags = defcntl(DC_GETFLAGS, 0); 1458 TURNOFF(flags, DC_CASE); 1459 (void) defcntl(DC_SETFLAGS, flags); 1460 1461 if (cp = defread(passvar)) { 1462 options = cp; 1463 while (*options != '\0') 1464 switch (getsubopt(&options, termopts, &value)) { 1465 case TERM_LOW: 1466 termpass |= TS_LOW; 1467 break; 1468 case TERM_HIGH: 1469 termpass |= TS_HIGH; 1470 break; 1471 } 1472 } 1473 (void) defopen(NULL); /* close default file */ 1474 } 1475 } 1476