Augmented new_fs() with an additional argument for a default form.
[mmh] / uip / rcvtty.c
1 /*
2 ** rcvtty.c -- a rcvmail program (a lot like rcvalert) handling IPC ttys
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 /* Changed to use getutent() and friends.  Assumes that when getutent() exists,
10 ** a number of other things also exist.  Please check.
11 ** Ruud de Rooij <ruud@ruud.org>  Sun, 28 May 2000 17:28:55 +0200
12 */
13
14 #include <h/mh.h>
15 #include <h/signals.h>
16 #include <h/rcvmail.h>
17 #include <h/scansbr.h>
18 #include <h/tws.h>
19 #include <signal.h>
20 #include <fcntl.h>
21
22 #include <utmp.h>
23
24 #ifndef HAVE_GETUTENT
25 # ifndef UTMP_FILE
26 #  ifdef _PATH_UTMP
27 #   define UTMP_FILE _PATH_UTMP
28 #  else
29 #   define UTMP_FILE "/etc/utmp"
30 #  endif
31 # endif
32 #endif
33
34 #define SCANFMT \
35 "%2(hour{dtimenow}):%02(min{dtimenow}): %<(size)%5(size) %>%<{encrypted}E%>\
36 %<(mymbox{from})%<{to}To:%14(friendly{to})%>%>%<(zero)%17(friendly{from})%>  \
37 %{subject}"
38
39 static struct swit switches[] = {
40 #define BIFFSW  0
41         { "biff", 0 },
42 #define FORMSW  1
43         { "form formatfile", 0 },
44 #define FMTSW  2
45         { "format string", 5 },
46 #define WIDTHSW 3
47         { "width columns", 0 },
48 #define NLSW  4
49         { "newline", 0 },
50 #define NNLSW   5
51         { "nonewline", 0 },
52 #define BELSW  6
53         { "bell", 0 },
54 #define NBELSW  7
55         { "nobell", 0 },
56 #define VERSIONSW 8
57         { "version", 0 },
58 #define HELPSW  9
59         { "help", 0 },
60         { NULL, 0 }
61 };
62
63 static jmp_buf myctx;
64 static int bell = 1;
65 static int newline = 1;
66 static int biff = 0;
67 static int width = 0;
68 static char *form = NULL;
69 static char *format = NULL;
70
71 /*
72 ** static prototypes
73 */
74 static RETSIGTYPE alrmser(int);
75 static int message_fd(char **);
76 static int header_fd(void);
77 static void alert(char *, int);
78
79
80 int
81 main(int argc, char **argv)
82 {
83         int md, vecp = 0;
84         char *cp, *user, buf[BUFSIZ], tty[BUFSIZ];
85         char **argp, **arguments, *vec[MAXARGS];
86 #ifdef HAVE_GETUTENT
87         struct utmp * utp;
88 #else
89         struct utmp ut;
90         register FILE *uf;
91 #endif
92
93 #ifdef LOCALE
94         setlocale(LC_ALL, "");
95 #endif
96         invo_name = mhbasename(argv[0]);
97
98         /* read user profile/context */
99         context_read();
100
101         arguments = getarguments(invo_name, argc, argv, 1);
102         argp = arguments;
103
104         while ((cp = *argp++)) {
105                 if (*cp == '-') {
106                         switch (smatch(++cp, switches)) {
107                         case AMBIGSW:
108                                 ambigsw(cp, switches);
109                                 done(1);
110                         case UNKWNSW:
111                                 vec[vecp++] = --cp;
112                                 continue;
113
114                         case HELPSW:
115                                 snprintf(buf, sizeof(buf), "%s [command ...]",
116                                                 invo_name);
117                                 print_help(buf, switches, 1);
118                                 done(1);
119                         case VERSIONSW:
120                                 print_version(invo_name);
121                                 done(1);
122
123                         case BIFFSW:
124                                 biff = 1;
125                                 continue;
126
127                         case FORMSW:
128                                 if (!(form = *argp++) || *form == '-')
129                                         adios(NULL, "missing argument to %s",
130                                                         argp[-2]);
131                                 format = NULL;
132                                 continue;
133                         case FMTSW:
134                                 if (!(format = *argp++) || *format == '-')
135                                         adios(NULL, "missing argument to %s",
136                                                         argp[-2]);
137                                 form = NULL;
138                                 continue;
139
140                         case WIDTHSW:
141                                 if (!(cp = *argp++) || *cp == '-')
142                                         adios(NULL, "missing argument to %s",
143                                                         argp[-2]);
144                                 width = atoi(cp);
145                                 continue;
146                         case NLSW:
147                                 newline = 1;
148                                 continue;
149                         case NNLSW:
150                                 newline = 0;
151                                 continue;
152                         case BELSW:
153                                 bell = 1;
154                                 continue;
155                         case NBELSW:
156                                 bell = 0;
157                                 continue;
158
159                         }
160                 }
161                 vec[vecp++] = cp;
162         }
163         vec[vecp] = 0;
164
165         if ((md = vecp ? message_fd(vec) : header_fd()) == NOTOK)
166                 exit(RCV_MBX);
167
168         user = getusername();
169
170 #ifdef HAVE_GETUTENT
171         setutent();
172         while ((utp = getutent()) != NULL) {
173                 if (
174 #ifdef HAVE_STRUCT_UTMP_UT_TYPE
175                                 utp->ut_type == USER_PROCESS &&
176 #endif
177                                 utp->ut_name[0] != 0 && utp->ut_line[0] != 0
178                                 && strncmp(user, utp->ut_name,
179                                 sizeof(utp->ut_name)) == 0) {
180                         strncpy(tty, utp->ut_line, sizeof(utp->ut_line));
181                         alert(tty, md);
182                 }
183         }
184         endutent();
185 #else
186         if ((uf = fopen(UTMP_FILE, "r")) == NULL)
187                 exit(RCV_MBX);
188         while (fread((char *) &ut, sizeof(ut), 1, uf) == 1)
189                 if (ut.ut_name[0] != 0 &&
190                                 strncmp(user, ut.ut_name, sizeof(ut.ut_name))
191                                 == 0) {
192                         strncpy(tty, ut.ut_line, sizeof(ut.ut_line));
193                         alert(tty, md);
194                 }
195         fclose(uf);
196 #endif
197
198         exit(RCV_MOK);
199         return 0;  /* dead code to satisfy the compiler */
200 }
201
202
203 static RETSIGTYPE
204 alrmser(int i)
205 {
206 #ifndef RELIABLE_SIGNALS
207         SIGNAL(SIGALRM, alrmser);
208 #endif
209
210         longjmp(myctx, 1);
211 }
212
213
214 static int
215 message_fd(char **vec)
216 {
217         pid_t child_id;
218         int bytes, fd, seconds;
219         char tmpfil[BUFSIZ];
220         struct stat st;
221
222 #ifdef HAVE_MKSTEMP
223         fd = mkstemp(strncpy(tmpfil, "/tmp/rcvttyXXXXX", sizeof(tmpfil)));
224 #else
225         unlink(mktemp(strncpy(tmpfil, "/tmp/rcvttyXXXXX", sizeof(tmpfil))));
226         if ((fd = open(tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK)
227                 return header_fd();
228 #endif
229         unlink(tmpfil);
230
231         if ((child_id = fork()) == NOTOK) {
232                 /* fork error */
233                 close(fd);
234                 return header_fd();
235         } else if (child_id) {
236                 /* parent process */
237                 if (!setjmp(myctx)) {
238                         SIGNAL(SIGALRM, alrmser);
239                         bytes = fstat(fileno(stdin), &st) != NOTOK ?
240                                         (int) st.st_size : 100;
241
242                         /* amount of time to wait depends on message size */
243                         if (bytes <= 100) {
244                                 /* give at least 5 minutes */
245                                 seconds = 300;
246                         } else if (bytes >= 90000) {
247                                 /* but 30 minutes should be long enough */
248                                 seconds = 1800;
249                         } else {
250                                 seconds = (bytes / 60) + 300;
251                         }
252                         alarm((unsigned int) seconds);
253                         pidwait(child_id, OK);
254                         alarm(0);
255
256                         if (fstat(fd, &st) != NOTOK && st.st_size > (off_t) 0)
257                                 return fd;
258                 } else {
259                         /*
260                         ** Ruthlessly kill the child and anything
261                         ** else in its process group.
262                         */
263                         KILLPG(child_id, SIGKILL);
264                 }
265                 close(fd);
266                 return header_fd();
267         }
268
269         /* child process */
270         rewind(stdin);
271         if (dup2(fd, 1) == NOTOK || dup2(fd, 2) == NOTOK)
272                 _exit(-1);
273         closefds(3);
274         setpgid((pid_t) 0, getpid());  /* put in own process group */
275         execvp(vec[0], vec);
276         _exit(-1);
277         return 1;  /* dead code to satisfy compiler */
278 }
279
280
281 static int
282 header_fd(void)
283 {
284         int fd;
285         char *nfs;
286         char *tfile = NULL;
287
288         tfile = m_mktemp2(NULL, invo_name, &fd, NULL);
289         if (tfile == NULL) return NOTOK;
290         unlink(tfile);
291
292         rewind(stdin);
293
294         /* get new format string */
295         nfs = new_fs(form, format, NULL, SCANFMT);
296         scan(stdin, 0, 0, nfs, width, 0, 0, NULL, 0L, 0);
297         if (newline)
298                 write(fd, "\n\r", 2);
299         write(fd, scanl, strlen(scanl));
300         if (bell)
301                 write(fd, "\007", 1);
302
303         return fd;
304 }
305
306
307 static void
308 alert(char *tty, int md)
309 {
310         int i, td, mask;
311         char buffer[BUFSIZ], ttyspec[BUFSIZ];
312         struct stat st;
313
314         snprintf(ttyspec, sizeof(ttyspec), "/dev/%s", tty);
315
316         /*
317         ** The mask depends on whether we are checking for
318         ** write permission based on `biff' or `mesg'.
319         */
320         mask = biff ? S_IEXEC : (S_IWRITE >> 3);
321         if (stat(ttyspec, &st) == NOTOK || (st.st_mode & mask) == 0)
322                 return;
323
324         if (!setjmp(myctx)) {
325                 SIGNAL(SIGALRM, alrmser);
326                 alarm(2);
327                 td = open(ttyspec, O_WRONLY);
328                 alarm(0);
329                 if (td == NOTOK)
330                         return;
331         } else {
332                 alarm(0);
333                 return;
334         }
335
336         lseek(md, (off_t) 0, SEEK_SET);
337
338         while ((i = read(md, buffer, sizeof(buffer))) > 0)
339                 if (write(td, buffer, i) != i)
340                         break;
341
342         close(td);
343 }