]>
Commit | Line | Data |
---|---|---|
6dbe3af9 | 1 | /* |
726f69e2 KZ |
2 | * Copyright (c) 1989, 1993 |
3 | * The Regents of the University of California. All rights reserved. | |
6dbe3af9 KZ |
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) | |
fd6b7a7f | 39 | * Mon Jul 1 17:01:39 MET DST 1996, janl@math.uio.no: |
9e930041 | 40 | * - Added fix from David.Chapell@mail.trincoll.edu enabling daemons |
fd6b7a7f KZ |
41 | * to use write. |
42 | * - ANSIed it since I was working on it anyway. | |
b50945d4 | 43 | * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> |
7eda085c KZ |
44 | * - added Native Language Support |
45 | * | |
6dbe3af9 KZ |
46 | */ |
47 | ||
22853e4a | 48 | #include <errno.h> |
5592bf0d SK |
49 | #include <getopt.h> |
50 | #include <paths.h> | |
6dbe3af9 | 51 | #include <pwd.h> |
22853e4a | 52 | #include <signal.h> |
5592bf0d SK |
53 | #include <stdio.h> |
54 | #include <stdlib.h> | |
55 | #include <string.h> | |
fd6b7a7f | 56 | #include <sys/param.h> |
fd6b7a7f | 57 | #include <sys/stat.h> |
5592bf0d SK |
58 | #include <time.h> |
59 | #include <unistd.h> | |
b4b919fe | 60 | #include <utmpx.h> |
cdd2a8c3 | 61 | |
5f51b8b2 | 62 | #include "c.h" |
66ee8158 | 63 | #include "carefulputc.h" |
cdd2a8c3 | 64 | #include "closestream.h" |
7eda085c | 65 | #include "nls.h" |
e4aaa0e5 | 66 | #include "strutils.h" |
a58b9025 | 67 | #include "ttyutils.h" |
a0fc344b | 68 | #include "xalloc.h" |
5f51b8b2 | 69 | |
97ebab4e SK |
70 | static sig_atomic_t signal_received = 0; |
71 | ||
b5a69379 SK |
72 | struct write_control { |
73 | uid_t src_uid; | |
74 | const char *src_login; | |
a58b9025 SK |
75 | const char *src_tty_path; |
76 | const char *src_tty_name; | |
b5a69379 | 77 | const char *dst_login; |
a58b9025 SK |
78 | char *dst_tty_path; |
79 | const char *dst_tty_name; | |
b5a69379 SK |
80 | }; |
81 | ||
9325dbfd | 82 | static void __attribute__((__noreturn__)) usage(void) |
aa44b95f | 83 | { |
9325dbfd | 84 | FILE *out = stdout; |
db433bf7 | 85 | fputs(USAGE_HEADER, out); |
4c27cd90 KZ |
86 | fprintf(out, |
87 | _(" %s [options] <user> [<ttyname>]\n"), | |
88 | program_invocation_short_name); | |
aa44b95f | 89 | |
451dbcfa BS |
90 | fputs(USAGE_SEPARATOR, out); |
91 | fputs(_("Send a message to another user.\n"), out); | |
92 | ||
db433bf7 | 93 | fputs(USAGE_OPTIONS, out); |
f45f3ec3 RM |
94 | printf(USAGE_HELP_OPTIONS(16)); |
95 | printf(USAGE_MAN_TAIL("write(1)")); | |
9325dbfd | 96 | exit(EXIT_SUCCESS); |
aa44b95f SK |
97 | } |
98 | ||
b985f372 | 99 | /* |
d732758e | 100 | * check_tty - check that a terminal exists, and get the message bit |
b985f372 SK |
101 | * and the access time |
102 | */ | |
a58b9025 | 103 | static int check_tty(const char *tty, int *tty_writeable, time_t *tty_atime, int showerror) |
5f51b8b2 | 104 | { |
b985f372 | 105 | struct stat s; |
6dbe3af9 | 106 | |
a58b9025 | 107 | if (stat(tty, &s) < 0) { |
b985f372 | 108 | if (showerror) |
a58b9025 | 109 | warn("%s", tty); |
b985f372 | 110 | return 1; |
6dbe3af9 | 111 | } |
b985f372 | 112 | if (getuid() == 0) /* root can always write */ |
d732758e | 113 | *tty_writeable = 1; |
50e417c8 SK |
114 | else { |
115 | if (getegid() != s.st_gid) { | |
a58b9025 | 116 | warnx(_("effective gid does not match group of %s"), tty); |
50e417c8 SK |
117 | return 1; |
118 | } | |
119 | *tty_writeable = s.st_mode & S_IWGRP; | |
120 | } | |
d732758e SK |
121 | if (tty_atime) |
122 | *tty_atime = s.st_atime; | |
b985f372 | 123 | return 0; |
6dbe3af9 KZ |
124 | } |
125 | ||
126 | /* | |
d732758e | 127 | * check_utmp - checks that the given user is actually logged in on |
6dbe3af9 KZ |
128 | * the given tty |
129 | */ | |
d732758e | 130 | static int check_utmp(const struct write_control *ctl) |
6dbe3af9 | 131 | { |
b4b919fe | 132 | struct utmpx *u; |
fd6b7a7f | 133 | int res = 1; |
6dbe3af9 | 134 | |
b4b919fe RM |
135 | utmpxname(_PATH_UTMP); |
136 | setutxent(); | |
6dbe3af9 | 137 | |
b4b919fe | 138 | while ((u = getutxent())) { |
c1d0a95e | 139 | if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) == 0 && |
a58b9025 | 140 | strncmp(ctl->dst_tty_name, u->ut_line, sizeof(u->ut_line)) == 0) { |
fd6b7a7f KZ |
141 | res = 0; |
142 | break; | |
6dbe3af9 | 143 | } |
fd6b7a7f | 144 | } |
6dbe3af9 | 145 | |
b4b919fe | 146 | endutxent(); |
5f51b8b2 | 147 | return res; |
6dbe3af9 KZ |
148 | } |
149 | ||
150 | /* | |
151 | * search_utmp - search utmp for the "best" terminal to write to | |
152 | * | |
153 | * Ignores terminals with messages disabled, and of the rest, returns | |
154 | * the one with the most recent access time. Returns as value the number | |
155 | * of the user's terminals with messages enabled, or -1 if the user is | |
156 | * not logged in at all. | |
157 | * | |
158 | * Special case for writing to yourself - ignore the terminal you're | |
159 | * writing from, unless that's the only terminal with messages enabled. | |
160 | */ | |
b5a69379 | 161 | static void search_utmp(struct write_control *ctl) |
6dbe3af9 | 162 | { |
b4b919fe | 163 | struct utmpx *u; |
d732758e SK |
164 | time_t best_atime = 0, tty_atime; |
165 | int num_ttys = 0, valid_ttys = 0, tty_writeable = 0, user_is_me = 0; | |
a924b400 | 166 | char path[sizeof(u->ut_line) + 6]; |
6dbe3af9 | 167 | |
b4b919fe RM |
168 | utmpxname(_PATH_UTMP); |
169 | setutxent(); | |
6dbe3af9 | 170 | |
b4b919fe | 171 | while ((u = getutxent())) { |
5592bf0d SK |
172 | if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) != 0) |
173 | continue; | |
174 | num_ttys++; | |
a58b9025 SK |
175 | sprintf(path, "/dev/%s", u->ut_line); |
176 | if (check_tty(path, &tty_writeable, &tty_atime, 0)) | |
5592bf0d SK |
177 | /* bad term? skip */ |
178 | continue; | |
179 | if (ctl->src_uid && !tty_writeable) | |
180 | /* skip ttys with msgs off */ | |
181 | continue; | |
a58b9025 | 182 | if (strcmp(u->ut_line, ctl->src_tty_name) == 0) { |
5592bf0d SK |
183 | user_is_me = 1; |
184 | /* don't write to yourself */ | |
185 | continue; | |
186 | } | |
187 | if (u->ut_type != USER_PROCESS) | |
188 | /* it's not a valid entry */ | |
189 | continue; | |
190 | valid_ttys++; | |
191 | if (best_atime < tty_atime) { | |
192 | best_atime = tty_atime; | |
a58b9025 SK |
193 | free(ctl->dst_tty_path); |
194 | ctl->dst_tty_path = xstrdup(path); | |
195 | ctl->dst_tty_name = ctl->dst_tty_path + 5; | |
6dbe3af9 | 196 | } |
fd6b7a7f | 197 | } |
6dbe3af9 | 198 | |
b4b919fe | 199 | endutxent(); |
d732758e | 200 | if (num_ttys == 0) |
b5a69379 | 201 | errx(EXIT_FAILURE, _("%s is not logged in"), ctl->dst_login); |
d732758e | 202 | if (valid_ttys == 0) { |
5f51b8b2 SK |
203 | if (user_is_me) { |
204 | /* ok, so write to yourself! */ | |
a58b9025 SK |
205 | if (!ctl->src_tty_path) |
206 | errx(EXIT_FAILURE, _("can't find your tty's name")); | |
207 | ctl->dst_tty_path = xstrdup(ctl->src_tty_path); | |
208 | ctl->dst_tty_name = ctl->dst_tty_path + 5; | |
6dbe3af9 KZ |
209 | return; |
210 | } | |
b5a69379 | 211 | errx(EXIT_FAILURE, _("%s has messages disabled"), ctl->dst_login); |
5592bf0d SK |
212 | } |
213 | if (1 < valid_ttys) | |
5f51b8b2 | 214 | warnx(_("%s is logged in more than once; writing to %s"), |
a58b9025 | 215 | ctl->dst_login, ctl->dst_tty_name); |
6dbe3af9 KZ |
216 | } |
217 | ||
218 | /* | |
97ebab4e | 219 | * signal_handler - cause write loop to exit |
6dbe3af9 | 220 | */ |
97ebab4e | 221 | static void signal_handler(int signo) |
6dbe3af9 | 222 | { |
97ebab4e | 223 | signal_received = signo; |
b985f372 | 224 | } |
6dbe3af9 | 225 | |
b985f372 | 226 | /* |
d732758e | 227 | * write_line - like fputs(), but makes control characters visible and |
b985f372 SK |
228 | * turns \n into \r\n. |
229 | */ | |
d732758e | 230 | static void write_line(char *s) |
b985f372 | 231 | { |
b985f372 | 232 | while (*s) { |
a8031743 SK |
233 | const int c = *s++; |
234 | ||
235 | if ((c == '\n' && fputc_careful('\r', stdout, '^') == EOF) | |
236 | || fputc_careful(c, stdout, '^') == EOF) | |
237 | err(EXIT_FAILURE, _("carefulputc failed")); | |
6dbe3af9 | 238 | } |
6dbe3af9 KZ |
239 | } |
240 | ||
241 | /* | |
242 | * do_write - actually make the connection | |
243 | */ | |
b5a69379 | 244 | static void do_write(const struct write_control *ctl) |
5f51b8b2 | 245 | { |
aefd6a9d | 246 | char *login, *pwuid; |
66ee8158 | 247 | struct passwd *pwd; |
22853e4a | 248 | time_t now; |
a0794392 | 249 | struct tm *tm; |
a58b9025 | 250 | char *host, line[512]; |
97ebab4e | 251 | struct sigaction sigact; |
6dbe3af9 | 252 | |
66ee8158 | 253 | /* Determine our login name(s) before the we reopen() stdout */ |
b5a69379 | 254 | if ((pwd = getpwuid(ctl->src_uid)) != NULL) |
66ee8158 KZ |
255 | pwuid = pwd->pw_name; |
256 | else | |
257 | pwuid = "???"; | |
258 | if ((login = getlogin()) == NULL) | |
259 | login = pwuid; | |
6dbe3af9 | 260 | |
a58b9025 SK |
261 | if ((freopen(ctl->dst_tty_path, "w", stdout)) == NULL) |
262 | err(EXIT_FAILURE, "%s", ctl->dst_tty_path); | |
6dbe3af9 | 263 | |
97ebab4e SK |
264 | sigact.sa_handler = signal_handler; |
265 | sigemptyset(&sigact.sa_mask); | |
266 | sigact.sa_flags = 0; | |
267 | sigaction(SIGINT, &sigact, NULL); | |
268 | sigaction(SIGHUP, &sigact, NULL); | |
6dbe3af9 | 269 | |
a0fc344b KZ |
270 | host = xgethostname(); |
271 | if (!host) | |
272 | host = xstrdup("???"); | |
273 | ||
a0794392 SK |
274 | now = time((time_t *)NULL); |
275 | tm = localtime(&now); | |
a0794392 SK |
276 | /* print greeting */ |
277 | printf("\r\n\a\a\a"); | |
66ee8158 | 278 | if (strcmp(login, pwuid)) |
aefd6a9d KZ |
279 | printf(_("Message from %s@%s (as %s) on %s at %02d:%02d ..."), |
280 | login, host, pwuid, ctl->src_tty_name, | |
281 | tm->tm_hour, tm->tm_min); | |
66ee8158 | 282 | else |
aefd6a9d KZ |
283 | printf(_("Message from %s@%s on %s at %02d:%02d ..."), |
284 | login, host, ctl->src_tty_name, | |
285 | tm->tm_hour, tm->tm_min); | |
a0fc344b | 286 | free(host); |
7eda085c | 287 | printf("\r\n"); |
6dbe3af9 | 288 | |
97ebab4e SK |
289 | while (fgets(line, sizeof(line), stdin) != NULL) { |
290 | if (signal_received) | |
291 | break; | |
d732758e | 292 | write_line(line); |
97ebab4e SK |
293 | } |
294 | printf("EOF\r\n"); | |
6dbe3af9 KZ |
295 | } |
296 | ||
b985f372 | 297 | int main(int argc, char **argv) |
5f51b8b2 | 298 | { |
285c1f3a | 299 | int tty_writeable = 0, c; |
b5a69379 | 300 | struct write_control ctl = { 0 }; |
6dbe3af9 | 301 | |
b985f372 SK |
302 | static const struct option longopts[] = { |
303 | {"version", no_argument, NULL, 'V'}, | |
304 | {"help", no_argument, NULL, 'h'}, | |
305 | {NULL, 0, NULL, 0} | |
306 | }; | |
6dbe3af9 | 307 | |
b985f372 SK |
308 | setlocale(LC_ALL, ""); |
309 | bindtextdomain(PACKAGE, LOCALEDIR); | |
310 | textdomain(PACKAGE); | |
2c308875 | 311 | close_stdout_atexit(); |
b985f372 SK |
312 | |
313 | while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) | |
314 | switch (c) { | |
315 | case 'V': | |
2c308875 | 316 | print_version(EXIT_SUCCESS); |
b985f372 | 317 | case 'h': |
9325dbfd | 318 | usage(); |
b985f372 | 319 | default: |
9325dbfd | 320 | errtryhelp(EXIT_FAILURE); |
b985f372 SK |
321 | } |
322 | ||
285c1f3a SK |
323 | if (get_terminal_name(&ctl.src_tty_path, &ctl.src_tty_name, NULL) == 0) { |
324 | /* check that sender has write enabled */ | |
a58b9025 | 325 | if (check_tty(ctl.src_tty_path, &tty_writeable, NULL, 1)) |
b985f372 | 326 | exit(EXIT_FAILURE); |
d732758e | 327 | if (!tty_writeable) |
b985f372 SK |
328 | errx(EXIT_FAILURE, |
329 | _("you have write permission turned off")); | |
d732758e | 330 | tty_writeable = 0; |
b985f372 | 331 | } else |
a58b9025 | 332 | ctl.src_tty_name = "<no tty>"; |
b985f372 | 333 | |
b5a69379 | 334 | ctl.src_uid = getuid(); |
b985f372 SK |
335 | |
336 | /* check args */ | |
337 | switch (argc) { | |
338 | case 2: | |
b5a69379 SK |
339 | ctl.dst_login = argv[1]; |
340 | search_utmp(&ctl); | |
341 | do_write(&ctl); | |
b985f372 SK |
342 | break; |
343 | case 3: | |
b5a69379 | 344 | ctl.dst_login = argv[1]; |
b985f372 | 345 | if (!strncmp(argv[2], "/dev/", 5)) |
a58b9025 | 346 | ctl.dst_tty_path = xstrdup(argv[2]); |
b5a69379 | 347 | else |
a58b9025 SK |
348 | xasprintf(&ctl.dst_tty_path, "/dev/%s", argv[2]); |
349 | ctl.dst_tty_name = ctl.dst_tty_path + 5; | |
d732758e | 350 | if (check_utmp(&ctl)) |
b985f372 SK |
351 | errx(EXIT_FAILURE, |
352 | _("%s is not logged in on %s"), | |
a58b9025 SK |
353 | ctl.dst_login, ctl.dst_tty_name); |
354 | if (check_tty(ctl.dst_tty_path, &tty_writeable, NULL, 1)) | |
b985f372 | 355 | exit(EXIT_FAILURE); |
d732758e | 356 | if (ctl.src_uid && !tty_writeable) |
b985f372 SK |
357 | errx(EXIT_FAILURE, |
358 | _("%s has messages disabled on %s"), | |
a58b9025 | 359 | ctl.dst_login, ctl.dst_tty_name); |
b5a69379 | 360 | do_write(&ctl); |
b985f372 SK |
361 | break; |
362 | default: | |
677ec86c | 363 | errtryhelp(EXIT_FAILURE); |
6dbe3af9 | 364 | } |
a58b9025 | 365 | free(ctl.dst_tty_path); |
97ebab4e | 366 | return EXIT_SUCCESS; |
6dbe3af9 | 367 | } |