]> ruderich.org/simon Gitweb - wall-notify/wall-notify.git/blob - src/wall-notify.c
initial commit
[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/stat.h>
33 #include <sys/time.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36
37 #ifdef USE_UTEMPTER
38 # include <utempter.h>
39 #endif
40 #ifdef USE_UTMPX
41 # include <utmpx.h>
42 #endif
43
44
45 static void sig_handler(int signal) {
46     (void)signal;
47 }
48 static void setup_signals(void) {
49     struct sigaction action;
50
51     memset(&action, 0, sizeof(action));
52     sigemptyset(&action.sa_mask);
53     action.sa_handler = sig_handler;
54
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);
62
63     /* Collect zombies automatically without having to call waitpid(2). */
64     signal(SIGCHLD, SIG_IGN);
65 }
66
67 static int open_tty(void) {
68     int ptm;
69     const char *name;
70
71     ptm = posix_openpt(O_RDWR);
72     if (ptm < 0) {
73         return -1;
74     }
75     if (grantpt(ptm) != 0) {
76         return -1;
77     }
78
79     /* Prevent write access for other users so they can't use wall to send
80      * messages to this program. */
81     name = ptsname(ptm);
82     if (!name) {
83         return -1;
84     }
85     if (chmod(name, S_IRUSR | S_IWUSR) != 0) {
86         return -1;
87     }
88
89     if (unlockpt(ptm) != 0) {
90         return -1;
91     }
92
93     return ptm;
94 }
95
96 #ifdef USE_UTMPX
97 static const char *skip_prefix(const char *string, const char *prefix) {
98     size_t length = strlen(prefix);
99
100     if (!strncmp(string, prefix, length)) {
101         return string + length;
102     } else {
103         return string;
104     }
105 }
106 static int set_utmpx(short type, int ptm) {
107     struct utmpx entry;
108
109     const char *tty, *user, *id, *line;
110     struct timeval now;
111
112     user = getpwuid(getuid())->pw_name;
113     gettimeofday(&now, NULL);
114
115     tty = ptsname(ptm);
116     if (!tty) {
117         return 0;
118     }
119
120     id   = skip_prefix(tty, "/dev/pts/");
121     line = skip_prefix(tty, "/dev/");
122
123     /* Create utmp entry for the given terminal. */
124     memset(&entry, 0, sizeof(entry));
125
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);
129
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;
134
135     /* Write the entry to the utmp file. */
136     setutxent();
137     if (!pututxline(&entry)) {
138         return 0;
139     }
140     endutxent();
141
142     return 1;
143 }
144 #endif
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);
150 #else
151 # error "neither USE_UTEMPTER nor USE_UTMPX defined"
152 #endif
153 }
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);
159 #else
160 # error "neither USE_UTEMPTER nor USE_UTMPX defined"
161 #endif
162 }
163
164 static void pass_buffer_to_program(const char *buffer, size_t length, char **argv) {
165     int fds[2];
166     FILE *fh;
167
168     pid_t pid;
169
170     /* Skip argv[0]. */
171     argv++;
172
173     if (pipe(fds) != 0) {
174         perror("pipe");
175         return;
176     }
177
178     fh = fdopen(fds[1] /* write side */, "w");
179     if (!fh) {
180         perror("fdopen");
181         close(fds[0]);
182         close(fds[1]);
183         return;
184     }
185
186     pid = fork();
187     if (pid < 0) {
188         perror("fork");
189         goto out;
190     } else if (pid == 0) {
191         /* child */
192
193         close(fds[1]); /* write side */
194
195         /* Pass read side as stdin to the program. */
196         if (dup2(fds[0], STDIN_FILENO) < 0) {
197             perror("dup2");
198             exit(EXIT_FAILURE);
199         }
200         close(fds[0]);
201
202         execvp(argv[0], argv);
203         perror("execvp");
204         exit(EXIT_FAILURE);
205     }
206     /* father */
207
208     if (fwrite(buffer, 1, length, fh) != length) {
209         perror("fwrite");
210         /* continue to perform cleanup */
211     }
212
213 out:
214     close(fds[0]); /* read side */
215     fclose(fh);
216 }
217 static void handle_wall(int fd, char **argv) {
218     char buffer[4096];
219     ssize_t r;
220
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);
224     }
225 }
226
227
228 int main(int argc, char **argv) {
229     int ptm, pts;
230     char *name;
231
232     if (argc < 2) {
233         fprintf(stderr, "usage: %s <cmd args..>\n", argv[0]);
234         exit(EXIT_FAILURE);
235     }
236
237     ptm = open_tty();
238     if (ptm < 0) {
239         perror("open_tty");
240         exit(EXIT_FAILURE);
241     }
242     name = ptsname(ptm);
243     if (!name) {
244         perror("ptsname");
245         exit(EXIT_FAILURE);
246     }
247
248 #ifdef DEBUG
249     printf("%s\n", name);
250 #endif
251
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);
255     if (pts < 0) {
256         perror(name);
257         exit(EXIT_FAILURE);
258     }
259
260     /* Cleanup on signals. Necessary before login(). */
261     setup_signals();
262
263     if (!login(ptm)) {
264         perror("login");
265         exit(EXIT_FAILURE);
266     }
267
268     /* Main loop. Handle all wall messages sent to our PTY. */
269     handle_wall(ptm, argv);
270
271     if (!logout(ptm)) {
272         perror("logout");
273         exit(EXIT_FAILURE);
274     }
275
276     close(ptm);
277     close(pts);
278
279     return EXIT_SUCCESS;
280 }