1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1989, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#include <sys/param.h>
36#include <sys/capsicum.h>
37#include <sys/filio.h>
38#include <sys/signal.h>
39#include <sys/stat.h>
40#include <sys/time.h>
41
42#include <capsicum_helpers.h>
43#include <ctype.h>
44#include <err.h>
45#include <errno.h>
46#include <locale.h>
47#include <paths.h>
48#include <pwd.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <unistd.h>
53#include <utmpx.h>
54#include <wchar.h>
55#include <wctype.h>
56
57void done(int);
58void do_write(int, char *, char *, const char *);
59static void usage(void) __dead2;
60int term_chk(int, char *, int *, time_t *, int);
61void wr_fputs(wchar_t *s);
62void search_utmp(int, char *, char *, char *, uid_t);
63int utmp_chk(char *, char *);
64
65int
66main(int argc, char **argv)
67{
68	unsigned long cmds[] = { TIOCGETA, TIOCGWINSZ, FIODGNAME };
69	cap_rights_t rights;
70	struct passwd *pwd;
71	time_t atime;
72	uid_t myuid;
73	int msgsok, myttyfd;
74	char tty[MAXPATHLEN], *mytty;
75	const char *login;
76	int devfd;
77
78	(void)setlocale(LC_CTYPE, "");
79
80	devfd = open(_PATH_DEV, O_RDONLY);
81	if (devfd < 0)
82		err(1, "open(/dev)");
83	cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_LOOKUP,
84	    CAP_PWRITE);
85	if (caph_rights_limit(devfd, &rights) < 0)
86		err(1, "can't limit devfd rights");
87
88	/*
89	 * Can't use capsicum helpers here because we need the additional
90	 * FIODGNAME ioctl.
91	 */
92	cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_READ,
93	    CAP_WRITE);
94	if (caph_rights_limit(STDIN_FILENO, &rights) < 0 ||
95	    caph_rights_limit(STDOUT_FILENO, &rights) < 0 ||
96	    caph_rights_limit(STDERR_FILENO, &rights) < 0 ||
97	    caph_ioctls_limit(STDIN_FILENO, cmds, nitems(cmds)) < 0 ||
98	    caph_ioctls_limit(STDOUT_FILENO, cmds, nitems(cmds)) < 0 ||
99	    caph_ioctls_limit(STDERR_FILENO, cmds, nitems(cmds)) < 0 ||
100	    caph_fcntls_limit(STDIN_FILENO, CAP_FCNTL_GETFL) < 0 ||
101	    caph_fcntls_limit(STDOUT_FILENO, CAP_FCNTL_GETFL) < 0 ||
102	    caph_fcntls_limit(STDERR_FILENO, CAP_FCNTL_GETFL) < 0)
103		err(1, "can't limit stdio rights");
104
105	caph_cache_catpages();
106	caph_cache_tzdata();
107
108	/*
109	 * Cache UTX database fds.
110	 */
111	setutxent();
112
113	/*
114	 * Determine our login name before we reopen() stdout
115	 * and before entering capability sandbox.
116	 */
117	myuid = getuid();
118	if ((login = getlogin()) == NULL) {
119		if ((pwd = getpwuid(myuid)))
120			login = pwd->pw_name;
121		else
122			login = "???";
123	}
124
125	if (caph_enter() < 0)
126		err(1, "cap_enter");
127
128	while (getopt(argc, argv, "") != -1)
129		usage();
130	argc -= optind;
131	argv += optind;
132
133	/* check that sender has write enabled */
134	if (isatty(fileno(stdin)))
135		myttyfd = fileno(stdin);
136	else if (isatty(fileno(stdout)))
137		myttyfd = fileno(stdout);
138	else if (isatty(fileno(stderr)))
139		myttyfd = fileno(stderr);
140	else
141		errx(1, "can't find your tty");
142	if (!(mytty = ttyname(myttyfd)))
143		errx(1, "can't find your tty's name");
144	if (!strncmp(mytty, _PATH_DEV, strlen(_PATH_DEV)))
145		mytty += strlen(_PATH_DEV);
146	if (term_chk(devfd, mytty, &msgsok, &atime, 1))
147		exit(1);
148	if (!msgsok)
149		errx(1, "you have write permission turned off");
150
151	/* check args */
152	switch (argc) {
153	case 1:
154		search_utmp(devfd, argv[0], tty, mytty, myuid);
155		do_write(devfd, tty, mytty, login);
156		break;
157	case 2:
158		if (!strncmp(argv[1], _PATH_DEV, strlen(_PATH_DEV)))
159			argv[1] += strlen(_PATH_DEV);
160		if (utmp_chk(argv[0], argv[1]))
161			errx(1, "%s is not logged in on %s", argv[0], argv[1]);
162		if (term_chk(devfd, argv[1], &msgsok, &atime, 1))
163			exit(1);
164		if (myuid && !msgsok)
165			errx(1, "%s has messages disabled on %s", argv[0], argv[1]);
166		do_write(devfd, argv[1], mytty, login);
167		break;
168	default:
169		usage();
170	}
171	done(0);
172	return (0);
173}
174
175static void
176usage(void)
177{
178	(void)fprintf(stderr, "usage: write user [tty]\n");
179	exit(1);
180}
181
182/*
183 * utmp_chk - checks that the given user is actually logged in on
184 *     the given tty
185 */
186int
187utmp_chk(char *user, char *tty)
188{
189	struct utmpx lu, *u;
190
191	strncpy(lu.ut_line, tty, sizeof lu.ut_line);
192	while ((u = getutxline(&lu)) != NULL)
193		if (u->ut_type == USER_PROCESS &&
194		    strcmp(user, u->ut_user) == 0) {
195			endutxent();
196			return(0);
197		}
198	endutxent();
199	return(1);
200}
201
202/*
203 * search_utmp - search utmp for the "best" terminal to write to
204 *
205 * Ignores terminals with messages disabled, and of the rest, returns
206 * the one with the most recent access time.  Returns as value the number
207 * of the user's terminals with messages enabled, or -1 if the user is
208 * not logged in at all.
209 *
210 * Special case for writing to yourself - ignore the terminal you're
211 * writing from, unless that's the only terminal with messages enabled.
212 */
213void
214search_utmp(int devfd, char *user, char *tty, char *mytty, uid_t myuid)
215{
216	struct utmpx *u;
217	time_t bestatime, atime;
218	int nloggedttys, nttys, msgsok, user_is_me;
219
220	nloggedttys = nttys = 0;
221	bestatime = 0;
222	user_is_me = 0;
223
224	while ((u = getutxent()) != NULL)
225		if (u->ut_type == USER_PROCESS &&
226		    strcmp(user, u->ut_user) == 0) {
227			++nloggedttys;
228			if (term_chk(devfd, u->ut_line, &msgsok, &atime, 0))
229				continue;	/* bad term? skip */
230			if (myuid && !msgsok)
231				continue;	/* skip ttys with msgs off */
232			if (strcmp(u->ut_line, mytty) == 0) {
233				user_is_me = 1;
234				continue;	/* don't write to yourself */
235			}
236			++nttys;
237			if (atime > bestatime) {
238				bestatime = atime;
239				(void)strlcpy(tty, u->ut_line, MAXPATHLEN);
240			}
241		}
242	endutxent();
243
244	if (nloggedttys == 0)
245		errx(1, "%s is not logged in", user);
246	if (nttys == 0) {
247		if (user_is_me) {		/* ok, so write to yourself! */
248			(void)strlcpy(tty, mytty, MAXPATHLEN);
249			return;
250		}
251		errx(1, "%s has messages disabled", user);
252	} else if (nttys > 1) {
253		warnx("%s is logged in more than once; writing to %s", user, tty);
254	}
255}
256
257/*
258 * term_chk - check that a terminal exists, and get the message bit
259 *     and the access time
260 */
261int
262term_chk(int devfd, char *tty, int *msgsokP, time_t *atimeP, int showerror)
263{
264	struct stat s;
265
266	if (fstatat(devfd, tty, &s, 0) < 0) {
267		if (showerror)
268			warn("%s%s", _PATH_DEV, tty);
269		return(1);
270	}
271	*msgsokP = (s.st_mode & (S_IWRITE >> 3)) != 0;	/* group write bit */
272	*atimeP = s.st_atime;
273	return(0);
274}
275
276/*
277 * do_write - actually make the connection
278 */
279void
280do_write(int devfd, char *tty, char *mytty, const char *login)
281{
282	char *nows;
283	time_t now;
284	char host[MAXHOSTNAMELEN];
285	wchar_t line[512];
286	int fd;
287
288	fd = openat(devfd, tty, O_WRONLY);
289	if (fd < 0)
290		err(1, "openat(%s%s)", _PATH_DEV, tty);
291	fclose(stdout);
292	stdout = fdopen(fd, "w");
293	if (stdout == NULL)
294		err(1, "%s%s", _PATH_DEV, tty);
295
296	(void)signal(SIGINT, done);
297	(void)signal(SIGHUP, done);
298
299	/* print greeting */
300	if (gethostname(host, sizeof(host)) < 0)
301		(void)strcpy(host, "???");
302	now = time((time_t *)NULL);
303	nows = ctime(&now);
304	nows[16] = '\0';
305	(void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
306	    login, host, mytty, nows + 11);
307
308	while (fgetws(line, sizeof(line)/sizeof(wchar_t), stdin) != NULL)
309		wr_fputs(line);
310}
311
312/*
313 * done - cleanup and exit
314 */
315void
316done(int n __unused)
317{
318	(void)printf("EOF\r\n");
319	exit(0);
320}
321
322/*
323 * wr_fputs - like fputs(), but makes control characters visible and
324 *     turns \n into \r\n
325 */
326void
327wr_fputs(wchar_t *s)
328{
329
330#define	PUTC(c)	if (putwchar(c) == WEOF) err(1, NULL);
331
332	for (; *s != L'\0'; ++s) {
333		if (*s == L'\n') {
334			PUTC(L'\r');
335			PUTC(L'\n');
336		} else if (iswprint(*s) || iswspace(*s)) {
337			PUTC(*s);
338		} else {
339			wprintf(L"<0x%X>", *s);
340		}
341	}
342	return;
343#undef PUTC
344}
345