1 /*
2  * Copyright (c) 1983 Eric P. Allman
3  * Copyright (c) 1988 Regents of the University of California.
4  * All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)headers.c	6.8 (Berkeley) 02/20/93";
11 #endif /* not lint */
12 
13 # include <errno.h>
14 # include "sendmail.h"
15 
16 /*
17 **  CHOMPHEADER -- process and save a header line.
18 **
19 **	Called by collect and by readcf to deal with header lines.
20 **
21 **	Parameters:
22 **		line -- header as a text line.
23 **		def -- if set, this is a default value.
24 **		e -- the envelope including this header.
25 **
26 **	Returns:
27 **		flags for this header.
28 **
29 **	Side Effects:
30 **		The header is saved on the header list.
31 **		Contents of 'line' are destroyed.
32 */
33 
34 chompheader(line, def, e)
35 	char *line;
36 	bool def;
37 	register ENVELOPE *e;
38 {
39 	register char *p;
40 	register HDR *h;
41 	HDR **hp;
42 	char *fname;
43 	char *fvalue;
44 	struct hdrinfo *hi;
45 	bool cond = FALSE;
46 	BITMAP mopts;
47 
48 	if (tTd(31, 6))
49 		printf("chompheader: %s\n", line);
50 
51 	/* strip off options */
52 	clrbitmap(mopts);
53 	p = line;
54 	if (*p == '?')
55 	{
56 		/* have some */
57 		register char *q = strchr(p + 1, *p);
58 
59 		if (q != NULL)
60 		{
61 			*q++ = '\0';
62 			while (*++p != '\0')
63 				setbitn(*p, mopts);
64 			p = q;
65 		}
66 		else
67 			usrerr("chompheader: syntax error, line \"%s\"", line);
68 		cond = TRUE;
69 	}
70 
71 	/* find canonical name */
72 	fname = p;
73 	p = strchr(p, ':');
74 	if (p == NULL)
75 	{
76 		syserr("chompheader: syntax error, line \"%s\"", line);
77 		return (0);
78 	}
79 	fvalue = &p[1];
80 	while (isascii(*--p) && isspace(*p))
81 		continue;
82 	*++p = '\0';
83 	makelower(fname);
84 
85 	/* strip field value on front */
86 	if (*fvalue == ' ')
87 		fvalue++;
88 
89 	/* see if it is a known type */
90 	for (hi = HdrInfo; hi->hi_field != NULL; hi++)
91 	{
92 		if (strcmp(hi->hi_field, fname) == 0)
93 			break;
94 	}
95 
96 	/* see if this is a resent message */
97 	if (!def && bitset(H_RESENT, hi->hi_flags))
98 		e->e_flags |= EF_RESENT;
99 
100 	/* if this means "end of header" quit now */
101 	if (bitset(H_EOH, hi->hi_flags))
102 		return (hi->hi_flags);
103 
104 	/* drop explicit From: if same as what we would generate -- for MH */
105 	p = "resent-from";
106 	if (!bitset(EF_RESENT, e->e_flags))
107 		p += 7;
108 	if (!def && !QueueRun && strcmp(fname, p) == 0)
109 	{
110 		if (e->e_from.q_paddr != NULL &&
111 		    strcmp(fvalue, e->e_from.q_paddr) == 0)
112 			return (hi->hi_flags);
113 	}
114 
115 	/* delete default value for this header */
116 	for (hp = &e->e_header; (h = *hp) != NULL; hp = &h->h_link)
117 	{
118 		if (strcmp(fname, h->h_field) == 0 &&
119 		    bitset(H_DEFAULT, h->h_flags) &&
120 		    !bitset(H_FORCE, h->h_flags))
121 			h->h_value = NULL;
122 	}
123 
124 	/* create a new node */
125 	h = (HDR *) xalloc(sizeof *h);
126 	h->h_field = newstr(fname);
127 	h->h_value = NULL;
128 	h->h_link = NULL;
129 	bcopy((char *) mopts, (char *) h->h_mflags, sizeof mopts);
130 	*hp = h;
131 	h->h_flags = hi->hi_flags;
132 	if (def)
133 		h->h_flags |= H_DEFAULT;
134 	if (cond)
135 		h->h_flags |= H_CHECK;
136 	if (h->h_value != NULL)
137 		free((char *) h->h_value);
138 	h->h_value = newstr(fvalue);
139 
140 	/* hack to see if this is a new format message */
141 	if (!def && bitset(H_RCPT|H_FROM, h->h_flags) &&
142 	    (strchr(fvalue, ',') != NULL || strchr(fvalue, '(') != NULL ||
143 	     strchr(fvalue, '<') != NULL || strchr(fvalue, ';') != NULL))
144 	{
145 		e->e_flags &= ~EF_OLDSTYLE;
146 	}
147 
148 	return (h->h_flags);
149 }
150 /*
151 **  ADDHEADER -- add a header entry to the end of the queue.
152 **
153 **	This bypasses the special checking of chompheader.
154 **
155 **	Parameters:
156 **		field -- the name of the header field.
157 **		value -- the value of the field.  It must be lower-cased.
158 **		e -- the envelope to add them to.
159 **
160 **	Returns:
161 **		none.
162 **
163 **	Side Effects:
164 **		adds the field on the list of headers for this envelope.
165 */
166 
167 addheader(field, value, e)
168 	char *field;
169 	char *value;
170 	ENVELOPE *e;
171 {
172 	register HDR *h;
173 	register struct hdrinfo *hi;
174 	HDR **hp;
175 
176 	/* find info struct */
177 	for (hi = HdrInfo; hi->hi_field != NULL; hi++)
178 	{
179 		if (strcmp(field, hi->hi_field) == 0)
180 			break;
181 	}
182 
183 	/* find current place in list -- keep back pointer? */
184 	for (hp = &e->e_header; (h = *hp) != NULL; hp = &h->h_link)
185 	{
186 		if (strcmp(field, h->h_field) == 0)
187 			break;
188 	}
189 
190 	/* allocate space for new header */
191 	h = (HDR *) xalloc(sizeof *h);
192 	h->h_field = field;
193 	h->h_value = newstr(value);
194 	h->h_link = *hp;
195 	h->h_flags = hi->hi_flags | H_DEFAULT;
196 	clrbitmap(h->h_mflags);
197 	*hp = h;
198 }
199 /*
200 **  HVALUE -- return value of a header.
201 **
202 **	Only "real" fields (i.e., ones that have not been supplied
203 **	as a default) are used.
204 **
205 **	Parameters:
206 **		field -- the field name.
207 **		e -- the envelope containing the header.
208 **
209 **	Returns:
210 **		pointer to the value part.
211 **		NULL if not found.
212 **
213 **	Side Effects:
214 **		none.
215 */
216 
217 char *
218 hvalue(field, e)
219 	char *field;
220 	register ENVELOPE *e;
221 {
222 	register HDR *h;
223 
224 	for (h = e->e_header; h != NULL; h = h->h_link)
225 	{
226 		if (!bitset(H_DEFAULT, h->h_flags) && strcmp(h->h_field, field) == 0)
227 			return (h->h_value);
228 	}
229 	return (NULL);
230 }
231 /*
232 **  ISHEADER -- predicate telling if argument is a header.
233 **
234 **	A line is a header if it has a single word followed by
235 **	optional white space followed by a colon.
236 **
237 **	Parameters:
238 **		s -- string to check for possible headerness.
239 **
240 **	Returns:
241 **		TRUE if s is a header.
242 **		FALSE otherwise.
243 **
244 **	Side Effects:
245 **		none.
246 */
247 
248 bool
249 isheader(s)
250 	register char *s;
251 {
252 	while (*s > ' ' && *s != ':' && *s != '\0')
253 		s++;
254 
255 	/* following technically violates RFC822 */
256 	while (isascii(*s) && isspace(*s))
257 		s++;
258 
259 	return (*s == ':');
260 }
261 /*
262 **  EATHEADER -- run through the stored header and extract info.
263 **
264 **	Parameters:
265 **		e -- the envelope to process.
266 **		queuejob -- set if running a queued job.
267 **
268 **	Returns:
269 **		none.
270 **
271 **	Side Effects:
272 **		Sets a bunch of global variables from information
273 **			in the collected header.
274 **		Aborts the message if the hop count is exceeded.
275 */
276 
277 eatheader(e, queuejob)
278 	register ENVELOPE *e;
279 	bool queuejob;
280 {
281 	register HDR *h;
282 	register char *p;
283 	int hopcnt = 0;
284 	char *msgid;
285 	char msgidbuf[MAXNAME];
286 
287 	if (tTd(32, 1))
288 		printf("----- collected header -----\n");
289 	msgid = "<none>";
290 	for (h = e->e_header; h != NULL; h = h->h_link)
291 	{
292 		extern char *capitalize();
293 
294 		if (tTd(32, 1))
295 			printf("%s: %s\n", capitalize(h->h_field), h->h_value);
296 
297 		/* count the number of times it has been processed */
298 		if (bitset(H_TRACE, h->h_flags))
299 			hopcnt++;
300 
301 		/* send to this person if we so desire */
302 		if (GrabTo && bitset(H_RCPT, h->h_flags) &&
303 		    !bitset(H_DEFAULT, h->h_flags) &&
304 		    (!bitset(EF_RESENT, e->e_flags) || bitset(H_RESENT, h->h_flags)))
305 		{
306 			(void) sendtolist(h->h_value, (ADDRESS *) NULL,
307 					  &e->e_sendqueue, e);
308 		}
309 
310 		/* save the message-id for logging */
311 		if (!queuejob && h->h_value != NULL &&
312 		    strcmp(h->h_field, "message-id") == 0)
313 		{
314 			msgid = h->h_value;
315 			if (bitset(H_DEFAULT, h->h_flags))
316 			{
317 				expand(msgid, msgidbuf,
318 					&msgidbuf[sizeof msgidbuf], e);
319 				msgid = msgidbuf;
320 			}
321 		}
322 
323 		/* see if this is a return-receipt header */
324 		if (bitset(H_RECEIPTTO, h->h_flags))
325 			e->e_receiptto = h->h_value;
326 
327 		/* see if this is an errors-to header */
328 		if (bitset(H_ERRORSTO, h->h_flags))
329 			(void) sendtolist(h->h_value, (ADDRESS *) NULL,
330 					  &e->e_errorqueue, e);
331 	}
332 	if (tTd(32, 1))
333 		printf("----------------------------\n");
334 
335 	/* store hop count */
336 	if (hopcnt > e->e_hopcount)
337 		e->e_hopcount = hopcnt;
338 
339 	/* message priority */
340 	p = hvalue("precedence", e);
341 	if (p != NULL)
342 		e->e_class = priencode(p);
343 	if (!queuejob)
344 		e->e_msgpriority = e->e_msgsize
345 				 - e->e_class * WkClassFact
346 				 + e->e_nrcpts * WkRecipFact;
347 
348 	/* full name of from person */
349 	p = hvalue("full-name", e);
350 	if (p != NULL)
351 		define('x', p, e);
352 
353 	/* date message originated */
354 	p = hvalue("posted-date", e);
355 	if (p == NULL)
356 		p = hvalue("date", e);
357 	if (p != NULL)
358 	{
359 		extern char *arpatounix();
360 
361 		define('a', p, e);
362 		if ((p = arpatounix(p, e)) != NULL)
363 			define('d', newstr(p), e);
364 	}
365 
366 	/*
367 	**  Log collection information.
368 	*/
369 
370 # ifdef LOG
371 	if (!queuejob && LogLevel > 4)
372 	{
373 		char *name;
374 		char hbuf[MAXNAME];
375 		char sbuf[MAXLINE];
376 
377 		if (RealHostName == NULL)
378 			name = "local";
379 		else if (RealHostName[0] == '[')
380 			name = RealHostName;
381 		else
382 		{
383 			extern char *inet_ntoa();
384 
385 			name = hbuf;
386 			(void)sprintf(hbuf, "%.80s (%s)",
387 			    RealHostName, inet_ntoa(RealHostAddr.sin_addr));
388 		}
389 
390 		/* some versions of syslog only take 5 printf args */
391 		sprintf(sbuf, "from=%.200s, size=%ld, class=%d, pri=%ld, nrcpts=%d, msgid=%.100s",
392 		    e->e_from.q_paddr, e->e_msgsize, e->e_class,
393 		    e->e_msgpriority, e->e_nrcpts, msgid);
394 		syslog(LOG_INFO, "%s: %s, received from %s",
395 		    e->e_id, sbuf, name);
396 	}
397 # endif /* LOG */
398 }
399 /*
400 **  PRIENCODE -- encode external priority names into internal values.
401 **
402 **	Parameters:
403 **		p -- priority in ascii.
404 **
405 **	Returns:
406 **		priority as a numeric level.
407 **
408 **	Side Effects:
409 **		none.
410 */
411 
412 priencode(p)
413 	char *p;
414 {
415 	register int i;
416 
417 	for (i = 0; i < NumPriorities; i++)
418 	{
419 		if (!strcasecmp(p, Priorities[i].pri_name))
420 			return (Priorities[i].pri_val);
421 	}
422 
423 	/* unknown priority */
424 	return (0);
425 }
426 /*
427 **  CRACKADDR -- parse an address and turn it into a macro
428 **
429 **	This doesn't actually parse the address -- it just extracts
430 **	it and replaces it with "$g".  The parse is totally ad hoc
431 **	and isn't even guaranteed to leave something syntactically
432 **	identical to what it started with.  However, it does leave
433 **	something semantically identical.
434 **
435 **	This algorithm has been cleaned up to handle a wider range
436 **	of cases -- notably quoted and backslash escaped strings.
437 **	This modification makes it substantially better at preserving
438 **	the original syntax.
439 **
440 **	Parameters:
441 **		addr -- the address to be cracked.
442 **
443 **	Returns:
444 **		a pointer to the new version.
445 **
446 **	Side Effects:
447 **		none.
448 **
449 **	Warning:
450 **		The return value is saved in local storage and should
451 **		be copied if it is to be reused.
452 */
453 
454 char *
455 crackaddr(addr)
456 	register char *addr;
457 {
458 	register char *p;
459 	register char c;
460 	int cmtlev;
461 	int realcmtlev;
462 	int anglelev, realanglelev;
463 	int copylev;
464 	bool qmode;
465 	bool realqmode;
466 	bool skipping;
467 	bool putgmac = FALSE;
468 	bool quoteit = FALSE;
469 	register char *bp;
470 	char *buflim;
471 	static char buf[MAXNAME];
472 
473 	if (tTd(33, 1))
474 		printf("crackaddr(%s)\n", addr);
475 
476 	/* strip leading spaces */
477 	while (*addr != '\0' && isascii(*addr) && isspace(*addr))
478 		addr++;
479 
480 	/*
481 	**  Start by assuming we have no angle brackets.  This will be
482 	**  adjusted later if we find them.
483 	*/
484 
485 	bp = buf;
486 	buflim = &buf[sizeof buf - 5];
487 	p = addr;
488 	copylev = anglelev = realanglelev = cmtlev = realcmtlev = 0;
489 	qmode = realqmode = FALSE;
490 
491 	while ((c = *p++) != '\0')
492 	{
493 		/*
494 		**  If the buffer is overful, go into a special "skipping"
495 		**  mode that tries to keep legal syntax but doesn't actually
496 		**  output things.
497 		*/
498 
499 		skipping = bp >= buflim;
500 
501 		if (copylev > 0 && !skipping)
502 			*bp++ = c;
503 
504 		/* check for backslash escapes */
505 		if (c == '\\')
506 		{
507 			if ((c = *p++) == '\0')
508 			{
509 				/* too far */
510 				p--;
511 				goto putg;
512 			}
513 			if (copylev > 0 && !skipping)
514 				*bp++ = c;
515 			goto putg;
516 		}
517 
518 		/* check for quoted strings */
519 		if (c == '"')
520 		{
521 			qmode = !qmode;
522 			if (copylev > 0 && !skipping)
523 				realqmode = !realqmode;
524 			continue;
525 		}
526 		if (qmode)
527 			goto putg;
528 
529 		/* check for comments */
530 		if (c == '(')
531 		{
532 			cmtlev++;
533 
534 			/* allow space for closing paren */
535 			if (!skipping)
536 			{
537 				buflim--;
538 				realcmtlev++;
539 				if (copylev++ <= 0)
540 				{
541 					*bp++ = ' ';
542 					*bp++ = c;
543 				}
544 			}
545 		}
546 		if (cmtlev > 0)
547 		{
548 			if (c == ')')
549 			{
550 				cmtlev--;
551 				copylev--;
552 				if (!skipping)
553 				{
554 					realcmtlev--;
555 					buflim++;
556 				}
557 			}
558 			continue;
559 		}
560 		else if (c == ')')
561 		{
562 			/* syntax error: unmatched ) */
563 			if (!skipping)
564 				bp--;
565 		}
566 
567 
568 		/* check for characters that may have to be quoted */
569 		if (strchr(".'@,;:\\()", c) != NULL)
570 		{
571 			/*
572 			**  If these occur as the phrase part of a <>
573 			**  construct, but are not inside of () or already
574 			**  quoted, they will have to be quoted.  Note that
575 			**  now (but don't actually do the quoting).
576 			*/
577 
578 			if (cmtlev <= 0 && !qmode)
579 				quoteit = TRUE;
580 		}
581 
582 		/* check for angle brackets */
583 		if (c == '<')
584 		{
585 			register char *q;
586 
587 			/* oops -- have to change our mind */
588 			anglelev++;
589 			if (!skipping)
590 				realanglelev++;
591 
592 			bp = buf;
593 			if (quoteit)
594 			{
595 				*bp++ = '"';
596 
597 				/* back up over the '<' and any spaces */
598 				--p;
599 				while (isascii(*--p) && isspace(*p))
600 					continue;
601 				p++;
602 			}
603 			for (q = addr; q < p; )
604 			{
605 				c = *q++;
606 				if (bp < buflim)
607 				{
608 					if (quoteit && c == '"')
609 						*bp++ = '\\';
610 					*bp++ = c;
611 				}
612 			}
613 			if (quoteit)
614 			{
615 				*bp++ = '"';
616 				while ((c = *p++) != '<')
617 				{
618 					if (bp < buflim)
619 						*bp++ = c;
620 				}
621 				*bp++ = c;
622 			}
623 			copylev = 0;
624 			putgmac = quoteit = FALSE;
625 			continue;
626 		}
627 
628 		if (c == '>')
629 		{
630 			if (anglelev > 0)
631 			{
632 				anglelev--;
633 				if (!skipping)
634 				{
635 					realanglelev--;
636 					buflim++;
637 				}
638 			}
639 			else if (!skipping)
640 			{
641 				/* syntax error: unmatched > */
642 				if (copylev > 0)
643 					bp--;
644 				continue;
645 			}
646 			if (copylev++ <= 0)
647 				*bp++ = c;
648 			continue;
649 		}
650 
651 		/* must be a real address character */
652 	putg:
653 		if (copylev <= 0 && !putgmac)
654 		{
655 			*bp++ = MACROEXPAND;
656 			*bp++ = 'g';
657 			putgmac = TRUE;
658 		}
659 	}
660 
661 	/* repair any syntactic damage */
662 	if (realqmode)
663 		*bp++ = '"';
664 	while (realcmtlev-- > 0)
665 		*bp++ = ')';
666 	while (realanglelev-- > 0)
667 		*bp++ = '>';
668 	*bp++ = '\0';
669 
670 	if (tTd(33, 1))
671 		printf("crackaddr=>`%s'\n", buf);
672 
673 	return (buf);
674 }
675 /*
676 **  PUTHEADER -- put the header part of a message from the in-core copy
677 **
678 **	Parameters:
679 **		fp -- file to put it on.
680 **		m -- mailer to use.
681 **		e -- envelope to use.
682 **
683 **	Returns:
684 **		none.
685 **
686 **	Side Effects:
687 **		none.
688 */
689 
690 /*
691  * Macro for fast max (not available in e.g. DG/UX, 386/ix).
692  */
693 #ifndef MAX
694 # define MAX(a,b) (((a)>(b))?(a):(b))
695 #endif
696 
697 putheader(fp, m, e)
698 	register FILE *fp;
699 	register MAILER *m;
700 	register ENVELOPE *e;
701 {
702 	char buf[MAX(MAXLINE,BUFSIZ)];
703 	register HDR *h;
704 	char obuf[MAXLINE];
705 
706 	for (h = e->e_header; h != NULL; h = h->h_link)
707 	{
708 		register char *p;
709 		extern bool bitintersect();
710 
711 		if (bitset(H_CHECK|H_ACHECK, h->h_flags) &&
712 		    !bitintersect(h->h_mflags, m->m_flags))
713 			continue;
714 
715 		/* handle Resent-... headers specially */
716 		if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
717 			continue;
718 
719 		p = h->h_value;
720 		if (bitset(H_DEFAULT, h->h_flags))
721 		{
722 			/* macro expand value if generated internally */
723 			expand(p, buf, &buf[sizeof buf], e);
724 			p = buf;
725 			if (p == NULL || *p == '\0')
726 				continue;
727 		}
728 
729 		if (bitset(H_FROM|H_RCPT, h->h_flags))
730 		{
731 			/* address field */
732 			bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
733 
734 			if (bitset(H_FROM, h->h_flags))
735 				oldstyle = FALSE;
736 			commaize(h, p, fp, oldstyle, m, e);
737 		}
738 		else
739 		{
740 			/* vanilla header line */
741 			register char *nlp;
742 			extern char *capitalize();
743 
744 			(void) sprintf(obuf, "%s: ", capitalize(h->h_field));
745 			while ((nlp = strchr(p, '\n')) != NULL)
746 			{
747 				*nlp = '\0';
748 				(void) strcat(obuf, p);
749 				*nlp = '\n';
750 				putline(obuf, fp, m);
751 				p = ++nlp;
752 				obuf[0] = '\0';
753 			}
754 			(void) strcat(obuf, p);
755 			putline(obuf, fp, m);
756 		}
757 	}
758 }
759 /*
760 **  COMMAIZE -- output a header field, making a comma-translated list.
761 **
762 **	Parameters:
763 **		h -- the header field to output.
764 **		p -- the value to put in it.
765 **		fp -- file to put it to.
766 **		oldstyle -- TRUE if this is an old style header.
767 **		m -- a pointer to the mailer descriptor.  If NULL,
768 **			don't transform the name at all.
769 **		e -- the envelope containing the message.
770 **
771 **	Returns:
772 **		none.
773 **
774 **	Side Effects:
775 **		outputs "p" to file "fp".
776 */
777 
778 commaize(h, p, fp, oldstyle, m, e)
779 	register HDR *h;
780 	register char *p;
781 	FILE *fp;
782 	bool oldstyle;
783 	register MAILER *m;
784 	register ENVELOPE *e;
785 {
786 	register char *obp;
787 	int opos;
788 	bool firstone = TRUE;
789 	char obuf[MAXLINE + 3];
790 	extern char *capitalize();
791 
792 	/*
793 	**  Output the address list translated by the
794 	**  mailer and with commas.
795 	*/
796 
797 	if (tTd(14, 2))
798 		printf("commaize(%s: %s)\n", h->h_field, p);
799 
800 	obp = obuf;
801 	(void) sprintf(obp, "%s: ", capitalize(h->h_field));
802 	opos = strlen(h->h_field) + 2;
803 	obp += opos;
804 
805 	/*
806 	**  Run through the list of values.
807 	*/
808 
809 	while (*p != '\0')
810 	{
811 		register char *name;
812 		register int c;
813 		char savechar;
814 		extern char *remotename();
815 		extern char *DelimChar;		/* defined in prescan */
816 
817 		/*
818 		**  Find the end of the name.  New style names
819 		**  end with a comma, old style names end with
820 		**  a space character.  However, spaces do not
821 		**  necessarily delimit an old-style name -- at
822 		**  signs mean keep going.
823 		*/
824 
825 		/* find end of name */
826 		while ((isascii(*p) && isspace(*p)) || *p == ',')
827 			p++;
828 		name = p;
829 		for (;;)
830 		{
831 			char *oldp;
832 			char pvpbuf[PSBUFSIZE];
833 			extern bool isatword();
834 			extern char **prescan();
835 
836 			(void) prescan(p, oldstyle ? ' ' : ',', pvpbuf);
837 			p = DelimChar;
838 
839 			/* look to see if we have an at sign */
840 			oldp = p;
841 			while (*p != '\0' && isascii(*p) && isspace(*p))
842 				p++;
843 
844 			if (*p != '@' && !isatword(p))
845 			{
846 				p = oldp;
847 				break;
848 			}
849 			p += *p == '@' ? 1 : 2;
850 			while (*p != '\0' && isascii(*p) && isspace(*p))
851 				p++;
852 		}
853 		/* at the end of one complete name */
854 
855 		/* strip off trailing white space */
856 		while (p >= name &&
857 		       ((isascii(*p) && isspace(*p)) || *p == ',' || *p == '\0'))
858 			p--;
859 		if (++p == name)
860 			continue;
861 		savechar = *p;
862 		*p = '\0';
863 
864 		/* translate the name to be relative */
865 		name = remotename(name, m, bitset(H_FROM, h->h_flags),
866 				  TRUE, FALSE, e);
867 		if (*name == '\0')
868 		{
869 			*p = savechar;
870 			continue;
871 		}
872 
873 		/* output the name with nice formatting */
874 		opos += strlen(name);
875 		if (!firstone)
876 			opos += 2;
877 		if (opos > 78 && !firstone)
878 		{
879 			(void) strcpy(obp, ",\n");
880 			putline(obuf, fp, m);
881 			obp = obuf;
882 			(void) sprintf(obp, "        ");
883 			opos = strlen(obp);
884 			obp += opos;
885 			opos += strlen(name);
886 		}
887 		else if (!firstone)
888 		{
889 			(void) sprintf(obp, ", ");
890 			obp += 2;
891 		}
892 
893 		/* strip off quote bits as we output */
894 		while ((c = *name++) != '\0' && obp < &obuf[MAXLINE])
895 		{
896 			if (bitnset(M_7BITS, m->m_flags))
897 				c &= 0177;
898 			*obp++ = c;
899 		}
900 		firstone = FALSE;
901 		*p = savechar;
902 	}
903 	(void) strcpy(obp, "\n");
904 	putline(obuf, fp, m);
905 }
906 /*
907 **  ISATWORD -- tell if the word we are pointing to is "at".
908 **
909 **	Parameters:
910 **		p -- word to check.
911 **
912 **	Returns:
913 **		TRUE -- if p is the word at.
914 **		FALSE -- otherwise.
915 **
916 **	Side Effects:
917 **		none.
918 */
919 
920 bool
921 isatword(p)
922 	register char *p;
923 {
924 	extern char lower();
925 
926 	if (lower(p[0]) == 'a' && lower(p[1]) == 't' &&
927 	    p[2] != '\0' && isascii(p[2]) && isspace(p[2]))
928 		return (TRUE);
929 	return (FALSE);
930 }
931