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.1 (Berkeley) 12/21/92";
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 (def && *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 (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 (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 **
267 **	Returns:
268 **		none.
269 **
270 **	Side Effects:
271 **		Sets a bunch of global variables from information
272 **			in the collected header.
273 **		Aborts the message if the hop count is exceeded.
274 */
275 
276 eatheader(e)
277 	register ENVELOPE *e;
278 {
279 	register HDR *h;
280 	register char *p;
281 	int hopcnt = 0;
282 	char *msgid;
283 	char msgidbuf[MAXNAME];
284 
285 	if (tTd(32, 1))
286 		printf("----- collected header -----\n");
287 	msgid = "<none>";
288 	for (h = e->e_header; h != NULL; h = h->h_link)
289 	{
290 		extern char *capitalize();
291 
292 		if (tTd(32, 1))
293 			printf("%s: %s\n", capitalize(h->h_field), h->h_value);
294 		/* count the number of times it has been processed */
295 		if (bitset(H_TRACE, h->h_flags))
296 			hopcnt++;
297 
298 		/* send to this person if we so desire */
299 		if (GrabTo && bitset(H_RCPT, h->h_flags) &&
300 		    !bitset(H_DEFAULT, h->h_flags) &&
301 		    (!bitset(EF_RESENT, e->e_flags) || bitset(H_RESENT, h->h_flags)))
302 		{
303 			sendtolist(h->h_value, (ADDRESS *) NULL,
304 				   &e->e_sendqueue, e);
305 		}
306 
307 		/* save the message-id for logging */
308 		if (!QueueRun && h->h_value != NULL &&
309 		    strcmp(h->h_field, "message-id") == 0)
310 		{
311 			msgid = h->h_value;
312 			if (bitset(H_DEFAULT, h->h_flags))
313 			{
314 				expand(msgid, msgidbuf,
315 					&msgidbuf[sizeof msgidbuf], e);
316 				msgid = msgidbuf;
317 			}
318 		}
319 	}
320 	if (tTd(32, 1))
321 		printf("----------------------------\n");
322 
323 	/* store hop count */
324 	if (hopcnt > e->e_hopcount)
325 		e->e_hopcount = hopcnt;
326 
327 	/* message priority */
328 	p = hvalue("precedence", e);
329 	if (p != NULL)
330 		e->e_class = priencode(p);
331 	if (!QueueRun)
332 		e->e_msgpriority = e->e_msgsize
333 				 - e->e_class * WkClassFact
334 				 + e->e_nrcpts * WkRecipFact;
335 
336 	/* return receipt to */
337 	p = hvalue("return-receipt-to", e);
338 	if (p != NULL)
339 		e->e_receiptto = p;
340 
341 	/* errors to */
342 	p = hvalue("errors-to", e);
343 	if (p != NULL)
344 		sendtolist(p, (ADDRESS *) NULL, &e->e_errorqueue, e);
345 
346 	/* full name of from person */
347 	p = hvalue("full-name", e);
348 	if (p != NULL)
349 		define('x', p, e);
350 
351 	/* date message originated */
352 	p = hvalue("posted-date", e);
353 	if (p == NULL)
354 		p = hvalue("date", e);
355 	if (p != NULL)
356 	{
357 		define('a', p, e);
358 		/* we don't have a good way to do canonical conversion ....
359 		define('d', newstr(arpatounix(p)), e);
360 		.... so we will ignore the problem for the time being */
361 	}
362 
363 	/*
364 	**  Log collection information.
365 	*/
366 
367 # ifdef LOG
368 	if (!QueueRun && LogLevel > 1)
369 	{
370 		char hbuf[MAXNAME];
371 		char *name = hbuf;
372 		extern char *inet_ntoa();
373 
374 		if (RealHostName == NULL)
375 			name = "local";
376 		else if (RealHostName[0] == '[')
377 			name = RealHostName;
378 		else
379 			(void)sprintf(hbuf, "%.80s (%s)",
380 			    RealHostName, inet_ntoa(RealHostAddr.sin_addr));
381 		syslog(LOG_INFO,
382 		    "%s: from=%s, size=%ld, class=%d, msgid=%s, received from %s\n",
383 		    e->e_id, e->e_from.q_paddr, e->e_msgsize,
384 		    e->e_class, msgid, name);
385 	}
386 # endif /* LOG */
387 }
388 /*
389 **  PRIENCODE -- encode external priority names into internal values.
390 **
391 **	Parameters:
392 **		p -- priority in ascii.
393 **
394 **	Returns:
395 **		priority as a numeric level.
396 **
397 **	Side Effects:
398 **		none.
399 */
400 
401 priencode(p)
402 	char *p;
403 {
404 	register int i;
405 
406 	for (i = 0; i < NumPriorities; i++)
407 	{
408 		if (!strcasecmp(p, Priorities[i].pri_name))
409 			return (Priorities[i].pri_val);
410 	}
411 
412 	/* unknown priority */
413 	return (0);
414 }
415 /*
416 **  CRACKADDR -- parse an address and turn it into a macro
417 **
418 **	This doesn't actually parse the address -- it just extracts
419 **	it and replaces it with "$g".  The parse is totally ad hoc
420 **	and isn't even guaranteed to leave something syntactically
421 **	identical to what it started with.  However, it does leave
422 **	something semantically identical.
423 **
424 **	This algorithm has been cleaned up to handle a wider range
425 **	of cases -- notably quoted and backslash escaped strings.
426 **	This modification makes it substantially better at preserving
427 **	the original syntax.
428 **
429 **	Parameters:
430 **		addr -- the address to be cracked.
431 **
432 **	Returns:
433 **		a pointer to the new version.
434 **
435 **	Side Effects:
436 **		none.
437 **
438 **	Warning:
439 **		The return value is saved in local storage and should
440 **		be copied if it is to be reused.
441 */
442 
443 char *
444 crackaddr(addr)
445 	register char *addr;
446 {
447 	register char *p;
448 	register char c;
449 	int cmtlev;
450 	int realcmtlev;
451 	int anglelev, realanglelev;
452 	int copylev;
453 	bool qmode;
454 	bool realqmode;
455 	bool skipping;
456 	bool putgmac = FALSE;
457 	bool quoteit = FALSE;
458 	register char *bp;
459 	char *buflim;
460 	static char buf[MAXNAME];
461 
462 	if (tTd(33, 1))
463 		printf("crackaddr(%s)\n", addr);
464 
465 	/* strip leading spaces */
466 	while (*addr != '\0' && isspace(*addr))
467 		addr++;
468 
469 	/*
470 	**  Start by assuming we have no angle brackets.  This will be
471 	**  adjusted later if we find them.
472 	*/
473 
474 	bp = buf;
475 	buflim = &buf[sizeof buf - 5];
476 	p = addr;
477 	copylev = anglelev = realanglelev = cmtlev = realcmtlev = 0;
478 	qmode = realqmode = FALSE;
479 
480 	while ((c = *p++) != '\0')
481 	{
482 		/*
483 		**  If the buffer is overful, go into a special "skipping"
484 		**  mode that tries to keep legal syntax but doesn't actually
485 		**  output things.
486 		*/
487 
488 		skipping = bp >= buflim;
489 
490 		if (copylev > 0 && !skipping)
491 			*bp++ = c;
492 
493 		/* check for backslash escapes */
494 		if (c == '\\')
495 		{
496 			if ((c = *p++) == '\0')
497 			{
498 				/* too far */
499 				p--;
500 				goto putg;
501 			}
502 			if (copylev > 0 && !skipping)
503 				*bp++ = c;
504 			goto putg;
505 		}
506 
507 		/* check for quoted strings */
508 		if (c == '"')
509 		{
510 			qmode = !qmode;
511 			if (copylev > 0 && !skipping)
512 				realqmode = !realqmode;
513 			continue;
514 		}
515 		if (qmode)
516 			goto putg;
517 
518 		/* check for comments */
519 		if (c == '(')
520 		{
521 			cmtlev++;
522 
523 			/* allow space for closing paren */
524 			if (!skipping)
525 			{
526 				buflim--;
527 				realcmtlev++;
528 				if (copylev++ <= 0)
529 				{
530 					*bp++ = ' ';
531 					*bp++ = c;
532 				}
533 			}
534 		}
535 		if (cmtlev > 0)
536 		{
537 			if (c == ')')
538 			{
539 				cmtlev--;
540 				copylev--;
541 				if (!skipping)
542 				{
543 					realcmtlev--;
544 					buflim++;
545 				}
546 			}
547 			continue;
548 		}
549 		else if (c == ')')
550 		{
551 			/* syntax error: unmatched ) */
552 			if (!skipping)
553 				bp--;
554 		}
555 
556 
557 		/* check for characters that may have to be quoted */
558 		if (strchr(".'@,;:\\()", c) != NULL)
559 		{
560 			/*
561 			**  If these occur as the phrase part of a <>
562 			**  construct, but are not inside of () or already
563 			**  quoted, they will have to be quoted.  Note that
564 			**  now (but don't actually do the quoting).
565 			*/
566 
567 			if (cmtlev <= 0 && !qmode)
568 				quoteit = TRUE;
569 		}
570 
571 		/* check for angle brackets */
572 		if (c == '<')
573 		{
574 			register char *q;
575 
576 			/* oops -- have to change our mind */
577 			anglelev++;
578 			if (!skipping)
579 				realanglelev++;
580 
581 			bp = buf;
582 			if (quoteit)
583 			{
584 				*bp++ = '"';
585 
586 				/* back up over the '<' and any spaces */
587 				--p;
588 				while (isspace(*--p))
589 					continue;
590 				p++;
591 			}
592 			for (q = addr; q < p; )
593 			{
594 				c = *q++;
595 				if (bp < buflim)
596 				{
597 					if (quoteit && c == '"')
598 						*bp++ = '\\';
599 					*bp++ = c;
600 				}
601 			}
602 			if (quoteit)
603 			{
604 				*bp++ = '"';
605 				while ((c = *p++) != '<')
606 				{
607 					if (bp < buflim)
608 						*bp++ = c;
609 				}
610 				*bp++ = c;
611 			}
612 			copylev = 0;
613 			putgmac = quoteit = FALSE;
614 			continue;
615 		}
616 
617 		if (c == '>')
618 		{
619 			if (anglelev > 0)
620 			{
621 				anglelev--;
622 				if (!skipping)
623 				{
624 					realanglelev--;
625 					buflim++;
626 				}
627 			}
628 			else if (!skipping)
629 			{
630 				/* syntax error: unmatched > */
631 				if (copylev > 0)
632 					bp--;
633 				continue;
634 			}
635 			if (copylev++ <= 0)
636 				*bp++ = c;
637 			continue;
638 		}
639 
640 		/* must be a real address character */
641 	putg:
642 		if (copylev <= 0 && !putgmac)
643 		{
644 			*bp++ = '\001';
645 			*bp++ = 'g';
646 			putgmac = TRUE;
647 		}
648 	}
649 
650 	/* repair any syntactic damage */
651 	if (realqmode)
652 		*bp++ = '"';
653 	while (realcmtlev-- > 0)
654 		*bp++ = ')';
655 	while (realanglelev-- > 0)
656 		*bp++ = '>';
657 	*bp++ = '\0';
658 
659 	if (tTd(33, 1))
660 		printf("crackaddr=>`%s'\n", buf);
661 
662 	return (buf);
663 }
664 /*
665 **  PUTHEADER -- put the header part of a message from the in-core copy
666 **
667 **	Parameters:
668 **		fp -- file to put it on.
669 **		m -- mailer to use.
670 **		e -- envelope to use.
671 **
672 **	Returns:
673 **		none.
674 **
675 **	Side Effects:
676 **		none.
677 */
678 
679 putheader(fp, m, e)
680 	register FILE *fp;
681 	register MAILER *m;
682 	register ENVELOPE *e;
683 {
684 	char buf[MAX(MAXLINE,BUFSIZ)];
685 	register HDR *h;
686 	extern char *arpadate();
687 	extern char *capitalize();
688 	char obuf[MAXLINE];
689 
690 	for (h = e->e_header; h != NULL; h = h->h_link)
691 	{
692 		register char *p;
693 		extern bool bitintersect();
694 
695 		if (bitset(H_CHECK|H_ACHECK, h->h_flags) &&
696 		    !bitintersect(h->h_mflags, m->m_flags))
697 			continue;
698 
699 		/* handle Resent-... headers specially */
700 		if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
701 			continue;
702 
703 		p = h->h_value;
704 		if (bitset(H_DEFAULT, h->h_flags))
705 		{
706 			/* macro expand value if generated internally */
707 			expand(p, buf, &buf[sizeof buf], e);
708 			p = buf;
709 			if (p == NULL || *p == '\0')
710 				continue;
711 		}
712 
713 		if (bitset(H_FROM|H_RCPT, h->h_flags))
714 		{
715 			/* address field */
716 			bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
717 
718 			if (bitset(H_FROM, h->h_flags))
719 				oldstyle = FALSE;
720 			commaize(h, p, fp, oldstyle, m, e);
721 		}
722 		else
723 		{
724 			/* vanilla header line */
725 			register char *nlp;
726 
727 			(void) sprintf(obuf, "%s: ", capitalize(h->h_field));
728 			while ((nlp = strchr(p, '\n')) != NULL)
729 			{
730 				*nlp = '\0';
731 				(void) strcat(obuf, p);
732 				*nlp = '\n';
733 				putline(obuf, fp, m);
734 				p = ++nlp;
735 				obuf[0] = '\0';
736 			}
737 			(void) strcat(obuf, p);
738 			putline(obuf, fp, m);
739 		}
740 	}
741 }
742 /*
743 **  COMMAIZE -- output a header field, making a comma-translated list.
744 **
745 **	Parameters:
746 **		h -- the header field to output.
747 **		p -- the value to put in it.
748 **		fp -- file to put it to.
749 **		oldstyle -- TRUE if this is an old style header.
750 **		m -- a pointer to the mailer descriptor.  If NULL,
751 **			don't transform the name at all.
752 **		e -- the envelope containing the message.
753 **
754 **	Returns:
755 **		none.
756 **
757 **	Side Effects:
758 **		outputs "p" to file "fp".
759 */
760 
761 commaize(h, p, fp, oldstyle, m, e)
762 	register HDR *h;
763 	register char *p;
764 	FILE *fp;
765 	bool oldstyle;
766 	register MAILER *m;
767 	register ENVELOPE *e;
768 {
769 	register char *obp;
770 	int opos;
771 	bool firstone = TRUE;
772 	char obuf[MAXLINE + 3];
773 
774 	/*
775 	**  Output the address list translated by the
776 	**  mailer and with commas.
777 	*/
778 
779 	if (tTd(14, 2))
780 		printf("commaize(%s: %s)\n", h->h_field, p);
781 
782 	obp = obuf;
783 	(void) sprintf(obp, "%s: ", capitalize(h->h_field));
784 	opos = strlen(h->h_field) + 2;
785 	obp += opos;
786 
787 	/*
788 	**  Run through the list of values.
789 	*/
790 
791 	while (*p != '\0')
792 	{
793 		register char *name;
794 		register int c;
795 		char savechar;
796 		extern char *remotename();
797 		extern char *DelimChar;		/* defined in prescan */
798 
799 		/*
800 		**  Find the end of the name.  New style names
801 		**  end with a comma, old style names end with
802 		**  a space character.  However, spaces do not
803 		**  necessarily delimit an old-style name -- at
804 		**  signs mean keep going.
805 		*/
806 
807 		/* find end of name */
808 		while (isspace(*p) || *p == ',')
809 			p++;
810 		name = p;
811 		for (;;)
812 		{
813 			char *oldp;
814 			char pvpbuf[PSBUFSIZE];
815 			extern bool isatword();
816 			extern char **prescan();
817 
818 			(void) prescan(p, oldstyle ? ' ' : ',', pvpbuf);
819 			p = DelimChar;
820 
821 			/* look to see if we have an at sign */
822 			oldp = p;
823 			while (*p != '\0' && isspace(*p))
824 				p++;
825 
826 			if (*p != '@' && !isatword(p))
827 			{
828 				p = oldp;
829 				break;
830 			}
831 			p += *p == '@' ? 1 : 2;
832 			while (*p != '\0' && isspace(*p))
833 				p++;
834 		}
835 		/* at the end of one complete name */
836 
837 		/* strip off trailing white space */
838 		while (p >= name && (isspace(*p) || *p == ',' || *p == '\0'))
839 			p--;
840 		if (++p == name)
841 			continue;
842 		savechar = *p;
843 		*p = '\0';
844 
845 		/* translate the name to be relative */
846 		name = remotename(name, m, bitset(H_FROM, h->h_flags), FALSE, e);
847 		if (*name == '\0')
848 		{
849 			*p = savechar;
850 			continue;
851 		}
852 
853 		/* output the name with nice formatting */
854 		opos += strlen(name);
855 		if (!firstone)
856 			opos += 2;
857 		if (opos > 78 && !firstone)
858 		{
859 			(void) strcpy(obp, ",\n");
860 			putline(obuf, fp, m);
861 			obp = obuf;
862 			(void) sprintf(obp, "        ");
863 			opos = strlen(obp);
864 			obp += opos;
865 			opos += strlen(name);
866 		}
867 		else if (!firstone)
868 		{
869 			(void) sprintf(obp, ", ");
870 			obp += 2;
871 		}
872 
873 		/* strip off quote bits as we output */
874 		while ((c = *name++) != '\0' && obp < &obuf[MAXLINE])
875 		{
876 			if (bitnset(M_7BITS, m->m_flags))
877 				c &= 0177;
878 			*obp++ = c;
879 		}
880 		firstone = FALSE;
881 		*p = savechar;
882 	}
883 	(void) strcpy(obp, "\n");
884 	putline(obuf, fp, m);
885 }
886 /*
887 **  ISATWORD -- tell if the word we are pointing to is "at".
888 **
889 **	Parameters:
890 **		p -- word to check.
891 **
892 **	Returns:
893 **		TRUE -- if p is the word at.
894 **		FALSE -- otherwise.
895 **
896 **	Side Effects:
897 **		none.
898 */
899 
900 bool
901 isatword(p)
902 	register char *p;
903 {
904 	extern char lower();
905 
906 	if (lower(p[0]) == 'a' && lower(p[1]) == 't' &&
907 	    p[2] != '\0' && isspace(p[2]))
908 		return (TRUE);
909 	return (FALSE);
910 }
911