]> ruderich.org/simon Gitweb - wall-notify/wall-notify.git/blob - src/wall-notify.c
343f3d0ff37bfbb5a6798ee70158dbbed0cceb4a
[wall-notify/wall-notify.git] / src / wall-notify.c
1 /*
2  * Receive wall messages and pass them to a notification program via stdin.
3  *
4  * Copyright (C) 2014  Simon Ruderich
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20
21 #include <config.h>
22
23 #include <assert.h>
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <pwd.h>
27 #include <signal.h>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/select.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <unistd.h>
37
38 #ifdef USE_UTEMPTER
39 # include <utempter.h>
40 #endif
41 #ifdef USE_UTMPX
42 # include <utmpx.h>
43 #endif
44
45
46 static void sig_handler(int signal) {
47     (void)signal;
48 }
49 static void setup_signals(void) {
50     struct sigaction action;
51
52     memset(&action, 0, sizeof(action));
53     sigemptyset(&action.sa_mask);
54     action.sa_handler = sig_handler;
55
56     /* Handle all important signals which might be sent to us so we break out
57      * of the read()-loop below and can perform our cleanup. */
58     sigaction(SIGHUP,  &action, NULL);
59     sigaction(SIGINT,  &action, NULL);
60     sigaction(SIGQUIT, &action, NULL);
61     sigaction(SIGUSR1, &action, NULL);
62     sigaction(SIGUSR2, &action, NULL);
63
64     /* Collect zombies automatically without having to call waitpid(2). */
65     signal(SIGCHLD, SIG_IGN);
66 }
67
68 static int open_tty(void) {
69     int ptm;
70     const char *name;
71
72     ptm = posix_openpt(O_RDWR);
73     if (ptm < 0) {
74         return -1;
75     }
76     if (grantpt(ptm) != 0) {
77         return -1;
78     }
79
80     /* Prevent write access for other users so they can't use wall to send
81      * messages to this program. */
82     name = ptsname(ptm);
83     if (!name) {
84         return -1;
85     }
86     if (chmod(name, S_IRUSR | S_IWUSR) != 0) {
87         return -1;
88     }
89
90     if (unlockpt(ptm) != 0) {
91         return -1;
92     }
93
94     return ptm;
95 }
96
97 #ifdef USE_UTMPX
98 static const char *skip_prefix(const char *string, const char *prefix) {
99     size_t length = strlen(prefix);
100
101     if (!strncmp(string, prefix, length)) {
102         return string + length;
103     } else {
104         return string;
105     }
106 }
107 static int set_utmpx(short type, int ptm) {
108     struct utmpx entry;
109
110     const char *tty, *user, *id, *line;
111     struct timeval now;
112
113     user = getpwuid(getuid())->pw_name;
114     gettimeofday(&now, NULL);
115
116     tty = ptsname(ptm);
117     if (!tty) {
118         return 0;
119     }
120
121     id   = skip_prefix(tty, "/dev/pts/");
122     line = skip_prefix(tty, "/dev/");
123
124     /* Create utmp entry for the given terminal. */
125     memset(&entry, 0, sizeof(entry));
126
127     snprintf(entry.ut_user, sizeof(entry.ut_user), "%s", user);
128     snprintf(entry.ut_id,   sizeof(entry.ut_id),   "%s", id);
129     snprintf(entry.ut_line, sizeof(entry.ut_line), "%s", line);
130
131     entry.ut_pid  = getpid();
132     entry.ut_type = type;
133     entry.ut_tv.tv_sec  = now.tv_sec;
134     entry.ut_tv.tv_usec = now.tv_usec;
135
136     /* Write the entry to the utmp file. */
137     setutxent();
138     if (!pututxline(&entry)) {
139         return 0;
140     }
141     endutxent();
142
143     return 1;
144 }
145 #endif
146 static int login(int ptm) {
147 #if defined(USE_UTEMPTER)
148     return utempter_add_record(ptm, NULL);
149 #elif defined(USE_UTMPX)
150     return set_utmpx(USER_PROCESS, ptm);
151 #else
152 # error "neither USE_UTEMPTER nor USE_UTMPX defined"
153 #endif
154 }
155 static int logout(int ptm) {
156 #if defined(USE_UTEMPTER)
157     return utempter_remove_record(ptm);
158 #elif defined(USE_UTMPX)
159     return set_utmpx(DEAD_PROCESS, ptm);
160 #else
161 # error "neither USE_UTEMPTER nor USE_UTMPX defined"
162 #endif
163 }
164
165 static int wait_for_write(int fd, int timeout) {
166     fd_set rfds;
167     struct timeval tv;
168     int result;
169
170     FD_ZERO(&rfds);
171     FD_SET(fd, &rfds);
172
173     tv.tv_sec  = timeout;
174     tv.tv_usec = 0;
175
176     result = select(fd + 1, &rfds, NULL, NULL, &tv);
177     if (result < 0) {
178         perror("select");
179         return 0;
180     }
181     if (result == 0) {
182         /* Timeout. */
183         return 0;
184     }
185
186     /* Got more data to read. */
187     return 1;
188 }
189
190 static void pass_buffer_to_program(const char *buffer, size_t length, char **argv) {
191     int fds[2];
192     FILE *fh;
193
194     pid_t pid;
195
196     /* Skip argv[0]. */
197     argv++;
198
199     if (pipe(fds) != 0) {
200         perror("pipe");
201         return;
202     }
203
204     fh = fdopen(fds[1] /* write side */, "w");
205     if (!fh) {
206         perror("fdopen");
207         close(fds[0]);
208         close(fds[1]);
209         return;
210     }
211
212     pid = fork();
213     if (pid < 0) {
214         perror("fork");
215         goto out;
216     } else if (pid == 0) {
217         /* child */
218
219         close(fds[1]); /* write side */
220
221         /* Pass read side as stdin to the program. */
222         if (dup2(fds[0], STDIN_FILENO) < 0) {
223             perror("dup2");
224             exit(EXIT_FAILURE);
225         }
226         close(fds[0]);
227
228         execvp(argv[0], argv);
229         perror("execvp");
230         exit(EXIT_FAILURE);
231     }
232     /* father */
233
234     if (fwrite(buffer, 1, length, fh) != length) {
235         perror("fwrite");
236         /* continue to perform cleanup */
237     }
238
239 out:
240     close(fds[0]); /* read side */
241     fclose(fh);
242 }
243 static void handle_wall(int fd, char **argv) {
244     char buffer[4096];
245     ssize_t r;
246
247     assert(SSIZE_MAX <= SIZE_MAX);
248     while ((r = read(fd, buffer, sizeof(buffer))) > 0) {
249         size_t space;
250         ssize_t r2;
251
252         /* To prevent partial messages (sometimes it takes multiple reads to
253          * get the complete message) wait for a short time to get the rest of
254          * the message. */
255         space = sizeof(buffer) - (size_t)r;
256         while (space > 0 && wait_for_write(fd, 1 /* second */)) {
257             r2 = read(fd, buffer + r, space);
258             if (r2 <= 0) {
259                 break;
260             }
261             r += r2;
262             space -= (size_t)r2;
263         }
264
265         pass_buffer_to_program(buffer, (size_t)r, argv);
266     }
267 }
268
269
270 int main(int argc, char **argv) {
271     int ptm, pts;
272     char *name;
273
274     if (argc < 2) {
275         fprintf(stderr, "usage: %s <cmd args..>\n", argv[0]);
276         exit(EXIT_FAILURE);
277     }
278
279     ptm = open_tty();
280     if (ptm < 0) {
281         perror("open_tty");
282         exit(EXIT_FAILURE);
283     }
284     name = ptsname(ptm);
285     if (!name) {
286         perror("ptsname");
287         exit(EXIT_FAILURE);
288     }
289
290 #ifdef DEBUG
291     printf("%s\n", name);
292 #endif
293
294     /* We need to open the slave or reading from the master yields EOF after
295      * the first wall write to it. */
296     pts = open(name, O_RDWR);
297     if (pts < 0) {
298         perror(name);
299         exit(EXIT_FAILURE);
300     }
301
302     /* Cleanup on signals. Necessary before login(). */
303     setup_signals();
304
305     if (!login(ptm)) {
306         perror("login");
307         exit(EXIT_FAILURE);
308     }
309
310     /* Main loop. Handle all wall messages sent to our PTY. */
311     handle_wall(ptm, argv);
312
313     if (!logout(ptm)) {
314         perror("logout");
315         exit(EXIT_FAILURE);
316     }
317
318     close(ptm);
319     close(pts);
320
321     return EXIT_SUCCESS;
322 }