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