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