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