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