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