]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - term-utils/wall.c
Make the ways of using output stream consistent in usage()
[thirdparty/util-linux.git] / term-utils / wall.c
index 996650638fbf5f6b1c9fbe9a6796f81f547b81d9..a3fe7d29a6a79be5b1e8e1d0a7016d128630f51e 100644 (file)
@@ -37,7 +37,7 @@
  * This program is not related to David Wall, whose Stanford Ph.D. thesis
  * is entitled "Mechanisms for Broadcast and Selective Broadcast".
  *
- * 1999-02-22 Arkadiusz Mikiewicz <misiek@pld.ORG.PL>
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
  * - added Native Language Support
  *
  */
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
-#include <utmp.h>
+#include <utmpx.h>
 #include <getopt.h>
+#include <sys/types.h>
+#include <grp.h>
+
+#if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1
+# include <systemd/sd-login.h>
+# include <systemd/sd-daemon.h>
+#endif
 
 #include "nls.h"
 #include "xalloc.h"
 #include "pathnames.h"
 #include "carefulputc.h"
 #include "c.h"
+#include "cctype.h"
 #include "fileutils.h"
 #include "closestream.h"
+#include "timeutils.h"
+#include "pwdutils.h"
 
-#define        IGNOREUSER      "sleeper"
-#define WRITE_TIME_OUT 300             /* in seconds */
+#define        TERM_WIDTH      79
+#define        WRITE_TIME_OUT  300             /* in seconds */
 
 /* Function prototypes */
-char *makemsg(char *fname, size_t *mbufsize, int print_banner);
-static void usage(FILE *out);
+static char *makemsg(char *fname, char **mvec, int mvecsz,
+                   size_t *mbufsize, int print_banner);
 
-static void __attribute__((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(void)
 {
-       fputs(_("\nUsage:\n"), out);
+       FILE *out = stdout;
+       fputs(USAGE_HEADER, out);
        fprintf(out,
-               _(" %s [options] [<file>]\n"),program_invocation_short_name);
+             _(" %s [options] [<file> | <message>]\n"), program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Write a message to all users.\n"), out);
 
-       fputs(_("\nOptions:\n"), out);
-       fputs(_(" -n, --nobanner          do not print banner, works only for root\n"
-               " -t, --timeout <timeout> write timeout in seconds\n"
-               " -V, --version           output version information and exit\n"
-               " -h, --help              display this help and exit\n\n"), out);
+       fputs(USAGE_OPTIONS, out);
+       fputs(_(" -g, --group <group>     only send message to group\n"), out);
+       fputs(_(" -n, --nobanner          do not print banner, works only for root\n"), out);
+       fputs(_(" -t, --timeout <timeout> write timeout in seconds\n"), out);
+       fputs(USAGE_SEPARATOR, out);
+       fprintf(out, USAGE_HELP_OPTIONS(25));
+       fprintf(out, USAGE_MAN_TAIL("wall(1)"));
 
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       exit(EXIT_SUCCESS);
 }
 
-int
-main(int argc, char **argv) {
+struct group_workspace {
+       gid_t   requested_group;
+       int     ngroups;
+
+/* getgrouplist() on OSX takes int* not gid_t* */
+#ifdef __APPLE__
+       int     *groups;
+#else
+       gid_t   *groups;
+#endif
+};
+
+static gid_t get_group_gid(const char *group)
+{
+       struct group *gr;
+       gid_t gid;
+
+       if ((gr = getgrnam(group)))
+               return gr->gr_gid;
+
+       gid = strtou32_or_err(group, _("invalid group argument"));
+       if (!getgrgid(gid))
+               errx(EXIT_FAILURE, _("%s: unknown gid"), group);
+
+       return gid;
+}
+
+static struct group_workspace *init_group_workspace(const char *group)
+{
+       struct group_workspace *buf = xmalloc(sizeof(struct group_workspace));
+
+       buf->requested_group = get_group_gid(group);
+       buf->ngroups = sysconf(_SC_NGROUPS_MAX) + 1;  /* room for the primary gid */
+       buf->groups = xcalloc(sizeof(*buf->groups), buf->ngroups);
+
+       return buf;
+}
+
+static void free_group_workspace(struct group_workspace *buf)
+{
+       if (!buf)
+               return;
+
+       free(buf->groups);
+       free(buf);
+}
+
+static int is_gr_member(const char *login, const struct group_workspace *buf)
+{
+       struct passwd *pw;
+       int ngroups = buf->ngroups;
+       int rc;
+
+       pw = getpwnam(login);
+       if (!pw)
+               return 0;
+
+       if (buf->requested_group == pw->pw_gid)
+               return 1;
+
+       rc = getgrouplist(login, pw->pw_gid, buf->groups, &ngroups);
+       if (rc < 0) {
+               /* buffer too small, not sure how this can happen, since
+                  we used sysconf to get the size... */
+               errx(EXIT_FAILURE,
+                       _("getgrouplist found more groups than sysconf allows"));
+       }
+
+       for (; ngroups >= 0; --ngroups) {
+               if (buf->requested_group == (gid_t) buf->groups[ngroups])
+                       return 1;
+       }
+
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
        int ch;
        struct iovec iov;
-       struct utmp *utmpptr;
+       struct utmpx *utmpptr;
        char *p;
        char line[sizeof(utmpptr->ut_line) + 1];
        int print_banner = TRUE;
-       char *mbuf;
+       struct group_workspace *group_buf = NULL;
+       char *mbuf, *fname = NULL;
        size_t mbufsize;
        unsigned timeout = WRITE_TIME_OUT;
+       char **mvec = NULL;
+       int mvecsz = 0;
+
+       static const struct option longopts[] = {
+               { "nobanner",   no_argument,            NULL, 'n' },
+               { "timeout",    required_argument,      NULL, 't' },
+               { "group",      required_argument,      NULL, 'g' },
+               { "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);
+       close_stdout_atexit();
 
-       static const struct option longopts[] = {
-               { "nobanner",   no_argument,            0, 'n' },
-               { "timeout",    required_argument,      0, 't' },
-               { "version",    no_argument,            0, 'V' },
-               { "help",       no_argument,            0, 'h' },
-               { NULL, 0, 0, 0 }
-       };
-
-       while ((ch = getopt_long(argc, argv, "nt:Vh", longopts, NULL)) != -1) {
+       while ((ch = getopt_long(argc, argv, "nt:g:Vh", longopts, NULL)) != -1) {
                switch (ch) {
                case 'n':
                        if (geteuid() == 0)
@@ -129,86 +225,122 @@ main(int argc, char **argv) {
                        if (timeout < 1)
                                errx(EXIT_FAILURE, _("invalid timeout argument: %s"), optarg);
                        break;
+               case 'g':
+                       group_buf = init_group_workspace(optarg);
+                       break;
+
                case 'V':
-                       printf(_("%s from %s\n"), program_invocation_short_name,
-                                                 PACKAGE_STRING);
-                       exit(EXIT_SUCCESS);
+                       print_version(EXIT_SUCCESS);
                case 'h':
-                       usage(stdout);
+                       usage();
                default:
-                       usage(stderr);
+                       errtryhelp(EXIT_FAILURE);
                }
        }
        argc -= optind;
        argv += optind;
-       if (argc > 1)
-               usage(stderr);
 
-       mbuf = makemsg(*argv, &mbufsize, print_banner);
+       if (argc == 1 && access(argv[0], F_OK) == 0)
+               fname = argv[0];
+       else if (argc >= 1) {
+               mvec = argv;
+               mvecsz = argc;
+       }
+
+       mbuf = makemsg(fname, mvec, mvecsz, &mbufsize, print_banner);
 
        iov.iov_base = mbuf;
        iov.iov_len = mbufsize;
-       while((utmpptr = getutent())) {
-               if (!utmpptr->ut_name[0] ||
-                   !strncmp(utmpptr->ut_name, IGNOREUSER,
-                            sizeof(utmpptr->ut_name)))
-                       continue;
+
+#if defined(USE_SYSTEMD) && HAVE_DECL_SD_SESSION_GET_USERNAME == 1
+       if (sd_booted() > 0) {
+               char **sessions_list;
+               int sessions;
+
+               sessions = sd_get_sessions(&sessions_list);
+               if (sessions < 0)
+                       errx(EXIT_FAILURE, _("error getting sessions: %s"),
+                               strerror(-sessions));
+
+               for (int i = 0; i < sessions; i++) {
+                       char *name, *tty;
+                       int r;
+
+                       if ((r = sd_session_get_username(sessions_list[i], &name)) < 0)
+                               errx(EXIT_FAILURE, _("get user name failed: %s"), strerror (-r));
+
+                       if (!(group_buf && !is_gr_member(name, group_buf))) {
+                               if (sd_session_get_tty(sessions_list[i], &tty) >= 0) {
+                                       if ((p = ttymsg(&iov, 1, tty, timeout)) != NULL)
+                                               warnx("%s", p);
+
+                                       free(tty);
+                               }
+                       }
+                       free(name);
+                       free(sessions_list[i]);
+               }
+               free(sessions_list);
+       } else
+#endif
+       {
+               while ((utmpptr = getutxent())) {
+                       if (!utmpptr->ut_user[0])
+                               continue;
 #ifdef USER_PROCESS
-               if (utmpptr->ut_type != USER_PROCESS)
-                       continue;
+                       if (utmpptr->ut_type != USER_PROCESS)
+                               continue;
 #endif
+                       /* Joey Hess reports that use-sessreg in /etc/X11/wdm/ produces
+                        * ut_line entries like :0, and a write to /dev/:0 fails.
+                        *
+                        * It also seems that some login manager may produce empty ut_line.
+                        */
+                       if (!*utmpptr->ut_line || *utmpptr->ut_line == ':')
+                               continue;
 
-               /* Joey Hess reports that use-sessreg in /etc/X11/wdm/
-                  produces ut_line entries like :0, and a write
-                  to /dev/:0 fails. */
-               if (utmpptr->ut_line[0] == ':')
-                       continue;
+                       if (group_buf && !is_gr_member(utmpptr->ut_user, group_buf))
+                               continue;
 
-               xstrncpy(line, utmpptr->ut_line, sizeof(utmpptr->ut_line));
-               if ((p = ttymsg(&iov, 1, line, timeout)) != NULL)
-                       warnx("%s", p);
+                       mem2strcpy(line, utmpptr->ut_line, sizeof(utmpptr->ut_line), sizeof(line));
+                       if ((p = ttymsg(&iov, 1, line, timeout)) != NULL)
+                               warnx("%s", p);
+               }
+               endutxent();
        }
-       endutent();
+
        free(mbuf);
+       free_group_workspace(group_buf);
        exit(EXIT_SUCCESS);
 }
 
-char *
-makemsg(char *fname, size_t *mbufsize, int print_banner)
+static char *makemsg(char *fname, char **mvec, int mvecsz,
+                    size_t *mbufsize, int print_banner)
 {
-       register int ch, cnt;
-       struct tm *lt;
-       struct passwd *pw;
-       struct stat sbuf;
-       time_t now;
-       FILE *fp;
-       char *p, *whom, *where, *hostname, *lbuf, *tmpname, *mbuf;
-       long line_max;
-
-       hostname = xmalloc(sysconf(_SC_HOST_NAME_MAX) + 1);
-       line_max = sysconf(_SC_LINE_MAX);
-       lbuf = xmalloc(line_max);
-
-       if ((fp = xfmkstemp(&tmpname, NULL)) == NULL)
-               err(EXIT_FAILURE, _("can't open temporary file"));
-       unlink(tmpname);
-       free(tmpname);
+       char *lbuf, *retbuf;
+       FILE * fs = open_memstream(&retbuf, mbufsize);
+       size_t lbuflen = 512;
+       lbuf = xmalloc(lbuflen);
 
        if (print_banner == TRUE) {
-               if (!(whom = getlogin()) || !*whom)
-                       whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
+               char *hostname = xgethostname();
+               char *whom, *where, date[CTIME_BUFSIZ];
+               time_t now;
+
+               whom = xgetlogin();
                if (!whom) {
-                       whom = "someone";
+                       whom = "<someone>";
                        warn(_("cannot get passwd uid"));
                }
                where = ttyname(STDOUT_FILENO);
                if (!where) {
                        where = "somewhere";
-                       warn(_("cannot get tty name"));
-               }
-               gethostname(hostname, sizeof(hostname));
+               } else if (strncmp(where, "/dev/", 5) == 0)
+                       where += 5;
+
                time(&now);
-               lt = localtime(&now);
+               ctime_r(&now, date);
+               date[strlen(date) - 1] = '\0';
 
                /*
                 * all this stuff is to blank out a square for the message;
@@ -219,62 +351,59 @@ makemsg(char *fname, size_t *mbufsize, int print_banner)
                 */
                /* snprintf is not always available, but the sprintf's here
                   will not overflow as long as %d takes at most 100 chars */
-               fprintf(fp, "\r%79s\r\n", " ");
-               sprintf(lbuf, _("Broadcast Message from %s@%s"),
-                             whom, hostname);
-               fprintf(fp, "%-79.79s\007\007\r\n", lbuf);
-               sprintf(lbuf, "        (%s) at %d:%02d ...",
-                             where, lt->tm_hour, lt->tm_min);
-               fprintf(fp, "%-79.79s\r\n", lbuf);
+               fprintf(fs, "\r%*s\r\n", TERM_WIDTH, " ");
+
+               snprintf(lbuf, lbuflen,
+                               _("Broadcast message from %s@%s (%s) (%s):"),
+                               whom, hostname, where, date);
+               fprintf(fs, "%-*.*s\007\007\r\n", TERM_WIDTH, TERM_WIDTH, lbuf);
+               free(hostname);
        }
-       fprintf(fp, "%79s\r\n", " ");
+       fprintf(fs, "%*s\r\n", TERM_WIDTH, " ");
 
-       free(hostname);
+        if (mvec) {
+               /*
+                * Read message from argv[]
+                */
+               int i;
 
-       if (fname) {
+               for (i = 0; i < mvecsz; i++) {
+                       fputs(mvec[i], fs);
+                       if (i < mvecsz - 1)
+                               fputc(' ', fs);
+               }
+               fputs("\r\n", fs);
+       } else {
                /*
-                * When we are not root, but suid or sgid, refuse to read files
-                * (e.g. device files) that the user may not have access to.
-                * After all, our invoker can easily do "wall < file"
-                * instead of "wall file".
+                * read message from <file>
                 */
-               uid_t uid = getuid();
-               if (uid && (uid != geteuid() || getgid() != getegid()))
-                       errx(EXIT_FAILURE, _("will not read %s - use stdin."),
-                            fname);
+               if (fname) {
+                       /*
+                        * When we are not root, but suid or sgid, refuse to read files
+                        * (e.g. device files) that the user may not have access to.
+                        * After all, our invoker can easily do "wall < file"
+                        * instead of "wall file".
+                        */
+                       uid_t uid = getuid();
+                       if (uid && (uid != geteuid() || getgid() != getegid()))
+                               errx(EXIT_FAILURE, _("will not read %s - use stdin."),
+                                    fname);
 
-               if (!freopen(fname, "r", stdin))
-                       err(EXIT_FAILURE, _("cannot open file %s"), fname);
-       }
+                       if (!freopen(fname, "r", stdin))
+                               err(EXIT_FAILURE, _("cannot open %s"), fname);
 
-       while (fgets(lbuf, line_max, stdin)) {
-               for (cnt = 0, p = lbuf; (ch = *p) != '\0'; ++p, ++cnt) {
-                       if (cnt == 79 || ch == '\n') {
-                               for (; cnt < 79; ++cnt)
-                                       putc(' ', fp);
-                               putc('\r', fp);
-                               putc('\n', fp);
-                               cnt = 0;
-                       }
-                       if (ch != '\n')
-                               carefulputc(ch, fp);
                }
+
+               /*
+                * Read message from stdin.
+                */
+               while (getline(&lbuf, &lbuflen, stdin) >= 0)
+                       fputs_careful(lbuf, fs, '^', true, TERM_WIDTH);
        }
-       fprintf(fp, "%79s\r\n", " ");
+       fprintf(fs, "%*s\r\n", TERM_WIDTH, " ");
 
        free(lbuf);
-       rewind(fp);
-
-       if (fstat(fileno(fp), &sbuf))
-               err(EXIT_FAILURE, _("fstat failed"));
-
-       *mbufsize = (size_t) sbuf.st_size;
-       mbuf = xmalloc(*mbufsize);
-
-       if (fread(mbuf, 1, *mbufsize, fp) != *mbufsize)
-               err(EXIT_FAILURE, _("fread failed"));
 
-       if (close_stream(fp) != 0)
-               errx(EXIT_FAILURE, _("write error"));
-       return mbuf;
+       fclose(fs);
+       return retbuf;
 }