#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 TERM_WIDTH 79
#define WRITE_TIME_OUT 300 /* in seconds */
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)
{
+ FILE *out = stdout;
fputs(USAGE_HEADER, out);
fprintf(out,
_(" %s [options] [<file> | <message>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Write a message to all users.\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);
- fputs(USAGE_HELP, out);
- fputs(USAGE_VERSION, out);
+ fprintf(out, USAGE_HELP_OPTIONS(25));
fprintf(out, USAGE_MAN_TAIL("wall(1)"));
- exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+ exit(EXIT_SUCCESS);
+}
+
+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;
+ struct group_workspace *group_buf = NULL;
char *mbuf, *fname = NULL;
size_t mbufsize;
unsigned timeout = WRITE_TIME_OUT;
int mvecsz = 0;
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 }
+ { "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();
- 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)
if (timeout < 1)
errx(EXIT_FAILURE, _("invalid timeout argument: %s"), optarg);
break;
+ case 'g':
+ group_buf = init_group_workspace(optarg);
+ break;
+
case 'V':
- printf(UTIL_LINUX_VERSION);
- exit(EXIT_SUCCESS);
+ print_version(EXIT_SUCCESS);
case 'h':
- usage(stdout);
+ usage();
default:
- usage(stderr);
+ errtryhelp(EXIT_FAILURE);
}
}
argc -= optind;
iov.iov_base = mbuf;
iov.iov_len = mbufsize;
- while((utmpptr = getutent())) {
- if (!utmpptr->ut_user[0])
- 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. */
- if (utmpptr->ut_line[0] == ':')
- continue;
-
- xstrncpy(line, utmpptr->ut_line, sizeof(utmpptr->ut_line));
- if ((p = ttymsg(&iov, 1, line, timeout)) != NULL)
- warnx("%s", p);
+ /* 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;
+
+ if (group_buf && !is_gr_member(utmpptr->ut_user, group_buf))
+ continue;
+
+ 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);
}
static char *makemsg(char *fname, char **mvec, int mvecsz,
size_t *mbufsize, int print_banner)
{
- register int ch, cnt;
- struct stat sbuf;
- FILE *fp;
- char *p, *lbuf, *tmpname, *mbuf;
- long line_max;
-
- line_max = sysconf(_SC_LINE_MAX);
- if (line_max <= 0)
- line_max = 512;
-
- 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) {
char *hostname = xgethostname();
- char *whom, *where, *date;
- struct passwd *pw;
+ char *whom, *where, date[CTIME_BUFSIZ];
time_t now;
- if (!(whom = getlogin()) || !*whom)
- whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
+ 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"));
} else if (strncmp(where, "/dev/", 5) == 0)
where += 5;
time(&now);
- date = xstrdup(ctime(&now));
+ ctime_r(&now, date);
date[strlen(date) - 1] = '\0';
/*
*/
/* 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%*s\r\n", TERM_WIDTH, " ");
- sprintf(lbuf, _("Broadcast message from %s@%s (%s) (%s):"),
- whom, hostname, where, date);
- fprintf(fp, "%-*.*s\007\007\r\n", TERM_WIDTH, TERM_WIDTH, 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);
- free(date);
}
- fprintf(fp, "%*s\r\n", TERM_WIDTH, " ");
+ fprintf(fs, "%*s\r\n", TERM_WIDTH, " ");
if (mvec) {
/*
int i;
for (i = 0; i < mvecsz; i++) {
- fputs(mvec[i], fp);
+ fputs(mvec[i], fs);
if (i < mvecsz - 1)
- fputc(' ', fp);
+ fputc(' ', fs);
}
- fputc('\r', fp);
- fputc('\n', fp);
-
+ fputs("\r\n", fs);
} else {
/*
* read message from <file>
/*
* Read message from stdin.
*/
- while (fgets(lbuf, line_max, stdin)) {
- for (cnt = 0, p = lbuf; (ch = *p) != '\0'; ++p, ++cnt) {
- if (cnt == TERM_WIDTH || ch == '\n') {
- for (; cnt < TERM_WIDTH; ++cnt)
- putc(' ', fp);
- putc('\r', fp);
- putc('\n', fp);
- cnt = 0;
- }
- if (ch == '\t')
- cnt += (7 - (cnt % 8));
- if (ch != '\n')
- fputc_careful(ch, fp, '^');
- }
- }
+ while (getline(&lbuf, &lbuflen, stdin) >= 0)
+ fputs_careful(lbuf, fs, '^', true, TERM_WIDTH);
}
- fprintf(fp, "%*s\r\n", TERM_WIDTH, " ");
+ fprintf(fs, "%*s\r\n", TERM_WIDTH, " ");
free(lbuf);
- rewind(fp);
-
- if (fstat(fileno(fp), &sbuf))
- err(EXIT_FAILURE, _("stat 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;
}