]> git.ipfire.org Git - thirdparty/util-linux.git/blob - term-utils/write.c
term-utils: verify writing to streams was successful
[thirdparty/util-linux.git] / term-utils / write.c
1 /*
2 * Copyright (c) 1989, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 *
36 * Modified for Linux, Mon Mar 8 18:16:24 1993, faith@cs.unc.edu
37 * Wed Jun 22 21:41:56 1994, faith@cs.unc.edu:
38 * Added fix from Mike Grupenhoff (kashmir@umiacs.umd.edu)
39 * Mon Jul 1 17:01:39 MET DST 1996, janl@math.uio.no:
40 * - Added fix from David.Chapell@mail.trincoll.edu enabeling daemons
41 * to use write.
42 * - ANSIed it since I was working on it anyway.
43 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
44 * - added Native Language Support
45 *
46 */
47
48 #include <stdio.h>
49 #include <unistd.h>
50 #include <utmp.h>
51 #include <errno.h>
52 #include <time.h>
53 #include <pwd.h>
54 #include <string.h>
55 #include <stdlib.h>
56 #include <signal.h>
57 #include <sys/param.h>
58 #include <sys/stat.h>
59 #include <paths.h>
60 #include <asm/param.h>
61 #include <getopt.h>
62
63 #include "c.h"
64 #include "carefulputc.h"
65 #include "closestream.h"
66 #include "nls.h"
67
68 static void __attribute__ ((__noreturn__)) usage(FILE * out);
69 void search_utmp(char *, char *, char *, uid_t);
70 void do_write(char *, char *, uid_t);
71 void wr_fputs(char *);
72 static void __attribute__ ((__noreturn__)) done(int);
73 int term_chk(char *, int *, time_t *, int);
74 int utmp_chk(char *, char *);
75
76 static gid_t myegid;
77
78 static void __attribute__ ((__noreturn__)) usage(FILE * out)
79 {
80 fputs(_("\nUsage:\n"), out);
81 fprintf(out,
82 _(" %s [options] <user> [<ttyname>]\n"),
83 program_invocation_short_name);
84
85 fputs(_("\nOptions:\n"), out);
86 fputs(_(" -V, --version output version information and exit\n"
87 " -h, --help display this help and exit\n\n"), out);
88
89 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
90 }
91
92 int main(int argc, char **argv)
93 {
94 time_t atime;
95 uid_t myuid;
96 int msgsok, myttyfd, c;
97 char tty[MAXPATHLEN], *mytty;
98
99 static const struct option longopts[] = {
100 {"version", no_argument, NULL, 'V'},
101 {"help", no_argument, NULL, 'h'},
102 {NULL, 0, NULL, 0}
103 };
104
105 setlocale(LC_ALL, "");
106 bindtextdomain(PACKAGE, LOCALEDIR);
107 textdomain(PACKAGE);
108 atexit(close_stdout);
109
110 while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
111 switch (c) {
112 case 'V':
113 printf(_("%s from %s\n"),
114 program_invocation_short_name,
115 PACKAGE_STRING);
116 return EXIT_SUCCESS;
117 case 'h':
118 usage(stdout);
119 default:
120 usage(stderr);
121 }
122
123 myegid = getegid();
124
125 /* check that sender has write enabled */
126 if (isatty(fileno(stdin)))
127 myttyfd = fileno(stdin);
128 else if (isatty(fileno(stdout)))
129 myttyfd = fileno(stdout);
130 else if (isatty(fileno(stderr)))
131 myttyfd = fileno(stderr);
132 else
133 myttyfd = -1;
134
135 if (myttyfd != -1) {
136 if (!(mytty = ttyname(myttyfd)))
137 errx(EXIT_FAILURE,
138 _("can't find your tty's name"));
139
140 /*
141 * We may have /dev/ttyN but also /dev/pts/xx. Below,
142 * term_chk() will put "/dev/" in front, so remove that
143 * part.
144 */
145 if (!strncmp(mytty, "/dev/", 5))
146 mytty += 5;
147 if (term_chk(mytty, &msgsok, &atime, 1))
148 exit(EXIT_FAILURE);
149 if (!msgsok)
150 errx(EXIT_FAILURE,
151 _("you have write permission turned off"));
152
153 } else
154 mytty = "<no tty>";
155
156 myuid = getuid();
157
158 /* check args */
159 switch (argc) {
160 case 2:
161 search_utmp(argv[1], tty, mytty, myuid);
162 do_write(tty, mytty, myuid);
163 break;
164 case 3:
165 if (!strncmp(argv[2], "/dev/", 5))
166 argv[2] += 5;
167 if (utmp_chk(argv[1], argv[2]))
168 errx(EXIT_FAILURE,
169 _("%s is not logged in on %s"),
170 argv[1], argv[2]);
171 if (term_chk(argv[2], &msgsok, &atime, 1))
172 exit(EXIT_FAILURE);
173 if (myuid && !msgsok)
174 errx(EXIT_FAILURE,
175 _("%s has messages disabled on %s"),
176 argv[1], argv[2]);
177 do_write(argv[2], mytty, myuid);
178 break;
179 default:
180 usage(stderr);
181 }
182
183 done(0);
184 /* NOTREACHED */
185 return EXIT_FAILURE;
186 }
187
188
189 /*
190 * utmp_chk - checks that the given user is actually logged in on
191 * the given tty
192 */
193 int utmp_chk(char *user, char *tty)
194 {
195 struct utmp u;
196 struct utmp *uptr;
197 int res = 1;
198
199 utmpname(_PATH_UTMP);
200 setutent();
201
202 while ((uptr = getutent())) {
203 memcpy(&u, uptr, sizeof(u));
204 if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0 &&
205 strncmp(tty, u.ut_line, sizeof(u.ut_line)) == 0) {
206 res = 0;
207 break;
208 }
209 }
210
211 endutent();
212 return res;
213 }
214
215 /*
216 * search_utmp - search utmp for the "best" terminal to write to
217 *
218 * Ignores terminals with messages disabled, and of the rest, returns
219 * the one with the most recent access time. Returns as value the number
220 * of the user's terminals with messages enabled, or -1 if the user is
221 * not logged in at all.
222 *
223 * Special case for writing to yourself - ignore the terminal you're
224 * writing from, unless that's the only terminal with messages enabled.
225 */
226 void search_utmp(char *user, char *tty, char *mytty, uid_t myuid)
227 {
228 struct utmp u;
229 struct utmp *uptr;
230 time_t bestatime, atime;
231 int nloggedttys, nttys, msgsok, user_is_me;
232 char atty[sizeof(u.ut_line) + 1];
233
234 utmpname(_PATH_UTMP);
235 setutent();
236
237 nloggedttys = nttys = 0;
238 bestatime = 0;
239 user_is_me = 0;
240 while ((uptr = getutent())) {
241 memcpy(&u, uptr, sizeof(u));
242 if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0) {
243 ++nloggedttys;
244 strncpy(atty, u.ut_line, sizeof(u.ut_line));
245 atty[sizeof(u.ut_line)] = '\0';
246 if (term_chk(atty, &msgsok, &atime, 0))
247 /* bad term? skip */
248 continue;
249 if (myuid && !msgsok)
250 /* skip ttys with msgs off */
251 continue;
252 if (strcmp(atty, mytty) == 0) {
253 user_is_me = 1;
254 /* don't write to yourself */
255 continue;
256 }
257 if (u.ut_type != USER_PROCESS)
258 /* it's not a valid entry */
259 continue;
260 ++nttys;
261 if (atime > bestatime) {
262 bestatime = atime;
263 strcpy(tty, atty);
264 }
265 }
266 }
267
268 endutent();
269 if (nloggedttys == 0)
270 errx(EXIT_FAILURE, _("%s is not logged in"), user);
271 if (nttys == 0) {
272 if (user_is_me) {
273 /* ok, so write to yourself! */
274 strcpy(tty, mytty);
275 return;
276 }
277 errx(EXIT_FAILURE, _("%s has messages disabled"), user);
278 } else if (nttys > 1) {
279 warnx(_("%s is logged in more than once; writing to %s"),
280 user, tty);
281 }
282 }
283
284 /*
285 * term_chk - check that a terminal exists, and get the message bit
286 * and the access time
287 */
288 int term_chk(char *tty, int *msgsokP, time_t * atimeP, int showerror)
289 {
290 struct stat s;
291 char path[MAXPATHLEN];
292
293 if (strlen(tty) + 6 > sizeof(path))
294 return 1;
295 sprintf(path, "/dev/%s", tty);
296 if (stat(path, &s) < 0) {
297 if (showerror)
298 warn("%s", path);
299 return 1;
300 }
301
302 /* group write bit and group ownership */
303 *msgsokP = (s.st_mode & (S_IWRITE >> 3)) && myegid == s.st_gid;
304 *atimeP = s.st_atime;
305 return 0;
306 }
307
308 /*
309 * do_write - actually make the connection
310 */
311 void do_write(char *tty, char *mytty, uid_t myuid)
312 {
313 char *login, *pwuid, *nows;
314 struct passwd *pwd;
315 time_t now;
316 char path[MAXPATHLEN], host[MAXHOSTNAMELEN], line[512];
317
318 /* Determine our login name(s) before the we reopen() stdout */
319 if ((pwd = getpwuid(myuid)) != NULL)
320 pwuid = pwd->pw_name;
321 else
322 pwuid = "???";
323 if ((login = getlogin()) == NULL)
324 login = pwuid;
325
326 if (strlen(tty) + 6 > sizeof(path))
327 errx(EXIT_FAILURE, _("tty path %s too long"), tty);
328 snprintf(path, sizeof(path), "/dev/%s", tty);
329 if ((freopen(path, "w", stdout)) == NULL)
330 err(EXIT_FAILURE, "%s", path);
331
332 signal(SIGINT, done);
333 signal(SIGHUP, done);
334
335 /* print greeting */
336 if (gethostname(host, sizeof(host)) < 0)
337 strcpy(host, "???");
338 now = time((time_t *) NULL);
339 nows = ctime(&now);
340 nows[16] = '\0';
341 printf("\r\n\007\007\007");
342 if (strcmp(login, pwuid))
343 printf(_("Message from %s@%s (as %s) on %s at %s ..."),
344 login, host, pwuid, mytty, nows + 11);
345 else
346 printf(_("Message from %s@%s on %s at %s ..."),
347 login, host, mytty, nows + 11);
348 printf("\r\n");
349
350 while (fgets(line, sizeof(line), stdin) != NULL)
351 wr_fputs(line);
352 }
353
354 /*
355 * done - cleanup and exit
356 */
357 static void __attribute__ ((__noreturn__))
358 done(int dummy __attribute__ ((__unused__)))
359 {
360 printf("EOF\r\n");
361 _exit(EXIT_SUCCESS);
362 }
363
364 /*
365 * wr_fputs - like fputs(), but makes control characters visible and
366 * turns \n into \r\n.
367 */
368 void wr_fputs(char *s)
369 {
370 char c;
371
372 #define PUTC(c) if (carefulputc(c, stdout) == EOF) \
373 err(EXIT_FAILURE, _("carefulputc failed"));
374 while (*s) {
375 c = *s++;
376 if (c == '\n')
377 PUTC('\r');
378 PUTC(c);
379 }
380 return;
381 #undef PUTC
382 }