xref: /illumos-gate/usr/src/cmd/wall/wall.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 #include <signal.h>
35 #include <stdio.h>
36 #include <grp.h>
37 #include <sys/types.h>
38 #include <unistd.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <ctype.h>
42 #include <sys/stat.h>
43 #include <utmpx.h>
44 #include <sys/utsname.h>
45 #include <dirent.h>
46 #include <pwd.h>
47 #include <fcntl.h>
48 #include <time.h>
49 #include <errno.h>
50 #include <locale.h>
51 #include <syslog.h>
52 #include <sys/wait.h>
53 #include <limits.h>
54 
55 /*
56  * utmpx defines wider fields for user and line.  For compatibility of output,
57  * we are limiting these to the old maximums in utmp. Define UTMPX_NAMELEN
58  * to use the full lengths.
59  */
60 #ifndef UTMPX_NAMELEN
61 /* XXX - utmp -fix name length */
62 #define	NMAX	(_POSIX_LOGIN_NAME_MAX - 1)
63 #define	LMAX	12
64 #else /* UTMPX_NAMELEN */
65 #define	NMAX	(sizeof (((struct utmpx *)0)->ut_user)
66 #define	LMAX	(sizeof (((struct utmpx *)0)->ut_line)
67 #endif /* UTMPX_NAMELEN */
68 
69 static char	mesg[3000];
70 static char	*infile;
71 static int	gflag;
72 static struct	group *pgrp;
73 static char	*grpname;
74 static char	line[MAXNAMLEN+1] = "???";
75 static char	systm[MAXNAMLEN+1];
76 static time_t	tloc;
77 static struct	utsname utsn;
78 static char	who[9]	= "???";
79 static char	time_buf[50];
80 #define	DATE_FMT	"%a %b %e %H:%M:%S"
81 
82 static void sendmes(struct utmpx *);
83 static int chkgrp(char *);
84 static char *copy_str_till(char *, char *, char, int);
85 
86 int
87 main(int argc, char *argv[])
88 {
89 	int	i = 0;
90 	struct utmpx *p;
91 	FILE	*f;
92 	char	*ptr, *start;
93 	struct	passwd *pwd;
94 	char	*term_name;
95 	int	c;
96 	int	aflag = 0;
97 	int	errflg = 0;
98 
99 	(void) setlocale(LC_ALL, "");
100 
101 	while ((c = getopt(argc, argv, "g:a")) != EOF)
102 		switch (c) {
103 		case 'a':
104 			aflag++;
105 			break;
106 		case 'g':
107 			if (gflag) {
108 				(void) fprintf(stderr,
109 				    "Only one group allowed\n");
110 				exit(1);
111 			}
112 			if ((pgrp = getgrnam(grpname = optarg)) == NULL) {
113 				(void) fprintf(stderr, "Unknown group %s\n",
114 				    grpname);
115 				exit(1);
116 			}
117 			gflag++;
118 			break;
119 		case '?':
120 			errflg++;
121 			break;
122 		}
123 
124 	if (errflg) {
125 		(void) fprintf(stderr,
126 		    "Usage: wall [-a] [-g group] [files...]\n");
127 		return (1);
128 	}
129 
130 	if (optind < argc)
131 		infile = argv[optind];
132 
133 	if (uname(&utsn) == -1) {
134 		(void) fprintf(stderr, "wall: uname() failed, %s\n",
135 		    strerror(errno));
136 		exit(2);
137 	}
138 	(void) strcpy(systm, utsn.nodename);
139 
140 	/*
141 	 * Get the name of the terminal wall is running from.
142 	 */
143 
144 	if ((term_name = ttyname(fileno(stderr))) != NULL) {
145 		/*
146 		 * skip the leading "/dev/" in term_name
147 		 */
148 		(void) strncpy(line, &term_name[5], sizeof (line) - 1);
149 	}
150 
151 	if (who[0] == '?') {
152 		if (pwd = getpwuid(getuid()))
153 			(void) strncpy(&who[0], pwd->pw_name, sizeof (who));
154 	}
155 
156 	f = stdin;
157 	if (infile) {
158 		f = fopen(infile, "r");
159 		if (f == NULL) {
160 			(void) fprintf(stderr, "Cannot open %s\n", infile);
161 			exit(1);
162 		}
163 	}
164 
165 	start = &mesg[0];
166 	ptr = start;
167 	while ((ptr - start) < 3000) {
168 		size_t n;
169 
170 		if (fgets(ptr, &mesg[sizeof (mesg)] - ptr, f) == NULL)
171 			break;
172 		if ((n = strlen(ptr)) == 0)
173 			break;
174 		ptr += n;
175 	}
176 	(void) fclose(f);
177 
178 	/*
179 	 * If the request is from the rwall daemon then use the caller's
180 	 * name and host.  We determine this if all of the following is true:
181 	 *	1) First 5 characters are "From "
182 	 *	2) Next non-white characters are of the form "name@host:"
183 	 */
184 	if (strcmp(line, "???") == 0) {
185 		char rwho[MAXNAMLEN+1];
186 		char rsystm[MAXNAMLEN+1];
187 		char *cp;
188 
189 		if (strncmp(mesg, "From ", 5) == 0) {
190 			cp = &mesg[5];
191 			cp = copy_str_till(rwho, cp, '@', MAXNAMLEN + 1);
192 			if (rwho[0] != '\0') {
193 				cp = copy_str_till(rsystm, ++cp, ':',
194 				    MAXNAMLEN + 1);
195 				if (rsystm[0] != '\0') {
196 					(void) strcpy(systm, rsystm);
197 					(void) strncpy(rwho, who, 9);
198 					(void) strcpy(line, "rpc.rwalld");
199 				}
200 			}
201 		}
202 	}
203 	(void) time(&tloc);
204 	(void) strftime(time_buf, sizeof (time_buf),
205 			    DATE_FMT, localtime(&tloc));
206 
207 	setutxent();
208 	while ((p = getutxent()) != NULL) {
209 		if (p->ut_type != USER_PROCESS)
210 			continue;
211 		/*
212 		 * if (-a option OR NOT pty window login), send the message
213 		 */
214 		if (aflag || !nonuser(*p))
215 			sendmes(p);
216 	}
217 	endutxent();
218 
219 	(void) alarm(60);
220 	do {
221 		i = (int)wait((int *)0);
222 	} while (i != -1 || errno != ECHILD);
223 
224 	return (0);
225 }
226 
227 /*
228  * Copy src to destination upto but not including the delim.
229  * Leave dst empty if delim not found or whitespace encountered.
230  * Return pointer to next character (delim, whitespace, or '\0')
231  */
232 static char *
233 copy_str_till(char *dst, char *src, char delim, int len)
234 {
235 	int i = 0;
236 
237 	while (*src != '\0' && i < len) {
238 		if (isspace(*src)) {
239 			dst[0] = '\0';
240 			return (src);
241 		}
242 		if (*src == delim) {
243 			dst[i] = '\0';
244 			return (src);
245 		}
246 		dst[i++] = *src++;
247 	}
248 	dst[0] = '\0';
249 	return (src);
250 }
251 
252 /*
253  * Note to future maintainers: with the change of wall to use the
254  * getutxent() API, the forked children (created by this function)
255  * must call _exit as opposed to exit. This is necessary to avoid
256  * unwanted fflushing of getutxent's stdio stream (caused by atexit
257  * processing).
258  */
259 static void
260 sendmes(struct utmpx *p)
261 {
262 	int i;
263 	char *s;
264 	static char device[LMAX + 6];
265 	char *bp;
266 	int ibp;
267 	FILE *f;
268 	int fd;
269 
270 	if (gflag)
271 		if (!chkgrp(p->ut_user))
272 			return;
273 	while ((i = (int)fork()) == -1) {
274 		(void) alarm(60);
275 		(void) wait((int *)0);
276 		(void) alarm(0);
277 	}
278 
279 	if (i)
280 		return;
281 
282 	(void) signal(SIGHUP, SIG_IGN);
283 	(void) alarm(60);
284 	s = &device[0];
285 	(void) snprintf(s, sizeof (device), "/dev/%.*s", LMAX, p->ut_line);
286 
287 	/* check if the device is really a tty */
288 	if ((fd = open(s, O_WRONLY|O_NOCTTY|O_NONBLOCK)) == -1) {
289 		(void) fprintf(stderr, "Cannot send to %.*s on %s\n",
290 		    NMAX, p->ut_user, s);
291 		perror("open");
292 		(void) fflush(stderr);
293 		_exit(1);
294 	} else {
295 		if (!isatty(fd)) {
296 			(void) fprintf(stderr,
297 			    "Cannot send to device %.*s %s\n",
298 			    LMAX, p->ut_line,
299 			    "because it's not a tty");
300 			openlog("wall", 0, LOG_AUTH);
301 			syslog(LOG_CRIT, "%.*s in utmpx is not a tty\n",
302 			    LMAX, p->ut_line);
303 			closelog();
304 			(void) fflush(stderr);
305 			_exit(1);
306 		}
307 	}
308 #ifdef DEBUG
309 	(void) close(fd);
310 	f = fopen("wall.debug", "a");
311 #else
312 	f = fdopen(fd, "w");
313 #endif
314 	if (f == NULL) {
315 		(void) fprintf(stderr, "Cannot send to %-.*s on %s\n",
316 		    NMAX, &p->ut_user[0], s);
317 		perror("open");
318 		(void) fflush(stderr);
319 		_exit(1);
320 	}
321 	(void) fprintf(f,
322 	    "\07\07\07Broadcast Message from %s (%s) on %s %19.19s",
323 	    who, line, systm, time_buf);
324 	if (gflag)
325 		(void) fprintf(f, " to group %s", grpname);
326 	(void) fprintf(f, "...\n");
327 #ifdef DEBUG
328 	(void) fprintf(f, "DEBUG: To %.8s on %s\n", p->ut_user, s);
329 #endif
330 	i = strlen(mesg);
331 	for (bp = mesg; --i >= 0; bp++) {
332 		ibp = (unsigned int)((unsigned char) *bp);
333 		if (*bp == '\n')
334 			(void) putc('\r', f);
335 		if (isprint(ibp) || *bp == '\r' || *bp == '\013' ||
336 		    *bp == ' ' || *bp == '\t' || *bp == '\n' || *bp == '\007') {
337 			(void) putc(*bp, f);
338 		} else {
339 			if (!isascii(*bp)) {
340 				(void) fputs("M-", f);
341 				*bp = toascii(*bp);
342 			}
343 			if (iscntrl(*bp)) {
344 				(void) putc('^', f);
345 				(void) putc(*bp + 0100, f);
346 			}
347 			else
348 				(void) putc(*bp, f);
349 		}
350 
351 		if (*bp == '\n')
352 			(void) fflush(f);
353 
354 		if (ferror(f) || feof(f)) {
355 			(void) printf("\n\007Write failed\n");
356 			(void) fflush(stdout);
357 			_exit(1);
358 		}
359 	}
360 	(void) fclose(f);
361 	(void) close(fd);
362 	_exit(0);
363 }
364 
365 
366 static int
367 chkgrp(char *name)
368 {
369 	int i;
370 	char *p;
371 
372 	for (i = 0; pgrp->gr_mem[i] && pgrp->gr_mem[i][0]; i++) {
373 		for (p = name; *p && *p != ' '; p++);
374 		*p = 0;
375 		if (strncmp(name, pgrp->gr_mem[i], 8) == 0)
376 			return (1);
377 	}
378 
379 	return (0);
380 }
381