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 */
21 static struct swit switches[] = {
31 { "cc all|to|cc|me", 0 },
35 { "editor editor", 0 },
37 { "filter filterfile", 0 },
41 { "form formfile", 0 },
51 { "whatnowproc program", 0 },
57 { "file file", 4 }, /* interface from msh */
59 { "build", 5 }, /* interface from mhe */
63 static struct swit ccswitches[] = {
75 static short ccto = -1;
76 static short cccc = -1;
77 static short ccme = -1;
78 static short querysw = 0;
80 static short groupreply = 0; /* Is this a group reply? */
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 */
88 static char *badaddrs = NULL;
89 static char *dfhost = NULL;
91 static struct mailname mq;
93 static struct format *fmt;
95 static int ncomps = 0; /* # of interesting components */
97 static int dat[5]; /* aux. data for format routine */
99 static char *addrcomps[] = {
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,
126 main(int argc, char **argv)
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;
137 filter = getcpy(etcpath(mhlreply));
139 setlocale(LC_ALL, "");
140 invo_name = mhbasename(argv[0]);
142 /* read user profile/context */
145 arguments = getarguments(invo_name, argc, argv, 1);
148 while ((cp = *argp++)) {
150 switch (smatch(++cp, switches)) {
152 ambigsw(cp, switches);
155 adios(EX_USAGE, NULL, "-%s unknown", cp);
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);
162 print_version(invo_name);
163 exit(argc == 2 ? EX_OK : EX_USAGE);
180 if (!(cp = *argp++) || *cp == '-')
181 adios(EX_USAGE, NULL, "missing argument to %s",
186 if (!(cp = *argp++) || *cp == '-')
187 adios(EX_USAGE, NULL, "missing argument to %s",
193 if (!(ed = *argp++) || *ed == '-')
194 adios(EX_USAGE, NULL, "missing argument to %s",
199 if (!(whatnowproc = *argp++) ||
201 adios(EX_USAGE, NULL, "missing argument to %s",
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",
215 file = getcpy(expanddir(cp));
218 if (!(form = *argp++) || *form == '-')
219 adios(EX_USAGE, NULL, "missing argument to %s",
224 if (!(cp = *argp++) || *cp == '-')
225 adios(EX_USAGE, NULL, "missing argument to %s",
227 filter = getcpy(etcpath(cp));
249 if (*cp == '+' || *cp == '@') {
251 adios(EX_USAGE, NULL, "only one folder at a time!");
253 folder = getcpy(expandfol(cp));
256 adios(EX_USAGE, NULL, "only one message at a time!");
271 if (file && (msg || folder))
272 adios(EX_USAGE, NULL, "can't mix files and folders/msgs");
274 strncpy(drft, buildsw ? toabsdir("reply") : m_draft(seq_beyond),
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
285 ** We are replying to a file.
287 anot = 0; /* we don't want to annotate a file */
290 ** We are replying to a message.
295 folder = getcurfol();
296 maildir = toabsdir(folder);
298 if (chdir(maildir) == NOTOK)
299 adios(EX_OSERR, maildir, "unable to change directory to");
301 /* read folder and create message structure */
302 if (!(mp = folder_read(folder)))
303 adios(EX_IOERR, NULL, "unable to read folder %s", folder);
305 /* check for empty folder */
307 adios(EX_DATAERR, NULL, "no messages in %s", folder);
309 /* parse the message range/sequence/name and set SELECTED */
310 if (!m_convert(mp, msg))
312 seq_setprev(mp); /* set the previous-sequence */
315 adios(EX_USAGE, NULL, "only one message at a time!");
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 */
323 msg = file ? file : getcpy(m_name(mp->lowsel));
325 if ((in = fopen(msg, "r")) == NULL)
326 adios(EX_IOERR, msg, "unable to open");
328 /* find form (components) file */
331 form = etcpath(replgroupcomps);
333 form = etcpath(replcomps);
336 replout(in, drft, mp, mime, form, filter);
341 what_now(ed, NOUSE, drft, msg, 0, mp, anot ? "Replied" : NULL, cwd);
346 docc(char *cp, int ccflag)
348 switch (smatch(cp, ccswitches)) {
350 ambigsw(cp, ccswitches);
353 adios(EX_USAGE, NULL, "-%scc %s unknown", ccflag ? "" : "no", cp);
368 ccto = cccc = ccme = ccflag;
377 replout(FILE *inb, char *drft, struct msgs *mp,
378 int mime, char *form, char *filter)
381 struct field f = {{0}};
385 int char_read = 0, format_len, mask;
390 mask = umask(~m_gmprot());
391 if ((out = fopen(drft, "w")) == NULL)
392 adios(EX_CANTCREAT, drft, "unable to create");
396 /* get new format string */
397 cp = new_fs(form, NULL);
398 format_len = strlen(cp);
400 /* compile format string */
401 ncomps = fmt_compile(cp, &fmt) + 1;
403 for (ap = addrcomps; *ap; ap++) {
406 cptr->c_type |= CT_ADDR;
410 ** ignore any components killed by command line switches
413 FINDCOMP(cptr, "to");
418 FINDCOMP(cptr, "cc");
422 if ((cp = getenv("USER"))) {
423 FINDCOMP(cptr, "user");
425 cptr->c_text = getcpy(cp);
431 ** pick any interesting stuff out of msg "inb"
433 for (state = FLD2;;) {
434 state = m_getfld2(state, &f, inb);
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).
444 if ((cptr = wantcomp[CHASH(f.name)])) {
446 if (mh_strcasecmp(f.name, cptr->c_name)!=0) {
449 char_read += strlen(f.value);
451 cptr->c_text = getcpy(f.value);
452 i = strlen(cptr->c_text) - 1;
453 if (cptr->c_text[i] == '\n') {
454 cptr->c_text[i] = '\0';
460 if (cptr->c_type & CT_ADDR) {
462 cp = add(",\n\t", cp);
467 cptr->c_text = add(f.value, cp);
470 } while ((cptr = cptr->c_next));
482 adios(EX_SOFTWARE, NULL, "m_getfld() returned %d", state);
487 ** format and output the header lines.
492 ** if there's a "Subject" component, strip any "Re:"s off it
494 FINDCOMP(cptr, "subject")
495 if (cptr && (cp = cptr->c_text)) {
507 if (sp != cptr->c_text) {
509 cptr->c_text = getcpy(sp);
513 i = format_len + char_read + 256;
514 scanl = mh_xmalloc((size_t) i + 2);
518 dat[3] = OUTPUTLINELEN;
520 fmt_scan(fmt, scanl, i, dat);
523 fputs("\nrepl: bad addresses:\n", out);
524 fputs( badaddrs, out);
527 /* Check if we should filter the message */
531 adios(EX_IOERR, drft, "error writing");
533 replfilter(inb, out, filter);
538 adios(EX_IOERR, drft, "error writing");
542 /* add an attachment header */
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");
554 /* return dynamically allocated buffers */
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 */
563 #define BUFINCR 512 /* how much to expand buf when if fills */
565 #define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; }
568 ** check if there's enough room in buf for str.
569 ** add more mem if needed
571 #define CHECKMEM(str) \
572 if ((len = strlen(str)) >= bufend - dst) {\
574 int n = last_dst - buf;\
575 bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\
576 buf = mh_xrealloc(buf, bufsiz);\
579 bufend = buf + bufsiz;\
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.
590 ** We try to not do a lot of malloc/copy/free's (which is why we
591 ** don't call "getcpy") but still place no upper limit on the
592 ** length of the result string.
594 ** This routine is an override for the equally named one in sbr/fmt_addr.c.
598 formataddr(char *orig, char *str)
601 char baddr[BUFSIZ], error[BUFSIZ];
606 struct mailname *mp = NULL;
608 /* if we don't have a buffer yet, get one */
610 buf = mh_xmalloc(BUFINCR);
611 last_dst = buf; /* XXX */
612 bufsiz = BUFINCR - 6; /* leave some slop */
613 bufend = buf + bufsiz;
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.
621 else if (!orig || !*orig) {
625 dst = last_dst; /* XXX */
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",
635 badaddrs = add(baddr, badaddrs);
638 if (isgroup && (mp->m_gname || !mp->m_ingrp)) {
643 /* if we get here we're going to add an address */
649 CHECKMEM(mp->m_gname);
669 insert(struct mailname *np)
674 if (np->m_mbox == NULL)
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))
682 if (!ccme && ismymbox(np))
686 snprintf(buffer, sizeof(buffer), "Reply to %s? ",
688 if (!gans(buffer, anoyes))
699 ** This function expects that argument out has been fflushed by the caller.
702 replfilter(FILE *in, FILE *out, char *filter)
710 if (access(filter, R_OK) == NOTOK)
711 adios(EX_IOERR, filter, "unable to read");
714 lseek(fileno(in), (off_t) 0, SEEK_SET);
716 switch (pid = fork()) {
718 adios(EX_OSERR, "fork", "unable to");
721 dup2(fileno(in), fileno(stdin));
722 dup2(fileno(out), fileno(stdout));
723 for (n=3; n<OPEN_MAX; n++) {
727 execlp("mhl", "mhl", "-form", filter, NULL);
728 errstr = strerror(errno);
729 write(2, "unable to exec mhl: ", 20);
730 write(2, errstr, strlen(errstr));
735 if (pidXwait(pid, "mhl"))
737 fseek(out, 0L, SEEK_END);