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