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