xref: /openbsd/usr.bin/write/write.c (revision 898184e3)
1 /*	$OpenBSD: write.c,v 1.26 2009/10/27 23:59:50 deraadt 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/param.h>
37 #include <sys/stat.h>
38 #include <ctype.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <signal.h>
43 #include <time.h>
44 #include <fcntl.h>
45 #include <paths.h>
46 #include <pwd.h>
47 #include <unistd.h>
48 #include <utmp.h>
49 #include <err.h>
50 #include <vis.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 
59 int
60 main(int argc, char *argv[])
61 {
62 	char tty[MAXPATHLEN], *mytty, *cp;
63 	int msgsok, myttyfd;
64 	time_t atime;
65 	uid_t myuid;
66 
67 	/* check that sender has write enabled */
68 	if (isatty(fileno(stdin)))
69 		myttyfd = fileno(stdin);
70 	else if (isatty(fileno(stdout)))
71 		myttyfd = fileno(stdout);
72 	else if (isatty(fileno(stderr)))
73 		myttyfd = fileno(stderr);
74 	else
75 		errx(1, "can't find your tty");
76 	if (!(mytty = ttyname(myttyfd)))
77 		errx(1, "can't find your tty's name");
78 	if ((cp = strrchr(mytty, '/')))
79 		mytty = cp + 1;
80 	if (term_chk(mytty, &msgsok, &atime, 1))
81 		exit(1);
82 	if (!msgsok)
83 		warnx("you have write permission turned off");
84 
85 	myuid = getuid();
86 
87 	/* check args */
88 	switch (argc) {
89 	case 2:
90 		search_utmp(argv[1], tty, sizeof tty, mytty, myuid);
91 		do_write(tty, mytty, myuid);
92 		break;
93 	case 3:
94 		if (!strncmp(argv[2], _PATH_DEV, sizeof(_PATH_DEV) - 1))
95 			argv[2] += sizeof(_PATH_DEV) - 1;
96 		if (utmp_chk(argv[1], argv[2]))
97 			errx(1, "%s is not logged in on %s",
98 			    argv[1], argv[2]);
99 		if (term_chk(argv[2], &msgsok, &atime, 1))
100 			exit(1);
101 		if (myuid && !msgsok)
102 			errx(1, "%s has messages disabled on %s",
103 			    argv[1], argv[2]);
104 		do_write(argv[2], mytty, myuid);
105 		break;
106 	default:
107 		(void)fprintf(stderr, "usage: write user [ttyname]\n");
108 		exit(1);
109 	}
110 	done(0);
111 
112 	/* NOTREACHED */
113 	return (0);
114 }
115 
116 /*
117  * utmp_chk - checks that the given user is actually logged in on
118  *     the given tty
119  */
120 int
121 utmp_chk(char *user, char *tty)
122 {
123 	struct utmp u;
124 	int ufd;
125 
126 	if ((ufd = open(_PATH_UTMP, O_RDONLY)) < 0)
127 		return(1);	/* no utmp, cannot talk to users */
128 
129 	while (read(ufd, (char *) &u, sizeof(u)) == sizeof(u))
130 		if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0 &&
131 		    strncmp(tty, u.ut_line, sizeof(u.ut_line)) == 0) {
132 			(void)close(ufd);
133 			return(0);
134 		}
135 
136 	(void)close(ufd);
137 	return(1);
138 }
139 
140 /*
141  * search_utmp - search utmp for the "best" terminal to write to
142  *
143  * Ignores terminals with messages disabled, and of the rest, returns
144  * the one with the most recent access time.  Returns as value the number
145  * of the user's terminals with messages enabled, or -1 if the user is
146  * not logged in at all.
147  *
148  * Special case for writing to yourself - ignore the terminal you're
149  * writing from, unless that's the only terminal with messages enabled.
150  */
151 void
152 search_utmp(char *user, char *tty, int ttyl, char *mytty, uid_t myuid)
153 {
154 	struct utmp u;
155 	time_t bestatime, atime;
156 	int ufd, nloggedttys, nttys, msgsok, user_is_me;
157 	char atty[UT_LINESIZE + 1];
158 
159 	if ((ufd = open(_PATH_UTMP, O_RDONLY)) < 0)
160 		err(1, "%s", _PATH_UTMP);
161 
162 	nloggedttys = nttys = 0;
163 	bestatime = 0;
164 	user_is_me = 0;
165 	while (read(ufd, (char *) &u, sizeof(u)) == sizeof(u))
166 		if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0) {
167 			++nloggedttys;
168 			(void)strncpy(atty, u.ut_line, UT_LINESIZE);
169 			atty[UT_LINESIZE] = '\0';
170 			if (term_chk(atty, &msgsok, &atime, 0))
171 				continue;	/* bad term? skip */
172 			if (myuid && !msgsok)
173 				continue;	/* skip ttys with msgs off */
174 			if (strcmp(atty, mytty) == 0) {
175 				user_is_me = 1;
176 				continue;	/* don't write to yourself */
177 			}
178 			++nttys;
179 			if (atime > bestatime) {
180 				bestatime = atime;
181 				(void)strlcpy(tty, atty, ttyl);
182 			}
183 		}
184 
185 	(void)close(ufd);
186 	if (nloggedttys == 0)
187 		errx(1, "%s is not logged in", user);
188 	if (nttys == 0) {
189 		if (user_is_me) {		/* ok, so write to yourself! */
190 			(void)strlcpy(tty, mytty, ttyl);
191 			return;
192 		}
193 		errx(1, "%s has messages disabled", user);
194 	} else if (nttys > 1)
195 		warnx("%s is logged in more than once; writing to %s",
196 		    user, tty);
197 }
198 
199 /*
200  * term_chk - check that a terminal exists, and get the message bit
201  *     and the access time
202  */
203 int
204 term_chk(char *tty, int *msgsokP, time_t *atimeP, int showerror)
205 {
206 	struct stat s;
207 	char path[MAXPATHLEN];
208 
209 	(void)snprintf(path, sizeof(path), "%s%s", _PATH_DEV, tty);
210 	if (stat(path, &s) < 0) {
211 		if (showerror)
212 			warn("%s", path);
213 		return(1);
214 	}
215 	*msgsokP = (s.st_mode & S_IWGRP) != 0;	/* group write bit */
216 	*atimeP = s.st_atime;
217 	return(0);
218 }
219 
220 /*
221  * do_write - actually make the connection
222  */
223 void
224 do_write(char *tty, char *mytty, uid_t myuid)
225 {
226 	char *login, *nows;
227 	struct passwd *pwd;
228 	time_t now;
229 	char path[MAXPATHLEN], host[MAXHOSTNAMELEN], line[512];
230 	gid_t gid;
231 
232 	/* Determine our login name before the we reopen() stdout */
233 	if ((login = getlogin()) == NULL) {
234 		if ((pwd = getpwuid(myuid)))
235 			login = pwd->pw_name;
236 		else
237 			login = "???";
238 	}
239 
240 	(void)snprintf(path, sizeof(path), "%s%s", _PATH_DEV, tty);
241 	if ((freopen(path, "w", stdout)) == NULL)
242 		err(1, "%s", path);
243 
244 	/* revoke privs, now that we have opened the tty */
245 	gid = getgid();
246 	if (setresgid(gid, gid, gid) == -1)
247 		err(1, "setresgid");
248 
249 	(void)signal(SIGINT, done);
250 	(void)signal(SIGHUP, done);
251 
252 	/* print greeting */
253 	if (gethostname(host, sizeof(host)) < 0)
254 		(void)strlcpy(host, "???", sizeof host);
255 	now = time((time_t *)NULL);
256 	nows = ctime(&now);
257 	nows[16] = '\0';
258 	(void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
259 	    login, host, mytty, nows + 11);
260 
261 	while (fgets(line, sizeof(line), stdin) != NULL)
262 		wr_fputs(line);
263 }
264 
265 /*
266  * done - cleanup and exit
267  */
268 void
269 done(int sig)
270 {
271 	(void)write(STDOUT_FILENO, "EOF\r\n", 5);
272 	if (sig)
273 		_exit(0);
274 	else
275 		exit(0);
276 }
277 
278 /*
279  * wr_fputs - like fputs(), but makes control characters visible and
280  *     turns \n into \r\n
281  */
282 void
283 wr_fputs(char *s)
284 {
285 	u_char c;
286 	char visout[5], *s2;
287 
288 #define	PUTC(c)	if (putchar(c) == EOF) goto err;
289 
290 	for (; *s != '\0'; ++s) {
291 		c = toascii(*s);
292 		if (c == '\n') {
293 			PUTC('\r');
294 			PUTC('\n');
295 			continue;
296 		}
297 		vis(visout, c, VIS_SAFE|VIS_NOSLASH, s[1]);
298 		for (s2 = visout; *s2; s2++)
299 			PUTC(*s2);
300 	}
301 	return;
302 
303 err:	err(1, NULL);
304 #undef PUTC
305 }
306