/* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Modified for Linux, Mon Mar 8 18:16:24 1993, faith@cs.unc.edu * Wed Jun 22 21:41:56 1994, faith@cs.unc.edu: * Added fix from Mike Grupenhoff (kashmir@umiacs.umd.edu) * Mon Jul 1 17:01:39 MET DST 1996, janl@math.uio.no: * - Added fix from David.Chapell@mail.trincoll.edu enabling daemons * to use write. * - ANSIed it since I was working on it anyway. * 1999-02-22 Arkadiusz Miƛkiewicz * - added Native Language Support * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "c.h" #include "carefulputc.h" #include "closestream.h" #include "nls.h" #include "strutils.h" #include "ttyutils.h" #include "xalloc.h" static sig_atomic_t signal_received = 0; struct write_control { uid_t src_uid; const char *src_login; const char *src_tty_path; const char *src_tty_name; const char *dst_login; char *dst_tty_path; const char *dst_tty_name; }; static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options] []\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Send a message to another user.\n"), out); fputs(USAGE_OPTIONS, out); printf(USAGE_HELP_OPTIONS(16)); printf(USAGE_MAN_TAIL("write(1)")); exit(EXIT_SUCCESS); } /* * check_tty - check that a terminal exists, and get the message bit * and the access time */ static int check_tty(const char *tty, int *tty_writeable, time_t *tty_atime, int showerror) { struct stat s; if (stat(tty, &s) < 0) { if (showerror) warn("%s", tty); return 1; } if (getuid() == 0) /* root can always write */ *tty_writeable = 1; else { if (getegid() != s.st_gid) { warnx(_("effective gid does not match group of %s"), tty); return 1; } *tty_writeable = s.st_mode & S_IWGRP; } if (tty_atime) *tty_atime = s.st_atime; return 0; } /* * check_utmp - checks that the given user is actually logged in on * the given tty */ static int check_utmp(const struct write_control *ctl) { struct utmpx *u; int res = 1; utmpxname(_PATH_UTMP); setutxent(); while ((u = getutxent())) { if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) == 0 && strncmp(ctl->dst_tty_name, u->ut_line, sizeof(u->ut_line)) == 0) { res = 0; break; } } endutxent(); return res; } /* * search_utmp - search utmp for the "best" terminal to write to * * Ignores terminals with messages disabled, and of the rest, returns * the one with the most recent access time. Returns as value the number * of the user's terminals with messages enabled, or -1 if the user is * not logged in at all. * * Special case for writing to yourself - ignore the terminal you're * writing from, unless that's the only terminal with messages enabled. */ static void search_utmp(struct write_control *ctl) { struct utmpx *u; time_t best_atime = 0, tty_atime; int num_ttys = 0, valid_ttys = 0, tty_writeable = 0, user_is_me = 0; char path[sizeof(u->ut_line) + 6]; utmpxname(_PATH_UTMP); setutxent(); while ((u = getutxent())) { if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) != 0) continue; num_ttys++; snprintf(path, sizeof(path), "/dev/%s", u->ut_line); if (check_tty(path, &tty_writeable, &tty_atime, 0)) /* bad term? skip */ continue; if (ctl->src_uid && !tty_writeable) /* skip ttys with msgs off */ continue; if (memcmp(u->ut_line, ctl->src_tty_name, strlen(ctl->src_tty_name) + 1) == 0) { user_is_me = 1; /* don't write to yourself */ continue; } if (u->ut_type != USER_PROCESS) /* it's not a valid entry */ continue; valid_ttys++; if (best_atime < tty_atime) { best_atime = tty_atime; free(ctl->dst_tty_path); ctl->dst_tty_path = xstrdup(path); ctl->dst_tty_name = ctl->dst_tty_path + 5; } } endutxent(); if (num_ttys == 0) errx(EXIT_FAILURE, _("%s is not logged in"), ctl->dst_login); if (valid_ttys == 0) { if (user_is_me) { /* ok, so write to yourself! */ if (!ctl->src_tty_path) errx(EXIT_FAILURE, _("can't find your tty's name")); ctl->dst_tty_path = xstrdup(ctl->src_tty_path); ctl->dst_tty_name = ctl->dst_tty_path + 5; return; } errx(EXIT_FAILURE, _("%s has messages disabled"), ctl->dst_login); } if (1 < valid_ttys) warnx(_("%s is logged in more than once; writing to %s"), ctl->dst_login, ctl->dst_tty_name); } /* * signal_handler - cause write loop to exit */ static void signal_handler(int signo) { signal_received = signo; } /* * write_line - like fputs(), but makes control characters visible and * turns \n into \r\n. */ static void write_line(char *s) { while (*s) { const int c = *s++; if ((c == '\n' && fputc_careful('\r', stdout, '^') == EOF) || fputc_careful(c, stdout, '^') == EOF) err(EXIT_FAILURE, _("carefulputc failed")); } } /* * do_write - actually make the connection */ static void do_write(const struct write_control *ctl) { char *login, *pwuid; struct passwd *pwd; time_t now; struct tm *tm; char *host, line[512]; struct sigaction sigact; /* Determine our login name(s) before the we reopen() stdout */ if ((pwd = getpwuid(ctl->src_uid)) != NULL) pwuid = pwd->pw_name; else pwuid = "???"; if ((login = getlogin()) == NULL) login = pwuid; if ((freopen(ctl->dst_tty_path, "w", stdout)) == NULL) err(EXIT_FAILURE, "%s", ctl->dst_tty_path); sigact.sa_handler = signal_handler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigaction(SIGINT, &sigact, NULL); sigaction(SIGHUP, &sigact, NULL); host = xgethostname(); if (!host) host = xstrdup("???"); now = time((time_t *)NULL); tm = localtime(&now); /* print greeting */ printf("\r\n\a\a\a"); if (strcmp(login, pwuid) != 0) printf(_("Message from %s@%s (as %s) on %s at %02d:%02d ..."), login, host, pwuid, ctl->src_tty_name, tm->tm_hour, tm->tm_min); else printf(_("Message from %s@%s on %s at %02d:%02d ..."), login, host, ctl->src_tty_name, tm->tm_hour, tm->tm_min); free(host); printf("\r\n"); while (fgets(line, sizeof(line), stdin) != NULL) { if (signal_received) break; write_line(line); } printf("EOF\r\n"); } int main(int argc, char **argv) { int tty_writeable = 0, c; struct write_control ctl = { 0 }; static const struct option longopts[] = { {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) switch (c) { case 'V': print_version(EXIT_SUCCESS); case 'h': usage(); default: errtryhelp(EXIT_FAILURE); } if (get_terminal_name(&ctl.src_tty_path, &ctl.src_tty_name, NULL) == 0) { /* check that sender has write enabled */ if (check_tty(ctl.src_tty_path, &tty_writeable, NULL, 1)) exit(EXIT_FAILURE); if (!tty_writeable) errx(EXIT_FAILURE, _("you have write permission turned off")); tty_writeable = 0; } else ctl.src_tty_name = ""; ctl.src_uid = getuid(); /* check args */ switch (argc) { case 2: ctl.dst_login = argv[1]; search_utmp(&ctl); do_write(&ctl); break; case 3: ctl.dst_login = argv[1]; if (!strncmp(argv[2], "/dev/", 5)) ctl.dst_tty_path = xstrdup(argv[2]); else xasprintf(&ctl.dst_tty_path, "/dev/%s", argv[2]); ctl.dst_tty_name = ctl.dst_tty_path + 5; if (check_utmp(&ctl)) errx(EXIT_FAILURE, _("%s is not logged in on %s"), ctl.dst_login, ctl.dst_tty_name); if (check_tty(ctl.dst_tty_path, &tty_writeable, NULL, 1)) exit(EXIT_FAILURE); if (ctl.src_uid && !tty_writeable) errx(EXIT_FAILURE, _("%s has messages disabled on %s"), ctl.dst_login, ctl.dst_tty_name); do_write(&ctl); break; default: errtryhelp(EXIT_FAILURE); } free(ctl.dst_tty_path); return EXIT_SUCCESS; }