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