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 }