]>
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 | |
5c62c270 TK |
62 | #if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1 |
63 | # include <systemd/sd-login.h> | |
64 | # include <systemd/sd-daemon.h> | |
65 | #endif | |
66 | ||
5f51b8b2 | 67 | #include "c.h" |
66ee8158 | 68 | #include "carefulputc.h" |
cdd2a8c3 | 69 | #include "closestream.h" |
7eda085c | 70 | #include "nls.h" |
e4aaa0e5 | 71 | #include "strutils.h" |
a58b9025 | 72 | #include "ttyutils.h" |
a0fc344b | 73 | #include "xalloc.h" |
5f51b8b2 | 74 | |
1fb77758 | 75 | static volatile sig_atomic_t signal_received = 0; |
97ebab4e | 76 | |
b5a69379 SK |
77 | struct write_control { |
78 | uid_t src_uid; | |
79 | const char *src_login; | |
a58b9025 SK |
80 | const char *src_tty_path; |
81 | const char *src_tty_name; | |
b5a69379 | 82 | const char *dst_login; |
a58b9025 SK |
83 | char *dst_tty_path; |
84 | const char *dst_tty_name; | |
b5a69379 SK |
85 | }; |
86 | ||
9325dbfd | 87 | static void __attribute__((__noreturn__)) usage(void) |
aa44b95f | 88 | { |
9325dbfd | 89 | FILE *out = stdout; |
db433bf7 | 90 | fputs(USAGE_HEADER, out); |
4c27cd90 KZ |
91 | fprintf(out, |
92 | _(" %s [options] <user> [<ttyname>]\n"), | |
93 | program_invocation_short_name); | |
aa44b95f | 94 | |
451dbcfa BS |
95 | fputs(USAGE_SEPARATOR, out); |
96 | fputs(_("Send a message to another user.\n"), out); | |
97 | ||
db433bf7 | 98 | fputs(USAGE_OPTIONS, out); |
bad4c729 MY |
99 | fprintf(out, USAGE_HELP_OPTIONS(16)); |
100 | fprintf(out, USAGE_MAN_TAIL("write(1)")); | |
9325dbfd | 101 | exit(EXIT_SUCCESS); |
aa44b95f SK |
102 | } |
103 | ||
b985f372 | 104 | /* |
d732758e | 105 | * check_tty - check that a terminal exists, and get the message bit |
b985f372 SK |
106 | * and the access time |
107 | */ | |
a58b9025 | 108 | static int check_tty(const char *tty, int *tty_writeable, time_t *tty_atime, int showerror) |
5f51b8b2 | 109 | { |
b985f372 | 110 | struct stat s; |
6dbe3af9 | 111 | |
a58b9025 | 112 | if (stat(tty, &s) < 0) { |
b985f372 | 113 | if (showerror) |
a58b9025 | 114 | warn("%s", tty); |
b985f372 | 115 | return 1; |
6dbe3af9 | 116 | } |
b985f372 | 117 | if (getuid() == 0) /* root can always write */ |
d732758e | 118 | *tty_writeable = 1; |
50e417c8 SK |
119 | else { |
120 | if (getegid() != s.st_gid) { | |
a58b9025 | 121 | warnx(_("effective gid does not match group of %s"), tty); |
50e417c8 SK |
122 | return 1; |
123 | } | |
124 | *tty_writeable = s.st_mode & S_IWGRP; | |
125 | } | |
d732758e SK |
126 | if (tty_atime) |
127 | *tty_atime = s.st_atime; | |
b985f372 | 128 | return 0; |
6dbe3af9 KZ |
129 | } |
130 | ||
131 | /* | |
d732758e | 132 | * check_utmp - checks that the given user is actually logged in on |
6dbe3af9 KZ |
133 | * the given tty |
134 | */ | |
d732758e | 135 | static int check_utmp(const struct write_control *ctl) |
6dbe3af9 | 136 | { |
b4b919fe | 137 | struct utmpx *u; |
fd6b7a7f | 138 | int res = 1; |
5c62c270 TK |
139 | #if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1 |
140 | if (sd_booted() > 0) { | |
141 | char **sessions_list; | |
142 | int sessions = sd_get_sessions(&sessions_list); | |
143 | if (sessions < 0) | |
144 | errx(EXIT_FAILURE, _("error getting sessions: %s"), | |
145 | strerror(-sessions)); | |
6dbe3af9 | 146 | |
5c62c270 | 147 | for (int i = 0; i < sessions; i++) { |
6dbe3af9 | 148 | |
5c62c270 TK |
149 | char *name, *tty; |
150 | int r; | |
151 | ||
152 | if ((r = sd_session_get_username(sessions_list[i], &name)) < 0) | |
153 | errx(EXIT_FAILURE, _("get user name failed: %s"), strerror (-r)); | |
154 | if (sd_session_get_tty(sessions_list[i], &tty) < 0) { | |
155 | free(name); | |
156 | continue; | |
157 | } | |
158 | ||
159 | if (strcmp(ctl->dst_login, name) == 0 && | |
160 | strcmp(ctl->dst_tty_name, tty) == 0) { | |
161 | free(name); | |
162 | free(tty); | |
163 | res = 0; | |
164 | break; | |
165 | } | |
166 | free(name); | |
167 | free(tty); | |
6dbe3af9 | 168 | } |
5c62c270 TK |
169 | for (int i = 0; i < sessions; i++) |
170 | free(sessions_list[i]); | |
171 | free(sessions_list); | |
172 | } else { | |
173 | #endif | |
174 | utmpxname(_PATH_UTMP); | |
175 | setutxent(); | |
6dbe3af9 | 176 | |
5c62c270 TK |
177 | while ((u = getutxent())) { |
178 | if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) == 0 && | |
179 | strncmp(ctl->dst_tty_name, u->ut_line, sizeof(u->ut_line)) == 0) { | |
180 | res = 0; | |
181 | break; | |
182 | } | |
183 | } | |
184 | ||
185 | endutxent(); | |
186 | #if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1 | |
187 | } | |
188 | #endif | |
5f51b8b2 | 189 | return res; |
6dbe3af9 KZ |
190 | } |
191 | ||
192 | /* | |
193 | * search_utmp - search utmp for the "best" terminal to write to | |
194 | * | |
195 | * Ignores terminals with messages disabled, and of the rest, returns | |
196 | * the one with the most recent access time. Returns as value the number | |
197 | * of the user's terminals with messages enabled, or -1 if the user is | |
198 | * not logged in at all. | |
199 | * | |
200 | * Special case for writing to yourself - ignore the terminal you're | |
201 | * writing from, unless that's the only terminal with messages enabled. | |
202 | */ | |
b5a69379 | 203 | static void search_utmp(struct write_control *ctl) |
6dbe3af9 | 204 | { |
b4b919fe | 205 | struct utmpx *u; |
d732758e SK |
206 | time_t best_atime = 0, tty_atime; |
207 | int num_ttys = 0, valid_ttys = 0, tty_writeable = 0, user_is_me = 0; | |
5c62c270 TK |
208 | |
209 | #if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1 | |
210 | if (sd_booted() > 0) { | |
211 | char path[256]; | |
212 | char **sessions_list; | |
213 | int sessions = sd_get_sessions(&sessions_list); | |
214 | if (sessions < 0) | |
215 | errx(EXIT_FAILURE, _("error getting sessions: %s"), | |
216 | strerror(-sessions)); | |
217 | ||
218 | for (int i = 0; i < sessions; i++) { | |
219 | char *name, *tty; | |
220 | int r; | |
221 | ||
222 | if ((r = sd_session_get_username(sessions_list[i], &name)) < 0) | |
223 | errx(EXIT_FAILURE, _("get user name failed: %s"), strerror (-r)); | |
224 | ||
225 | if (strcmp(ctl->dst_login, name) != 0) { | |
226 | free(name); | |
227 | continue; | |
228 | } | |
229 | ||
230 | if (sd_session_get_tty(sessions_list[i], &tty) < 0) { | |
231 | free(name); | |
232 | continue; | |
233 | } | |
234 | ||
235 | num_ttys++; | |
236 | snprintf(path, sizeof(path), "/dev/%s", tty); | |
237 | if (check_tty(path, &tty_writeable, &tty_atime, 0)) { | |
238 | /* bad term? skip */ | |
239 | free(name); | |
240 | free(tty); | |
241 | continue; | |
242 | } | |
243 | if (ctl->src_uid && !tty_writeable) { | |
244 | /* skip ttys with msgs off */ | |
245 | free(name); | |
246 | free(tty); | |
247 | continue; | |
248 | } | |
249 | if (strcmp(tty, ctl->src_tty_name) == 0) { | |
250 | user_is_me = 1; | |
251 | free(name); | |
252 | free(tty); | |
253 | /* don't write to yourself */ | |
254 | continue; | |
255 | } | |
256 | valid_ttys++; | |
257 | if (best_atime < tty_atime) { | |
258 | best_atime = tty_atime; | |
259 | free(ctl->dst_tty_path); | |
260 | ctl->dst_tty_path = xstrdup(path); | |
261 | ctl->dst_tty_name = ctl->dst_tty_path + 5; | |
262 | } | |
263 | free(name); | |
264 | free(tty); | |
265 | } | |
266 | for (int i = 0; i < sessions; i++) | |
267 | free(sessions_list[i]); | |
268 | free(sessions_list); | |
71abc103 | 269 | } else |
5c62c270 | 270 | #endif |
71abc103 KZ |
271 | { |
272 | char path[sizeof(u->ut_line) + 6]; | |
6dbe3af9 | 273 | |
71abc103 KZ |
274 | utmpxname(_PATH_UTMP); |
275 | setutxent(); | |
6dbe3af9 | 276 | |
71abc103 KZ |
277 | while ((u = getutxent())) { |
278 | if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) != 0) | |
279 | continue; | |
280 | num_ttys++; | |
281 | snprintf(path, sizeof(path), "/dev/%s", u->ut_line); | |
282 | if (check_tty(path, &tty_writeable, &tty_atime, 0)) | |
283 | /* bad term? skip */ | |
284 | continue; | |
285 | if (ctl->src_uid && !tty_writeable) | |
286 | /* skip ttys with msgs off */ | |
287 | continue; | |
288 | if (memcmp(u->ut_line, ctl->src_tty_name, strlen(ctl->src_tty_name) + 1) == 0) { | |
289 | user_is_me = 1; | |
290 | /* don't write to yourself */ | |
291 | continue; | |
292 | } | |
293 | if (u->ut_type != USER_PROCESS) | |
294 | /* it's not a valid entry */ | |
295 | continue; | |
296 | valid_ttys++; | |
297 | if (best_atime < tty_atime) { | |
298 | best_atime = tty_atime; | |
299 | free(ctl->dst_tty_path); | |
300 | ctl->dst_tty_path = xstrdup(path); | |
301 | ctl->dst_tty_name = ctl->dst_tty_path + 5; | |
302 | } | |
6dbe3af9 KZ |
303 | } |
304 | ||
71abc103 | 305 | endutxent(); |
5c62c270 | 306 | } |
71abc103 | 307 | |
d732758e | 308 | if (num_ttys == 0) |
b5a69379 | 309 | errx(EXIT_FAILURE, _("%s is not logged in"), ctl->dst_login); |
d732758e | 310 | if (valid_ttys == 0) { |
5f51b8b2 SK |
311 | if (user_is_me) { |
312 | /* ok, so write to yourself! */ | |
a58b9025 SK |
313 | if (!ctl->src_tty_path) |
314 | errx(EXIT_FAILURE, _("can't find your tty's name")); | |
315 | ctl->dst_tty_path = xstrdup(ctl->src_tty_path); | |
316 | ctl->dst_tty_name = ctl->dst_tty_path + 5; | |
6dbe3af9 KZ |
317 | return; |
318 | } | |
b5a69379 | 319 | errx(EXIT_FAILURE, _("%s has messages disabled"), ctl->dst_login); |
5592bf0d SK |
320 | } |
321 | if (1 < valid_ttys) | |
5f51b8b2 | 322 | warnx(_("%s is logged in more than once; writing to %s"), |
a58b9025 | 323 | ctl->dst_login, ctl->dst_tty_name); |
6dbe3af9 KZ |
324 | } |
325 | ||
326 | /* | |
97ebab4e | 327 | * signal_handler - cause write loop to exit |
6dbe3af9 | 328 | */ |
97ebab4e | 329 | static void signal_handler(int signo) |
6dbe3af9 | 330 | { |
97ebab4e | 331 | signal_received = signo; |
b985f372 | 332 | } |
6dbe3af9 | 333 | |
6dbe3af9 KZ |
334 | /* |
335 | * do_write - actually make the connection | |
336 | */ | |
b5a69379 | 337 | static void do_write(const struct write_control *ctl) |
5f51b8b2 | 338 | { |
aefd6a9d | 339 | char *login, *pwuid; |
66ee8158 | 340 | struct passwd *pwd; |
22853e4a | 341 | time_t now; |
a0794392 | 342 | struct tm *tm; |
8a7b8456 | 343 | char *host, *line = NULL; |
344 | size_t linelen = 0; | |
97ebab4e | 345 | struct sigaction sigact; |
6dbe3af9 | 346 | |
66ee8158 | 347 | /* Determine our login name(s) before the we reopen() stdout */ |
b5a69379 | 348 | if ((pwd = getpwuid(ctl->src_uid)) != NULL) |
66ee8158 KZ |
349 | pwuid = pwd->pw_name; |
350 | else | |
351 | pwuid = "???"; | |
352 | if ((login = getlogin()) == NULL) | |
353 | login = pwuid; | |
6dbe3af9 | 354 | |
a58b9025 SK |
355 | if ((freopen(ctl->dst_tty_path, "w", stdout)) == NULL) |
356 | err(EXIT_FAILURE, "%s", ctl->dst_tty_path); | |
6dbe3af9 | 357 | |
97ebab4e SK |
358 | sigact.sa_handler = signal_handler; |
359 | sigemptyset(&sigact.sa_mask); | |
360 | sigact.sa_flags = 0; | |
361 | sigaction(SIGINT, &sigact, NULL); | |
362 | sigaction(SIGHUP, &sigact, NULL); | |
6dbe3af9 | 363 | |
a0fc344b KZ |
364 | host = xgethostname(); |
365 | if (!host) | |
366 | host = xstrdup("???"); | |
367 | ||
a0794392 SK |
368 | now = time((time_t *)NULL); |
369 | tm = localtime(&now); | |
a0794392 SK |
370 | /* print greeting */ |
371 | printf("\r\n\a\a\a"); | |
ad296391 | 372 | if (strcmp(login, pwuid) != 0) |
aefd6a9d KZ |
373 | printf(_("Message from %s@%s (as %s) on %s at %02d:%02d ..."), |
374 | login, host, pwuid, ctl->src_tty_name, | |
375 | tm->tm_hour, tm->tm_min); | |
66ee8158 | 376 | else |
aefd6a9d KZ |
377 | printf(_("Message from %s@%s on %s at %02d:%02d ..."), |
378 | login, host, ctl->src_tty_name, | |
379 | tm->tm_hour, tm->tm_min); | |
a0fc344b | 380 | free(host); |
7eda085c | 381 | printf("\r\n"); |
6dbe3af9 | 382 | |
8a7b8456 | 383 | while (getline(&line, &linelen, stdin) >= 0) { |
97ebab4e SK |
384 | if (signal_received) |
385 | break; | |
8a7b8456 | 386 | |
aa13246a | 387 | if (fputs_careful(line, stdout, '^', true, 0) == EOF) |
8a7b8456 | 388 | err(EXIT_FAILURE, _("carefulputc failed")); |
97ebab4e | 389 | } |
8a7b8456 | 390 | free(line); |
97ebab4e | 391 | printf("EOF\r\n"); |
6dbe3af9 KZ |
392 | } |
393 | ||
b985f372 | 394 | int main(int argc, char **argv) |
5f51b8b2 | 395 | { |
285c1f3a | 396 | int tty_writeable = 0, c; |
b5a69379 | 397 | struct write_control ctl = { 0 }; |
6dbe3af9 | 398 | |
b985f372 SK |
399 | static const struct option longopts[] = { |
400 | {"version", no_argument, NULL, 'V'}, | |
401 | {"help", no_argument, NULL, 'h'}, | |
402 | {NULL, 0, NULL, 0} | |
403 | }; | |
6dbe3af9 | 404 | |
b985f372 SK |
405 | setlocale(LC_ALL, ""); |
406 | bindtextdomain(PACKAGE, LOCALEDIR); | |
407 | textdomain(PACKAGE); | |
2c308875 | 408 | close_stdout_atexit(); |
b985f372 SK |
409 | |
410 | while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) | |
411 | switch (c) { | |
412 | case 'V': | |
2c308875 | 413 | print_version(EXIT_SUCCESS); |
b985f372 | 414 | case 'h': |
9325dbfd | 415 | usage(); |
b985f372 | 416 | default: |
9325dbfd | 417 | errtryhelp(EXIT_FAILURE); |
b985f372 SK |
418 | } |
419 | ||
285c1f3a SK |
420 | if (get_terminal_name(&ctl.src_tty_path, &ctl.src_tty_name, NULL) == 0) { |
421 | /* check that sender has write enabled */ | |
a58b9025 | 422 | if (check_tty(ctl.src_tty_path, &tty_writeable, NULL, 1)) |
b985f372 | 423 | exit(EXIT_FAILURE); |
d732758e | 424 | if (!tty_writeable) |
b985f372 SK |
425 | errx(EXIT_FAILURE, |
426 | _("you have write permission turned off")); | |
d732758e | 427 | tty_writeable = 0; |
b985f372 | 428 | } else |
a58b9025 | 429 | ctl.src_tty_name = "<no tty>"; |
b985f372 | 430 | |
b5a69379 | 431 | ctl.src_uid = getuid(); |
b985f372 SK |
432 | |
433 | /* check args */ | |
434 | switch (argc) { | |
435 | case 2: | |
b5a69379 SK |
436 | ctl.dst_login = argv[1]; |
437 | search_utmp(&ctl); | |
438 | do_write(&ctl); | |
b985f372 SK |
439 | break; |
440 | case 3: | |
b5a69379 | 441 | ctl.dst_login = argv[1]; |
b985f372 | 442 | if (!strncmp(argv[2], "/dev/", 5)) |
a58b9025 | 443 | ctl.dst_tty_path = xstrdup(argv[2]); |
b5a69379 | 444 | else |
a58b9025 SK |
445 | xasprintf(&ctl.dst_tty_path, "/dev/%s", argv[2]); |
446 | ctl.dst_tty_name = ctl.dst_tty_path + 5; | |
d732758e | 447 | if (check_utmp(&ctl)) |
b985f372 SK |
448 | errx(EXIT_FAILURE, |
449 | _("%s is not logged in on %s"), | |
a58b9025 SK |
450 | ctl.dst_login, ctl.dst_tty_name); |
451 | if (check_tty(ctl.dst_tty_path, &tty_writeable, NULL, 1)) | |
b985f372 | 452 | exit(EXIT_FAILURE); |
d732758e | 453 | if (ctl.src_uid && !tty_writeable) |
b985f372 SK |
454 | errx(EXIT_FAILURE, |
455 | _("%s has messages disabled on %s"), | |
a58b9025 | 456 | ctl.dst_login, ctl.dst_tty_name); |
b5a69379 | 457 | do_write(&ctl); |
b985f372 SK |
458 | break; |
459 | default: | |
677ec86c | 460 | errtryhelp(EXIT_FAILURE); |
6dbe3af9 | 461 | } |
a58b9025 | 462 | free(ctl.dst_tty_path); |
97ebab4e | 463 | return EXIT_SUCCESS; |
6dbe3af9 | 464 | } |