1/*	$NetBSD: ttymsg.c,v 1.22 2005/08/27 17:16:06 elad Exp $	*/
2
3/*
4 * Copyright (c) 1989, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#if defined(LIBC_SCCS) && !defined(lint)
34#if 0
35static char sccsid[] = "@(#)ttymsg.c	8.2 (Berkeley) 11/16/93";
36#else
37__RCSID("$NetBSD: ttymsg.c,v 1.22 2005/08/27 17:16:06 elad Exp $");
38#endif
39#endif /* LIBC_SCCS and not lint */
40
41#include <sys/types.h>
42#include <sys/uio.h>
43
44#include <assert.h>
45#include <dirent.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <paths.h>
49#include <signal.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <unistd.h>
54#include <util.h>
55
56/*
57 * Display the contents of a uio structure on a terminal.  Used by wall(1),
58 * syslogd(8), and talkd(8).  Forks and finishes in child if write would block,
59 * waiting up to tmout seconds.  Returns pointer to error string on unexpected
60 * error; string is not newline-terminated.  Various "normal" errors are
61 * ignored (exclusive-use, lack of permission, etc.).
62 */
63char *
64ttymsg(struct iovec *iov, int iovcnt, const char *line, int tmout)
65{
66	static char errbuf[1024];
67	char device[MAXNAMLEN];
68	const char *ptr;
69	int fd, ret;
70	struct iovec localiov[32];
71	sigset_t nset;
72	int forked = 0;
73	size_t cnt, left, wret;
74
75	_DIAGASSERT(iov != NULL);
76	_DIAGASSERT(iovcnt >= 0);
77	_DIAGASSERT(line != NULL);
78
79	if (iovcnt < 0) {
80		(void)snprintf(errbuf, sizeof(errbuf),
81		    "%s: negative iovcnt", __func__);
82		return errbuf;
83	}
84
85	if ((size_t)iovcnt >= sizeof(localiov) / sizeof(localiov[0])) {
86		(void)snprintf(errbuf, sizeof(errbuf),
87		    "%s: too many iov's (%d) max is %zu", __func__,
88		    iovcnt, sizeof(localiov) / sizeof(localiov[0]));
89		return errbuf;
90	}
91
92	ptr = strncmp(line, "pts/", (size_t)4) == 0 ? line + 4 : line;
93	if (strcspn(ptr, "./") != strlen(ptr)) {
94		/* A slash or dot is an attempt to break security... */
95		(void)snprintf(errbuf, sizeof(errbuf),
96		    "%s: '/' or '.' in \"%s\"", __func__, line);
97		return errbuf;
98	}
99	ret = snprintf(device, sizeof(device), "%s%s", _PATH_DEV, line);
100	if (ret == -1 || ret >= (int)sizeof(device)) {
101		(void) snprintf(errbuf, sizeof(errbuf),
102		    "%s: line `%s' too long", __func__, line);
103		return errbuf;
104	}
105	cnt = (size_t)ret;
106
107	/*
108	 * open will fail on slip lines or exclusive-use lines
109	 * if not running as root; not an error.
110	 */
111	if ((fd = open(device, O_WRONLY|O_NONBLOCK, 0)) < 0) {
112		if (errno == EBUSY || errno == EACCES)
113			return NULL;
114		(void)snprintf(errbuf, sizeof(errbuf),
115		    "%s: Cannot open `%s' (%s)",
116		    __func__, device, strerror(errno));
117		return errbuf;
118	}
119	if (!isatty(fd)) {
120		(void)snprintf(errbuf, sizeof(errbuf),
121		    "%s: line `%s' is not a tty device", __func__, device);
122		(void)close(fd);
123		return errbuf;
124	}
125
126	for (cnt = left = 0; cnt < (size_t)iovcnt; ++cnt)
127		left += iov[cnt].iov_len;
128
129	for (;;) {
130		wret = writev(fd, iov, iovcnt);
131		if (wret >= left)
132			break;
133		if (wret > 0) {
134			left -= wret;
135			if (iov != localiov) {
136				(void)memcpy(localiov, iov,
137				    iovcnt * sizeof(struct iovec));
138				iov = localiov;
139			}
140			for (cnt = 0; wret >= iov->iov_len; ++cnt) {
141				wret -= iov->iov_len;
142				++iov;
143				--iovcnt;
144			}
145			if (wret) {
146				iov->iov_base =
147				    (char *)iov->iov_base + wret;
148				iov->iov_len -= wret;
149			}
150			continue;
151		} else if (wret == 0) {
152			(void)snprintf(errbuf, sizeof(errbuf),
153			    "%s: failed writing %zu bytes to `%s'", __func__,
154			    left, device);
155			(void) close(fd);
156			if (forked)
157				_exit(1);
158			return errbuf;
159		}
160		if (errno == EWOULDBLOCK) {
161			pid_t cpid;
162
163			if (forked) {
164				(void)close(fd);
165				_exit(1);
166			}
167			cpid = fork();
168			if (cpid < 0) {
169				(void)snprintf(errbuf, sizeof(errbuf),
170				    "%s: Cannot fork (%s)", __func__,
171				    strerror(errno));
172				(void)close(fd);
173				return errbuf;
174			}
175			if (cpid) {	/* parent */
176				(void)close(fd);
177				return NULL;
178			}
179			forked++;
180			/* wait at most tmout seconds */
181			(void)signal(SIGALRM, SIG_DFL);
182			(void)signal(SIGTERM, SIG_DFL); /* XXX */
183			sigfillset(&nset);
184			(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
185			(void)alarm((u_int)tmout);
186			(void)fcntl(fd, F_SETFL, 0);	/* clear O_NONBLOCK */
187			continue;
188		}
189		/*
190		 * We get ENODEV on a slip line if we're running as root,
191		 * and EIO if the line just went away.
192		 */
193		if (errno == ENODEV || errno == EIO)
194			break;
195		(void) close(fd);
196		if (forked)
197			_exit(1);
198		(void)snprintf(errbuf, sizeof(errbuf),
199		    "%s: Write to line `%s' failed (%s)", __func__,
200		    device, strerror(errno));
201		return errbuf;
202	}
203
204	(void) close(fd);
205	if (forked)
206		_exit(0);
207	return NULL;
208}
209