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