xref: /illumos-gate/usr/src/cmd/wall/wall.c (revision b6805bf78d2bbbeeaea8909a05623587b42d58b3)
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 }
506