2 * Copyright (c) 1989, 1993
3 * The Regents of the University of California. All rights reserved.
5 * This code is derived from software contributed to Berkeley by
6 * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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
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 enabling daemons
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
56 #include <sys/param.h>
62 #if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1
63 # include <systemd/sd-login.h>
64 # include <systemd/sd-daemon.h>
68 #include "carefulputc.h"
69 #include "closestream.h"
75 static volatile sig_atomic_t signal_received
= 0;
77 struct write_control
{
79 const char *src_login
;
80 const char *src_tty_path
;
81 const char *src_tty_name
;
82 const char *dst_login
;
84 const char *dst_tty_name
;
87 static void __attribute__((__noreturn__
)) usage(void)
90 fputs(USAGE_HEADER
, out
);
92 _(" %s [options] <user> [<ttyname>]\n"),
93 program_invocation_short_name
);
95 fputs(USAGE_SEPARATOR
, out
);
96 fputs(_("Send a message to another user.\n"), out
);
98 fputs(USAGE_OPTIONS
, out
);
99 fprintf(out
, USAGE_HELP_OPTIONS(16));
100 fprintf(out
, USAGE_MAN_TAIL("write(1)"));
105 * check_tty - check that a terminal exists, and get the message bit
106 * and the access time
108 static int check_tty(const char *tty
, int *tty_writeable
, time_t *tty_atime
, int showerror
)
112 if (stat(tty
, &s
) < 0) {
117 if (getuid() == 0) /* root can always write */
120 if (getegid() != s
.st_gid
) {
121 warnx(_("effective gid does not match group of %s"), tty
);
124 *tty_writeable
= s
.st_mode
& S_IWGRP
;
127 *tty_atime
= s
.st_atime
;
132 * check_utmp - checks that the given user is actually logged in on
135 static int check_utmp(const struct write_control
*ctl
)
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
);
144 errx(EXIT_FAILURE
, _("error getting sessions: %s"),
145 strerror(-sessions
));
147 for (int i
= 0; i
< sessions
; i
++) {
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) {
159 if (strcmp(ctl
->dst_login
, name
) == 0 &&
160 strcmp(ctl
->dst_tty_name
, tty
) == 0) {
169 for (int i
= 0; i
< sessions
; i
++)
170 free(sessions_list
[i
]);
174 utmpxname(_PATH_UTMP
);
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) {
186 #if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1
193 * search_utmp - search utmp for the "best" terminal to write to
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.
200 * Special case for writing to yourself - ignore the terminal you're
201 * writing from, unless that's the only terminal with messages enabled.
203 static void search_utmp(struct write_control
*ctl
)
206 time_t best_atime
= 0, tty_atime
;
207 int num_ttys
= 0, valid_ttys
= 0, tty_writeable
= 0, user_is_me
= 0;
209 #if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1
210 if (sd_booted() > 0) {
212 char **sessions_list
;
213 int sessions
= sd_get_sessions(&sessions_list
);
215 errx(EXIT_FAILURE
, _("error getting sessions: %s"),
216 strerror(-sessions
));
218 for (int i
= 0; i
< sessions
; i
++) {
222 if ((r
= sd_session_get_username(sessions_list
[i
], &name
)) < 0)
223 errx(EXIT_FAILURE
, _("get user name failed: %s"), strerror (-r
));
225 if (strcmp(ctl
->dst_login
, name
) != 0) {
230 if (sd_session_get_tty(sessions_list
[i
], &tty
) < 0) {
236 snprintf(path
, sizeof(path
), "/dev/%s", tty
);
237 if (check_tty(path
, &tty_writeable
, &tty_atime
, 0)) {
243 if (ctl
->src_uid
&& !tty_writeable
) {
244 /* skip ttys with msgs off */
249 if (strcmp(tty
, ctl
->src_tty_name
) == 0) {
253 /* don't write to yourself */
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;
266 for (int i
= 0; i
< sessions
; i
++)
267 free(sessions_list
[i
]);
272 char path
[sizeof(u
->ut_line
) + 6];
274 utmpxname(_PATH_UTMP
);
277 while ((u
= getutxent())) {
278 if (strncmp(ctl
->dst_login
, u
->ut_user
, sizeof(u
->ut_user
)) != 0)
281 snprintf(path
, sizeof(path
), "/dev/%s", u
->ut_line
);
282 if (check_tty(path
, &tty_writeable
, &tty_atime
, 0))
285 if (ctl
->src_uid
&& !tty_writeable
)
286 /* skip ttys with msgs off */
288 if (memcmp(u
->ut_line
, ctl
->src_tty_name
, strlen(ctl
->src_tty_name
) + 1) == 0) {
290 /* don't write to yourself */
293 if (u
->ut_type
!= USER_PROCESS
)
294 /* it's not a valid entry */
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;
309 errx(EXIT_FAILURE
, _("%s is not logged in"), ctl
->dst_login
);
310 if (valid_ttys
== 0) {
312 /* ok, so write to yourself! */
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;
319 errx(EXIT_FAILURE
, _("%s has messages disabled"), ctl
->dst_login
);
322 warnx(_("%s is logged in more than once; writing to %s"),
323 ctl
->dst_login
, ctl
->dst_tty_name
);
327 * signal_handler - cause write loop to exit
329 static void signal_handler(int signo
)
331 signal_received
= signo
;
335 * do_write - actually make the connection
337 static void do_write(const struct write_control
*ctl
)
343 char *host
, *line
= NULL
;
345 struct sigaction sigact
;
347 /* Determine our login name(s) before the we reopen() stdout */
348 if ((pwd
= getpwuid(ctl
->src_uid
)) != NULL
)
349 pwuid
= pwd
->pw_name
;
352 if ((login
= getlogin()) == NULL
)
355 if ((freopen(ctl
->dst_tty_path
, "w", stdout
)) == NULL
)
356 err(EXIT_FAILURE
, "%s", ctl
->dst_tty_path
);
358 sigact
.sa_handler
= signal_handler
;
359 sigemptyset(&sigact
.sa_mask
);
361 sigaction(SIGINT
, &sigact
, NULL
);
362 sigaction(SIGHUP
, &sigact
, NULL
);
364 host
= xgethostname();
366 host
= xstrdup("???");
368 now
= time((time_t *)NULL
);
369 tm
= localtime(&now
);
371 printf("\r\n\a\a\a");
372 if (strcmp(login
, pwuid
) != 0)
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
);
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
);
383 while (getline(&line
, &linelen
, stdin
) >= 0) {
387 if (fputs_careful(line
, stdout
, '^', true, 0) == EOF
)
388 err(EXIT_FAILURE
, _("carefulputc failed"));
394 int main(int argc
, char **argv
)
396 int tty_writeable
= 0, c
;
397 struct write_control ctl
= { 0 };
399 static const struct option longopts
[] = {
400 {"version", no_argument
, NULL
, 'V'},
401 {"help", no_argument
, NULL
, 'h'},
405 setlocale(LC_ALL
, "");
406 bindtextdomain(PACKAGE
, LOCALEDIR
);
408 close_stdout_atexit();
410 while ((c
= getopt_long(argc
, argv
, "Vh", longopts
, NULL
)) != -1)
413 print_version(EXIT_SUCCESS
);
417 errtryhelp(EXIT_FAILURE
);
420 if (get_terminal_name(&ctl
.src_tty_path
, &ctl
.src_tty_name
, NULL
) == 0) {
421 /* check that sender has write enabled */
422 if (check_tty(ctl
.src_tty_path
, &tty_writeable
, NULL
, 1))
426 _("you have write permission turned off"));
429 ctl
.src_tty_name
= "<no tty>";
431 ctl
.src_uid
= getuid();
436 ctl
.dst_login
= argv
[1];
441 ctl
.dst_login
= argv
[1];
442 if (!strncmp(argv
[2], "/dev/", 5))
443 ctl
.dst_tty_path
= xstrdup(argv
[2]);
445 xasprintf(&ctl
.dst_tty_path
, "/dev/%s", argv
[2]);
446 ctl
.dst_tty_name
= ctl
.dst_tty_path
+ 5;
447 if (check_utmp(&ctl
))
449 _("%s is not logged in on %s"),
450 ctl
.dst_login
, ctl
.dst_tty_name
);
451 if (check_tty(ctl
.dst_tty_path
, &tty_writeable
, NULL
, 1))
453 if (ctl
.src_uid
&& !tty_writeable
)
455 _("%s has messages disabled on %s"),
456 ctl
.dst_login
, ctl
.dst_tty_name
);
460 errtryhelp(EXIT_FAILURE
);
462 free(ctl
.dst_tty_path
);