1/*
2 * Copyright (c) 1988, 1990, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include <sys/cdefs.h>
35
36__FBSDID("$FreeBSD: src/usr.bin/wall/wall.c,v 1.25 2008/01/15 07:40:30 das Exp $");
37
38#ifndef lint
39__attribute__((__used__))
40static const char copyright[] =
41"@(#) Copyright (c) 1988, 1990, 1993\n\
42	The Regents of the University of California.  All rights reserved.\n";
43#endif
44
45#ifndef lint
46__attribute__((__used__))
47static const char sccsid[] = "@(#)wall.c	8.2 (Berkeley) 11/16/93";
48#endif
49
50/*
51 * This program is not related to David Wall, whose Stanford Ph.D. thesis
52 * is entitled "Mechanisms for Broadcast and Selective Broadcast".
53 */
54
55#include <sys/param.h>
56#include <sys/stat.h>
57#include <sys/uio.h>
58
59#include <ctype.h>
60#include <err.h>
61#include <grp.h>
62#include <locale.h>
63#include <paths.h>
64#include <pwd.h>
65#include <stdio.h>
66#include <stdlib.h>
67#include <string.h>
68#include <time.h>
69#include <unistd.h>
70#ifdef __APPLE__
71#include <utmpx.h>
72#else
73#include <utmp.h>
74#endif
75
76#include "ttymsg.h"
77
78static void makemsg(char *);
79static void usage(void);
80
81struct wallgroup {
82	struct wallgroup *next;
83	char		*name;
84	gid_t		gid;
85} *grouplist;
86int nobanner;
87int mbufsize;
88char *mbuf;
89
90#ifndef __APPLE__
91static int
92ttystat(char *line, int sz)
93{
94	struct stat sb;
95	char ttybuf[MAXPATHLEN];
96
97	(void)snprintf(ttybuf, sizeof(ttybuf), "%s%.*s", _PATH_DEV, sz, line);
98	if (stat(ttybuf, &sb) == 0) {
99		return (0);
100	} else
101		return (-1);
102}
103#endif
104
105int
106main(int argc, char *argv[])
107{
108	struct iovec iov;
109#ifdef __APPLE__
110	// rdar://problem/4433603
111	struct utmpx *u;
112#else
113	struct utmp utmp;
114#endif
115	int ch;
116	int ingroup;
117#ifndef __APPLE__
118	FILE *fp;
119#endif
120	struct wallgroup *g;
121	struct group *grp;
122	char **np;
123	const char *p;
124	struct passwd *pw;
125#ifdef __APPLE__
126	char line[sizeof(u->ut_line) + 1];
127	char username[sizeof(u->ut_user) + 1];
128#else
129	char line[sizeof(utmp.ut_line) + 1];
130	char username[sizeof(utmp.ut_name) + 1];
131#endif
132
133	(void)setlocale(LC_CTYPE, "");
134
135	while ((ch = getopt(argc, argv, "g:n")) != -1)
136		switch (ch) {
137		case 'n':
138			/* undoc option for shutdown: suppress banner */
139			if (geteuid() == 0)
140				nobanner = 1;
141			break;
142		case 'g':
143			g = (struct wallgroup *)malloc(sizeof *g);
144			g->next = grouplist;
145			g->name = optarg;
146			g->gid = -1;
147			grouplist = g;
148			break;
149		case '?':
150		default:
151			usage();
152		}
153	argc -= optind;
154	argv += optind;
155	if (argc > 1)
156		usage();
157
158	for (g = grouplist; g; g = g->next) {
159		grp = getgrnam(g->name);
160		if (grp != NULL)
161			g->gid = grp->gr_gid;
162		else
163			warnx("%s: no such group", g->name);
164	}
165
166	makemsg(*argv);
167
168#ifdef __APPLE__
169	setutxent();
170#else
171	if (!(fp = fopen(_PATH_UTMP, "r")))
172		err(1, "cannot read %s", _PATH_UTMP);
173#endif
174	iov.iov_base = mbuf;
175	iov.iov_len = mbufsize;
176	/* NOSTRICT */
177#ifdef __APPLE__
178	while ((u = getutxent()) != NULL) {
179		if (!u->ut_user[0] || u->ut_type != USER_PROCESS)
180			continue;
181#else
182	while (fread((char *)&utmp, sizeof(utmp), 1, fp) == 1) {
183		if (!utmp.ut_name[0])
184			continue;
185		if (ttystat(utmp.ut_line, UT_LINESIZE) != 0)
186			continue;
187#endif
188		if (grouplist) {
189			ingroup = 0;
190#ifdef __APPLE__
191			strlcpy(username, u->ut_user, sizeof(username));
192#else
193			strlcpy(username, utmp.ut_name, sizeof(utmp.ut_name));
194#endif
195			pw = getpwnam(username);
196			if (!pw)
197				continue;
198			for (g = grouplist; g && ingroup == 0; g = g->next) {
199				if (g->gid == (gid_t)-1)
200					continue;
201				if (g->gid == pw->pw_gid)
202					ingroup = 1;
203				else if ((grp = getgrgid(g->gid)) != NULL) {
204					for (np = grp->gr_mem; *np; np++) {
205						if (strcmp(*np, username) == 0) {
206							ingroup = 1;
207							break;
208						}
209					}
210				}
211			}
212			if (ingroup == 0)
213				continue;
214		}
215#ifdef __APPLE__
216		strlcpy(line, u->ut_line, sizeof(line));
217#else
218		strncpy(line, utmp.ut_line, sizeof(utmp.ut_line));
219		line[sizeof(utmp.ut_line)] = '\0';
220#endif
221		if ((p = ttymsg(&iov, 1, line, 60*5)) != NULL)
222			warnx("%s", p);
223	}
224	exit(0);
225}
226
227static void
228usage()
229{
230	(void)fprintf(stderr, "usage: wall [-g group] [file]\n");
231	exit(1);
232}
233
234void
235makemsg(char *fname)
236{
237	int cnt;
238	unsigned char ch;
239	struct tm *lt;
240	struct passwd *pw;
241	struct stat sbuf;
242	time_t now;
243	FILE *fp;
244	int fd;
245	char *p, hostname[MAXHOSTNAMELEN], lbuf[256], tmpname[64];
246	const char *tty;
247	const char *whom;
248	gid_t egid;
249
250	(void)snprintf(tmpname, sizeof(tmpname), "%s/wall.XXXXXX", _PATH_TMP);
251	if ((fd = mkstemp(tmpname)) == -1 || !(fp = fdopen(fd, "r+")))
252		err(1, "can't open temporary file");
253	(void)unlink(tmpname);
254
255	if (!nobanner) {
256		tty = ttyname(STDERR_FILENO);
257		if (tty == NULL)
258			tty = "no tty";
259
260		if (!(whom = getlogin()))
261			whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
262		(void)gethostname(hostname, sizeof(hostname));
263		(void)time(&now);
264		lt = localtime(&now);
265
266		/*
267		 * all this stuff is to blank out a square for the message;
268		 * we wrap message lines at column 79, not 80, because some
269		 * terminals wrap after 79, some do not, and we can't tell.
270		 * Which means that we may leave a non-blank character
271		 * in column 80, but that can't be helped.
272		 */
273		(void)fprintf(fp, "\r%79s\r\n", " ");
274		(void)snprintf(lbuf, sizeof(lbuf),
275		    "Broadcast Message from %s@%s",
276		    whom, hostname);
277		(void)fprintf(fp, "%-79.79s\007\007\r\n", lbuf);
278		(void)snprintf(lbuf, sizeof(lbuf),
279		    "        (%s) at %d:%02d %s...", tty,
280		    lt->tm_hour, lt->tm_min, lt->tm_zone);
281		(void)fprintf(fp, "%-79.79s\r\n", lbuf);
282	}
283	(void)fprintf(fp, "%79s\r\n", " ");
284
285	if (fname) {
286		egid = getegid();
287		setegid(getgid());
288	       	if (freopen(fname, "r", stdin) == NULL)
289			err(1, "can't read %s", fname);
290		setegid(egid);
291	}
292	while (fgets(lbuf, sizeof(lbuf), stdin)) {
293		for (cnt = 0, p = lbuf; (ch = *p) != '\0'; ++p, ++cnt) {
294			if (ch == '\r') {
295				putc('\r', fp);
296				cnt = 0;
297				continue;
298			} else if (ch == '\n') {
299				for (; cnt < 79; ++cnt)
300					putc(' ', fp);
301				putc('\r', fp);
302				putc('\n', fp);
303				break;
304			}
305			if (cnt == 79) {
306				putc('\r', fp);
307				putc('\n', fp);
308				cnt = 0;
309			}
310#ifdef __APPLE__
311			else // rdar://problem/3066405
312#endif
313			if (((ch & 0x80) && ch < 0xA0) ||
314				   /* disable upper controls */
315				   (!isprint(ch) && !isspace(ch) &&
316				    ch != '\a' && ch != '\b')
317				  ) {
318				if (ch & 0x80) {
319					ch &= 0x7F;
320					putc('M', fp);
321					if (++cnt == 79) {
322						putc('\r', fp);
323						putc('\n', fp);
324						cnt = 0;
325					}
326					putc('-', fp);
327					if (++cnt == 79) {
328						putc('\r', fp);
329						putc('\n', fp);
330						cnt = 0;
331					}
332				}
333				if (iscntrl(ch)) {
334					ch ^= 040;
335					putc('^', fp);
336					if (++cnt == 79) {
337						putc('\r', fp);
338						putc('\n', fp);
339						cnt = 0;
340					}
341				}
342			}
343#ifdef __APPLE__
344			// rdar://problem/4557295
345			if (ch != '\n')
346#endif
347			putc(ch, fp);
348		}
349	}
350	(void)fprintf(fp, "%79s\r\n", " ");
351	rewind(fp);
352
353	if (fstat(fd, &sbuf))
354		err(1, "can't stat temporary file");
355	mbufsize = sbuf.st_size;
356	if (!(mbuf = malloc((u_int)mbufsize)))
357		err(1, "out of memory");
358	if ((int)fread(mbuf, sizeof(*mbuf), mbufsize, fp) != mbufsize)
359		err(1, "can't read temporary file");
360	(void)close(fd);
361}
362