b0956fcbd4b6667e6f616f4e7510ed4acffed132
[mmh] / uip / repl.c
1 /*
2 ** repl.c -- reply to a message
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 #include <h/mh.h>
10 #include <h/utils.h>
11 #include <h/addrsbr.h>
12 #include <h/fmt_scan.h>
13 #include <sys/file.h>  /* L_SET */
14 #include <errno.h>
15
16
17 static struct swit switches[] = {
18 #define GROUPSW  0
19         { "group", 0 },
20 #define NGROUPSW  1
21         { "nogroup", 0 },
22 #define ANNOSW  2
23         { "annotate", 0 },
24 #define NANNOSW  3
25         { "noannotate", 0 },
26 #define CCSW  4
27         { "cc all|to|cc|me", 0 },
28 #define NCCSW  5
29         { "nocc type", 0 },
30 #define EDITRSW  6
31         { "editor editor", 0 },
32 #define NEDITSW  7
33         { "noedit", 0 },
34 #define FILTSW  8
35         { "filter filterfile", 0 },
36 #define NFILTSW  9
37         { "nofilter", 0 },
38 #define FORMSW  10
39         { "form formfile", 0 },
40 #define MIMESW  11
41         { "mime", 0 },
42 #define NMIMESW  12
43         { "nomime", 0 },
44 #define QURYSW  13
45         { "query", 0 },
46 #define NQURYSW  14
47         { "noquery", 0 },
48 #define WHATSW  15
49         { "whatnowproc program", 0 },
50 #define VERSIONSW  16
51         { "version", 0 },
52 #define HELPSW  17
53         { "help", 0 },
54 #define FILESW  18
55         { "file file", 4 },  /* interface from msh */
56 #ifdef MHE
57 # define BILDSW  19
58         { "build", 5 },  /* interface from mhe */
59 #endif
60         { NULL, 0 }
61 };
62
63 static struct swit ccswitches[] = {
64 #define CTOSW  0
65         { "to", 0 },
66 #define CCCSW  1
67         { "cc", 0 },
68 #define CMESW  2
69         { "me", 0 },
70 #define CALSW  3
71         { "all", 0 },
72         { NULL, 0 }
73 };
74
75 /*
76 ** Buffer size for content part of header fields.
77 ** We want this to be large enough so that we don't
78 ** do a lot of extra FLDPLUS calls on m_getfld but
79 ** small enough so that we don't snarf the entire
80 ** message body when we're not going to use any of it.
81 */
82 #define SBUFSIZ 256
83
84 static short ccto = -1;
85 static short cccc = -1;
86 static short ccme = -1;
87 static short querysw = 0;
88
89 static short groupreply = 0;  /* Is this a group reply? */
90
91 static int mime = 0;  /* include original as MIME part */
92 static char *form   = NULL;  /* form (components) file */
93 static char *filter = NULL;  /* message filter file */
94
95 static int dftype=0;
96
97 static char *badaddrs = NULL;
98 static char *dfhost = NULL;
99
100 static struct mailname mq;
101
102 static struct format *fmt;
103
104 static int ncomps = 0;  /* # of interesting components */
105 static char **compbuffers = NULL;  /* buffers for component text */
106 static struct comp **used_buf = NULL;  /* stack for comp that use buffers */
107
108 static int dat[5];  /* aux. data for format routine */
109
110 static char *addrcomps[] = {
111         "from",
112         "sender",
113         "reply-to",
114         "to",
115         "cc",
116         "bcc",
117         "resent-from",
118         "resent-sender",
119         "resent-reply-to",
120         "resent-to",
121         "resent-cc",
122         "resent-bcc",
123         NULL
124 };
125
126 /*
127 ** static prototypes
128 */
129 static void docc(char *, int);
130 static int insert(struct mailname *);
131 static void replfilter(FILE *, FILE *, char *);
132 static void replout(FILE *, char *, struct msgs *, int,
133                 char *, char *);
134
135
136 int
137 main(int argc, char **argv)
138 {
139         int anot = 0;
140         int nedit = 0;
141         char *cp, *cwd, *maildir, *file = NULL;
142         char *folder = NULL, *msg = NULL;
143         char *ed = NULL, drft[BUFSIZ], buf[BUFSIZ];
144         char **argp, **arguments;
145         struct msgs *mp = NULL;
146         FILE *in;
147
148 #ifdef MHE
149         int buildsw = 0;
150 #endif /* MHE */
151
152         filter = getcpy(etcpath(mhlreply));
153
154 #ifdef LOCALE
155         setlocale(LC_ALL, "");
156 #endif
157         invo_name = mhbasename(argv[0]);
158
159         /* read user profile/context */
160         context_read();
161
162         arguments = getarguments(invo_name, argc, argv, 1);
163         argp = arguments;
164
165         while ((cp = *argp++)) {
166                 if (*cp == '-') {
167                         switch (smatch(++cp, switches)) {
168                         case AMBIGSW:
169                                 ambigsw(cp, switches);
170                                 done(1);
171                         case UNKWNSW:
172                                 adios(NULL, "-%s unknown", cp);
173
174                         case HELPSW:
175                                 snprintf(buf, sizeof(buf), "%s: [+folder] [msg] [switches]", invo_name);
176                                 print_help(buf, switches, 1);
177                                 done(0);
178                         case VERSIONSW:
179                                 print_version(invo_name);
180                                 done(1);
181
182                         case GROUPSW:
183                                 groupreply++;
184                                 continue;
185                         case NGROUPSW:
186                                 groupreply = 0;
187                                 continue;
188
189                         case ANNOSW:
190                                 anot++;
191                                 continue;
192                         case NANNOSW:
193                                 anot = 0;
194                                 continue;
195
196                         case CCSW:
197                                 if (!(cp = *argp++) || *cp == '-')
198                                         adios(NULL, "missing argument to %s",
199                                                         argp[-2]);
200                                 docc(cp, 1);
201                                 continue;
202                         case NCCSW:
203                                 if (!(cp = *argp++) || *cp == '-')
204                                         adios(NULL, "missing argument to %s",
205                                                         argp[-2]);
206                                 docc(cp, 0);
207                                 continue;
208
209                         case EDITRSW:
210                                 if (!(ed = *argp++) || *ed == '-')
211                                         adios(NULL, "missing argument to %s",
212                                                         argp[-2]);
213                                 nedit = 0;
214                                 continue;
215                         case NEDITSW:
216                                 nedit++;
217                                 continue;
218
219                         case WHATSW:
220                                 if (!(whatnowproc = *argp++) ||
221                                                 *whatnowproc == '-')
222                                         adios(NULL, "missing argument to %s",
223                                                         argp[-2]);
224                                 continue;
225 #ifdef MHE
226                         case BILDSW:
227                                 buildsw++;  /* fall... */
228                                 continue;
229 #endif /* MHE */
230
231                         case FILESW:
232                                 if (file)
233                                         adios(NULL, "only one file at a time!");
234                                 if (!(cp = *argp++) || *cp == '-')
235                                         adios(NULL, "missing argument to %s",
236                                                         argp[-2]);
237                                 file = getcpy(expanddir(cp));
238                                 continue;
239                         case FORMSW:
240                                 if (!(form = *argp++) || *form == '-')
241                                         adios(NULL, "missing argument to %s",
242                                                         argp[-2]);
243                                 continue;
244
245                         case FILTSW:
246                                 if (!(cp = *argp++) || *cp == '-')
247                                         adios(NULL, "missing argument to %s",
248                                                         argp[-2]);
249                                 filter = getcpy(etcpath(cp));
250                                 continue;
251                         case NFILTSW:
252                                 filter = NULL;
253                                 continue;
254
255                         case MIMESW:
256                                 mime++;
257                                 continue;
258                         case NMIMESW:
259                                 mime = 0;
260                                 continue;
261
262                         case QURYSW:
263                                 querysw++;
264                                 continue;
265                         case NQURYSW:
266                                 querysw = 0;
267                                 continue;
268
269                         }
270                 }
271                 if (*cp == '+' || *cp == '@') {
272                         if (folder)
273                                 adios(NULL, "only one folder at a time!");
274                         else
275                                 folder = getcpy(expandfol(cp));
276                 } else {
277                         if (msg)
278                                 adios(NULL, "only one message at a time!");
279                         else
280                                 msg = cp;
281                 }
282         }
283
284         if (ccto == -1)
285                 ccto = groupreply;
286         if (cccc == -1)
287                 cccc = groupreply;
288         if (ccme == -1)
289                 ccme = groupreply;
290
291         cwd = getcpy(pwd());
292
293         if (file && (msg || folder))
294                 adios(NULL, "can't mix files and folders/msgs");
295
296 #ifdef MHE
297         strncpy(drft, buildsw ? toabsdir("reply")
298                 : m_draft(seq_beyond), sizeof(drft));
299 #else
300         strncpy(drft, m_draft(seq_beyond), sizeof(drft));
301 #endif /* MHE */
302
303         if (file) {
304                 /*
305                 ** We are replying to a file.
306                 */
307                 anot = 0;  /* we don't want to annotate a file */
308         } else {
309                 /*
310                 ** We are replying to a message.
311                 */
312                 if (!msg)
313                         msg = seq_cur;
314                 if (!folder)
315                         folder = getcurfol();
316                 maildir = toabsdir(folder);
317
318                 if (chdir(maildir) == NOTOK)
319                         adios(maildir, "unable to change directory to");
320
321                 /* read folder and create message structure */
322                 if (!(mp = folder_read(folder)))
323                         adios(NULL, "unable to read folder %s", folder);
324
325                 /* check for empty folder */
326                 if (mp->nummsg == 0)
327                         adios(NULL, "no messages in %s", folder);
328
329                 /* parse the message range/sequence/name and set SELECTED */
330                 if (!m_convert(mp, msg))
331                         done(1);
332                 seq_setprev(mp);  /* set the previous-sequence */
333
334                 if (mp->numsel > 1)
335                         adios(NULL, "only one message at a time!");
336
337                 context_replace(curfolder, folder); /* update current folder */
338                 seq_setcur(mp, mp->lowsel);  /* update current message  */
339                 seq_save(mp);  /* synchronize sequences   */
340                 context_save();  /* save the context file   */
341         }
342
343         msg = file ? file : getcpy(m_name(mp->lowsel));
344
345         if ((in = fopen(msg, "r")) == NULL)
346                 adios(msg, "unable to open");
347
348         /* find form (components) file */
349         if (!form) {
350                 if (groupreply)
351                         form = etcpath(replgroupcomps);
352                 else
353                         form = etcpath(replcomps);
354         }
355
356         replout(in, drft, mp, mime, form, filter);
357         fclose(in);
358
359         if (buildsw)
360                 done(0);
361         what_now(ed, nedit, NOUSE, drft, msg, 0, mp, anot ? "Replied" : NULL,
362                         cwd);
363         done(1);
364         return 1;
365 }
366
367 static void
368 docc(char *cp, int ccflag)
369 {
370         switch (smatch(cp, ccswitches)) {
371         case AMBIGSW:
372                 ambigsw(cp, ccswitches);
373                 done(1);
374         case UNKWNSW:
375                 adios(NULL, "-%scc %s unknown", ccflag ? "" : "no", cp);
376
377         case CTOSW:
378                 ccto = ccflag;
379                 break;
380
381         case CCCSW:
382                 cccc = ccflag;
383                 break;
384
385         case CMESW:
386                 ccme = ccflag;
387                 break;
388
389         case CALSW:
390                 ccto = cccc = ccme = ccflag;
391                 break;
392         }
393 }
394
395
396
397
398 static void
399 replout(FILE *inb, char *drft, struct msgs *mp,
400         int mime, char *form, char *filter)
401 {
402         register int state, i;
403         register struct comp *cptr;
404         register char *tmpbuf;
405         register char **nxtbuf;
406         register char **ap;
407         register struct comp **savecomp;
408         int char_read = 0, format_len, mask;
409         char name[NAMESZ], *scanl;
410         unsigned char *cp;
411         FILE *out;
412
413         mask = umask(~m_gmprot());
414         if ((out = fopen(drft, "w")) == NULL)
415                 adios(drft, "unable to create");
416
417         umask(mask);
418
419         /* get new format string */
420         cp = new_fs(form, NULL);
421         format_len = strlen(cp);
422
423         /* compile format string */
424         ncomps = fmt_compile(cp, &fmt) + 1;
425
426         if (!(nxtbuf = compbuffers = (char **)
427                         calloc((size_t) ncomps, sizeof(char *))))
428                 adios(NULL, "unable to allocate component buffers");
429         if (!(savecomp = used_buf = (struct comp **)
430                         calloc((size_t) (ncomps+1), sizeof(struct comp *))))
431                 adios(NULL, "unable to allocate component buffer stack");
432         savecomp += ncomps + 1;
433         *--savecomp = NULL;  /* point at zero'd end minus 1 */
434
435         for (i = ncomps; i--; )
436                 *nxtbuf++ = mh_xmalloc(SBUFSIZ);
437
438         nxtbuf = compbuffers;  /* point at start */
439         tmpbuf = *nxtbuf++;
440
441         for (ap = addrcomps; *ap; ap++) {
442                 FINDCOMP(cptr, *ap);
443                 if (cptr)
444                         cptr->c_type |= CT_ADDR;
445         }
446
447         /*
448         ** ignore any components killed by command line switches
449         */
450         if (!ccto) {
451                 FINDCOMP(cptr, "to");
452                 if (cptr)
453                         cptr->c_name = "";
454         }
455         if (!cccc) {
456                 FINDCOMP(cptr, "cc");
457                 if (cptr)
458                         cptr->c_name = "";
459         }
460         if ((cp = getenv("USER"))) {
461                 FINDCOMP(cptr, "user");
462                 if (cptr)
463                         cptr->c_text = getcpy(cp);
464         }
465         if (!ccme)
466                 ismymbox(NULL);
467
468         /*
469         ** pick any interesting stuff out of msg "inb"
470         */
471         for (state = FLD;;) {
472                 state = m_getfld(state, name, tmpbuf, SBUFSIZ, inb);
473                 switch (state) {
474                 case FLD:
475                 case FLDPLUS:
476                         /*
477                         ** if we're interested in this component, save
478                         ** a pointer to the component text, then start
479                         ** using our next free buffer as the component
480                         ** temp buffer (buffer switching saves an extra
481                         ** copy of the component text).
482                         */
483                         if ((cptr = wantcomp[CHASH(name)]))
484                                 do {
485                                         if (!mh_strcasecmp(name, cptr->c_name)) {
486                                                 char_read += msg_count;
487                                                 if (! cptr->c_text) {
488                                                         i = strlen(cptr->c_text = tmpbuf) - 1;
489                                                         if (tmpbuf[i] == '\n')
490                                                                 tmpbuf[i] = '\0';
491                                                         *--savecomp = cptr;
492                                                         tmpbuf = *nxtbuf++;
493                                                 } else {
494                                                         i = strlen(cp = cptr->c_text) - 1;
495                                                         if (cp[i] == '\n') {
496                                                                 if (cptr->c_type & CT_ADDR) {
497                                                                         cp[i] = '\0';
498                                                                         cp = add(",\n\t", cp);
499                                                                 } else {
500                                                                         cp = add("\t", cp);
501                                                                 }
502                                                         }
503                                                         cptr->c_text = add(tmpbuf, cp);
504                                                 }
505                                                 while (state == FLDPLUS) {
506                                                         state = m_getfld(state, name, tmpbuf,
507                                                                                           SBUFSIZ, inb);
508                                                         cptr->c_text = add(tmpbuf, cptr->c_text);
509                                                         char_read += msg_count;
510                                                 }
511                                                 break;
512                                         }
513                                 } while ((cptr = cptr->c_next));
514
515                         while (state == FLDPLUS)
516                                 state = m_getfld(state, name, tmpbuf,
517                                                 SBUFSIZ, inb);
518                         break;
519
520                 case LENERR:
521                 case FMTERR:
522                 case BODY:
523                 case FILEEOF:
524                         goto finished;
525
526                 default:
527                         adios(NULL, "m_getfld() returned %d", state);
528                 }
529         }
530
531         /*
532         ** format and output the header lines.
533         */
534 finished:
535
536         /*
537         ** if there's a "Subject" component, strip any "Re:"s off it
538         */
539         FINDCOMP(cptr, "subject")
540         if (cptr && (cp = cptr->c_text)) {
541                 register char *sp = cp;
542
543                 for (;;) {
544                         while (isspace(*cp))
545                                 cp++;
546                         if(uprf(cp, "re:"))
547                                 cp += 3;
548                         else
549                                 break;
550                         sp = cp;
551                 }
552                 if (sp != cptr->c_text) {
553                         cp = cptr->c_text;
554                         cptr->c_text = getcpy(sp);
555                         free(cp);
556                 }
557         }
558         i = format_len + char_read + 256;
559         scanl = mh_xmalloc((size_t) i + 2);
560         dat[0] = 0;
561         dat[1] = 0;
562         dat[2] = 0;
563         dat[3] = OUTPUTLINELEN;
564         dat[4] = 0;
565         fmt_scan(fmt, scanl, i, dat);
566         fputs(scanl, out);
567         if (badaddrs) {
568                 fputs("\nrepl: bad addresses:\n", out);
569                 fputs( badaddrs, out);
570         }
571
572         /* Check if we should filter the message */
573         if (filter) {
574                 fflush(out);
575                 if (ferror(out))
576                         adios(drft, "error writing");
577
578                 replfilter(inb, out, filter);
579         }
580
581         fflush(out);
582         if (ferror(out))
583                 adios(drft, "error writing");
584         fclose(out);
585
586         if (mime && mp) {
587                 /* add an attachment header */
588                 char buffer[BUFSIZ];
589
590                 snprintf(buffer, sizeof buffer, "anno -append -nodate '%s' "
591                                 "-comp '%s' -text '+%s %s'",
592                                 drft,
593                                 attach_hdr, mp->foldpath, m_name(mp->lowsel));
594                 if (system(buffer) != 0) {
595                         advise(NULL, "unable to add attachment header");
596                 }
597         }
598
599         /* return dynamically allocated buffers */
600         free(scanl);
601         for (nxtbuf = compbuffers, i = ncomps; (cptr = *savecomp++);
602                         nxtbuf++, i--)
603                 free(cptr->c_text);  /* if not nxtbuf, nxtbuf already freed */
604         while ( i-- > 0)
605                 free(*nxtbuf++);  /* free unused nxtbufs */
606         free((char *) compbuffers);
607         free((char *) used_buf);
608 }
609
610 static char *buf;  /* our current working buffer */
611 static char *bufend;  /* end of working buffer */
612 static char *last_dst;  /* buf ptr at end of last call */
613 static unsigned int bufsiz=0;  /* current size of buf */
614
615 #define BUFINCR 512  /* how much to expand buf when if fills */
616
617 #define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; }
618
619 /*
620 ** check if there's enough room in buf for str.
621 ** add more mem if needed
622 */
623 #define CHECKMEM(str) \
624         if ((len = strlen(str)) >= bufend - dst) {\
625                 int i = dst - buf;\
626                 int n = last_dst - buf;\
627                 bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\
628                 buf = mh_xrealloc(buf, bufsiz);\
629                 dst = buf + i;\
630                 last_dst = buf + n;\
631                 bufend = buf + bufsiz;\
632         }
633
634
635 /*
636 ** fmt_scan will call this routine if the user includes the function
637 ** "(formataddr {component})" in a format string.  "orig" is the
638 ** original contents of the string register.  "str" is the address
639 ** string to be formatted and concatenated onto orig.  This routine
640 ** returns a pointer to the concatenated address string.
641 **
642 ** We try to not do a lot of malloc/copy/free's (which is why we
643 ** don't call "getcpy") but still place no upper limit on the
644 ** length of the result string.
645 **
646 ** This routine is an override for the equally named one in sbr/fmt_addr.c.
647 ** Don't delete it!
648 */
649 char *
650 formataddr(char *orig, char *str)
651 {
652         register int len;
653         char baddr[BUFSIZ], error[BUFSIZ];
654         register int isgroup;
655         register char *dst;
656         register char *cp;
657         register char *sp;
658         register struct mailname *mp = NULL;
659
660         /* if we don't have a buffer yet, get one */
661         if (bufsiz == 0) {
662                 buf = mh_xmalloc(BUFINCR);
663                 last_dst = buf;  /* XXX */
664                 bufsiz = BUFINCR - 6;  /* leave some slop */
665                 bufend = buf + bufsiz;
666         }
667         /*
668         ** If "orig" points to our buffer we can just pick up where we
669         ** left off.  Otherwise we have to copy orig into our buffer.
670         */
671         if (orig == buf)
672                 dst = last_dst;
673         else if (!orig || !*orig) {
674                 dst = buf;
675                 *dst = '\0';
676         } else {
677                 dst = last_dst;  /* XXX */
678                 CHECKMEM(orig);
679                 CPY(orig);
680         }
681
682         /* concatenate all the new addresses onto 'buf' */
683         for (isgroup = 0; (cp = getname(str)); ) {
684                 if ((mp = getm(cp, dfhost, dftype, AD_NAME, error)) == NULL) {
685                         snprintf(baddr, sizeof(baddr), "\t%s -- %s\n",
686                                         cp, error);
687                         badaddrs = add(baddr, badaddrs);
688                         continue;
689                 }
690                 if (isgroup && (mp->m_gname || !mp->m_ingrp)) {
691                         *dst++ = ';';
692                         isgroup = 0;
693                 }
694                 if (insert(mp)) {
695                         /* if we get here we're going to add an address */
696                         if (dst != buf) {
697                                 *dst++ = ',';
698                                 *dst++ = ' ';
699                         }
700                         if (mp->m_gname) {
701                                 CHECKMEM(mp->m_gname);
702                                 CPY(mp->m_gname);
703                                 isgroup++;
704                         }
705                         sp = adrformat(mp);
706                         CHECKMEM(sp);
707                         CPY(sp);
708                 }
709         }
710
711         if (isgroup)
712                 *dst++ = ';';
713
714         *dst = '\0';
715         last_dst = dst;
716         return (buf);
717 }
718
719
720 static int
721 insert(struct mailname *np)
722 {
723         char buffer[BUFSIZ];
724         register struct mailname *mp;
725
726         if (np->m_mbox == NULL)
727                 return 0;
728
729         for (mp = &mq; mp->m_next; mp = mp->m_next) {
730                 if (!mh_strcasecmp(np->m_host, mp->m_next->m_host) &&
731                                 !mh_strcasecmp(np->m_mbox, mp->m_next->m_mbox))
732                         return 0;
733         }
734         if (!ccme && ismymbox(np))
735                 return 0;
736
737         if (querysw) {
738                 snprintf(buffer, sizeof(buffer), "Reply to %s? ",
739                                 adrformat(np));
740                 if (!gans(buffer, anoyes))
741                         return 0;
742         }
743         mp->m_next = np;
744         return 1;
745 }
746
747
748 /*
749 ** Call mhl
750 **
751 ** This function expects that argument out has been fflushed by the caller.
752 */
753 static void
754 replfilter(FILE *in, FILE *out, char *filter)
755 {
756         int pid, n;
757         char *errstr;
758
759         if (filter == NULL)
760                 return;
761
762         if (access(filter, R_OK) == NOTOK)
763                 adios(filter, "unable to read");
764
765         rewind(in);
766         lseek(fileno(in), (off_t) 0, SEEK_SET);
767
768         switch (pid = fork()) {
769         case NOTOK:
770                 adios("fork", "unable to");
771
772         case OK:
773                 dup2(fileno(in), fileno(stdin));
774                 dup2(fileno(out), fileno(stdout));
775                 for (n=3; n<OPEN_MAX; n++) {
776                         close(n);
777                 }
778
779                 execlp("mhl", "mhl", "-form", filter, NULL);
780                 errstr = strerror(errno);
781                 write(2, "unable to exec mhl: ", 20);
782                 write(2, errstr, strlen(errstr));
783                 write(2, "\n", 1);
784                 _exit(-1);
785
786         default:
787                 if (pidXwait(pid, "mhl"))
788                         done(1);
789                 fseek(out, 0L, SEEK_END);
790                 break;
791         }
792 }