2 ** repl.c -- reply to a message
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.
11 #include <h/addrsbr.h>
12 #include <h/fmt_scan.h>
13 #include <sys/file.h> /* L_SET */
17 static struct swit switches[] = {
27 { "cc all|to|cc|me", 0 },
31 { "editor editor", 0 },
33 { "filter filterfile", 0 },
37 { "form formfile", 0 },
47 { "whatnowproc program", 0 },
53 { "file file", 4 }, /* interface from msh */
55 { "build", 5 }, /* interface from mhe */
59 static struct swit ccswitches[] = {
72 ** Buffer size for content part of header fields.
73 ** We want this to be large enough so that we don't
74 ** do a lot of extra FLDPLUS calls on m_getfld but
75 ** small enough so that we don't snarf the entire
76 ** message body when we're not going to use any of it.
80 static short ccto = -1;
81 static short cccc = -1;
82 static short ccme = -1;
83 static short querysw = 0;
85 static short groupreply = 0; /* Is this a group reply? */
87 static int mime = 0; /* include original as MIME part */
88 static char *form = NULL; /* form (components) file */
89 static char *filter = NULL; /* message filter file */
93 static char *badaddrs = NULL;
94 static char *dfhost = NULL;
96 static struct mailname mq;
98 static struct format *fmt;
100 static int ncomps = 0; /* # of interesting components */
101 static char **compbuffers = NULL; /* buffers for component text */
102 static struct comp **used_buf = NULL; /* stack for comp that use buffers */
104 static int dat[5]; /* aux. data for format routine */
106 static char *addrcomps[] = {
125 static void docc(char *, int);
126 static int insert(struct mailname *);
127 static void replfilter(FILE *, FILE *, char *);
128 static void replout(FILE *, char *, struct msgs *, int,
133 main(int argc, char **argv)
136 char *cp, *cwd, *maildir, *file = NULL;
137 char *folder = NULL, *msg = NULL;
138 char *ed = NULL, drft[BUFSIZ], buf[BUFSIZ];
139 char **argp, **arguments;
140 struct msgs *mp = NULL;
144 filter = getcpy(etcpath(mhlreply));
147 setlocale(LC_ALL, "");
149 invo_name = mhbasename(argv[0]);
151 /* read user profile/context */
154 arguments = getarguments(invo_name, argc, argv, 1);
157 while ((cp = *argp++)) {
159 switch (smatch(++cp, switches)) {
161 ambigsw(cp, switches);
164 adios(NULL, "-%s unknown", cp);
167 snprintf(buf, sizeof(buf), "%s: [+folder] [msg] [switches]", invo_name);
168 print_help(buf, switches, 1);
171 print_version(invo_name);
189 if (!(cp = *argp++) || *cp == '-')
190 adios(NULL, "missing argument to %s",
195 if (!(cp = *argp++) || *cp == '-')
196 adios(NULL, "missing argument to %s",
202 if (!(ed = *argp++) || *ed == '-')
203 adios(NULL, "missing argument to %s",
208 if (!(whatnowproc = *argp++) ||
210 adios(NULL, "missing argument to %s",
220 adios(NULL, "only one file at a time!");
221 if (!(cp = *argp++) || *cp == '-')
222 adios(NULL, "missing argument to %s",
224 file = getcpy(expanddir(cp));
227 if (!(form = *argp++) || *form == '-')
228 adios(NULL, "missing argument to %s",
233 if (!(cp = *argp++) || *cp == '-')
234 adios(NULL, "missing argument to %s",
236 filter = getcpy(etcpath(cp));
258 if (*cp == '+' || *cp == '@') {
260 adios(NULL, "only one folder at a time!");
262 folder = getcpy(expandfol(cp));
265 adios(NULL, "only one message at a time!");
280 if (file && (msg || folder))
281 adios(NULL, "can't mix files and folders/msgs");
283 strncpy(drft, buildsw ? toabsdir("reply") : m_draft(seq_beyond),
286 ** FIXME: (concerning MHE support (buildsw) only)
287 ** There's no check if the draft already exists. mmh has removed
288 ** this case by having the draft folder. I won't add code only to
289 ** handle this legacy issue for MHE. -- meillo@marmaro.de 2012-05
294 ** We are replying to a file.
296 anot = 0; /* we don't want to annotate a file */
299 ** We are replying to a message.
304 folder = getcurfol();
305 maildir = toabsdir(folder);
307 if (chdir(maildir) == NOTOK)
308 adios(maildir, "unable to change directory to");
310 /* read folder and create message structure */
311 if (!(mp = folder_read(folder)))
312 adios(NULL, "unable to read folder %s", folder);
314 /* check for empty folder */
316 adios(NULL, "no messages in %s", folder);
318 /* parse the message range/sequence/name and set SELECTED */
319 if (!m_convert(mp, msg))
321 seq_setprev(mp); /* set the previous-sequence */
324 adios(NULL, "only one message at a time!");
326 context_replace(curfolder, folder); /* update current folder */
327 seq_setcur(mp, mp->lowsel); /* update current message */
328 seq_save(mp); /* synchronize sequences */
329 context_save(); /* save the context file */
332 msg = file ? file : getcpy(m_name(mp->lowsel));
334 if ((in = fopen(msg, "r")) == NULL)
335 adios(msg, "unable to open");
337 /* find form (components) file */
340 form = etcpath(replgroupcomps);
342 form = etcpath(replcomps);
345 replout(in, drft, mp, mime, form, filter);
350 what_now(ed, NOUSE, drft, msg, 0, mp, anot ? "Replied" : NULL, cwd);
356 docc(char *cp, int ccflag)
358 switch (smatch(cp, ccswitches)) {
360 ambigsw(cp, ccswitches);
363 adios(NULL, "-%scc %s unknown", ccflag ? "" : "no", cp);
378 ccto = cccc = ccme = ccflag;
387 replout(FILE *inb, char *drft, struct msgs *mp,
388 int mime, char *form, char *filter)
390 register int state, i;
391 register struct comp *cptr;
392 register char *tmpbuf;
393 register char **nxtbuf;
395 register struct comp **savecomp;
396 int char_read = 0, format_len, mask;
397 char name[NAMESZ], *scanl;
401 mask = umask(~m_gmprot());
402 if ((out = fopen(drft, "w")) == NULL)
403 adios(drft, "unable to create");
407 /* get new format string */
408 cp = new_fs(form, NULL);
409 format_len = strlen(cp);
411 /* compile format string */
412 ncomps = fmt_compile(cp, &fmt) + 1;
414 if (!(nxtbuf = compbuffers = (char **)
415 calloc((size_t) ncomps, sizeof(char *))))
416 adios(NULL, "unable to allocate component buffers");
417 if (!(savecomp = used_buf = (struct comp **)
418 calloc((size_t) (ncomps+1), sizeof(struct comp *))))
419 adios(NULL, "unable to allocate component buffer stack");
420 savecomp += ncomps + 1;
421 *--savecomp = NULL; /* point at zero'd end minus 1 */
423 for (i = ncomps; i--; )
424 *nxtbuf++ = mh_xmalloc(SBUFSIZ);
426 nxtbuf = compbuffers; /* point at start */
429 for (ap = addrcomps; *ap; ap++) {
432 cptr->c_type |= CT_ADDR;
436 ** ignore any components killed by command line switches
439 FINDCOMP(cptr, "to");
444 FINDCOMP(cptr, "cc");
448 if ((cp = getenv("USER"))) {
449 FINDCOMP(cptr, "user");
451 cptr->c_text = getcpy(cp);
457 ** pick any interesting stuff out of msg "inb"
459 for (state = FLD;;) {
460 state = m_getfld(state, name, tmpbuf, SBUFSIZ, inb);
465 ** if we're interested in this component, save
466 ** a pointer to the component text, then start
467 ** using our next free buffer as the component
468 ** temp buffer (buffer switching saves an extra
469 ** copy of the component text).
471 if ((cptr = wantcomp[CHASH(name)]))
473 if (!mh_strcasecmp(name, cptr->c_name)) {
474 char_read += msg_count;
475 if (! cptr->c_text) {
476 i = strlen(cptr->c_text = tmpbuf) - 1;
477 if (tmpbuf[i] == '\n')
482 i = strlen(cp = cptr->c_text) - 1;
484 if (cptr->c_type & CT_ADDR) {
486 cp = add(",\n\t", cp);
491 cptr->c_text = add(tmpbuf, cp);
493 while (state == FLDPLUS) {
494 state = m_getfld(state, name, tmpbuf,
496 cptr->c_text = add(tmpbuf, cptr->c_text);
497 char_read += msg_count;
501 } while ((cptr = cptr->c_next));
503 while (state == FLDPLUS)
504 state = m_getfld(state, name, tmpbuf,
515 adios(NULL, "m_getfld() returned %d", state);
520 ** format and output the header lines.
525 ** if there's a "Subject" component, strip any "Re:"s off it
527 FINDCOMP(cptr, "subject")
528 if (cptr && (cp = cptr->c_text)) {
529 register char *sp = cp;
540 if (sp != cptr->c_text) {
542 cptr->c_text = getcpy(sp);
546 i = format_len + char_read + 256;
547 scanl = mh_xmalloc((size_t) i + 2);
551 dat[3] = OUTPUTLINELEN;
553 fmt_scan(fmt, scanl, i, dat);
556 fputs("\nrepl: bad addresses:\n", out);
557 fputs( badaddrs, out);
560 /* Check if we should filter the message */
564 adios(drft, "error writing");
566 replfilter(inb, out, filter);
571 adios(drft, "error writing");
575 /* add an attachment header */
578 snprintf(buffer, sizeof buffer, "anno -append -nodate '%s' "
579 "-comp '%s' -text '+%s %s'",
581 attach_hdr, mp->foldpath, m_name(mp->lowsel));
582 if (system(buffer) != 0) {
583 advise(NULL, "unable to add attachment header");
587 /* return dynamically allocated buffers */
589 for (nxtbuf = compbuffers, i = ncomps; (cptr = *savecomp++);
591 free(cptr->c_text); /* if not nxtbuf, nxtbuf already freed */
593 free(*nxtbuf++); /* free unused nxtbufs */
594 free((char *) compbuffers);
595 free((char *) used_buf);
598 static char *buf; /* our current working buffer */
599 static char *bufend; /* end of working buffer */
600 static char *last_dst; /* buf ptr at end of last call */
601 static unsigned int bufsiz=0; /* current size of buf */
603 #define BUFINCR 512 /* how much to expand buf when if fills */
605 #define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; }
608 ** check if there's enough room in buf for str.
609 ** add more mem if needed
611 #define CHECKMEM(str) \
612 if ((len = strlen(str)) >= bufend - dst) {\
614 int n = last_dst - buf;\
615 bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\
616 buf = mh_xrealloc(buf, bufsiz);\
619 bufend = buf + bufsiz;\
624 ** fmt_scan will call this routine if the user includes the function
625 ** "(formataddr {component})" in a format string. "orig" is the
626 ** original contents of the string register. "str" is the address
627 ** string to be formatted and concatenated onto orig. This routine
628 ** returns a pointer to the concatenated address string.
630 ** We try to not do a lot of malloc/copy/free's (which is why we
631 ** don't call "getcpy") but still place no upper limit on the
632 ** length of the result string.
634 ** This routine is an override for the equally named one in sbr/fmt_addr.c.
638 formataddr(char *orig, char *str)
641 char baddr[BUFSIZ], error[BUFSIZ];
642 register int isgroup;
646 register struct mailname *mp = NULL;
648 /* if we don't have a buffer yet, get one */
650 buf = mh_xmalloc(BUFINCR);
651 last_dst = buf; /* XXX */
652 bufsiz = BUFINCR - 6; /* leave some slop */
653 bufend = buf + bufsiz;
656 ** If "orig" points to our buffer we can just pick up where we
657 ** left off. Otherwise we have to copy orig into our buffer.
661 else if (!orig || !*orig) {
665 dst = last_dst; /* XXX */
670 /* concatenate all the new addresses onto 'buf' */
671 for (isgroup = 0; (cp = getname(str)); ) {
672 if ((mp = getm(cp, dfhost, dftype, AD_NAME, error)) == NULL) {
673 snprintf(baddr, sizeof(baddr), "\t%s -- %s\n",
675 badaddrs = add(baddr, badaddrs);
678 if (isgroup && (mp->m_gname || !mp->m_ingrp)) {
683 /* if we get here we're going to add an address */
689 CHECKMEM(mp->m_gname);
709 insert(struct mailname *np)
712 register struct mailname *mp;
714 if (np->m_mbox == NULL)
717 for (mp = &mq; mp->m_next; mp = mp->m_next) {
718 if (!mh_strcasecmp(np->m_host, mp->m_next->m_host) &&
719 !mh_strcasecmp(np->m_mbox, mp->m_next->m_mbox))
722 if (!ccme && ismymbox(np))
726 snprintf(buffer, sizeof(buffer), "Reply to %s? ",
728 if (!gans(buffer, anoyes))
739 ** This function expects that argument out has been fflushed by the caller.
742 replfilter(FILE *in, FILE *out, char *filter)
750 if (access(filter, R_OK) == NOTOK)
751 adios(filter, "unable to read");
754 lseek(fileno(in), (off_t) 0, SEEK_SET);
756 switch (pid = fork()) {
758 adios("fork", "unable to");
761 dup2(fileno(in), fileno(stdin));
762 dup2(fileno(out), fileno(stdout));
763 for (n=3; n<OPEN_MAX; n++) {
767 execlp("mhl", "mhl", "-form", filter, NULL);
768 errstr = strerror(errno);
769 write(2, "unable to exec mhl: ", 20);
770 write(2, errstr, strlen(errstr));
775 if (pidXwait(pid, "mhl"))
777 fseek(out, 0L, SEEK_END);