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