2 * Receive wall messages and pass them to a notification program via stdin.
4 * Copyright (C) 2014 Simon Ruderich
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
34 #include <sys/types.h>
38 # include <utempter.h>
45 static void sig_handler(int signal) {
48 static void setup_signals(void) {
49 struct sigaction action;
51 memset(&action, 0, sizeof(action));
52 sigemptyset(&action.sa_mask);
53 action.sa_handler = sig_handler;
55 /* Handle all important signals which might be sent to us so we break out
56 * of the read()-loop below and can perform our cleanup. */
57 sigaction(SIGHUP, &action, NULL);
58 sigaction(SIGINT, &action, NULL);
59 sigaction(SIGQUIT, &action, NULL);
60 sigaction(SIGUSR1, &action, NULL);
61 sigaction(SIGUSR2, &action, NULL);
63 /* Collect zombies automatically without having to call waitpid(2). */
64 signal(SIGCHLD, SIG_IGN);
67 static int open_tty(void) {
71 ptm = posix_openpt(O_RDWR);
75 if (grantpt(ptm) != 0) {
79 /* Prevent write access for other users so they can't use wall to send
80 * messages to this program. */
85 if (chmod(name, S_IRUSR | S_IWUSR) != 0) {
89 if (unlockpt(ptm) != 0) {
97 static const char *skip_prefix(const char *string, const char *prefix) {
98 size_t length = strlen(prefix);
100 if (!strncmp(string, prefix, length)) {
101 return string + length;
106 static int set_utmpx(short type, int ptm) {
109 const char *tty, *user, *id, *line;
112 user = getpwuid(getuid())->pw_name;
113 gettimeofday(&now, NULL);
120 id = skip_prefix(tty, "/dev/pts/");
121 line = skip_prefix(tty, "/dev/");
123 /* Create utmp entry for the given terminal. */
124 memset(&entry, 0, sizeof(entry));
126 snprintf(entry.ut_user, sizeof(entry.ut_user), "%s", user);
127 snprintf(entry.ut_id, sizeof(entry.ut_id), "%s", id);
128 snprintf(entry.ut_line, sizeof(entry.ut_line), "%s", line);
130 entry.ut_pid = getpid();
131 entry.ut_type = type;
132 entry.ut_tv.tv_sec = now.tv_sec;
133 entry.ut_tv.tv_usec = now.tv_usec;
135 /* Write the entry to the utmp file. */
137 if (!pututxline(&entry)) {
145 static int login(int ptm) {
146 #if defined(USE_UTEMPTER)
147 return utempter_add_record(ptm, NULL);
148 #elif defined(USE_UTMPX)
149 return set_utmpx(USER_PROCESS, ptm);
151 # error "neither USE_UTEMPTER nor USE_UTMPX defined"
154 static int logout(int ptm) {
155 #if defined(USE_UTEMPTER)
156 return utempter_remove_record(ptm);
157 #elif defined(USE_UTMPX)
158 return set_utmpx(DEAD_PROCESS, ptm);
160 # error "neither USE_UTEMPTER nor USE_UTMPX defined"
164 static void pass_buffer_to_program(const char *buffer, size_t length, char **argv) {
173 if (pipe(fds) != 0) {
178 fh = fdopen(fds[1] /* write side */, "w");
190 } else if (pid == 0) {
193 close(fds[1]); /* write side */
195 /* Pass read side as stdin to the program. */
196 if (dup2(fds[0], STDIN_FILENO) < 0) {
202 execvp(argv[0], argv);
208 if (fwrite(buffer, 1, length, fh) != length) {
210 /* continue to perform cleanup */
214 close(fds[0]); /* read side */
217 static void handle_wall(int fd, char **argv) {
221 while ((r = read(fd, buffer, sizeof(buffer))) > 0) {
222 assert(SSIZE_MAX <= SIZE_MAX);
223 pass_buffer_to_program(buffer, (size_t)r, argv);
228 int main(int argc, char **argv) {
233 fprintf(stderr, "usage: %s <cmd args..>\n", argv[0]);
249 printf("%s\n", name);
252 /* We need to open the slave or reading from the master yields EOF after
253 * the first wall write to it. */
254 pts = open(name, O_RDWR);
260 /* Cleanup on signals. Necessary before login(). */
268 /* Main loop. Handle all wall messages sent to our PTY. */
269 handle_wall(ptm, argv);