1 /* $Header: /src/pub/tcsh/tc.who.c,v 3.37 2002/11/21 20:02:01 christos Exp $ */ 2 /* 3 * tc.who.c: Watch logins and logouts... 4 */ 5 /*- 6 * Copyright (c) 1980, 1991 The Regents of the University of California. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 #include "sh.h" 34 35 RCSID("$Id: tc.who.c,v 3.37 2002/11/21 20:02:01 christos Exp $") 36 37 #include "tc.h" 38 39 #ifndef HAVENOUTMP 40 /* 41 * kfk 26 Jan 1984 - for login watch functions. 42 */ 43 #include <ctype.h> 44 45 #ifdef HAVEUTMPX 46 # include <utmpx.h> 47 /* I just redefine a few words here. Changing every occurrence below 48 * seems like too much of work. All UTMP functions have equivalent 49 * UTMPX counterparts, so they can be added all here when needed. 50 * Kimmo Suominen, Oct 14 1991 51 */ 52 # ifndef _PATH_UTMP 53 # if defined(__UTMPX_FILE) && !defined(UTMPX_FILE) 54 # define _PATH_UTMP __UTMPX_FILE 55 # elif defined(_PATH_UTMPX) 56 # define _PATH_UTMP _PATH_UTMPX 57 # else 58 # define _PATH_UTMP UTMPX_FILE 59 # endif /* __UTMPX_FILE && !UTMPX_FILE */ 60 # endif /* _PATH_UTMP */ 61 # define utmp utmpx 62 # ifdef __MVS__ 63 # define ut_time ut_tv.tv_sec 64 # define ut_name ut_user 65 # else 66 # define ut_time ut_xtime 67 # endif /* __MVS__ */ 68 #else /* !HAVEUTMPX */ 69 # ifndef WINNT_NATIVE 70 # include <utmp.h> 71 # endif /* WINNT_NATIVE */ 72 #endif /* HAVEUTMPX */ 73 74 #ifndef BROKEN_CC 75 # define UTNAMLEN sizeof(((struct utmp *) 0)->ut_name) 76 # define UTLINLEN sizeof(((struct utmp *) 0)->ut_line) 77 # ifdef UTHOST 78 # ifdef _SEQUENT_ 79 # define UTHOSTLEN 100 80 # else 81 # define UTHOSTLEN sizeof(((struct utmp *) 0)->ut_host) 82 # endif 83 # endif /* UTHOST */ 84 #else 85 /* give poor cc a little help if it needs it */ 86 struct utmp __ut; 87 88 # define UTNAMLEN sizeof(__ut.ut_name) 89 # define UTLINLEN sizeof(__ut.ut_line) 90 # ifdef UTHOST 91 # ifdef _SEQUENT_ 92 # define UTHOSTLEN 100 93 # else 94 # define UTHOSTLEN sizeof(__ut.ut_host) 95 # endif 96 # endif /* UTHOST */ 97 #endif /* BROKEN_CC */ 98 99 #ifndef _PATH_UTMP 100 # ifdef UTMP_FILE 101 # define _PATH_UTMP UTMP_FILE 102 # else 103 # define _PATH_UTMP "/etc/utmp" 104 # endif /* UTMP_FILE */ 105 #endif /* _PATH_UTMP */ 106 107 108 struct who { 109 struct who *who_next; 110 struct who *who_prev; 111 char who_name[UTNAMLEN + 1]; 112 char who_new[UTNAMLEN + 1]; 113 char who_tty[UTLINLEN + 1]; 114 #ifdef UTHOST 115 char who_host[UTHOSTLEN + 1]; 116 #endif /* UTHOST */ 117 time_t who_time; 118 int who_status; 119 }; 120 121 static struct who whohead, whotail; 122 static time_t watch_period = 0; 123 static time_t stlast = 0; 124 #ifdef WHODEBUG 125 static void debugwholist __P((struct who *, struct who *)); 126 #endif 127 static void print_who __P((struct who *)); 128 129 130 #define ONLINE 01 131 #define OFFLINE 02 132 #define CHANGED 04 133 #define STMASK 07 134 #define ANNOUNCE 010 135 136 /* 137 * Karl Kleinpaste, 26 Jan 1984. 138 * Initialize the dummy tty list for login watch. 139 * This dummy list eliminates boundary conditions 140 * when doing pointer-chase searches. 141 */ 142 void 143 initwatch() 144 { 145 whohead.who_next = &whotail; 146 whotail.who_prev = &whohead; 147 stlast = 1; 148 #ifdef WHODEBUG 149 debugwholist(NULL, NULL); 150 #endif /* WHODEBUG */ 151 } 152 153 void 154 resetwatch() 155 { 156 watch_period = 0; 157 stlast = 0; 158 } 159 160 /* 161 * Karl Kleinpaste, 26 Jan 1984. 162 * Watch /etc/utmp for login/logout changes. 163 */ 164 void 165 watch_login(force) 166 int force; 167 { 168 int utmpfd, comp = -1, alldone; 169 int firsttime = stlast == 1; 170 #ifdef BSDSIGS 171 sigmask_t omask; 172 #endif /* BSDSIGS */ 173 struct utmp utmp; 174 struct who *wp, *wpnew; 175 struct varent *v; 176 Char **vp = NULL; 177 time_t t, interval = MAILINTVL; 178 struct stat sta; 179 #if defined(UTHOST) && defined(_SEQUENT_) 180 char *host, *ut_find_host(); 181 #endif 182 #ifdef WINNT_NATIVE 183 static int ncbs_posted = 0; 184 USE(utmp); 185 USE(utmpfd); 186 USE(sta); 187 USE(wpnew); 188 #endif /* WINNT_NATIVE */ 189 190 /* stop SIGINT, lest our login list get trashed. */ 191 #ifdef BSDSIGS 192 omask = sigblock(sigmask(SIGINT)); 193 #else 194 (void) sighold(SIGINT); 195 #endif 196 197 v = adrof(STRwatch); 198 if ((v == NULL || v->vec == NULL) && !force) { 199 #ifdef BSDSIGS 200 (void) sigsetmask(omask); 201 #else 202 (void) sigrelse(SIGINT); 203 #endif 204 return; /* no names to watch */ 205 } 206 if (!force) { 207 trim(vp = v->vec); 208 if (blklen(vp) % 2) /* odd # args: 1st == # minutes. */ 209 interval = (number(*vp)) ? (getn(*vp++) * 60) : MAILINTVL; 210 } 211 else 212 interval = 0; 213 214 (void) time(&t); 215 #ifdef WINNT_NATIVE 216 /* 217 * Since NCB_ASTATs take time, start em async at least 90 secs 218 * before we are due -amol 6/5/97 219 */ 220 if (!ncbs_posted) { 221 unsigned long tdiff = t - watch_period; 222 if (!watch_period || ((tdiff > 0) && (tdiff > (interval - 90)))) { 223 start_ncbs(vp); 224 ncbs_posted = 1; 225 } 226 } 227 #endif /* WINNT_NATIVE */ 228 if (t - watch_period < interval) { 229 #ifdef BSDSIGS 230 (void) sigsetmask(omask); 231 #else 232 (void) sigrelse(SIGINT); 233 #endif 234 return; /* not long enough yet... */ 235 } 236 watch_period = t; 237 #ifdef WINNT_NATIVE 238 ncbs_posted = 0; 239 #else /* !WINNT_NATIVE */ 240 241 /* 242 * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de> 243 * Don't open utmp all the time, stat it first... 244 */ 245 if (stat(_PATH_UTMP, &sta)) { 246 if (!force) 247 xprintf(CGETS(26, 1, 248 "cannot stat %s. Please \"unset watch\".\n"), 249 _PATH_UTMP); 250 # ifdef BSDSIGS 251 (void) sigsetmask(omask); 252 # else 253 (void) sigrelse(SIGINT); 254 # endif 255 return; 256 } 257 if (stlast == sta.st_mtime) { 258 # ifdef BSDSIGS 259 (void) sigsetmask(omask); 260 # else 261 (void) sigrelse(SIGINT); 262 # endif 263 return; 264 } 265 stlast = sta.st_mtime; 266 if ((utmpfd = open(_PATH_UTMP, O_RDONLY|O_LARGEFILE)) < 0) { 267 if (!force) 268 xprintf(CGETS(26, 2, 269 "%s cannot be opened. Please \"unset watch\".\n"), 270 _PATH_UTMP); 271 # ifdef BSDSIGS 272 (void) sigsetmask(omask); 273 # else 274 (void) sigrelse(SIGINT); 275 # endif 276 return; 277 } 278 279 /* 280 * xterm clears the entire utmp entry - mark everyone on the status list 281 * OFFLINE or we won't notice X "logouts" 282 */ 283 for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) { 284 wp->who_status = OFFLINE; 285 wp->who_time = 0; 286 } 287 288 /* 289 * Read in the utmp file, sort the entries, and update existing entries or 290 * add new entries to the status list. 291 */ 292 while (read(utmpfd, (char *) &utmp, sizeof utmp) == sizeof utmp) { 293 294 # ifdef DEAD_PROCESS 295 # ifndef IRIS4D 296 if (utmp.ut_type != USER_PROCESS) 297 continue; 298 # else 299 /* Why is that? Cause the utmp file is always corrupted??? */ 300 if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS) 301 continue; 302 # endif /* IRIS4D */ 303 # endif /* DEAD_PROCESS */ 304 305 if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0') 306 continue; /* completely void entry */ 307 # ifdef DEAD_PROCESS 308 if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0') 309 continue; 310 # endif /* DEAD_PROCESS */ 311 wp = whohead.who_next; 312 while (wp->who_next && (comp = strncmp(wp->who_tty, utmp.ut_line, UTLINLEN)) < 0) 313 wp = wp->who_next;/* find that tty! */ 314 315 if (wp->who_next && comp == 0) { /* found the tty... */ 316 # ifdef DEAD_PROCESS 317 if (utmp.ut_type == DEAD_PROCESS) { 318 wp->who_time = utmp.ut_time; 319 wp->who_status = OFFLINE; 320 } 321 else 322 # endif /* DEAD_PROCESS */ 323 if (utmp.ut_name[0] == '\0') { 324 wp->who_time = utmp.ut_time; 325 wp->who_status = OFFLINE; 326 } 327 else if (strncmp(utmp.ut_name, wp->who_name, UTNAMLEN) == 0) { 328 /* someone is logged in */ 329 wp->who_time = utmp.ut_time; 330 wp->who_status = 0; /* same guy */ 331 } 332 else { 333 (void) strncpy(wp->who_new, utmp.ut_name, UTNAMLEN); 334 # ifdef UTHOST 335 # ifdef _SEQUENT_ 336 host = ut_find_host(wp->who_tty); 337 if (host) 338 (void) strncpy(wp->who_host, host, UTHOSTLEN); 339 else 340 wp->who_host[0] = 0; 341 # else 342 (void) strncpy(wp->who_host, utmp.ut_host, UTHOSTLEN); 343 # endif 344 # endif /* UTHOST */ 345 wp->who_time = utmp.ut_time; 346 if (wp->who_name[0] == '\0') 347 wp->who_status = ONLINE; 348 else 349 wp->who_status = CHANGED; 350 } 351 } 352 else { /* new tty in utmp */ 353 wpnew = (struct who *) xcalloc(1, sizeof *wpnew); 354 (void) strncpy(wpnew->who_tty, utmp.ut_line, UTLINLEN); 355 # ifdef UTHOST 356 # ifdef _SEQUENT_ 357 host = ut_find_host(wpnew->who_tty); 358 if (host) 359 (void) strncpy(wpnew->who_host, host, UTHOSTLEN); 360 else 361 wpnew->who_host[0] = 0; 362 # else 363 (void) strncpy(wpnew->who_host, utmp.ut_host, UTHOSTLEN); 364 # endif 365 # endif /* UTHOST */ 366 wpnew->who_time = utmp.ut_time; 367 # ifdef DEAD_PROCESS 368 if (utmp.ut_type == DEAD_PROCESS) 369 wpnew->who_status = OFFLINE; 370 else 371 # endif /* DEAD_PROCESS */ 372 if (utmp.ut_name[0] == '\0') 373 wpnew->who_status = OFFLINE; 374 else { 375 (void) strncpy(wpnew->who_new, utmp.ut_name, UTNAMLEN); 376 wpnew->who_status = ONLINE; 377 } 378 # ifdef WHODEBUG 379 debugwholist(wpnew, wp); 380 # endif /* WHODEBUG */ 381 382 wpnew->who_next = wp; /* link in a new 'who' */ 383 wpnew->who_prev = wp->who_prev; 384 wpnew->who_prev->who_next = wpnew; 385 wp->who_prev = wpnew; /* linked in now */ 386 } 387 } 388 (void) close(utmpfd); 389 # if defined(UTHOST) && defined(_SEQUENT_) 390 endutent(); 391 # endif 392 #endif /* !WINNT_NATIVE */ 393 394 if (force || vp == NULL) 395 return; 396 397 /* 398 * The state of all logins is now known, so we can search the user's list 399 * of watchables to print the interesting ones. 400 */ 401 for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' && 402 *(vp + 1) != NULL && **(vp + 1) != '\0'; 403 vp += 2) { /* args used in pairs... */ 404 405 if (eq(*vp, STRany) && eq(*(vp + 1), STRany)) 406 alldone = 1; 407 408 for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) { 409 if (wp->who_status & ANNOUNCE || 410 (!eq(STRany, vp[0]) && 411 !Gmatch(str2short(wp->who_name), vp[0]) && 412 !Gmatch(str2short(wp->who_new), vp[0])) || 413 (!Gmatch(str2short(wp->who_tty), vp[1]) && 414 !eq(STRany, vp[1]))) 415 continue; /* entry doesn't qualify */ 416 /* already printed or not right one to print */ 417 418 419 if (wp->who_time == 0)/* utmp entry was cleared */ 420 wp->who_time = watch_period; 421 422 if ((wp->who_status & OFFLINE) && 423 (wp->who_name[0] != '\0')) { 424 if (!firsttime) 425 print_who(wp); 426 wp->who_name[0] = '\0'; 427 wp->who_status |= ANNOUNCE; 428 continue; 429 } 430 if (wp->who_status & ONLINE) { 431 if (!firsttime) 432 print_who(wp); 433 (void) strcpy(wp->who_name, wp->who_new); 434 wp->who_status |= ANNOUNCE; 435 continue; 436 } 437 if (wp->who_status & CHANGED) { 438 if (!firsttime) 439 print_who(wp); 440 (void) strcpy(wp->who_name, wp->who_new); 441 wp->who_status |= ANNOUNCE; 442 continue; 443 } 444 } 445 } 446 #ifdef BSDSIGS 447 (void) sigsetmask(omask); 448 #else 449 (void) sigrelse(SIGINT); 450 #endif 451 } 452 453 #ifdef WHODEBUG 454 static void 455 debugwholist(new, wp) 456 register struct who *new, *wp; 457 { 458 register struct who *a; 459 460 a = whohead.who_next; 461 while (a->who_next != NULL) { 462 xprintf("%s/%s -> ", a->who_name, a->who_tty); 463 a = a->who_next; 464 } 465 xprintf("TAIL\n"); 466 if (a != &whotail) { 467 xprintf(CGETS(26, 3, "BUG! last element is not whotail!\n")); 468 abort(); 469 } 470 a = whotail.who_prev; 471 xprintf(CGETS(26, 4, "backward: ")); 472 while (a->who_prev != NULL) { 473 xprintf("%s/%s -> ", a->who_name, a->who_tty); 474 a = a->who_prev; 475 } 476 xprintf("HEAD\n"); 477 if (a != &whohead) { 478 xprintf(CGETS(26, 5, "BUG! first element is not whohead!\n")); 479 abort(); 480 } 481 if (new) 482 xprintf(CGETS(26, 6, "new: %s/%s\n"), new->who_name, new->who_tty); 483 if (wp) 484 xprintf("wp: %s/%s\n", wp->who_name, wp->who_tty); 485 } 486 #endif /* WHODEBUG */ 487 488 489 static void 490 print_who(wp) 491 struct who *wp; 492 { 493 #ifdef UTHOST 494 Char *cp = str2short(CGETS(26, 7, "%n has %a %l from %m.")); 495 #else 496 Char *cp = str2short(CGETS(26, 8, "%n has %a %l.")); 497 #endif /* UTHOST */ 498 struct varent *vp = adrof(STRwho); 499 Char buf[BUFSIZE]; 500 501 if (vp && vp->vec && vp->vec[0]) 502 cp = vp->vec[0]; 503 504 tprintf(FMT_WHO, buf, cp, BUFSIZE, NULL, wp->who_time, (ptr_t) wp); 505 for (cp = buf; *cp;) 506 xputchar(*cp++); 507 xputchar('\n'); 508 } /* end print_who */ 509 510 511 const char * 512 who_info(ptr, c, wbuf, wbufsiz) 513 ptr_t ptr; 514 int c; 515 char *wbuf; 516 size_t wbufsiz; 517 { 518 struct who *wp = (struct who *) ptr; 519 #ifdef UTHOST 520 char *wb = wbuf; 521 int flg; 522 char *pb; 523 #endif /* UTHOST */ 524 525 switch (c) { 526 case 'n': /* user name */ 527 switch (wp->who_status & STMASK) { 528 case ONLINE: 529 case CHANGED: 530 return wp->who_new; 531 case OFFLINE: 532 return wp->who_name; 533 default: 534 break; 535 } 536 break; 537 538 case 'a': 539 switch (wp->who_status & STMASK) { 540 case ONLINE: 541 return CGETS(26, 9, "logged on"); 542 case OFFLINE: 543 return CGETS(26, 10, "logged off"); 544 case CHANGED: 545 xsnprintf(wbuf, wbufsiz, CGETS(26, 11, "replaced %s on"), 546 wp->who_name); 547 return wbuf; 548 default: 549 break; 550 } 551 break; 552 553 #ifdef UTHOST 554 case 'm': 555 if (wp->who_host[0] == '\0') 556 return CGETS(26, 12, "local"); 557 else { 558 /* the ':' stuff is for <host>:<display>.<screen> */ 559 for (pb = wp->who_host, flg = Isdigit(*pb) ? '\0' : '.'; 560 *pb != '\0' && 561 (*pb != flg || ((pb = strchr(pb, ':')) != 0)); 562 pb++) { 563 if (*pb == ':') 564 flg = '\0'; 565 *wb++ = Isupper(*pb) ? Tolower(*pb) : *pb; 566 } 567 *wb = '\0'; 568 return wbuf; 569 } 570 571 case 'M': 572 if (wp->who_host[0] == '\0') 573 return CGETS(26, 12, "local"); 574 else { 575 for (pb = wp->who_host; *pb != '\0'; pb++) 576 *wb++ = Isupper(*pb) ? Tolower(*pb) : *pb; 577 *wb = '\0'; 578 return wbuf; 579 } 580 #endif /* UTHOST */ 581 582 case 'l': 583 return wp->who_tty; 584 585 default: 586 wbuf[0] = '%'; 587 wbuf[1] = (char) c; 588 wbuf[2] = '\0'; 589 return wbuf; 590 } 591 return NULL; 592 } 593 594 void 595 /*ARGSUSED*/ 596 dolog(v, c) 597 Char **v; 598 struct command *c; 599 { 600 struct who *wp; 601 struct varent *vp; 602 603 USE(v); 604 USE(c); 605 vp = adrof(STRwatch); /* lint insists vp isn't used unless we */ 606 if (vp == NULL) /* unless we assign it outside the if */ 607 stderror(ERR_NOWATCH); 608 resetwatch(); 609 wp = whohead.who_next; 610 while (wp->who_next != NULL) { 611 wp->who_name[0] = '\0'; 612 wp = wp->who_next; 613 } 614 } 615 616 # ifdef UTHOST 617 size_t 618 utmphostsize() 619 { 620 return UTHOSTLEN; 621 } 622 623 char * 624 utmphost() 625 { 626 char *tty = short2str(varval(STRtty)); 627 struct who *wp; 628 char *host = NULL; 629 630 watch_login(1); 631 632 for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) { 633 if (strcmp(tty, wp->who_tty) == 0) 634 host = wp->who_host; 635 wp->who_name[0] = '\0'; 636 } 637 resetwatch(); 638 return host; 639 } 640 # endif /* UTHOST */ 641 642 #ifdef WINNT_NATIVE 643 void add_to_who_list(name, mach_nm) 644 char *name; 645 char *mach_nm; 646 { 647 648 struct who *wp, *wpnew; 649 int comp = -1; 650 651 wp = whohead.who_next; 652 while (wp->who_next && (comp = strncmp(wp->who_tty,mach_nm,UTLINLEN)) < 0) 653 wp = wp->who_next;/* find that tty! */ 654 655 if (wp->who_next && comp == 0) { /* found the tty... */ 656 657 if (*name == '\0') { 658 wp->who_time = 0; 659 wp->who_status = OFFLINE; 660 } 661 else if (strncmp(name, wp->who_name, UTNAMLEN) == 0) { 662 /* someone is logged in */ 663 wp->who_time = 0; 664 wp->who_status = 0; /* same guy */ 665 } 666 else { 667 (void) strncpy(wp->who_new, name, UTNAMLEN); 668 wp->who_time = 0; 669 if (wp->who_name[0] == '\0') 670 wp->who_status = ONLINE; 671 else 672 wp->who_status = CHANGED; 673 } 674 } 675 else { 676 wpnew = (struct who *) xcalloc(1, sizeof *wpnew); 677 (void) strncpy(wpnew->who_tty, mach_nm, UTLINLEN); 678 wpnew->who_time = 0; 679 if (*name == '\0') 680 wpnew->who_status = OFFLINE; 681 else { 682 (void) strncpy(wpnew->who_new, name, UTNAMLEN); 683 wpnew->who_status = ONLINE; 684 } 685 #ifdef WHODEBUG 686 debugwholist(wpnew, wp); 687 #endif /* WHODEBUG */ 688 689 wpnew->who_next = wp; /* link in a new 'who' */ 690 wpnew->who_prev = wp->who_prev; 691 wpnew->who_prev->who_next = wpnew; 692 wp->who_prev = wpnew; /* linked in now */ 693 } 694 } 695 #endif /* WINNT_NATIVE */ 696 #endif /* HAVENOUTMP */ 697