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 flock rpath cpath wpath", 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 |
|
|
} |