xref: /freebsd/contrib/sendmail/src/collect.c (revision 4cf49a43559ed9fdad601bdcccd2c55963008675)
1 /*
2  * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
3  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
4  * Copyright (c) 1988, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * By using this file, you agree to the terms and conditions set
8  * forth in the LICENSE file which can be found at the top level of
9  * the sendmail distribution.
10  *
11  */
12 
13 #ifndef lint
14 static char sccsid[] = "@(#)collect.c	8.93 (Berkeley) 1/26/1999";
15 #endif /* not lint */
16 
17 # include <errno.h>
18 # include "sendmail.h"
19 
20 /*
21 **  COLLECT -- read & parse message header & make temp file.
22 **
23 **	Creates a temporary file name and copies the standard
24 **	input to that file.  Leading UNIX-style "From" lines are
25 **	stripped off (after important information is extracted).
26 **
27 **	Parameters:
28 **		fp -- file to read.
29 **		smtpmode -- if set, we are running SMTP: give an RFC821
30 **			style message to say we are ready to collect
31 **			input, and never ignore a single dot to mean
32 **			end of message.
33 **		hdrp -- the location to stash the header.
34 **		e -- the current envelope.
35 **
36 **	Returns:
37 **		none.
38 **
39 **	Side Effects:
40 **		Temp file is created and filled.
41 **		The from person may be set.
42 */
43 
44 static jmp_buf	CtxCollectTimeout;
45 static void	collecttimeout __P((time_t));
46 static bool	CollectProgress;
47 static EVENT	*CollectTimeout;
48 
49 /* values for input state machine */
50 #define IS_NORM		0	/* middle of line */
51 #define IS_BOL		1	/* beginning of line */
52 #define IS_DOT		2	/* read a dot at beginning of line */
53 #define IS_DOTCR	3	/* read ".\r" at beginning of line */
54 #define IS_CR		4	/* read a carriage return */
55 
56 /* values for message state machine */
57 #define MS_UFROM	0	/* reading Unix from line */
58 #define MS_HEADER	1	/* reading message header */
59 #define MS_BODY		2	/* reading message body */
60 #define MS_DISCARD	3	/* discarding rest of message */
61 
62 void
63 collect(fp, smtpmode, hdrp, e)
64 	FILE *fp;
65 	bool smtpmode;
66 	HDR **hdrp;
67 	register ENVELOPE *e;
68 {
69 	register FILE *volatile tf;
70 	volatile bool ignrdot = smtpmode ? FALSE : IgnrDot;
71 	volatile time_t dbto = smtpmode ? TimeOuts.to_datablock : 0;
72 	register char *volatile bp;
73 	volatile int c = EOF;
74 	volatile bool inputerr = FALSE;
75 	bool headeronly;
76 	char *volatile buf;
77 	volatile int buflen;
78 	volatile int istate;
79 	volatile int mstate;
80 	u_char *volatile pbp;
81 	int hdrslen = 0;
82 	u_char peekbuf[8];
83 	char dfname[MAXQFNAME];
84 	char bufbuf[MAXLINE];
85 	extern bool isheader __P((char *));
86 	extern void tferror __P((FILE *volatile, ENVELOPE *));
87 
88 	headeronly = hdrp != NULL;
89 
90 	/*
91 	**  Create the temp file name and create the file.
92 	*/
93 
94 	if (!headeronly)
95 	{
96 		int tfd;
97 		struct stat stbuf;
98 
99 		strcpy(dfname, queuename(e, 'd'));
100 		tfd = dfopen(dfname, O_WRONLY|O_CREAT|O_TRUNC, FileMode, SFF_ANYFILE);
101 		if (tfd < 0 || (tf = fdopen(tfd, "w")) == NULL)
102 		{
103 			syserr("Cannot create %s", dfname);
104 			e->e_flags |= EF_NO_BODY_RETN;
105 			finis(TRUE, ExitStat);
106 		}
107 		if (fstat(fileno(tf), &stbuf) < 0)
108 			e->e_dfino = -1;
109 		else
110 		{
111 			e->e_dfdev = stbuf.st_dev;
112 			e->e_dfino = stbuf.st_ino;
113 		}
114 		HasEightBits = FALSE;
115 		e->e_msgsize = 0;
116 		e->e_flags |= EF_HAS_DF;
117 	}
118 
119 	/*
120 	**  Tell ARPANET to go ahead.
121 	*/
122 
123 	if (smtpmode)
124 		message("354 Enter mail, end with \".\" on a line by itself");
125 
126 	if (tTd(30, 2))
127 		printf("collect\n");
128 
129 	/*
130 	**  Read the message.
131 	**
132 	**	This is done using two interleaved state machines.
133 	**	The input state machine is looking for things like
134 	**	hidden dots; the message state machine is handling
135 	**	the larger picture (e.g., header versus body).
136 	*/
137 
138 	buf = bp = bufbuf;
139 	buflen = sizeof bufbuf;
140 	pbp = peekbuf;
141 	istate = IS_BOL;
142 	mstate = SaveFrom ? MS_HEADER : MS_UFROM;
143 	CollectProgress = FALSE;
144 
145 	if (dbto != 0)
146 	{
147 		/* handle possible input timeout */
148 		if (setjmp(CtxCollectTimeout) != 0)
149 		{
150 			if (LogLevel > 2)
151 				sm_syslog(LOG_NOTICE, e->e_id,
152 				    "timeout waiting for input from %s during message collect",
153 				    CurHostName ? CurHostName : "<local machine>");
154 			errno = 0;
155 			usrerr("451 timeout waiting for input during message collect");
156 			goto readerr;
157 		}
158 		CollectTimeout = setevent(dbto, collecttimeout, dbto);
159 	}
160 
161 	for (;;)
162 	{
163 		if (tTd(30, 35))
164 			printf("top, istate=%d, mstate=%d\n", istate, mstate);
165 		for (;;)
166 		{
167 			if (pbp > peekbuf)
168 				c = *--pbp;
169 			else
170 			{
171 				while (!feof(fp) && !ferror(fp))
172 				{
173 					errno = 0;
174 					c = getc(fp);
175 					if (errno != EINTR)
176 						break;
177 					clearerr(fp);
178 				}
179 				CollectProgress = TRUE;
180 				if (TrafficLogFile != NULL && !headeronly)
181 				{
182 					if (istate == IS_BOL)
183 						fprintf(TrafficLogFile, "%05d <<< ",
184 							(int) getpid());
185 					if (c == EOF)
186 						fprintf(TrafficLogFile, "[EOF]\n");
187 					else
188 						putc(c, TrafficLogFile);
189 				}
190 				if (c == EOF)
191 					goto readerr;
192 				if (SevenBitInput)
193 					c &= 0x7f;
194 				else
195 					HasEightBits |= bitset(0x80, c);
196 			}
197 			if (tTd(30, 94))
198 				printf("istate=%d, c=%c (0x%x)\n",
199 					istate, c, c);
200 			switch (istate)
201 			{
202 			  case IS_BOL:
203 				if (c == '.')
204 				{
205 					istate = IS_DOT;
206 					continue;
207 				}
208 				break;
209 
210 			  case IS_DOT:
211 				if (c == '\n' && !ignrdot &&
212 				    !bitset(EF_NL_NOT_EOL, e->e_flags))
213 					goto readerr;
214 				else if (c == '\r' &&
215 					 !bitset(EF_CRLF_NOT_EOL, e->e_flags))
216 				{
217 					istate = IS_DOTCR;
218 					continue;
219 				}
220 				else if (c != '.' ||
221 					 (OpMode != MD_SMTP &&
222 					  OpMode != MD_DAEMON &&
223 					  OpMode != MD_ARPAFTP))
224 				{
225 					*pbp++ = c;
226 					c = '.';
227 				}
228 				break;
229 
230 			  case IS_DOTCR:
231 				if (c == '\n' && !ignrdot)
232 					goto readerr;
233 				else
234 				{
235 					/* push back the ".\rx" */
236 					*pbp++ = c;
237 					*pbp++ = '\r';
238 					c = '.';
239 				}
240 				break;
241 
242 			  case IS_CR:
243 				if (c == '\n')
244 					istate = IS_BOL;
245 				else
246 				{
247 					ungetc(c, fp);
248 					c = '\r';
249 					istate = IS_NORM;
250 				}
251 				goto bufferchar;
252 			}
253 
254 			if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags))
255 			{
256 				istate = IS_CR;
257 				continue;
258 			}
259 			else if (c == '\n' && !bitset(EF_NL_NOT_EOL, e->e_flags))
260 				istate = IS_BOL;
261 			else
262 				istate = IS_NORM;
263 
264 bufferchar:
265 			if (!headeronly)
266 				e->e_msgsize++;
267 			switch (mstate)
268 			{
269 			  case MS_BODY:
270 				/* just put the character out */
271 				if (MaxMessageSize <= 0 ||
272 				    e->e_msgsize <= MaxMessageSize)
273 					putc(c, tf);
274 
275 				/* fall through */
276 
277 			  case MS_DISCARD:
278 				continue;
279 			}
280 
281 			/* header -- buffer up */
282 			if (bp >= &buf[buflen - 2])
283 			{
284 				char *obuf;
285 
286 				if (mstate != MS_HEADER)
287 					break;
288 
289 				/* out of space for header */
290 				obuf = buf;
291 				if (buflen < MEMCHUNKSIZE)
292 					buflen *= 2;
293 				else
294 					buflen += MEMCHUNKSIZE;
295 				buf = xalloc(buflen);
296 				bcopy(obuf, buf, bp - obuf);
297 				bp = &buf[bp - obuf];
298 				if (obuf != bufbuf)
299 					free(obuf);
300 			}
301 			if (c >= 0200 && c <= 0237)
302 			{
303 #if 0	/* causes complaints -- figure out something for 8.9 */
304 				usrerr("Illegal character 0x%x in header", c);
305 #endif
306 			}
307 			else if (c != '\0')
308 			{
309 				*bp++ = c;
310 				if (MaxHeadersLength > 0 &&
311 				    ++hdrslen > MaxHeadersLength)
312 				{
313 					sm_syslog(LOG_NOTICE, e->e_id,
314 						  "headers too large (%d max) from %s during message collect",
315 						  MaxHeadersLength,
316 						  CurHostName != NULL ? CurHostName : "localhost");
317 					errno = 0;
318 					e->e_flags |= EF_CLRQUEUE;
319 					e->e_status = "5.6.0";
320 					usrerr("552 Headers too large (%d max)",
321 						MaxHeadersLength);
322 					mstate = MS_DISCARD;
323 				}
324 			}
325 			if (istate == IS_BOL)
326 				break;
327 		}
328 		*bp = '\0';
329 
330 nextstate:
331 		if (tTd(30, 35))
332 			printf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n",
333 				istate, mstate, buf);
334 		switch (mstate)
335 		{
336 		  case MS_UFROM:
337 			mstate = MS_HEADER;
338 #ifndef NOTUNIX
339 			if (strncmp(buf, "From ", 5) == 0)
340 			{
341 				extern void eatfrom __P((char *volatile, ENVELOPE *));
342 
343 				bp = buf;
344 				eatfrom(buf, e);
345 				continue;
346 			}
347 #endif
348 			/* fall through */
349 
350 		  case MS_HEADER:
351 			if (!isheader(buf))
352 			{
353 				mstate = MS_BODY;
354 				goto nextstate;
355 			}
356 
357 			/* check for possible continuation line */
358 			do
359 			{
360 				clearerr(fp);
361 				errno = 0;
362 				c = getc(fp);
363 			} while (errno == EINTR);
364 			if (c != EOF)
365 				ungetc(c, fp);
366 			if (c == ' ' || c == '\t')
367 			{
368 				/* yep -- defer this */
369 				continue;
370 			}
371 
372 			/* trim off trailing CRLF or NL */
373 			if (*--bp != '\n' || *--bp != '\r')
374 				bp++;
375 			*bp = '\0';
376 
377 			if (bitset(H_EOH, chompheader(buf, FALSE, hdrp, e)))
378 			{
379 				mstate = MS_BODY;
380 				goto nextstate;
381 			}
382 			break;
383 
384 		  case MS_BODY:
385 			if (tTd(30, 1))
386 				printf("EOH\n");
387 			if (headeronly)
388 				goto readerr;
389 			bp = buf;
390 
391 			/* toss blank line */
392 			if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) &&
393 				bp[0] == '\r' && bp[1] == '\n') ||
394 			    (!bitset(EF_NL_NOT_EOL, e->e_flags) &&
395 				bp[0] == '\n'))
396 			{
397 				break;
398 			}
399 
400 			/* if not a blank separator, write it out */
401 			if (MaxMessageSize <= 0 ||
402 			    e->e_msgsize <= MaxMessageSize)
403 			{
404 				while (*bp != '\0')
405 					putc(*bp++, tf);
406 			}
407 			break;
408 		}
409 		bp = buf;
410 	}
411 
412 readerr:
413 	if ((feof(fp) && smtpmode) || ferror(fp))
414 	{
415 		const char *errmsg = errstring(errno);
416 
417 		if (tTd(30, 1))
418 			printf("collect: premature EOM: %s\n", errmsg);
419 		if (LogLevel >= 2)
420 			sm_syslog(LOG_WARNING, e->e_id,
421 				"collect: premature EOM: %s", errmsg);
422 		inputerr = TRUE;
423 	}
424 
425 	/* reset global timer */
426 	clrevent(CollectTimeout);
427 
428 	if (headeronly)
429 		return;
430 
431 	if (tf != NULL &&
432 	    (fflush(tf) != 0 || ferror(tf) ||
433 	     (SuperSafe && fsync(fileno(tf)) < 0) ||
434 	     fclose(tf) < 0))
435 	{
436 		tferror(tf, e);
437 		flush_errors(TRUE);
438 		finis(TRUE, ExitStat);
439 	}
440 
441 	/* An EOF when running SMTP is an error */
442 	if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
443 	{
444 		char *host;
445 		char *problem;
446 
447 		host = RealHostName;
448 		if (host == NULL)
449 			host = "localhost";
450 
451 		if (feof(fp))
452 			problem = "unexpected close";
453 		else if (ferror(fp))
454 			problem = "I/O error";
455 		else
456 			problem = "read timeout";
457 		if (LogLevel > 0 && feof(fp))
458 			sm_syslog(LOG_NOTICE, e->e_id,
459 			    "collect: %s on connection from %.100s, sender=%s: %s",
460 			    problem, host,
461 			    shortenstring(e->e_from.q_paddr, MAXSHORTSTR),
462 			    errstring(errno));
463 		if (feof(fp))
464 			usrerr("451 collect: %s on connection from %s, from=%s",
465 				problem, host,
466 				shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
467 		else
468 			syserr("451 collect: %s on connection from %s, from=%s",
469 				problem, host,
470 				shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
471 
472 		/* don't return an error indication */
473 		e->e_to = NULL;
474 		e->e_flags &= ~EF_FATALERRS;
475 		e->e_flags |= EF_CLRQUEUE;
476 
477 		/* and don't try to deliver the partial message either */
478 		if (InChild)
479 			ExitStat = EX_QUIT;
480 		finis(TRUE, ExitStat);
481 	}
482 
483 	/*
484 	**  Find out some information from the headers.
485 	**	Examples are who is the from person & the date.
486 	*/
487 
488 	eatheader(e, TRUE);
489 
490 	if (GrabTo && e->e_sendqueue == NULL)
491 		usrerr("No recipient addresses found in header");
492 
493 	/* collect statistics */
494 	if (OpMode != MD_VERIFY)
495 		markstats(e, (ADDRESS *) NULL, FALSE);
496 
497 #if _FFR_DSN_RRT_OPTION
498 	/*
499 	**  If we have a Return-Receipt-To:, turn it into a DSN.
500 	*/
501 
502 	if (RrtImpliesDsn && hvalue("return-receipt-to", e->e_header) != NULL)
503 	{
504 		ADDRESS *q;
505 
506 		for (q = e->e_sendqueue; q != NULL; q = q->q_next)
507 			if (!bitset(QHASNOTIFY, q->q_flags))
508 				q->q_flags |= QHASNOTIFY|QPINGONSUCCESS;
509 	}
510 #endif
511 
512 	/*
513 	**  Add an Apparently-To: line if we have no recipient lines.
514 	*/
515 
516 	if (hvalue("to", e->e_header) != NULL ||
517 	    hvalue("cc", e->e_header) != NULL ||
518 	    hvalue("apparently-to", e->e_header) != NULL)
519 	{
520 		/* have a valid recipient header -- delete Bcc: headers */
521 		e->e_flags |= EF_DELETE_BCC;
522 	}
523 	else if (hvalue("bcc", e->e_header) == NULL)
524 	{
525 		/* no valid recipient headers */
526 		register ADDRESS *q;
527 		char *hdr = NULL;
528 
529 		/* create an Apparently-To: field */
530 		/*    that or reject the message.... */
531 		switch (NoRecipientAction)
532 		{
533 		  case NRA_ADD_APPARENTLY_TO:
534 			hdr = "Apparently-To";
535 			break;
536 
537 		  case NRA_ADD_TO:
538 			hdr = "To";
539 			break;
540 
541 		  case NRA_ADD_BCC:
542 			addheader("Bcc", " ", &e->e_header);
543 			break;
544 
545 		  case NRA_ADD_TO_UNDISCLOSED:
546 			addheader("To", "undisclosed-recipients:;", &e->e_header);
547 			break;
548 		}
549 
550 		if (hdr != NULL)
551 		{
552 			for (q = e->e_sendqueue; q != NULL; q = q->q_next)
553 			{
554 				if (q->q_alias != NULL)
555 					continue;
556 				if (tTd(30, 3))
557 					printf("Adding %s: %s\n",
558 						hdr, q->q_paddr);
559 				addheader(hdr, q->q_paddr, &e->e_header);
560 			}
561 		}
562 	}
563 
564 	/* check for message too large */
565 	if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize)
566 	{
567 		e->e_flags |= EF_NO_BODY_RETN|EF_CLRQUEUE;
568 		e->e_status = "5.2.3";
569 		usrerr("552 Message exceeds maximum fixed size (%ld)",
570 			MaxMessageSize);
571 		if (LogLevel > 6)
572 			sm_syslog(LOG_NOTICE, e->e_id,
573 				"message size (%ld) exceeds maximum (%ld)",
574 				e->e_msgsize, MaxMessageSize);
575 	}
576 
577 	/* check for illegal 8-bit data */
578 	if (HasEightBits)
579 	{
580 		e->e_flags |= EF_HAS8BIT;
581 		if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode) &&
582 		    !bitset(EF_IS_MIME, e->e_flags))
583 		{
584 			e->e_status = "5.6.1";
585 			usrerr("554 Eight bit data not allowed");
586 		}
587 	}
588 	else
589 	{
590 		/* if it claimed to be 8 bits, well, it lied.... */
591 		if (e->e_bodytype != NULL &&
592 		    strcasecmp(e->e_bodytype, "8BITMIME") == 0)
593 			e->e_bodytype = "7BIT";
594 	}
595 
596 	if ((e->e_dfp = fopen(dfname, "r")) == NULL)
597 	{
598 		/* we haven't acked receipt yet, so just chuck this */
599 		syserr("Cannot reopen %s", dfname);
600 		finis(TRUE, ExitStat);
601 	}
602 }
603 
604 
605 static void
606 collecttimeout(timeout)
607 	time_t timeout;
608 {
609 	/* if no progress was made, die now */
610 	if (!CollectProgress)
611 		longjmp(CtxCollectTimeout, 1);
612 
613 	/* otherwise reset the timeout */
614 	CollectTimeout = setevent(timeout, collecttimeout, timeout);
615 	CollectProgress = FALSE;
616 }
617 /*
618 **  TFERROR -- signal error on writing the temporary file.
619 **
620 **	Parameters:
621 **		tf -- the file pointer for the temporary file.
622 **		e -- the current envelope.
623 **
624 **	Returns:
625 **		none.
626 **
627 **	Side Effects:
628 **		Gives an error message.
629 **		Arranges for following output to go elsewhere.
630 */
631 
632 void
633 tferror(tf, e)
634 	FILE *volatile tf;
635 	register ENVELOPE *e;
636 {
637 	setstat(EX_IOERR);
638 	if (errno == ENOSPC)
639 	{
640 #if STAT64 > 0
641 		struct stat64 st;
642 #else
643 		struct stat st;
644 #endif
645 		long avail;
646 		long bsize;
647 		extern long freediskspace __P((char *, long *));
648 
649 		e->e_flags |= EF_NO_BODY_RETN;
650 
651 		if (
652 #if STAT64 > 0
653 		    fstat64(fileno(tf), &st)
654 #else
655 		    fstat(fileno(tf), &st)
656 #endif
657 		    < 0)
658 		  st.st_size = 0;
659 		(void) freopen(queuename(e, 'd'), "w", tf);
660 		if (st.st_size <= 0)
661 			fprintf(tf, "\n*** Mail could not be accepted");
662 		else if (sizeof st.st_size > sizeof (long))
663 			fprintf(tf, "\n*** Mail of at least %s bytes could not be accepted\n",
664 				quad_to_string(st.st_size));
665 		else
666 			fprintf(tf, "\n*** Mail of at least %lu bytes could not be accepted\n",
667 				(unsigned long) st.st_size);
668 		fprintf(tf, "*** at %s due to lack of disk space for temp file.\n",
669 			MyHostName);
670 		avail = freediskspace(QueueDir, &bsize);
671 		if (avail > 0)
672 		{
673 			if (bsize > 1024)
674 				avail *= bsize / 1024;
675 			else if (bsize < 1024)
676 				avail /= 1024 / bsize;
677 			fprintf(tf, "*** Currently, %ld kilobytes are available for mail temp files.\n",
678 				avail);
679 		}
680 		e->e_status = "4.3.1";
681 		usrerr("452 Out of disk space for temp file");
682 	}
683 	else
684 		syserr("collect: Cannot write tf%s", e->e_id);
685 	if (freopen("/dev/null", "w", tf) == NULL)
686 		sm_syslog(LOG_ERR, e->e_id,
687 			  "tferror: freopen(\"/dev/null\") failed: %s",
688 			  errstring(errno));
689 }
690 /*
691 **  EATFROM -- chew up a UNIX style from line and process
692 **
693 **	This does indeed make some assumptions about the format
694 **	of UNIX messages.
695 **
696 **	Parameters:
697 **		fm -- the from line.
698 **
699 **	Returns:
700 **		none.
701 **
702 **	Side Effects:
703 **		extracts what information it can from the header,
704 **		such as the date.
705 */
706 
707 # ifndef NOTUNIX
708 
709 char	*DowList[] =
710 {
711 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
712 };
713 
714 char	*MonthList[] =
715 {
716 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
717 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
718 	NULL
719 };
720 
721 void
722 eatfrom(fm, e)
723 	char *volatile fm;
724 	register ENVELOPE *e;
725 {
726 	register char *p;
727 	register char **dt;
728 
729 	if (tTd(30, 2))
730 		printf("eatfrom(%s)\n", fm);
731 
732 	/* find the date part */
733 	p = fm;
734 	while (*p != '\0')
735 	{
736 		/* skip a word */
737 		while (*p != '\0' && *p != ' ')
738 			p++;
739 		while (*p == ' ')
740 			p++;
741 		if (!(isascii(*p) && isupper(*p)) ||
742 		    p[3] != ' ' || p[13] != ':' || p[16] != ':')
743 			continue;
744 
745 		/* we have a possible date */
746 		for (dt = DowList; *dt != NULL; dt++)
747 			if (strncmp(*dt, p, 3) == 0)
748 				break;
749 		if (*dt == NULL)
750 			continue;
751 
752 		for (dt = MonthList; *dt != NULL; dt++)
753 			if (strncmp(*dt, &p[4], 3) == 0)
754 				break;
755 		if (*dt != NULL)
756 			break;
757 	}
758 
759 	if (*p != '\0')
760 	{
761 		char *q;
762 
763 		/* we have found a date */
764 		q = xalloc(25);
765 		(void) strncpy(q, p, 25);
766 		q[24] = '\0';
767 		q = arpadate(q);
768 		define('a', newstr(q), e);
769 	}
770 }
771 
772 # endif /* NOTUNIX */
773