diff options
author | Karel Zak | 2016-07-14 13:07:25 +0200 |
---|---|---|
committer | Karel Zak | 2016-07-14 13:07:25 +0200 |
commit | 85a37ca8d14060a29b46fc677eeed19c8a99db5e (patch) | |
tree | 90544fbf8618df1293d863d9adcfa409b65c81c9 /term-utils/write.c | |
parent | sulogin: remove __nonnull__ function attribute (diff) | |
parent | lib: try to find tty in get_terminal_name() (diff) | |
download | kernel-qcow2-util-linux-85a37ca8d14060a29b46fc677eeed19c8a99db5e.tar.gz kernel-qcow2-util-linux-85a37ca8d14060a29b46fc677eeed19c8a99db5e.tar.xz kernel-qcow2-util-linux-85a37ca8d14060a29b46fc677eeed19c8a99db5e.zip |
Merge branch 'write-improvements' of git://github.com/kerolasa/lelux-utiliteetit
* 'write-improvements' of git://github.com/kerolasa/lelux-utiliteetit:
lib: try to find tty in get_terminal_name()
write: stop removing and adding /dev/ in front of tty string
write: tell when effective gid and tty path group mismatch
write: improve coding style
write: remove PUTC macro
write: make timestamp to be obviously just a clock time
write: remove unnecessary utmp variables
write: improve function and variable names
write: add control structure to clarify what is going on
write: run atexit() checks at the end of execution
write: use xstrncpy() from strutils.h
write: set atime value in term_chk() only when needed
write: remove pointless fileno(3) calls
write: get rid of function prototypes
write: remove unused variable
Diffstat (limited to 'term-utils/write.c')
-rw-r--r-- | term-utils/write.c | 416 |
1 files changed, 198 insertions, 218 deletions
diff --git a/term-utils/write.c b/term-utils/write.c index 7c1523136..6bd393441 100644 --- a/term-utils/write.c +++ b/term-utils/write.c @@ -45,37 +45,41 @@ * */ -#include <stdio.h> -#include <unistd.h> -#include <utmp.h> #include <errno.h> -#include <time.h> +#include <getopt.h> +#include <paths.h> #include <pwd.h> -#include <string.h> -#include <stdlib.h> #include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <sys/param.h> #include <sys/stat.h> -#include <paths.h> -#include <getopt.h> +#include <time.h> +#include <unistd.h> +#include <utmp.h> #include "c.h" #include "carefulputc.h" #include "closestream.h" #include "nls.h" +#include "strutils.h" +#include "ttyutils.h" #include "xalloc.h" -static void __attribute__ ((__noreturn__)) usage(FILE * out); -void search_utmp(char *, char *, char *, uid_t); -void do_write(char *, char *, uid_t); -void wr_fputs(char *); -static void __attribute__ ((__noreturn__)) done(int); -int term_chk(char *, int *, time_t *, int); -int utmp_chk(char *, char *); +static sig_atomic_t signal_received = 0; -static gid_t root_access; +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(FILE * out) +static void __attribute__((__noreturn__)) usage(FILE *out) { fputs(USAGE_HEADER, out); fprintf(out, @@ -93,118 +97,48 @@ static void __attribute__ ((__noreturn__)) usage(FILE * out) exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); } -int main(int argc, char **argv) +/* + * 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) { - time_t atime; - uid_t myuid; - int msgsok = 0, myttyfd, c; - char tty[PATH_MAX], *mytty; - - 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); - atexit(close_stdout); + struct stat s; - while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) - switch (c) { - case 'V': - printf(UTIL_LINUX_VERSION); - return EXIT_SUCCESS; - case 'h': - usage(stdout); - default: - usage(stderr); + 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; } - - root_access = !getegid(); - - /* check that sender has write enabled */ - if (isatty(fileno(stdin))) - myttyfd = fileno(stdin); - else if (isatty(fileno(stdout))) - myttyfd = fileno(stdout); - else if (isatty(fileno(stderr))) - myttyfd = fileno(stderr); - else - myttyfd = -1; - - if (myttyfd != -1) { - if (!(mytty = ttyname(myttyfd))) - errx(EXIT_FAILURE, - _("can't find your tty's name")); - - /* - * We may have /dev/ttyN but also /dev/pts/xx. Below, - * term_chk() will put "/dev/" in front, so remove that - * part. - */ - if (!strncmp(mytty, "/dev/", 5)) - mytty += 5; - if (term_chk(mytty, &msgsok, &atime, 1)) - exit(EXIT_FAILURE); - if (!msgsok) - errx(EXIT_FAILURE, - _("you have write permission turned off")); - msgsok = 0; - } else - mytty = "<no tty>"; - - myuid = getuid(); - - /* check args */ - switch (argc) { - case 2: - search_utmp(argv[1], tty, mytty, myuid); - do_write(tty, mytty, myuid); - break; - case 3: - if (!strncmp(argv[2], "/dev/", 5)) - argv[2] += 5; - if (utmp_chk(argv[1], argv[2])) - errx(EXIT_FAILURE, - _("%s is not logged in on %s"), - argv[1], argv[2]); - if (term_chk(argv[2], &msgsok, &atime, 1)) - exit(EXIT_FAILURE); - if (myuid && !msgsok) - errx(EXIT_FAILURE, - _("%s has messages disabled on %s"), - argv[1], argv[2]); - do_write(argv[2], mytty, myuid); - break; - default: - usage(stderr); + *tty_writeable = s.st_mode & S_IWGRP; } - - done(0); - /* NOTREACHED */ - return EXIT_FAILURE; + if (tty_atime) + *tty_atime = s.st_atime; + return 0; } - /* - * utmp_chk - checks that the given user is actually logged in on + * check_utmp - checks that the given user is actually logged in on * the given tty */ -int utmp_chk(char *user, char *tty) +static int check_utmp(const struct write_control *ctl) { - struct utmp u; - struct utmp *uptr; + struct utmp *u; int res = 1; utmpname(_PATH_UTMP); setutent(); - while ((uptr = getutent())) { - memcpy(&u, uptr, sizeof(u)); - if (strncmp(user, u.ut_user, sizeof(u.ut_user)) == 0 && - strncmp(tty, u.ut_line, sizeof(u.ut_line)) == 0) { + while ((u = getutent())) { + 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; } @@ -225,164 +159,210 @@ int utmp_chk(char *user, char *tty) * Special case for writing to yourself - ignore the terminal you're * writing from, unless that's the only terminal with messages enabled. */ -void search_utmp(char *user, char *tty, char *mytty, uid_t myuid) +static void search_utmp(struct write_control *ctl) { - struct utmp u; - struct utmp *uptr; - time_t bestatime, atime; - int nloggedttys, nttys, msgsok = 0, user_is_me; - char atty[sizeof(u.ut_line) + 1]; + struct utmp *u; + time_t best_atime = 0, tty_atime; + int num_ttys = 0, valid_ttys = 0, tty_writeable = 0, user_is_me = 0; + char path[UT_LINESIZE + 6]; utmpname(_PATH_UTMP); setutent(); - nloggedttys = nttys = 0; - bestatime = 0; - user_is_me = 0; - while ((uptr = getutent())) { - memcpy(&u, uptr, sizeof(u)); - if (strncmp(user, u.ut_user, sizeof(u.ut_user)) == 0) { - ++nloggedttys; - strncpy(atty, u.ut_line, sizeof(u.ut_line)); - atty[sizeof(u.ut_line)] = '\0'; - if (term_chk(atty, &msgsok, &atime, 0)) - /* bad term? skip */ - continue; - if (myuid && !msgsok) - /* skip ttys with msgs off */ - continue; - if (strcmp(atty, mytty) == 0) { - user_is_me = 1; - /* don't write to yourself */ - continue; - } - if (u.ut_type != USER_PROCESS) - /* it's not a valid entry */ - continue; - ++nttys; - if (atime > bestatime) { - bestatime = atime; - strcpy(tty, atty); - } + while ((u = getutent())) { + if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) != 0) + continue; + num_ttys++; + sprintf(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 (strcmp(u->ut_line, ctl->src_tty_name) == 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; } } endutent(); - if (nloggedttys == 0) - errx(EXIT_FAILURE, _("%s is not logged in"), user); - if (nttys == 0) { + 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! */ - strcpy(tty, mytty); + 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"), user); - } else if (nttys > 1) { - warnx(_("%s is logged in more than once; writing to %s"), - user, tty); + 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); } /* - * term_chk - check that a terminal exists, and get the message bit - * and the access time + * signal_handler - cause write loop to exit */ -int term_chk(char *tty, int *msgsokP, time_t * atimeP, int showerror) +static void signal_handler(int signo) { - struct stat s; - char path[PATH_MAX]; + signal_received = signo; +} - if (strlen(tty) + 6 > sizeof(path)) - return 1; - sprintf(path, "/dev/%s", tty); - if (stat(path, &s) < 0) { - if (showerror) - warn("%s", path); - return 1; +/* + * 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")); } - if (getuid() == 0) /* root can always write */ - *msgsokP = 1; - else - *msgsokP = (s.st_mode & S_IWGRP) && (getegid() == s.st_gid); - *atimeP = s.st_atime; - return 0; } /* * do_write - actually make the connection */ -void do_write(char *tty, char *mytty, uid_t myuid) +static void do_write(const struct write_control *ctl) { - char *login, *pwuid, *nows; + char *login, *pwuid, timestamp[6]; struct passwd *pwd; time_t now; - char path[PATH_MAX], *host, line[512]; + struct tm *tm; + char *host, line[512]; + struct sigaction sigact; /* Determine our login name(s) before the we reopen() stdout */ - if ((pwd = getpwuid(myuid)) != NULL) + if ((pwd = getpwuid(ctl->src_uid)) != NULL) pwuid = pwd->pw_name; else pwuid = "???"; if ((login = getlogin()) == NULL) login = pwuid; - if (strlen(tty) + 6 > sizeof(path)) - errx(EXIT_FAILURE, _("tty path %s too long"), tty); - snprintf(path, sizeof(path), "/dev/%s", tty); - if ((freopen(path, "w", stdout)) == NULL) - err(EXIT_FAILURE, "%s", path); + if ((freopen(ctl->dst_tty_path, "w", stdout)) == NULL) + err(EXIT_FAILURE, "%s", ctl->dst_tty_path); - signal(SIGINT, done); - signal(SIGHUP, done); + sigact.sa_handler = signal_handler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGHUP, &sigact, NULL); - /* print greeting */ host = xgethostname(); if (!host) host = xstrdup("???"); - now = time((time_t *) NULL); - nows = ctime(&now); - nows[16] = '\0'; - printf("\r\n\007\007\007"); + now = time((time_t *)NULL); + tm = localtime(&now); + strftime(timestamp, sizeof(timestamp), "%H:%M", tm); + /* print greeting */ + printf("\r\n\a\a\a"); if (strcmp(login, pwuid)) printf(_("Message from %s@%s (as %s) on %s at %s ..."), - login, host, pwuid, mytty, nows + 11); + login, host, pwuid, ctl->src_tty_name, timestamp); else printf(_("Message from %s@%s on %s at %s ..."), - login, host, mytty, nows + 11); + login, host, ctl->src_tty_name, timestamp); free(host); printf("\r\n"); - while (fgets(line, sizeof(line), stdin) != NULL) - wr_fputs(line); -} - -/* - * done - cleanup and exit - */ -static void __attribute__ ((__noreturn__)) - done(int dummy __attribute__ ((__unused__))) -{ + while (fgets(line, sizeof(line), stdin) != NULL) { + if (signal_received) + break; + write_line(line); + } printf("EOF\r\n"); - _exit(EXIT_SUCCESS); } -/* - * wr_fputs - like fputs(), but makes control characters visible and - * turns \n into \r\n. - */ -void wr_fputs(char *s) +int main(int argc, char **argv) { - char c; + int tty_writeable = 0, c; + struct write_control ctl = { 0 }; -#define PUTC(c) if (fputc_careful(c, stdout, '^') == EOF) \ - err(EXIT_FAILURE, _("carefulputc failed")); - while (*s) { - c = *s++; - if (c == '\n') - PUTC('\r'); - PUTC(c); + 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); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (c) { + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + + 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 = "<no tty>"; + + 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: + usage(stderr); } - return; -#undef PUTC + free(ctl.dst_tty_path); + return EXIT_SUCCESS; } |