1 /* $OpenBSD: write.c,v 1.36 2021/10/24 21:24:18 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/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
main(int argc,char * argv[])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
utmp_chk(char * user,char * tty)122 utmp_chk(char *user, char *tty)
123 {
124 struct utmp u;
125 int ufd;
126
127 if ((ufd = open(_PATH_UTMP, O_RDONLY)) == -1)
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
search_utmp(char * user,char * tty,int ttyl,char * mytty,uid_t myuid)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)) == -1)
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
term_chk(char * tty,int * msgsokP,time_t * atimeP,int showerror)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) == -1) {
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
do_write(char * tty,char * mytty,uid_t myuid)225 do_write(char *tty, char *mytty, uid_t myuid)
226 {
227 const char *login;
228 char *nows;
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 login = user_from_uid(myuid, 0);
237
238 (void)snprintf(path, sizeof(path), "%s%s", _PATH_DEV, tty);
239 fd = open(path, O_WRONLY);
240 if (fd == -1)
241 err(1, "open %s", path);
242 fflush(stdout);
243 if (dup2(fd, STDOUT_FILENO) == -1)
244 err(1, "dup2 %s", path);
245 if (fd != STDOUT_FILENO)
246 close(fd);
247
248 /* revoke privs, now that we have opened the tty */
249 gid = getgid();
250 if (setresgid(gid, gid, gid) == -1)
251 err(1, "setresgid");
252
253 /*
254 * Unfortunately this is rather late - well after utmp
255 * parsing, then pinned by the tty open and setresgid
256 */
257 if (pledge("stdio", NULL) == -1)
258 err(1, "pledge");
259
260 (void)signal(SIGINT, done);
261 (void)signal(SIGHUP, done);
262
263 /* print greeting */
264 if (gethostname(host, sizeof(host)) == -1)
265 (void)strlcpy(host, "???", sizeof host);
266 now = time(NULL);
267 nows = ctime(&now);
268 nows[16] = '\0';
269 (void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
270 login, host, mytty, nows + 11);
271
272 while (fgets(line, sizeof(line), stdin) != NULL)
273 wr_fputs(line);
274 }
275
276 /*
277 * done - cleanup and exit
278 */
279 void
done(int sig)280 done(int sig)
281 {
282 (void)write(STDOUT_FILENO, "EOF\r\n", 5);
283 if (sig)
284 _exit(0);
285 else
286 exit(0);
287 }
288
289 /*
290 * wr_fputs - like fputs(), but makes control characters visible and
291 * turns \n into \r\n
292 */
293 void
wr_fputs(char * s)294 wr_fputs(char *s)
295 {
296
297 #define PUTC(c) if (putchar(c) == EOF) goto err;
298
299 for (; *s != '\0'; ++s) {
300 if (*s == '\n') {
301 PUTC('\r');
302 PUTC('\n');
303 continue;
304 }
305 if (isu8cont(*s))
306 continue;
307 if (isprint(*s) || isspace(*s) || *s == '\a') {
308 PUTC(*s);
309 } else {
310 PUTC('?');
311 }
312
313 }
314 return;
315
316 err: err(1, NULL);
317 #undef PUTC
318 }
319
320 static int
isu8cont(unsigned char c)321 isu8cont(unsigned char c)
322 {
323 return (c & (0x80 | 0x40)) == 0x80;
324 }
325