]> diplodocus.org Git - nmh/blob - uip/replsbr.c
Use va_copy() to get a copy of va_list, instead of using original.
[nmh] / uip / replsbr.c
1 /* replsbr.c -- routines to help repl along...
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include <h/mh.h>
9 #include <h/addrsbr.h>
10 #include <h/fmt_scan.h>
11 #include "h/done.h"
12 #include <h/utils.h>
13 #include <sys/file.h> /* L_SET */
14 #include "replsbr.h"
15
16 short ccto = -1;
17 short cccc = -1;
18 short ccme = -1;
19 short querysw = 0;
20
21 static int dftype=0;
22
23 static char *badaddrs = NULL;
24 static char *dfhost = NULL;
25
26 static struct mailname mq;
27 static bool nodupcheck; /* If set, no check for duplicates */
28
29 static char *addrcomps[] = {
30 "from",
31 "sender",
32 "reply-to",
33 "to",
34 "cc",
35 "bcc",
36 "resent-from",
37 "resent-sender",
38 "resent-reply-to",
39 "resent-to",
40 "resent-cc",
41 "resent-bcc",
42 NULL
43 };
44
45 /*
46 * static prototypes
47 */
48 static int insert (struct mailname *);
49 static void replfilter (FILE *, FILE *, char *, int);
50 static char *replformataddr(char *, char *);
51 static char *replconcataddr(char *, char *);
52 static char *fix_addresses (char *);
53
54
55 void
56 replout (FILE *inb, char *msg, char *drft, struct msgs *mp, int outputlinelen,
57 int mime, char *form, char *filter, char *fcc, int fmtproc)
58 {
59 int state, i;
60 struct comp *cptr;
61 char tmpbuf[NMH_BUFSIZ];
62 struct format *fmt;
63 char **ap;
64 int char_read = 0, format_len, mask;
65 char name[NAMESZ], *cp;
66 charstring_t scanl;
67 static int dat[5]; /* aux. data for format routine */
68 m_getfld_state_t gstate;
69 struct fmt_callbacks cb;
70
71 FILE *out;
72 NMH_UNUSED (msg);
73
74 mask = umask(~m_gmprot());
75 if ((out = fopen (drft, "w")) == NULL)
76 adios (drft, "unable to create");
77
78 umask(mask);
79
80 /* get new format string */
81 cp = new_fs (form, NULL, NULL);
82 format_len = strlen (cp);
83
84 /* compile format string */
85 fmt_compile (cp, &fmt, 1);
86
87 for (ap = addrcomps; *ap; ap++) {
88 cptr = fmt_findcomp (*ap);
89 if (cptr)
90 cptr->c_type |= CT_ADDR;
91 }
92
93 /*
94 * ignore any components killed by command line switches
95 *
96 * This prevents the component from being found via fmt_findcomp(),
97 * which makes sure no text gets added to it when the message is processed.
98 */
99 if (!ccto) {
100 cptr = fmt_findcomp ("to");
101 if (cptr)
102 cptr->c_name = mh_xstrdup("");
103 }
104 if (!cccc) {
105 cptr = fmt_findcomp("cc");
106 if (cptr)
107 cptr->c_name = mh_xstrdup("");
108 }
109 if (!ccme)
110 ismymbox (NULL);
111
112 /*
113 * pick any interesting stuff out of msg "inb"
114 */
115 gstate = m_getfld_state_init(inb);
116 for (;;) {
117 int msg_count = sizeof tmpbuf;
118 state = m_getfld2(&gstate, name, tmpbuf, &msg_count);
119 switch (state) {
120 case FLD:
121 case FLDPLUS:
122 /*
123 * if we're interested in this component, save a pointer
124 * to the component text, then start using our next free
125 * buffer as the component temp buffer (buffer switching
126 * saves an extra copy of the component text).
127 */
128
129 i = fmt_addcomptext(name, tmpbuf);
130 if (i != -1) {
131 char_read += msg_count;
132 while (state == FLDPLUS) {
133 msg_count= sizeof tmpbuf;
134 state = m_getfld2(&gstate, name, tmpbuf, &msg_count);
135 fmt_appendcomp(i, name, tmpbuf);
136 char_read += msg_count;
137 }
138 }
139
140 while (state == FLDPLUS) {
141 msg_count= sizeof tmpbuf;
142 state = m_getfld2(&gstate, name, tmpbuf, &msg_count);
143 }
144 break;
145
146 case LENERR:
147 case FMTERR:
148 case BODY:
149 case FILEEOF:
150 goto finished;
151
152 default:
153 die("m_getfld2() returned %d", state);
154 }
155 }
156
157 /*
158 * format and output the header lines.
159 */
160 finished:
161 m_getfld_state_destroy (&gstate);
162
163 /* set up the "fcc" pseudo-component */
164 cptr = fmt_findcomp ("fcc");
165 if (cptr) {
166 free(cptr->c_text);
167 if (fcc)
168 cptr->c_text = mh_xstrdup(fcc);
169 else
170 cptr->c_text = NULL;
171 }
172 cptr = fmt_findcomp ("user");
173 if (cptr) {
174 free(cptr->c_text);
175 if ((cp = getenv("USER")))
176 cptr->c_text = mh_xstrdup(cp);
177 else
178 cptr = NULL;
179 }
180
181 /*
182 * if there's a "Subject" component, strip any "Re:"s off it
183 */
184 cptr = fmt_findcomp ("subject");
185 if (cptr && (cp = cptr->c_text)) {
186 char *sp = cp;
187
188 for (;;) {
189 while (isspace((unsigned char) *cp))
190 cp++;
191 if(uprf(cp, "re:"))
192 cp += 3;
193 else
194 break;
195 sp = cp;
196 }
197 if (sp != cptr->c_text) {
198 cp = cptr->c_text;
199 cptr->c_text = mh_xstrdup(sp);
200 free (cp);
201 }
202 }
203 i = format_len + char_read + 256;
204 scanl = charstring_create (i + 2);
205 dat[0] = 0;
206 dat[1] = 0;
207 dat[2] = 0;
208 dat[3] = outputlinelen;
209 dat[4] = 0;
210 ZERO(&cb);
211 cb.formataddr = replformataddr;
212 cb.concataddr = replconcataddr;
213 fmt_scan (fmt, scanl, i, dat, &cb);
214 fputs (charstring_buffer (scanl), out);
215 if (badaddrs) {
216 fputs ("\nrepl: bad addresses:\n", out);
217 fputs ( badaddrs, out);
218 }
219
220 /*
221 * Check if we should filter the message
222 * or add mhn directives
223 */
224 if (filter) {
225 fflush(out);
226 if (ferror (out))
227 adios (drft, "error writing");
228
229 replfilter (inb, out, filter, fmtproc);
230 } else if (mime && mp) {
231 fprintf (out, "#forw [original message] +%s %s\n",
232 mp->foldpath, m_name (mp->lowsel));
233 }
234
235 fflush(out);
236 if (ferror (out))
237 adios (drft, "error writing");
238 fclose (out);
239
240 /* return dynamically allocated buffers */
241 charstring_free (scanl);
242 fmt_free(fmt, 1);
243 }
244
245 static char *buf; /* our current working buffer */
246 static char *bufend; /* end of working buffer */
247 static char *last_dst; /* buf ptr at end of last call */
248 static unsigned int bufsiz=0; /* current size of buf */
249
250 #define BUFINCR 512 /* how much to expand buf when if fills */
251
252 #define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; }
253
254 /*
255 * check if there's enough room in buf for str.
256 * add more mem if needed
257 */
258 #define CHECKMEM(str) \
259 if ((len = strlen (str)) >= bufend - dst) {\
260 int i = dst - buf;\
261 int n = last_dst - buf;\
262 bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\
263 buf = mh_xrealloc (buf, bufsiz);\
264 dst = buf + i;\
265 last_dst = buf + n;\
266 bufend = buf + bufsiz;\
267 }
268
269
270 /*
271 * fmt_scan will call this routine if the user includes the function
272 * "(formataddr {component})" in a format string. "orig" is the
273 * original contents of the string register. "str" is the address
274 * string to be formatted and concatenated onto orig. This routine
275 * returns a pointer to the concatenated address string.
276 *
277 * We try to not do a lot of malloc/copy/free's (which is why we
278 * don't call "getcpy") but still place no upper limit on the
279 * length of the result string.
280 */
281 static char *
282 replformataddr (char *orig, char *str)
283 {
284 int len;
285 char baddr[BUFSIZ], error[BUFSIZ];
286 bool isgroup;
287 char *dst;
288 char *cp;
289 char *sp;
290 struct mailname *mp = NULL;
291 char *fixed_str = fix_addresses (str);
292
293 /* if we don't have a buffer yet, get one */
294 if (bufsiz == 0) {
295 buf = mh_xmalloc (BUFINCR);
296 last_dst = buf; /* XXX */
297 bufsiz = BUFINCR - 6; /* leave some slop */
298 bufend = buf + bufsiz;
299 }
300 /*
301 * If "orig" points to our buffer we can just pick up where we
302 * left off. Otherwise we have to copy orig into our buffer.
303 */
304 if (orig == buf)
305 dst = last_dst;
306 else if (!orig || !*orig) {
307 dst = buf;
308 *dst = '\0';
309 } else {
310 dst = last_dst; /* XXX */
311 CHECKMEM (orig);
312 CPY (orig);
313 }
314
315 /* concatenate all the new addresses onto 'buf' */
316 for (isgroup = false; (cp = getname (fixed_str)); ) {
317 if ((mp = getm (cp, dfhost, dftype, error, sizeof(error))) == NULL) {
318 snprintf (baddr, sizeof(baddr), "\t%s -- %s\n", cp, error);
319 badaddrs = add (baddr, badaddrs);
320 continue;
321 }
322 if (isgroup && (mp->m_gname || !mp->m_ingrp)) {
323 *dst++ = ';';
324 isgroup = false;
325 }
326 if (insert (mp)) {
327 /* if we get here we're going to add an address */
328 if (dst != buf) {
329 *dst++ = ',';
330 *dst++ = ' ';
331 }
332 if (mp->m_gname) {
333 CHECKMEM (mp->m_gname);
334 CPY (mp->m_gname);
335 isgroup = true;
336 }
337 sp = adrformat (mp);
338 CHECKMEM (sp);
339 CPY (sp);
340 }
341 }
342
343 free (fixed_str);
344
345 if (isgroup)
346 *dst++ = ';';
347
348 *dst = '\0';
349 last_dst = dst;
350 return buf;
351 }
352
353
354 /*
355 * fmt_scan will call this routine if the user includes the function
356 * "(concataddr {component})" in a format string. This behaves exactly
357 * like formataddr, except that it does NOT suppress duplicate addresses
358 * between calls.
359 *
360 * As an implementation detail: I thought about splitting out replformataddr()
361 * into the generic part and duplicate-suppressing part, but the call to
362 * insert() was buried deep within a couple of loops and I didn't see a
363 * way to do it easily. So instead we simply set a special flag to stop
364 * the duplicate check and call replformataddr().
365 */
366 static char *
367 replconcataddr(char *orig, char *str)
368 {
369 char *cp;
370
371 nodupcheck = true;
372 cp = replformataddr(orig, str);
373 nodupcheck = false;
374 return cp;
375 }
376
377 static int
378 insert (struct mailname *np)
379 {
380 char buffer[BUFSIZ];
381 struct mailname *mp;
382
383 if (nodupcheck)
384 return 1;
385
386 if (np->m_mbox == NULL)
387 return 0;
388
389 for (mp = &mq; mp->m_next; mp = mp->m_next) {
390 if (!strcasecmp (FENDNULL(np->m_host),
391 FENDNULL(mp->m_next->m_host)) &&
392 !strcasecmp (FENDNULL(np->m_mbox),
393 FENDNULL(mp->m_next->m_mbox)))
394 return 0;
395 }
396 if (!ccme && ismymbox (np))
397 return 0;
398
399 if (querysw) {
400 snprintf (buffer, sizeof(buffer), "Reply to %s? ", adrformat (np));
401 if (!read_switch (buffer, anoyes))
402 return 0;
403 }
404 mp->m_next = np;
405
406 return 1;
407 }
408
409
410 /*
411 * Call the mhlproc
412 *
413 * This function expects that argument out has been fflushed by the caller.
414 */
415
416 static void
417 replfilter (FILE *in, FILE *out, char *filter, int fmtproc)
418 {
419 int pid;
420 char *mhl;
421 char *errstr;
422 char **arglist;
423 int argnum;
424
425 if (filter == NULL)
426 return;
427
428 if (access (filter, R_OK) == NOTOK)
429 adios (filter, "unable to read");
430
431 rewind (in);
432 lseek(fileno(in), 0, SEEK_SET);
433
434 arglist = argsplit(mhlproc, &mhl, &argnum);
435
436 switch (pid = fork()) {
437 case NOTOK:
438 adios ("fork", "unable to");
439
440 case OK:
441 dup2 (fileno (in), fileno (stdin));
442 dup2 (fileno (out), fileno (stdout));
443 closefds (3);
444
445 /*
446 * We're not allocating the memory for the extra arguments,
447 * because we never call arglist_free(). But if we ever change
448 * that be sure to use getcpy() for the extra arguments.
449 */
450 arglist[argnum++] = "-form";
451 arglist[argnum++] = filter;
452 arglist[argnum++] = "-noclear";
453
454 switch (fmtproc) {
455 case 1:
456 arglist[argnum++] = "-fmtproc";
457 arglist[argnum++] = formatproc;
458 break;
459 case 0:
460 arglist[argnum++] = "-nofmtproc";
461 break;
462 }
463
464 arglist[argnum++] = NULL;
465
466 execvp (mhl, arglist);
467 errstr = strerror(errno);
468 if (write(2, "unable to exec ", 15) < 0 ||
469 write(2, mhlproc, strlen(mhlproc)) < 0 ||
470 write(2, ": ", 2) < 0 ||
471 write(2, errstr, strlen(errstr)) < 0 ||
472 write(2, "\n", 1) < 0) {
473 advise ("stderr", "write");
474 }
475 _exit(1);
476
477 default:
478 if (pidXwait (pid, mhl))
479 done (1);
480 fseek (out, 0L, SEEK_END);
481 arglist_free(mhl, arglist);
482 break;
483 }
484 }
485
486
487 static char *
488 fix_addresses (char *str)
489 {
490 char *fixed_str = NULL;
491 bool fixed_address = false;
492
493 if (str) {
494 /*
495 * Attempt to parse each of the addresses in str. If any fail
496 * and can be fixed with escape_local_part(), do that. This
497 * is extra ugly because getm()'s state can only be reset by
498 * call getname(), and getname() needs to be called repeatedly
499 * until it returns NULL to reset its state.
500 */
501 struct adr_node {
502 char *adr;
503 int escape_local_part;
504 int fixed;
505 struct adr_node *next;
506 } *adrs = NULL;
507 struct adr_node *np = adrs;
508 char *cp;
509
510 /*
511 * First, put each of the addresses in a linked list. Note
512 * invalid addresses that might be fixed by escaping the
513 * local part.
514 */
515 while ((cp = getname (str))) {
516 struct adr_node *adr_nodep;
517 char error[BUFSIZ];
518 struct mailname *mp;
519
520 NEW(adr_nodep);
521 adr_nodep->adr = mh_xstrdup (cp);
522 adr_nodep->escape_local_part = 0;
523 adr_nodep->fixed = 0;
524 adr_nodep->next = NULL;
525
526 /* With AD_NAME, errors are not reported to user. */
527 if ((mp = getm (cp, dfhost, dftype, error,
528 sizeof(error))) == NULL) {
529 const char *no_at_sign = "no at-sign after local-part";
530
531 adr_nodep->escape_local_part =
532 has_prefix(error, no_at_sign);
533 } else {
534 mnfree (mp);
535 }
536
537 if (np) {
538 np = np->next = adr_nodep;
539 } else {
540 np = adrs = adr_nodep;
541 }
542 }
543
544 /*
545 * Walk the list and try to fix broken addresses.
546 */
547 for (np = adrs; np; np = np->next) {
548 char *display_name = mh_xstrdup (np->adr);
549 size_t len = strlen (display_name);
550
551 if (np->escape_local_part) {
552 char *local_part_end = strrchr (display_name, '<');
553 char *angle_addr = mh_xstrdup (local_part_end);
554 struct mailname *mp;
555 char *new_adr, *adr;
556
557 *local_part_end = '\0';
558 /* Trim any trailing whitespace. */
559 while (local_part_end > display_name &&
560 isspace ((unsigned char) *--local_part_end)) {
561 *local_part_end = '\0';
562 }
563 escape_local_part (display_name, len);
564 new_adr = concat (display_name, " ", angle_addr, NULL);
565 adr = getname (new_adr);
566 if (adr != NULL &&
567 (mp = getm (adr, dfhost, dftype, NULL, 0)) != NULL) {
568 fixed_address = true;
569 mnfree (mp);
570 }
571 free (angle_addr);
572 free (new_adr);
573 free (np->adr);
574 np->adr = mh_xstrdup (adr);
575
576 /* Need to flush getname() */
577 while ((cp = getname (""))) continue;
578 } /* else the np->adr is OK, so use it as-is. */
579
580 free (display_name);
581 }
582
583 /*
584 * If any addresses were repaired, build new address string,
585 * replacing broken addresses.
586 */
587 for (np = adrs; np; ) {
588 struct adr_node *next = np->next;
589
590 if (fixed_address) {
591 if (fixed_str) {
592 char *new_str = concat (fixed_str, ", ", np->adr, NULL);
593
594 free (fixed_str);
595 fixed_str = new_str;
596 } else {
597 fixed_str = mh_xstrdup (np->adr);
598 }
599 }
600
601 free (np->adr);
602 free (np);
603 np = next;
604 }
605 }
606
607 if (fixed_address) {
608 return fixed_str;
609 }
610 free (fixed_str);
611 return str ? mh_xstrdup (str) : NULL;
612 }