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 /*
  28  * Copyright 1988-2003 Sun Microsystems, Inc.  All rights reserved.
  29  * Use is subject to license terms.
  30  */
  31 
  32 /*
  33  * Copyright 2012 Joyent, Inc. All rights reserved.
  34  */
  35 
  36 #include <signal.h>
  37 #include <stdio.h>
  38 #include <stdlib.h>
  39 #include <grp.h>
  40 #include <sys/types.h>
  41 #include <unistd.h>
  42 #include <string.h>
  43 #include <ctype.h>
  44 #include <sys/stat.h>
  45 #include <utmpx.h>
  46 #include <sys/utsname.h>
  47 #include <dirent.h>
  48 #include <pwd.h>
  49 #include <fcntl.h>
  50 #include <time.h>
  51 #include <errno.h>
  52 #include <locale.h>
  53 #include <syslog.h>
  54 #include <sys/wait.h>
  55 #include <limits.h>
  56 #include <libzonecfg.h>
  57 #include <zone.h>
  58 #include <sys/contract/process.h>
  59 #include <libcontract.h>
  60 #include <sys/ctfs.h>
  61 
  62 /*
  63  * utmpx defines wider fields for user and line.  For compatibility of output,
  64  * we are limiting these to the old maximums in utmp. Define UTMPX_NAMELEN
  65  * to use the full lengths.
  66  */
  67 #ifndef UTMPX_NAMELEN
  68 /* XXX - utmp -fix name length */
  69 #define NMAX    (_POSIX_LOGIN_NAME_MAX - 1)
  70 #define LMAX    12
  71 #else /* UTMPX_NAMELEN */
  72 #define NMAX    (sizeof (((struct utmpx *)0)->ut_user)
  73 #define LMAX    (sizeof (((struct utmpx *)0)->ut_line)
  74 #endif /* UTMPX_NAMELEN */
  75 
  76 static char     mesg[3000];
  77 static char     *infile;
  78 static int      gflag;
  79 static struct   group *pgrp;
  80 static char     *grpname;
  81 static char     line[MAXNAMLEN+1] = "???";
  82 static char     systm[MAXNAMLEN+1];
  83 static time_t   tloc;
  84 static struct   utsname utsn;
  85 static char     who[9]  = "???";
  86 static char     time_buf[50];
  87 #define DATE_FMT        "%a %b %e %H:%M:%S"
  88 
  89 static void sendmes(struct utmpx *, zoneid_t);
  90 static void sendmes_tozone(zoneid_t, int);
  91 static int chkgrp(char *);
  92 static char *copy_str_till(char *, char *, char, int);
  93 
  94 static int init_template(void);
  95 int contract_abandon_id(ctid_t);
  96 
  97 int
  98 main(int argc, char *argv[])
  99 {
 100         FILE    *f;
 101         char    *ptr, *start;
 102         struct  passwd *pwd;
 103         char    *term_name;
 104         int     c;
 105         int     aflag = 0;
 106         int     errflg = 0;
 107         int zflg = 0;
 108         int Zflg = 0;
 109 
 110         char *zonename = NULL;
 111         zoneid_t *zoneidlist = NULL;
 112         uint_t nzids_saved, nzids = 0;
 113 
 114         (void) setlocale(LC_ALL, "");
 115 
 116         while ((c = getopt(argc, argv, "g:az:Z")) != EOF)
 117                 switch (c) {
 118                 case 'a':
 119                         aflag++;
 120                         break;
 121                 case 'g':
 122                         if (gflag) {
 123                                 (void) fprintf(stderr,
 124                                     "Only one group allowed\n");
 125                                 return (1);
 126                         }
 127                         if ((pgrp = getgrnam(grpname = optarg)) == NULL) {
 128                                 (void) fprintf(stderr, "Unknown group %s\n",
 129                                     grpname);
 130                                 return (1);
 131                         }
 132                         gflag++;
 133                         break;
 134                 case 'z':
 135                         zflg++;
 136                         zonename = optarg;
 137                         if (getzoneidbyname(zonename) == -1) {
 138                                 (void) fprintf(stderr, "Specified zone %s "
 139                                     "is invalid", zonename);
 140                                 return (1);
 141                         }
 142                         break;
 143                 case 'Z':
 144                         Zflg++;
 145                         break;
 146                 case '?':
 147                         errflg++;
 148                         break;
 149                 }
 150 
 151         if (errflg) {
 152                 (void) fprintf(stderr,
 153                     "Usage: wall [-a] [-g group] [-z zone] [-Z] [files...]\n");
 154                 return (1);
 155         }
 156 
 157         if (zflg && Zflg) {
 158                 (void) fprintf(stderr, "Cannot use -z with -Z\n");
 159                 return (1);
 160         }
 161 
 162         if (optind < argc)
 163                 infile = argv[optind];
 164 
 165         if (uname(&utsn) == -1) {
 166                 (void) fprintf(stderr, "wall: uname() failed, %s\n",
 167                     strerror(errno));
 168                 return (2);
 169         }
 170         (void) strcpy(systm, utsn.nodename);
 171 
 172         /*
 173          * Get the name of the terminal wall is running from.
 174          */
 175 
 176         if ((term_name = ttyname(fileno(stderr))) != NULL) {
 177                 /*
 178                  * skip the leading "/dev/" in term_name
 179                  */
 180                 (void) strncpy(line, &term_name[5], sizeof (line) - 1);
 181         }
 182 
 183         if (who[0] == '?') {
 184                 if (pwd = getpwuid(getuid()))
 185                         (void) strncpy(&who[0], pwd->pw_name, sizeof (who));
 186         }
 187 
 188         f = stdin;
 189         if (infile) {
 190                 f = fopen(infile, "r");
 191                 if (f == NULL) {
 192                         (void) fprintf(stderr, "Cannot open %s\n", infile);
 193                         return (1);
 194                 }
 195         }
 196 
 197         start = &mesg[0];
 198         ptr = start;
 199         while ((ptr - start) < 3000) {
 200                 size_t n;
 201 
 202                 if (fgets(ptr, &mesg[sizeof (mesg)] - ptr, f) == NULL)
 203                         break;
 204                 if ((n = strlen(ptr)) == 0)
 205                         break;
 206                 ptr += n;
 207         }
 208         (void) fclose(f);
 209 
 210         /*
 211          * If the request is from the rwall daemon then use the caller's
 212          * name and host.  We determine this if all of the following is true:
 213          *      1) First 5 characters are "From "
 214          *      2) Next non-white characters are of the form "name@host:"
 215          */
 216         if (strcmp(line, "???") == 0) {
 217                 char rwho[MAXNAMLEN+1];
 218                 char rsystm[MAXNAMLEN+1];
 219                 char *cp;
 220 
 221                 if (strncmp(mesg, "From ", 5) == 0) {
 222                         cp = &mesg[5];
 223                         cp = copy_str_till(rwho, cp, '@', MAXNAMLEN + 1);
 224                         if (rwho[0] != '\0') {
 225                                 cp = copy_str_till(rsystm, ++cp, ':',
 226                                     MAXNAMLEN + 1);
 227                                 if (rsystm[0] != '\0') {
 228                                         (void) strcpy(systm, rsystm);
 229                                         (void) strncpy(rwho, who, 9);
 230                                         (void) strcpy(line, "rpc.rwalld");
 231                                 }
 232                         }
 233                 }
 234         }
 235         (void) time(&tloc);
 236         (void) strftime(time_buf, sizeof (time_buf),
 237             DATE_FMT, localtime(&tloc));
 238 
 239         if (zflg != 0) {
 240                 if ((zoneidlist =
 241                     malloc(sizeof (zoneid_t))) == NULL ||
 242                     (*zoneidlist = getzoneidbyname(zonename)) == -1)
 243                         return (errno);
 244                 nzids = 1;
 245         } else if (Zflg != 0) {
 246                 if (zone_list(NULL, &nzids) != 0)
 247                         return (errno);
 248 again:
 249                 nzids *= 2;
 250                 if ((zoneidlist = malloc(nzids * sizeof (zoneid_t))) == NULL)
 251                         exit(errno);
 252                 nzids_saved = nzids;
 253                 if (zone_list(zoneidlist, &nzids) != 0) {
 254                         (void) free(zoneidlist);
 255                         return (errno);
 256                 }
 257                 if (nzids > nzids_saved) {
 258                         free(zoneidlist);
 259                         goto again;
 260                 }
 261         }
 262         if (zflg || Zflg) {
 263                 for (; nzids > 0; --nzids)
 264                         sendmes_tozone(zoneidlist[nzids-1], aflag);
 265                 free(zoneidlist);
 266         } else
 267                 sendmes_tozone(getzoneid(), aflag);
 268 
 269         return (0);
 270 }
 271 
 272 /*
 273  * Copy src to destination upto but not including the delim.
 274  * Leave dst empty if delim not found or whitespace encountered.
 275  * Return pointer to next character (delim, whitespace, or '\0')
 276  */
 277 static char *
 278 copy_str_till(char *dst, char *src, char delim, int len)
 279 {
 280         int i = 0;
 281 
 282         while (*src != '\0' && i < len) {
 283                 if (isspace(*src)) {
 284                         dst[0] = '\0';
 285                         return (src);
 286                 }
 287                 if (*src == delim) {
 288                         dst[i] = '\0';
 289                         return (src);
 290                 }
 291                 dst[i++] = *src++;
 292         }
 293         dst[0] = '\0';
 294         return (src);
 295 }
 296 
 297 static void
 298 sendmes_tozone(zoneid_t zid, int aflag) {
 299         int i = 0;
 300         char zonename[ZONENAME_MAX], root[MAXPATHLEN];
 301         struct utmpx *p;
 302 
 303         if (zid != getzoneid()) {
 304                 root[0] = '\0';
 305                 (void) getzonenamebyid(zid, zonename, ZONENAME_MAX);
 306                 (void) zone_get_rootpath(zonename, root, sizeof (root));
 307                 (void) strlcat(root, UTMPX_FILE, sizeof (root));
 308                 if (!utmpxname(root)) {
 309                         (void) fprintf(stderr, "Cannot open %s\n", root);
 310                         return;
 311                 }
 312         } else {
 313                 (void) utmpxname(UTMPX_FILE);
 314         }
 315         setutxent();
 316         while ((p = getutxent()) != NULL) {
 317                 if (p->ut_type != USER_PROCESS)
 318                         continue;
 319                 /*
 320                  * if (-a option OR NOT pty window login), send the message
 321                  */
 322                 if (aflag || !nonuser(*p))
 323                         sendmes(p, zid);
 324         }
 325         endutxent();
 326 
 327         (void) alarm(60);
 328         do {
 329                 i = (int)wait((int *)0);
 330         } while (i != -1 || errno != ECHILD);
 331 
 332 }
 333 
 334 /*
 335  * Note to future maintainers: with the change of wall to use the
 336  * getutxent() API, the forked children (created by this function)
 337  * must call _exit as opposed to exit. This is necessary to avoid
 338  * unwanted fflushing of getutxent's stdio stream (caused by atexit
 339  * processing).
 340  */
 341 static void
 342 sendmes(struct utmpx *p, zoneid_t zid)
 343 {
 344         int i;
 345         char *s;
 346         static char device[LMAX + 6];
 347         char *bp;
 348         int ibp;
 349         FILE *f;
 350         int fd, tmpl_fd;
 351         boolean_t zoneenter = B_FALSE;
 352 
 353         if (zid != getzoneid()) {
 354                 zoneenter = B_TRUE;
 355                 tmpl_fd = init_template();
 356                 if (tmpl_fd == -1) {
 357                         (void) fprintf(stderr, "Could not initialize "
 358                             "process contract");
 359                         return;
 360                 }
 361         }
 362 
 363         while ((i = (int)fork()) == -1) {
 364                 (void) alarm(60);
 365                 (void) wait((int *)0);
 366                 (void) alarm(0);
 367         }
 368 
 369         if (i)
 370                 return;
 371 
 372         if (zoneenter && zone_enter(zid) == -1) {
 373                 char zonename[ZONENAME_MAX];
 374                 (void) getzonenamebyid(zid, zonename, ZONENAME_MAX);
 375                 (void) fprintf(stderr, "Could not enter zone "
 376                     "%s\n", zonename);
 377         }
 378         if (zoneenter)
 379                 (void) ct_tmpl_clear(tmpl_fd);
 380 
 381         if (gflag)
 382                 if (!chkgrp(p->ut_user))
 383                         _exit(0);
 384 
 385         (void) signal(SIGHUP, SIG_IGN);
 386         (void) alarm(60);
 387         s = &device[0];
 388         (void) snprintf(s, sizeof (device), "/dev/%.*s", LMAX, p->ut_line);
 389 
 390         /* check if the device is really a tty */
 391         if ((fd = open(s, O_WRONLY|O_NOCTTY|O_NONBLOCK)) == -1) {
 392                 (void) fprintf(stderr, "Cannot send to %.*s on %s\n",
 393                     NMAX, p->ut_user, s);
 394                 perror("open");
 395                 (void) fflush(stderr);
 396                 _exit(1);
 397         } else {
 398                 if (!isatty(fd)) {
 399                         (void) fprintf(stderr,
 400                             "Cannot send to device %.*s %s\n",
 401                             LMAX, p->ut_line,
 402                             "because it's not a tty");
 403                         openlog("wall", 0, LOG_AUTH);
 404                         syslog(LOG_CRIT, "%.*s in utmpx is not a tty\n",
 405                             LMAX, p->ut_line);
 406                         closelog();
 407                         (void) fflush(stderr);
 408                         _exit(1);
 409                 }
 410         }
 411 #ifdef DEBUG
 412         (void) close(fd);
 413         f = fopen("wall.debug", "a");
 414 #else
 415         f = fdopen(fd, "w");
 416 #endif
 417         if (f == NULL) {
 418                 (void) fprintf(stderr, "Cannot send to %-.*s on %s\n",
 419                     NMAX, &p->ut_user[0], s);
 420                 perror("open");
 421                 (void) fflush(stderr);
 422                 _exit(1);
 423         }
 424         (void) fprintf(f,
 425             "\07\07\07Broadcast Message from %s (%s) on %s %19.19s",
 426             who, line, systm, time_buf);
 427         if (gflag)
 428                 (void) fprintf(f, " to group %s", grpname);
 429         (void) fprintf(f, "...\n");
 430 #ifdef DEBUG
 431         (void) fprintf(f, "DEBUG: To %.8s on %s\n", p->ut_user, s);
 432 #endif
 433         i = strlen(mesg);
 434         for (bp = mesg; --i >= 0; bp++) {
 435                 ibp = (unsigned int)((unsigned char) *bp);
 436                 if (*bp == '\n')
 437                         (void) putc('\r', f);
 438                 if (isprint(ibp) || *bp == '\r' || *bp == '\013' ||
 439                     *bp == ' ' || *bp == '\t' || *bp == '\n' || *bp == '\007') {
 440                         (void) putc(*bp, f);
 441                 } else {
 442                         if (!isascii(*bp)) {
 443                                 (void) fputs("M-", f);
 444                                 *bp = toascii(*bp);
 445                         }
 446                         if (iscntrl(*bp)) {
 447                                 (void) putc('^', f);
 448                                 (void) putc(*bp + 0100, f);
 449                         }
 450                         else
 451                                 (void) putc(*bp, f);
 452                 }
 453 
 454                 if (*bp == '\n')
 455                         (void) fflush(f);
 456 
 457                 if (ferror(f) || feof(f)) {
 458                         (void) printf("\n\007Write failed\n");
 459                         (void) fflush(stdout);
 460                         _exit(1);
 461                 }
 462         }
 463         (void) fclose(f);
 464         (void) close(fd);
 465         _exit(0);
 466 }
 467 
 468 
 469 static int
 470 chkgrp(char *name)
 471 {
 472         int i;
 473         char *p;
 474 
 475         for (i = 0; pgrp->gr_mem[i] && pgrp->gr_mem[i][0]; i++) {
 476                 for (p = name; *p && *p != ' '; p++)
 477                 ;
 478                 *p = 0;
 479                 if (strncmp(name, pgrp->gr_mem[i], 8) == 0)
 480                         return (1);
 481         }
 482 
 483         return (0);
 484 }
 485 
 486 static int
 487 init_template(void) {
 488         int fd = 0;
 489         int err = 0;
 490 
 491         fd = open64(CTFS_ROOT "/process/template", O_RDWR);
 492         if (fd == -1)
 493                 return (-1);
 494 
 495         err |= ct_tmpl_set_critical(fd, 0);
 496         err |= ct_tmpl_set_informative(fd, 0);
 497         err |= ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR);
 498         err |= ct_pr_tmpl_set_param(fd, CT_PR_PGRPONLY | CT_PR_REGENT);
 499         if (err || ct_tmpl_activate(fd)) {
 500                 (void) close(fd);
 501                 return (-1);
 502         }
 503 
 504         return (fd);
 505 }