Eliminated compilation warnings.
[mmh] / uip / sendsbr.c
1
2 /*
3  * sendsbr.c -- routines to help WhatNow/Send along
4  *
5  * $Id$
6  */
7
8 #include <h/mh.h>
9 #include <h/signals.h>
10 #include <setjmp.h>
11 #include <signal.h>
12 #include <fcntl.h>
13 #include <h/mime.h>
14
15 int debugsw = 0;                /* global */
16 int forwsw  = 1;
17 int inplace = 1;
18 int pushsw  = 0;
19 int splitsw = -1;
20 int unique  = 0;
21 int verbsw  = 0;
22
23 char *altmsg   = NULL;          /*  .. */
24 char *annotext = NULL;
25 char *distfile = NULL;
26
27 static int armed = 0;
28 static jmp_buf env;
29
30 /*
31  * external prototypes
32  */
33 int sendsbr (char **, int, char *, struct stat *, int);
34 int done (int);
35 char *getusername (void);
36
37 /*
38  * static prototypes
39  */
40 static void alert (char *, int);
41 static int tmp_fd (void);
42 static void anno (int, struct stat *);
43 static void annoaux (int);
44 static int splitmsg (char **, int, char *, struct stat *, int);
45 static int sendaux (char **, int, char *, struct stat *);
46
47
48 /*
49  * Entry point into (back-end) routines to send message.
50  */
51
52 int
53 sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
54 {
55     int status;
56     char buffer[BUFSIZ], file[BUFSIZ];
57     struct stat sts;
58
59     armed++;
60     switch (setjmp (env)) {
61     case OK: 
62         /*
63          * If given -push and -unique (which is undocumented), then
64          * rename the draft file.  I'm not quite sure why.
65          */
66         if (pushsw && unique) {
67             if (rename (drft, strncpy (file, m_scratch (drft, invo_name), sizeof(file)))
68                     == NOTOK)
69                 adios (file, "unable to rename %s to", drft);
70             drft = file;
71         }
72
73         /*
74          * Check if we need to split the message into
75          * multiple messages of type "message/partial".
76          */
77         if (splitsw >= 0 && !distfile && stat (drft, &sts) != NOTOK
78                 && sts.st_size >= CPERMSG) {
79             status = splitmsg (vec, vecp, drft, st, splitsw) ? NOTOK : OK;
80         } else {
81             status = sendaux (vec, vecp, drft, st) ? NOTOK : OK;
82         }
83
84         /* rename the original draft */
85         if (rename_drft && status == OK &&
86                 rename (drft, strncpy (buffer, m_backup (drft), sizeof(buffer))) == NOTOK)
87             advise (buffer, "unable to rename %s to", drft);
88         break;
89
90     default: 
91         status = DONE;
92         break;
93     }
94
95     armed = 0;
96     if (distfile)
97         unlink (distfile);
98
99     return status;
100 }
101
102
103 /*
104  * Split large message into several messages of
105  * type "message/partial" and send them.
106  */
107
108 static int
109 splitmsg (char **vec, int vecp, char *drft, struct stat *st, int delay)
110 {
111     int compnum, nparts, partno, state, status;
112     long pos, start;
113     time_t clock;
114     char *cp, *dp, buffer[BUFSIZ], msgid[BUFSIZ];
115     char subject[BUFSIZ];
116     char name[NAMESZ], partnum[BUFSIZ];
117     FILE *in;
118
119     if ((in = fopen (drft, "r")) == NULL)
120         adios (drft, "unable to open for reading");
121
122     cp = dp = NULL;
123     start = 0L;
124
125     /*
126      * Scan through the message and examine the various header fields,
127      * as well as locate the beginning of the message body.
128      */
129     for (compnum = 1, state = FLD;;) {
130         switch (state = m_getfld (state, name, buffer, sizeof(buffer), in)) {
131             case FLD:
132             case FLDPLUS:
133             case FLDEOF:
134                 compnum++;
135
136                 /*
137                  * This header field is discarded.
138                  */
139                 if (!strcasecmp (name, "Message-ID")) {
140                     while (state == FLDPLUS)
141                         state = m_getfld (state, name, buffer, sizeof(buffer), in);
142                 } else if (uprf (name, XXX_FIELD_PRF)
143                         || !strcasecmp (name, VRSN_FIELD)
144                         || !strcasecmp (name, "Subject")
145                         || !strcasecmp (name, "Encrypted")) {
146                     /*
147                      * These header fields are copied to the enclosed
148                      * header of the first message in the collection
149                      * of message/partials.  For the "Subject" header
150                      * field, we also record it, so that a modified
151                      * version of it, can be copied to the header
152                      * of each messsage/partial in the collection.
153                      */
154                     if (!strcasecmp (name, "Subject")) {
155                         size_t sublen;
156
157                         strncpy (subject, buffer, BUFSIZ);
158                         sublen = strlen (subject);
159                         if (sublen > 0 && subject[sublen - 1] == '\n')
160                             subject[sublen - 1] = '\0';
161                     }
162
163                     dp = add (concat (name, ":", buffer, NULL), dp);
164                     while (state == FLDPLUS) {
165                         state = m_getfld (state, name, buffer, sizeof(buffer), in);
166                         dp = add (buffer, dp);
167                     }
168                 } else {
169                     /*
170                      * These header fields are copied to the header of
171                      * each message/partial in the collection.
172                      */
173                     cp = add (concat (name, ":", buffer, NULL), cp);
174                     while (state == FLDPLUS) {
175                         state = m_getfld (state, name, buffer, sizeof(buffer), in);
176                         cp = add (buffer, cp);
177                     }
178                 }
179
180                 if (state != FLDEOF) {
181                     start = ftell (in) + 1;
182                     continue;
183                 }
184                 /* else fall... */
185
186            case BODY:
187            case BODYEOF:
188            case FILEEOF:
189                 break;
190
191            case LENERR:
192            case FMTERR:
193                 adios (NULL, "message format error in component #%d", compnum);
194
195            default:
196                 adios (NULL, "getfld () returned %d", state);
197         }
198
199         break;
200     }
201     if (cp == NULL)
202         adios (NULL, "headers missing from draft");
203
204     nparts = 1;
205     pos = start;
206     while (fgets (buffer, sizeof(buffer) - 1, in)) {
207         long len;
208
209         if ((pos += (len = strlen (buffer))) > CPERMSG) {
210             nparts++;
211             pos = len;
212         }
213     }
214
215     /* Only one part, nothing to split */
216     if (nparts == 1) {
217         free (cp);
218         if (dp)
219             free (dp);
220
221         fclose (in);
222         return sendaux (vec, vecp, drft, st);
223     }
224
225     if (!pushsw) {
226         printf ("Sending as %d Partial Messages\n", nparts);
227         fflush (stdout);
228     }
229     status = OK;
230
231     vec[vecp++] = "-partno";
232     vec[vecp++] = partnum;
233     if (delay == 0)
234         vec[vecp++] = "-queued";
235
236     time (&clock);
237     snprintf (msgid, sizeof(msgid), "<%d.%ld@%s>",
238                 (int) getpid(), (long) clock, LocalName());
239
240     fseek (in, start, SEEK_SET);
241     for (partno = 1; partno <= nparts; partno++) {
242         char tmpdrf[BUFSIZ];
243         FILE *out;
244
245         strncpy (tmpdrf, m_scratch (drft, invo_name), sizeof(tmpdrf));
246         if ((out = fopen (tmpdrf, "w")) == NULL)
247             adios (tmpdrf, "unable to open for writing");
248         chmod (tmpdrf, 0600);
249
250         /*
251          * Output the header fields
252          */
253         fputs (cp, out);
254         fprintf (out, "Subject: %s (part %d of %d)\n", subject, partno, nparts);
255         fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
256         fprintf (out, "%s: message/partial; id=\"%s\";\n", TYPE_FIELD, msgid);
257         fprintf (out, "\tnumber=%d; total=%d\n", partno, nparts);
258         fprintf (out, "%s: part %d of %d\n\n", DESCR_FIELD, partno, nparts);
259
260         /*
261          * If this is the first in the collection, output the
262          * header fields we are encapsulating at the beginning
263          * of the body of the first message.
264          */
265         if (partno == 1) {
266             if (dp)
267                 fputs (dp, out);
268             fprintf (out, "Message-ID: %s\n", msgid);
269             fprintf (out, "\n");
270         }
271
272         pos = 0;
273         for (;;) {
274             long len;
275
276             if (!fgets (buffer, sizeof(buffer) - 1, in)) {
277                 if (partno == nparts)
278                     break;
279                 adios (NULL, "premature eof");
280             }
281             
282             if ((pos += (len = strlen (buffer))) > CPERMSG) {
283                 fseek (in, -len, SEEK_CUR);
284                 break;
285             }
286
287             fputs (buffer, out);
288         }
289
290         if (fflush (out))
291             adios (tmpdrf, "error writing to");
292
293         fclose (out);
294
295         if (!pushsw && verbsw) {
296             printf ("\n");
297             fflush (stdout);
298         }
299
300         /* Pause here, if a delay is specified */
301         if (delay > 0 && 1 < partno && partno <= nparts) {
302             if (!pushsw) {
303                 printf ("pausing %d seconds before sending part %d...\n",
304                         delay, partno);
305                 fflush (stdout);
306             }
307             sleep ((unsigned int) delay);
308         }
309
310         snprintf (partnum, sizeof(partnum), "%d", partno);
311         status = sendaux (vec, vecp, tmpdrf, st);
312         unlink (tmpdrf);
313         if (status != OK)
314             break;
315
316         /*
317          * This is so sendaux will only annotate
318          * the altmsg the first time it is called.
319          */
320         annotext = NULL;
321     }
322
323     free (cp);
324     if (dp)
325         free (dp);
326
327     fclose (in);        /* close the draft */
328     return status;
329 }
330
331
332 /*
333  * Annotate original message, and
334  * call `postproc' to send message.
335  */
336
337 static int
338 sendaux (char **vec, int vecp, char *drft, struct stat *st)
339 {
340     pid_t child_id;
341     int i, status, fd, fd2;
342     char backup[BUFSIZ], buf[BUFSIZ];
343
344     fd = pushsw ? tmp_fd () : NOTOK;
345     fd2 = NOTOK;
346
347     vec[vecp++] = drft;
348     if (annotext) {
349         if ((fd2 = tmp_fd ()) != NOTOK) {
350             vec[vecp++] = "-idanno";
351             snprintf (buf, sizeof(buf), "%d", fd2);
352             vec[vecp++] = buf;
353         } else {
354             admonish (NULL, "unable to create file for annotation list");
355         }
356     }
357     if (distfile && distout (drft, distfile, backup) == NOTOK)
358         done (1);
359     vec[vecp] = NULL;
360
361     for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
362         sleep (5);
363
364     switch (child_id) {
365     case -1:
366         /* oops -- fork error */
367         adios ("fork", "unable to");
368         break;  /* NOT REACHED */
369
370     case 0:
371         /*
372          * child process -- send it
373          *
374          * If fd is ok, then we are pushing and fd points to temp
375          * file, so capture anything on stdout and stderr there.
376          */
377         if (fd != NOTOK) {
378             dup2 (fd, fileno (stdout));
379             dup2 (fd, fileno (stderr));
380             close (fd);
381         }
382         execvp (postproc, vec);
383         fprintf (stderr, "unable to exec ");
384         perror (postproc);
385         _exit (-1);
386         break;  /* NOT REACHED */
387
388     default:
389         /*
390          * parent process -- wait for it
391          */
392         if ((status = pidwait(child_id, NOTOK)) == OK) {
393             if (annotext && fd2 != NOTOK)
394                 anno (fd2, st);
395         } else {
396             /*
397              * If postproc failed, and we have good fd (which means
398              * we pushed), then mail error message (and possibly the
399              * draft) back to the user.
400              */
401             if (fd != NOTOK) {
402                 alert (drft, fd);
403                 close (fd);
404             } else {
405                 advise (NULL, "message not delivered to anyone");
406             }
407             if (annotext && fd2 != NOTOK)
408                 close (fd2);
409             if (distfile) {
410                 unlink (drft);
411                 if (rename (backup, drft) == NOTOK)
412                     advise (drft, "unable to rename %s to", backup);
413             }
414         }
415         break;
416     }
417
418     return status;
419 }
420
421
422 /*
423  * Mail error notification (and possibly a copy of the
424  * message) back to the user, using the mailproc
425  */
426
427 static void
428 alert (char *file, int out)
429 {
430     pid_t child_id;
431     int i, in;
432     char buf[BUFSIZ];
433
434     for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
435         sleep (5);
436
437     switch (child_id) {
438         case NOTOK:
439             /* oops -- fork error */
440             advise ("fork", "unable to");
441
442         case OK:
443             /* child process -- send it */
444             SIGNAL (SIGHUP, SIG_IGN);
445             SIGNAL (SIGINT, SIG_IGN);
446             SIGNAL (SIGQUIT, SIG_IGN);
447             SIGNAL (SIGTERM, SIG_IGN);
448             if (forwsw) {
449                 if ((in = open (file, O_RDONLY)) == NOTOK) {
450                     admonish (file, "unable to re-open");
451                 } else {
452                     lseek (out, (off_t) 0, SEEK_END);
453                     strncpy (buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
454                     write (out, buf, strlen (buf));
455                     strncpy (buf, "\n------- Unsent Draft\n\n", sizeof(buf));
456                     write (out, buf, strlen (buf));
457                     cpydgst (in, out, file, "temporary file");
458                     close (in);
459                     strncpy (buf, "\n------- End of Unsent Draft\n", sizeof(buf));
460                     write (out, buf, strlen (buf));
461                     if (rename (file, strncpy (buf, m_backup (file), sizeof(buf))) == NOTOK)
462                         admonish (buf, "unable to rename %s to", file);
463                 }
464             }
465             lseek (out, (off_t) 0, SEEK_SET);
466             dup2 (out, fileno (stdin));
467             close (out);
468             /* create subject for error notification */
469             snprintf (buf, sizeof(buf), "send failed on %s",
470                         forwsw ? "enclosed draft" : file);
471
472             execlp (mailproc, r1bindex (mailproc, '/'), getusername (),
473                     "-subject", buf, NULL);
474             fprintf (stderr, "unable to exec ");
475             perror (mailproc);
476             _exit (-1);
477
478         default:                /* no waiting... */
479             break;
480     }
481 }
482
483
484 static int
485 tmp_fd (void)
486 {
487     int fd;
488     char tmpfil[BUFSIZ];
489
490     strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil));
491     if ((fd = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK)
492         return NOTOK;
493     if (debugsw)
494         advise (NULL, "temporary file %s selected", tmpfil);
495     else
496         if (unlink (tmpfil) == NOTOK)
497             advise (tmpfil, "unable to remove");
498
499     return fd;
500 }
501
502
503 static void
504 anno (int fd, struct stat *st)
505 {
506     pid_t child_id;
507     sigset_t set, oset;
508     static char *cwd = NULL;
509     struct stat st2;
510
511     if (altmsg &&
512             (stat (altmsg, &st2) == NOTOK
513                 || st->st_mtime != st2.st_mtime
514                 || st->st_dev != st2.st_dev
515                 || st->st_ino != st2.st_ino)) {
516         if (debugsw)
517             admonish (NULL, "$mhaltmsg mismatch");
518         return;
519     }
520
521     child_id = debugsw ? NOTOK : fork ();
522     switch (child_id) {
523         case NOTOK:             /* oops */
524             if (!debugsw)
525                 advise (NULL,
526                             "unable to fork, so doing annotations by hand...");
527             if (cwd == NULL)
528                 cwd = getcpy (pwd ());
529
530         case OK: 
531             /* block a few signals */
532             sigemptyset (&set);
533             sigaddset (&set, SIGHUP);
534             sigaddset (&set, SIGINT);
535             sigaddset (&set, SIGQUIT);
536             sigaddset (&set, SIGTERM);
537             SIGPROCMASK (SIG_BLOCK, &set, &oset);
538
539             annoaux (fd);
540             if (child_id == OK)
541                 _exit (0);
542
543             /* reset the signal mask */
544             SIGPROCMASK (SIG_SETMASK, &oset, &set);
545
546             chdir (cwd);
547             break;
548
549         default:                /* no waiting... */
550             close (fd);
551             break;
552     }
553 }
554
555
556 static void
557 annoaux (int fd)
558 {
559     int fd2, fd3, msgnum;
560     char *cp, *folder, *maildir;
561     char buffer[BUFSIZ], **ap;
562     FILE *fp;
563     struct msgs *mp;
564
565     if ((folder = getenv ("mhfolder")) == NULL || *folder == 0) {
566         if (debugsw)
567             admonish (NULL, "$mhfolder not set");
568         return;
569     }
570     maildir = m_maildir (folder);
571     if (chdir (maildir) == NOTOK) {
572         if (debugsw)
573             admonish (maildir, "unable to change directory to");
574         return;
575     }
576     if (!(mp = folder_read (folder))) {
577         if (debugsw)
578             admonish (NULL, "unable to read folder %s");
579         return;
580     }
581
582     /* check for empty folder */
583     if (mp->nummsg == 0) {
584         if (debugsw)
585             admonish (NULL, "no messages in %s", folder);
586         goto oops;
587     }
588
589     if ((cp = getenv ("mhmessages")) == NULL || *cp == 0) {
590         if (debugsw)
591             admonish (NULL, "$mhmessages not set");
592         goto oops;
593     }
594     if (!debugsw                        /* MOBY HACK... */
595             && pushsw
596             && (fd3 = open ("/dev/null", O_RDWR)) != NOTOK
597             && (fd2 = dup (fileno (stderr))) != NOTOK) {
598         dup2 (fd3, fileno (stderr));
599         close (fd3);
600     }
601     else
602         fd2 = NOTOK;
603     for (ap = brkstring (cp = getcpy (cp), " ", NULL); *ap; ap++)
604         m_convert (mp, *ap);
605     free (cp);
606     if (fd2 != NOTOK)
607         dup2 (fd2, fileno (stderr));
608     if (mp->numsel == 0) {
609         if (debugsw)
610             admonish (NULL, "no messages to annotate");
611         goto oops;
612     }
613
614     lseek (fd, (off_t) 0, SEEK_SET);
615     if ((fp = fdopen (fd, "r")) == NULL) {
616         if (debugsw)
617             admonish (NULL, "unable to fdopen annotation list");
618         goto oops;
619     }
620     cp = NULL;
621     while (fgets (buffer, sizeof(buffer), fp) != NULL)
622         cp = add (buffer, cp);
623     fclose (fp);
624
625     if (debugsw)
626         advise (NULL, "annotate%s with %s: \"%s\"",
627                 inplace ? " inplace" : "", annotext, cp);
628     for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
629         if (is_selected(mp, msgnum)) {
630             if (debugsw)
631                 advise (NULL, "annotate message %d", msgnum);
632             annotate (m_name (msgnum), annotext, cp, inplace, 1);
633         }
634     }
635
636     free (cp);
637
638 oops:
639     folder_free (mp);   /* free folder/message structure */
640 }
641
642
643 int
644 done (int status)
645 {
646     if (armed)
647         longjmp (env, status ? status : NOTOK);
648
649     exit (status);
650     return 1;  /* dead code to satisfy the compiler */
651 }