xref: /openbsd/usr.bin/write/write.c (revision 5af055cd)
1 /*	$OpenBSD: write.c,v 1.33 2016/02/05 19:00:39 martijn Exp $	*/
2 /*	$NetBSD: write.c,v 1.5 1995/08/31 21:48:32 jtc Exp $	*/
3 
4 /*
5  * Copyright (c) 1989, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include <sys/stat.h>
37 
38 #include <ctype.h>
39 #include <err.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <paths.h>
43 #include <pwd.h>
44 #include <signal.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <time.h>
49 #include <unistd.h>
50 #include <utmp.h>
51 
52 void done(int sig);
53 void do_write(char *, char *, uid_t);
54 void wr_fputs(char *);
55 void search_utmp(char *, char *, int, char *, uid_t);
56 int term_chk(char *, int *, time_t *, int);
57 int utmp_chk(char *, char *);
58 static int isu8cont(unsigned char c);
59 
60 int
61 main(int argc, char *argv[])
62 {
63 	char tty[PATH_MAX], *mytty, *cp;
64 	int msgsok, myttyfd;
65 	time_t atime;
66 	uid_t myuid;
67 
68 	/* check that sender has write enabled */
69 	if (isatty(fileno(stdin)))
70 		myttyfd = fileno(stdin);
71 	else if (isatty(fileno(stdout)))
72 		myttyfd = fileno(stdout);
73 	else if (isatty(fileno(stderr)))
74 		myttyfd = fileno(stderr);
75 	else
76 		errx(1, "can't find your tty");
77 	if (!(mytty = ttyname(myttyfd)))
78 		errx(1, "can't find your tty's name");
79 	if ((cp = strrchr(mytty, '/')))
80 		mytty = cp + 1;
81 	if (term_chk(mytty, &msgsok, &atime, 1))
82 		exit(1);
83 	if (!msgsok)
84 		warnx("you have write permission turned off");
85 
86 	myuid = getuid();
87 
88 	/* check args */
89 	switch (argc) {
90 	case 2:
91 		search_utmp(argv[1], tty, sizeof tty, mytty, myuid);
92 		do_write(tty, mytty, myuid);
93 		break;
94 	case 3:
95 		if (!strncmp(argv[2], _PATH_DEV, sizeof(_PATH_DEV) - 1))
96 			argv[2] += sizeof(_PATH_DEV) - 1;
97 		if (utmp_chk(argv[1], argv[2]))
98 			errx(1, "%s is not logged in on %s",
99 			    argv[1], argv[2]);
100 		if (term_chk(argv[2], &msgsok, &atime, 1))
101 			exit(1);
102 		if (myuid && !msgsok)
103 			errx(1, "%s has messages disabled on %s",
104 			    argv[1], argv[2]);
105 		do_write(argv[2], mytty, myuid);
106 		break;
107 	default:
108 		(void)fprintf(stderr, "usage: write user [ttyname]\n");
109 		exit(1);
110 	}
111 	done(0);
112 
113 	/* NOTREACHED */
114 	return (0);
115 }
116 
117 /*
118  * utmp_chk - checks that the given user is actually logged in on
119  *     the given tty
120  */
121 int
122 utmp_chk(char *user, char *tty)
123 {
124 	struct utmp u;
125 	int ufd;
126 
127 	if ((ufd = open(_PATH_UTMP, O_RDONLY)) < 0)
128 		return(1);	/* no utmp, cannot talk to users */
129 
130 	while (read(ufd, (char *) &u, sizeof(u)) == sizeof(u))
131 		if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0 &&
132 		    strncmp(tty, u.ut_line, sizeof(u.ut_line)) == 0) {
133 			(void)close(ufd);
134 			return(0);
135 		}
136 
137 	(void)close(ufd);
138 	return(1);
139 }
140 
141 /*
142  * search_utmp - search utmp for the "best" terminal to write to
143  *
144  * Ignores terminals with messages disabled, and of the rest, returns
145  * the one with the most recent access time.  Returns as value the number
146  * of the user's terminals with messages enabled, or -1 if the user is
147  * not logged in at all.
148  *
149  * Special case for writing to yourself - ignore the terminal you're
150  * writing from, unless that's the only terminal with messages enabled.
151  */
152 void
153 search_utmp(char *user, char *tty, int ttyl, char *mytty, uid_t myuid)
154 {
155 	struct utmp u;
156 	time_t bestatime, atime;
157 	int ufd, nloggedttys, nttys, msgsok, user_is_me;
158 	char atty[UT_LINESIZE + 1];
159 
160 	if ((ufd = open(_PATH_UTMP, O_RDONLY)) < 0)
161 		err(1, "%s", _PATH_UTMP);
162 
163 	nloggedttys = nttys = 0;
164 	bestatime = 0;
165 	user_is_me = 0;
166 	while (read(ufd, (char *) &u, sizeof(u)) == sizeof(u))
167 		if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0) {
168 			++nloggedttys;
169 			(void)strncpy(atty, u.ut_line, UT_LINESIZE);
170 			atty[UT_LINESIZE] = '\0';
171 			if (term_chk(atty, &msgsok, &atime, 0))
172 				continue;	/* bad term? skip */
173 			if (myuid && !msgsok)
174 				continue;	/* skip ttys with msgs off */
175 			if (strcmp(atty, mytty) == 0) {
176 				user_is_me = 1;
177 				continue;	/* don't write to yourself */
178 			}
179 			++nttys;
180 			if (atime > bestatime) {
181 				bestatime = atime;
182 				(void)strlcpy(tty, atty, ttyl);
183 			}
184 		}
185 
186 	(void)close(ufd);
187 	if (nloggedttys == 0)
188 		errx(1, "%s is not logged in", user);
189 	if (nttys == 0) {
190 		if (user_is_me) {		/* ok, so write to yourself! */
191 			(void)strlcpy(tty, mytty, ttyl);
192 			return;
193 		}
194 		errx(1, "%s has messages disabled", user);
195 	} else if (nttys > 1)
196 		warnx("%s is logged in more than once; writing to %s",
197 		    user, tty);
198 }
199 
200 /*
201  * term_chk - check that a terminal exists, and get the message bit
202  *     and the access time
203  */
204 int
205 term_chk(char *tty, int *msgsokP, time_t *atimeP, int showerror)
206 {
207 	struct stat s;
208 	char path[PATH_MAX];
209 
210 	(void)snprintf(path, sizeof(path), "%s%s", _PATH_DEV, tty);
211 	if (stat(path, &s) < 0) {
212 		if (showerror)
213 			warn("%s", path);
214 		return(1);
215 	}
216 	*msgsokP = (s.st_mode & S_IWGRP) != 0;	/* group write bit */
217 	*atimeP = s.st_atime;
218 	return(0);
219 }
220 
221 /*
222  * do_write - actually make the connection
223  */
224 void
225 do_write(char *tty, char *mytty, uid_t myuid)
226 {
227 	char *login, *nows;
228 	struct passwd *pwd;
229 	time_t now;
230 	char path[PATH_MAX], host[HOST_NAME_MAX+1], line[512];
231 	gid_t gid;
232 	int fd;
233 
234 	/* Determine our login name before the we reopen() stdout */
235 	if ((login = getlogin()) == NULL) {
236 		if ((pwd = getpwuid(myuid)))
237 			login = pwd->pw_name;
238 		else
239 			login = "???";
240 	}
241 
242 	(void)snprintf(path, sizeof(path), "%s%s", _PATH_DEV, tty);
243 	fd = open(path, O_WRONLY, 0666);
244 	if (fd == -1)
245 		err(1, "open %s", path);
246 	fflush(stdout);
247 	if (dup2(fd, STDOUT_FILENO) == -1)
248 		err(1, "dup2 %s", path);
249 	if (fd != STDOUT_FILENO)
250 		close(fd);
251 
252 	/* revoke privs, now that we have opened the tty */
253 	gid = getgid();
254 	if (setresgid(gid, gid, gid) == -1)
255 		err(1, "setresgid");
256 
257 	/*
258 	 * Unfortunately this is rather late - well after utmp
259 	 * parsing, then pinned by the tty open and setresgid
260 	 */
261 	if (pledge("stdio", NULL) == -1)
262 		err(1, "pledge");
263 
264 	(void)signal(SIGINT, done);
265 	(void)signal(SIGHUP, done);
266 
267 	/* print greeting */
268 	if (gethostname(host, sizeof(host)) < 0)
269 		(void)strlcpy(host, "???", sizeof host);
270 	now = time(NULL);
271 	nows = ctime(&now);
272 	nows[16] = '\0';
273 	(void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
274 	    login, host, mytty, nows + 11);
275 
276 	while (fgets(line, sizeof(line), stdin) != NULL)
277 		wr_fputs(line);
278 }
279 
280 /*
281  * done - cleanup and exit
282  */
283 void
284 done(int sig)
285 {
286 	(void)write(STDOUT_FILENO, "EOF\r\n", 5);
287 	if (sig)
288 		_exit(0);
289 	else
290 		exit(0);
291 }
292 
293 /*
294  * wr_fputs - like fputs(), but makes control characters visible and
295  *     turns \n into \r\n
296  */
297 void
298 wr_fputs(char *s)
299 {
300 
301 #define	PUTC(c)	if (putchar(c) == EOF) goto err;
302 
303 	for (; *s != '\0'; ++s) {
304 		if (*s == '\n') {
305 			PUTC('\r');
306 			PUTC('\n');
307 			continue;
308 		}
309 		if (isu8cont(*s))
310 			continue;
311 		if (isprint(*s) || isspace(*s) || *s == '\a') {
312 			PUTC(*s);
313 		} else {
314 			PUTC('?');
315 		}
316 
317 	}
318 	return;
319 
320 err:	err(1, NULL);
321 #undef PUTC
322 }
323 
324 static int
325 isu8cont(unsigned char c)
326 {
327 	return (c & (0x80 | 0x40)) == 0x80;
328 }
329