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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 23 /* All Rights Reserved */ 24 25 26 /* 27 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 28 * Use is subject to license terms. 29 * Copyright (c) 2016 by Delphix. All rights reserved. 30 * Copyright (c) 2018, Joyent, Inc. 31 */ 32 33 #pragma ident "%Z%%M% %I% %E% SMI" 34 35 #include <ctype.h> 36 #include <string.h> 37 #include <stdio.h> 38 #include <signal.h> 39 #include <sys/wait.h> 40 #include <sys/types.h> 41 #include <sys/stat.h> 42 #include <sys/utsname.h> 43 #include <stdlib.h> 44 #include <unistd.h> 45 #include <time.h> 46 #include <utmpx.h> 47 #include <pwd.h> 48 #include <fcntl.h> 49 #include <stdarg.h> 50 #include <locale.h> 51 #include <stdlib.h> 52 #include <limits.h> 53 #include <wctype.h> 54 #include <errno.h> 55 #include <syslog.h> 56 57 #define TRUE 1 58 #define FALSE 0 59 #define FAILURE -1 60 #define DATE_FMT "%a %b %e %H:%M:%S" 61 #define UTMP_HACK /* work around until utmpx is world writable */ 62 /* 63 * DATE-TIME format 64 * %a abbreviated weekday name 65 * %b abbreviated month name 66 * %e day of month 67 * %H hour - 24 hour clock 68 * %M minute 69 * %S second 70 * 71 */ 72 73 static int permit1(int); 74 static int permit(char *); 75 static int readcsi(int, char *, int); 76 static void setsignals(); 77 static void shellcmd(char *); 78 static void openfail(); 79 static void eof(); 80 81 static struct utsname utsn; 82 83 static FILE *fp; /* File pointer for receipient's terminal */ 84 static char *rterm, *receipient; /* Pointer to receipient's terminal & name */ 85 static char *thissys; 86 87 int 88 main(int argc, char **argv) 89 { 90 int i; 91 struct utmpx *ubuf; 92 static struct utmpx self; 93 char ownname[sizeof (self.ut_user) + 1]; 94 static char rterminal[sizeof ("/dev/") + sizeof (self.ut_line)] = 95 "/dev/"; 96 extern char *rterm, *receipient; 97 char *terminal, *ownterminal, *oterminal; 98 short count; 99 extern FILE *fp; 100 char input[134+MB_LEN_MAX]; 101 char *ptr; 102 time_t tod; 103 char time_buf[40]; 104 struct passwd *passptr; 105 char badterm[20][20]; 106 int bad = 0; 107 uid_t myuid; 108 char *bp; 109 int n; 110 wchar_t wc; 111 int c; 112 int newline; 113 114 (void) setlocale(LC_ALL, ""); 115 #if !defined(TEXT_DOMAIN) 116 #define TEXT_DOMAIN "SYS_TEST" 117 #endif 118 (void) textdomain(TEXT_DOMAIN); 119 120 while ((c = getopt(argc, argv, "")) != EOF) 121 switch (c) { 122 case '?': 123 (void) fprintf(stderr, "Usage: write %s\n", 124 gettext("user_name [terminal]")); 125 exit(2); 126 } 127 myuid = geteuid(); 128 uname(&utsn); 129 thissys = utsn.nodename; 130 131 /* Set "rterm" to location where receipient's terminal will go. */ 132 133 rterm = &rterminal[sizeof ("/dev/") - 1]; 134 terminal = NULL; 135 136 if (--argc <= 0) { 137 (void) fprintf(stderr, "Usage: write %s\n", 138 gettext("user_name [terminal]")); 139 exit(1); 140 } 141 else 142 { 143 receipient = *++argv; 144 } 145 146 /* Was a terminal name supplied? If so, save it. */ 147 148 if (--argc > 1) { 149 (void) fprintf(stderr, "Usage: write %s\n", 150 gettext("user_name [terminal]")); 151 exit(1); 152 } else { 153 terminal = *++argv; 154 } 155 156 /* One of the standard file descriptors must be attached to a */ 157 /* terminal in "/dev". */ 158 159 if ((ownterminal = ttyname(fileno(stdin))) == NULL && 160 (ownterminal = ttyname(fileno(stdout))) == NULL && 161 (ownterminal = ttyname(fileno(stderr))) == NULL) { 162 (void) fprintf(stderr, 163 gettext("I cannot determine your terminal name." 164 " No reply possible.\n")); 165 ownterminal = "/dev/???"; 166 } 167 168 /* 169 * Set "ownterminal" past the "/dev/" at the beginning of 170 * the device name. 171 */ 172 oterminal = ownterminal + sizeof ("/dev/")-1; 173 174 /* 175 * Scan through the "utmpx" file for your own entry and the 176 * entry for the person we want to send to. 177 */ 178 for (self.ut_pid = 0, count = 0; (ubuf = getutxent()) != NULL; ) { 179 /* Is this a USER_PROCESS entry? */ 180 181 if (ubuf->ut_type == USER_PROCESS) { 182 /* Is it our entry? (ie. The line matches ours?) */ 183 184 if (strncmp(&ubuf->ut_line[0], oterminal, 185 sizeof (ubuf->ut_line)) == 0) self = *ubuf; 186 187 /* Is this the person we want to send to? */ 188 189 if (strncmp(receipient, &ubuf->ut_user[0], 190 sizeof (ubuf->ut_user)) == 0) { 191 /* If a terminal name was supplied, is this login at the correct */ 192 /* terminal? If not, ignore. If it is right place, copy over the */ 193 /* name. */ 194 195 if (terminal != NULL) { 196 if (strncmp(terminal, &ubuf->ut_line[0], 197 sizeof (ubuf->ut_line)) == 0) { 198 strlcpy(rterm, &ubuf->ut_line[0], 199 sizeof (rterminal) - (rterm - rterminal)); 200 if (myuid && !permit(rterminal)) { 201 bad++; 202 rterm[0] = '\0'; 203 } 204 } 205 } 206 207 /* If no terminal was supplied, then take this terminal if no */ 208 /* other terminal has been encountered already. */ 209 210 else 211 { 212 /* If this is the first encounter, copy the string into */ 213 /* "rterminal". */ 214 215 if (*rterm == '\0') { 216 strlcpy(rterm, &ubuf->ut_line[0], 217 sizeof (rterminal) - (rterm - rterminal)); 218 if (myuid && !permit(rterminal)) { 219 if (bad < 20) { 220 strlcpy(badterm[bad++], rterm, 221 sizeof (badterm[bad++])); 222 } 223 rterm[0] = '\0'; 224 } else if (bad > 0) { 225 (void) fprintf(stderr, 226 gettext( 227 "%s is logged on more than one place.\n" 228 "You are connected to \"%s\".\nOther locations are:\n"), 229 receipient, rterm); 230 for (i = 0; i < bad; i++) 231 (void) fprintf(stderr, "%s\n", badterm[i]); 232 } 233 } 234 235 /* If this is the second terminal, print out the first. In all */ 236 /* cases of multiple terminals, list out all the other terminals */ 237 /* so the user can restart knowing what their choices are. */ 238 239 else if (terminal == NULL) { 240 if (count == 1 && bad == 0) { 241 (void) fprintf(stderr, 242 gettext( 243 "%s is logged on more than one place.\n" 244 "You are connected to \"%s\".\nOther locations are:\n"), 245 receipient, rterm); 246 } 247 fwrite(&ubuf->ut_line[0], sizeof (ubuf->ut_line), 248 1, stderr); 249 (void) fprintf(stderr, "\n"); 250 } 251 252 count++; 253 } /* End of "else" */ 254 } /* End of "else if (strncmp" */ 255 } /* End of "if (USER_PROCESS" */ 256 } /* End of "for(count=0" */ 257 258 /* Did we find a place to talk to? If we were looking for a */ 259 /* specific spot and didn't find it, complain and quit. */ 260 261 if (terminal != NULL && *rterm == '\0') { 262 if (bad > 0) { 263 (void) fprintf(stderr, gettext("Permission denied.\n")); 264 exit(1); 265 } else { 266 #ifdef UTMP_HACK 267 if (strlcat(rterminal, terminal, sizeof (rterminal)) >= 268 sizeof (rterminal)) { 269 (void) fprintf(stderr, 270 gettext("Terminal name too long.\n")); 271 exit(1); 272 } 273 if (self.ut_pid == 0) { 274 if ((passptr = getpwuid(getuid())) == NULL) { 275 (void) fprintf(stderr, 276 gettext("Cannot determine who you are.\n")); 277 exit(1); 278 } 279 (void) strlcpy(&ownname[0], &passptr->pw_name[0], 280 sizeof (ownname)); 281 } else { 282 (void) strlcpy(&ownname[0], self.ut_user, 283 sizeof (self.ut_user)); 284 } 285 if (!permit(rterminal)) { 286 (void) fprintf(stderr, 287 gettext("%s permission denied\n"), terminal); 288 exit(1); 289 } 290 #else 291 (void) fprintf(stderr, gettext("%s is not at \"%s\".\n"), 292 receipient, terminal); 293 exit(1); 294 #endif /* UTMP_HACK */ 295 } 296 } 297 298 /* If we were just looking for anyplace to talk and didn't find */ 299 /* one, complain and quit. */ 300 /* If permissions prevent us from sending to this person - exit */ 301 302 else if (*rterm == '\0') { 303 if (bad > 0) 304 (void) fprintf(stderr, gettext("Permission denied.\n")); 305 else 306 (void) fprintf(stderr, 307 gettext("%s is not logged on.\n"), receipient); 308 exit(1); 309 } 310 311 /* Did we find our own entry? */ 312 313 else if (self.ut_pid == 0) { 314 /* Use the user id instead of utmp name if the entry in the */ 315 /* utmp file couldn't be found. */ 316 317 if ((passptr = getpwuid(getuid())) == (struct passwd *)NULL) { 318 (void) fprintf(stderr, 319 gettext("Cannot determine who you are.\n")); 320 exit(1); 321 } 322 strncpy(&ownname[0], &passptr->pw_name[0], sizeof (ownname)); 323 } 324 else 325 { 326 strncpy(&ownname[0], self.ut_user, sizeof (self.ut_user)); 327 } 328 ownname[sizeof (ownname)-1] = '\0'; 329 330 if (!permit1(1)) 331 (void) fprintf(stderr, 332 gettext("Warning: You have your terminal set to \"mesg -n\"." 333 " No reply possible.\n")); 334 /* Close the utmpx files. */ 335 336 endutxent(); 337 338 /* Try to open up the line to the receipient's terminal. */ 339 340 signal(SIGALRM, openfail); 341 alarm(5); 342 fp = fopen(&rterminal[0], "w"); 343 alarm(0); 344 345 /* Make sure executed subshell doesn't inherit this fd - close-on-exec */ 346 347 if (fcntl(fileno(fp), F_SETFD, FD_CLOEXEC) < 0) { 348 perror("fcntl(F_SETFD)"); 349 exit(1); 350 } 351 352 /* Catch signals SIGHUP, SIGINT, SIGQUIT, and SIGTERM, and send */ 353 /* <EOT> message to receipient before dying away. */ 354 355 setsignals(eof); 356 357 /* Get the time of day, convert it to a string and throw away the */ 358 /* year information at the end of the string. */ 359 360 time(&tod); 361 (void) strftime(time_buf, sizeof (time_buf), 362 dcgettext(NULL, DATE_FMT, LC_TIME), localtime(&tod)); 363 364 (void) fprintf(fp, 365 gettext("\n\007\007\007\tMessage from %s on %s (%s) [ %s ] ...\n"), 366 &ownname[0], thissys, oterminal, time_buf); 367 fflush(fp); 368 (void) fprintf(stderr, "\007\007"); 369 370 /* Get input from user and send to receipient unless it begins */ 371 /* with a !, when it is to be a shell command. */ 372 newline = 1; 373 while ((i = readcsi(0, &input[0], sizeof (input))) > 0) { 374 ptr = &input[0]; 375 /* Is this a shell command? */ 376 377 if ((newline) && (*ptr == '!')) 378 shellcmd(++ptr); 379 380 /* Send line to the receipient. */ 381 382 else { 383 if (myuid && !permit1(fileno(fp))) { 384 (void) fprintf(stderr, 385 gettext("Can no longer write to %s\n"), rterminal); 386 break; 387 } 388 389 /* 390 * All non-printable characters are displayed using a special notation: 391 * Control characters shall be displayed using the two character 392 * sequence of ^ (carat) and the ASCII character - decimal 64 greater 393 * that the character being encoded - eg., a \003 is displayed ^C. 394 * Characters with the eighth bit set shall be displayed using 395 * the three or four character meta notation - e.g., \372 is 396 * displayed M-z and \203 is displayed M-^C. 397 */ 398 399 newline = 0; 400 for (bp = &input[0]; --i >= 0; bp++) { 401 if (*bp == '\n') { 402 newline = 1; 403 putc('\r', fp); 404 } 405 if (*bp == ' ' || 406 *bp == '\t' || *bp == '\n' || 407 *bp == '\r' || *bp == '\013' || 408 *bp == '\007') { 409 putc(*bp, fp); 410 } else if (((n = mbtowc(&wc, bp, MB_CUR_MAX)) > 0) && 411 iswprint(wc)) { 412 for (; n > 0; --n, --i, ++bp) 413 putc(*bp, fp); 414 bp--, ++i; 415 } else { 416 if (!isascii(*bp)) { 417 fputs("M-", fp); 418 *bp = toascii(*bp); 419 } 420 if (iscntrl(*bp)) { 421 putc('^', fp); 422 /* add decimal 64 to the control character */ 423 putc(*bp + 0100, fp); 424 } 425 else 426 putc(*bp, fp); 427 } 428 if (*bp == '\n') 429 fflush(fp); 430 if (ferror(fp) || feof(fp)) { 431 printf(gettext( 432 "\n\007Write failed (%s logged out?)\n"), 433 receipient); 434 exit(1); 435 } 436 } /* for */ 437 fflush(fp); 438 } /* else */ 439 } /* while */ 440 441 /* Since "end of file" received, send <EOT> message to receipient. */ 442 443 eof(); 444 return (0); 445 } 446 447 448 static void 449 setsignals(catch) 450 void (*catch)(); 451 { 452 signal(SIGHUP, catch); 453 signal(SIGINT, catch); 454 signal(SIGQUIT, catch); 455 signal(SIGTERM, catch); 456 } 457 458 459 static void 460 shellcmd(command) 461 char *command; 462 { 463 register pid_t child; 464 extern void eof(); 465 466 if ((child = fork()) == (pid_t)FAILURE) 467 { 468 (void) fprintf(stderr, 469 gettext("Unable to fork. Try again later.\n")); 470 return; 471 } else if (child == (pid_t)0) { 472 /* Reset the signals to the default actions and exec a shell. */ 473 474 if (setgid(getgid()) < 0) 475 exit(1); 476 execl("/usr/bin/sh", "sh", "-c", command, 0); 477 exit(0); 478 } 479 else 480 { 481 /* Allow user to type <del> and <quit> without dying during */ 482 /* commands. */ 483 484 signal(SIGINT, SIG_IGN); 485 signal(SIGQUIT, SIG_IGN); 486 487 /* As parent wait around for user to finish spunoff command. */ 488 489 while (wait(NULL) != child); 490 491 /* Reset the signals to their normal state. */ 492 493 setsignals(eof); 494 } 495 (void) fprintf(stdout, "!\n"); 496 } 497 498 static void 499 openfail() 500 { 501 extern char *rterm, *receipient; 502 503 (void) fprintf(stderr, 504 gettext("Timeout trying to open %s's line(%s).\n"), 505 receipient, rterm); 506 exit(1); 507 } 508 509 static void 510 eof() 511 { 512 extern FILE *fp; 513 514 (void) fprintf(fp, "%s\n", gettext("<EOT>")); 515 exit(0); 516 } 517 518 /* 519 * permit: check mode of terminal - if not writable by all disallow writing to 520 * (even the user cannot therefore write to their own tty) 521 */ 522 523 static int 524 permit(term) 525 char *term; 526 { 527 struct stat buf; 528 int fildes; 529 530 if ((fildes = open(term, O_WRONLY|O_NOCTTY)) < 0) 531 return (0); 532 /* check if the device really is a tty */ 533 if (!isatty(fildes)) { 534 (void) fprintf(stderr, 535 gettext("%s in utmpx is not a tty\n"), term); 536 openlog("write", 0, LOG_AUTH); 537 syslog(LOG_CRIT, "%s in utmpx is not a tty\n", term); 538 closelog(); 539 close(fildes); 540 return (0); 541 } 542 fstat(fildes, &buf); 543 close(fildes); 544 return (buf.st_mode & (S_IWGRP|S_IWOTH)); 545 } 546 547 548 549 /* 550 * permit1: check mode of terminal - if not writable by all disallow writing 551 * to (even the user themself cannot therefore write to their own tty) 552 */ 553 554 /* this is used with fstat (which is faster than stat) where possible */ 555 556 static int 557 permit1(fildes) 558 int fildes; 559 { 560 struct stat buf; 561 562 fstat(fildes, &buf); 563 return (buf.st_mode & (S_IWGRP|S_IWOTH)); 564 } 565 566 567 /* 568 * Read a string of multi-byte characters from specified file. 569 * The requested # of bytes are attempted to read. 570 * readcsi() tries to complete the last multibyte character 571 * by calling mbtowc(), if the leftovers form mbtowc(), 572 * left the last char imcomplete, moves into delta_spool to use later, 573 * next called. The caller must reserve 574 * nbytereq+MB_LEN_MAX bytes for the buffer. When the attempt 575 * is failed, it truncate the last char. 576 * Returns the number of bytes that constitutes the valid multi-byte characters. 577 */ 578 579 580 static int readcsi(d, buf, nbytereq) 581 int d; 582 char *buf; 583 int nbytereq; 584 { 585 static char delta_pool[MB_LEN_MAX * 2]; 586 static char delta_size; 587 char *cp, *nextp, *lastp; 588 int n; 589 int r_size; 590 591 if (delta_size) { 592 memcpy(buf, delta_pool, delta_size); 593 cp = buf + delta_size; 594 r_size = nbytereq - delta_size; 595 } else { 596 cp = buf; 597 r_size = nbytereq; 598 } 599 600 if ((r_size = read(d, cp, r_size)) < 0) 601 r_size = 0; 602 if ((n = delta_size + r_size) <= 0) 603 return (n); 604 605 /* Scan the result to test the completeness of each EUC characters. */ 606 nextp = buf; 607 lastp = buf + n; /* Lastp points to the first junk byte. */ 608 while (nextp < lastp) { 609 if ((n = (lastp - nextp)) > (unsigned int)MB_CUR_MAX) 610 n = (unsigned int)MB_CUR_MAX; 611 if ((n = mbtowc((wchar_t *)0, nextp, n)) <= 0) { 612 if ((lastp - nextp) < (unsigned int)MB_CUR_MAX) 613 break; 614 n = 1; 615 } 616 nextp += n; 617 } 618 /* How many bytes needed to complete the last char? */ 619 delta_size = lastp - nextp; 620 if (delta_size > 0) { 621 if (nextp[delta_size - 1] != '\n') { 622 /* the remnants store into delta_pool */ 623 memcpy(delta_pool, nextp, delta_size); 624 } else 625 nextp = lastp; 626 } 627 *nextp = '\0'; 628 return (nextp-buf); /* Return # of bytes. */ 629 } 630