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