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