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