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