1 # include <stdio.h>
2 # include <pwd.h>
3 # include <signal.h>
4 # include <ctype.h>
5 # include "postbox.h"
6 # ifdef LOG
7 # include <syslog.h>
8 # endif LOG
9 
10 static char SccsId[] = "@(#)deliver.c	3.2	03/07/81";
11 
12 /*
13 **  DELIVER -- Deliver a message to a particular address.
14 **
15 **	Algorithm:
16 **		Compute receiving network (i.e., mailer), host, & user.
17 **		If local, see if this is really a program name.
18 **		Build argument for the mailer.
19 **		Create pipe through edit fcn if appropriate.
20 **		Fork.
21 **			Child: call mailer
22 **		Parent: call editfcn if specified.
23 **		Wait for mailer to finish.
24 **		Interpret exit status.
25 **
26 **	Parameters:
27 **		to -- the address to deliver the message to.
28 **		editfcn -- if non-NULL, we want to call this function
29 **			to output the letter (instead of just out-
30 **			putting it raw).
31 **
32 **	Returns:
33 **		zero -- successfully delivered.
34 **		else -- some failure, see ExitStat for more info.
35 **
36 **	Side Effects:
37 **		The standard input is passed off to someone.
38 **
39 **	WARNING:
40 **		The standard input is shared amongst all children,
41 **		including the file pointer.  It is critical that the
42 **		parent waits for the child to finish before forking
43 **		another child.
44 **
45 **	Called By:
46 **		main
47 **		savemail
48 **
49 **	Files:
50 **		standard input -- must be opened to the message to
51 **			deliver.
52 */
53 
54 deliver(to, editfcn)
55 	ADDRESS *to;
56 	int (*editfcn)();
57 {
58 	register struct mailer *m;
59 	char *host;
60 	char *user;
61 	extern struct passwd *getpwnam();
62 	char **pvp;
63 	extern char **buildargv();
64 	auto int st;
65 	register int i;
66 	register char *p;
67 	int pid;
68 	int pvect[2];
69 	extern FILE *fdopen();
70 	extern int errno;
71 	FILE *mfile;
72 	extern putmessage();
73 	extern pipesig();
74 	extern char *index();
75 	extern bool checkcompat();
76 
77 	/*
78 	**  Compute receiving mailer, host, and to addreses.
79 	**	Do some initialization first.  To is the to address
80 	**	for error messages.
81 	*/
82 
83 	To = to->q_paddr;
84 	m = to->q_mailer;
85 	user = to->q_user;
86 	host = to->q_host;
87 	Errors = 0;
88 	errno = 0;
89 # ifdef DEBUG
90 	if (Debug)
91 		printf("deliver(%s [%d, `%s', `%s'])\n", To, m, host, user);
92 # endif DEBUG
93 
94 	/*
95 	**  Check to see that these people are allowed to talk to each other.
96 	*/
97 
98 	if (!checkcompat(to))
99 		return(giveresponse(EX_UNAVAILABLE, TRUE, m));
100 
101 	/*
102 	**  Remove quote bits from user/host.
103 	*/
104 
105 	for (p = user; (*p++ &= 0177) != '\0'; )
106 		continue;
107 	if (host != NULL)
108 		for (p = host; (*p++ &= 0177) != '\0'; )
109 			continue;
110 
111 	/*
112 	**  Strip quote bits from names if the mailer wants it.
113 	*/
114 
115 	if (bitset(M_STRIPQ, m->m_flags))
116 	{
117 		stripquotes(user);
118 		stripquotes(host);
119 	}
120 
121 	/*
122 	**  See if this user name is "special".
123 	**	If the user is a program, diddle with the mailer spec.
124 	**	If the user name has a slash in it, assume that this
125 	**		is a file -- send it off without further ado.
126 	**		Note that this means that editfcn's will not
127 	**		be applied to the message.
128 	*/
129 
130 	if (m == &Mailer[0])
131 	{
132 		if (*user == '|')
133 		{
134 			user++;
135 			m = &Mailer[1];
136 		}
137 		else
138 		{
139 			if (index(user, '/') != NULL)
140 			{
141 				i = mailfile(user);
142 				giveresponse(i, TRUE, m);
143 				return (i);
144 			}
145 		}
146 	}
147 
148 	/*
149 	**  See if the user exists.
150 	**	Strictly, this is only needed to print a pretty
151 	**	error message.
152 	**
153 	**	>>>>>>>>>> This clause assumes that the local mailer
154 	**	>> NOTE >> cannot do any further aliasing; that
155 	**	>>>>>>>>>> function is subsumed by postbox.
156 	*/
157 
158 	if (m == &Mailer[0])
159 	{
160 		if (getpwnam(user) == NULL)
161 		{
162 			giveresponse(EX_NOUSER, TRUE, m);
163 			return (EX_NOUSER);
164 		}
165 	}
166 
167 	/*
168 	**  Call the mailer.
169 	**	The argument vector gets built, pipes
170 	**	are created as necessary, and we fork & exec as
171 	**	appropriate.
172 	*/
173 
174 	pvp = buildargv(m, host, user, From.q_paddr);
175 	if (pvp == NULL)
176 	{
177 		usrerr("name too long");
178 		return (-1);
179 	}
180 	rewind(stdin);
181 
182 	/* create a pipe to shove the mail through */
183 	if (pipe(pvect) < 0)
184 	{
185 		syserr("pipe");
186 		return (-1);
187 	}
188 # ifdef VFORK
189 	pid = vfork();
190 # else
191 	pid = fork();
192 # endif
193 	if (pid < 0)
194 	{
195 		syserr("Cannot fork");
196 		close(pvect[0]);
197 		close(pvect[1]);
198 		return (-1);
199 	}
200 	else if (pid == 0)
201 	{
202 		/* child -- set up input & exec mailer */
203 		/* make diagnostic output be standard output */
204 		close(2);
205 		dup(1);
206 		signal(SIGINT, SIG_IGN);
207 		close(0);
208 		if (dup(pvect[0]) < 0)
209 		{
210 			syserr("Cannot dup to zero!");
211 			_exit(EX_OSERR);
212 		}
213 		close(pvect[0]);
214 		close(pvect[1]);
215 		if (!bitset(M_RESTR, m->m_flags))
216 			setuid(getuid());
217 # ifndef VFORK
218 		/*
219 		**  We have to be careful with vfork - we can't mung up the
220 		**  memory but we don't want the mailer to inherit any extra
221 		**  open files.  Chances are the mailer won't
222 		**  care about an extra file, but then again you never know.
223 		**  Actually, we would like to close(fileno(pwf)), but it's
224 		**  declared static so we can't.  But if we fclose(pwf), which
225 		**  is what endpwent does, it closes it in the parent too and
226 		**  the next getpwnam will be slower.  If you have a weird
227 		**  mailer that chokes on the extra file you should do the
228 		**  endpwent().
229 		**
230 		**  Similar comments apply to log.  However, openlog is
231 		**  clever enough to set the FIOCLEX mode on the file,
232 		**  so it will be closed automatically on the exec.
233 		*/
234 
235 		endpwent();
236 # ifdef LOG
237 		closelog();
238 # endif LOG
239 # endif VFORK
240 		execv(m->m_mailer, pvp);
241 		/* syserr fails because log is closed */
242 		/* syserr("Cannot exec %s", m->m_mailer); */
243 		printf("Cannot exec %s\n", m->m_mailer);
244 		fflush(stdout);
245 		_exit(EX_UNAVAILABLE);
246 	}
247 
248 	/* write out message to mailer */
249 	close(pvect[0]);
250 	signal(SIGPIPE, pipesig);
251 	mfile = fdopen(pvect[1], "w");
252 	if (editfcn == NULL)
253 		editfcn = putmessage;
254 	(*editfcn)(mfile, m);
255 	fclose(mfile);
256 
257 	/*
258 	**  Wait for child to die and report status.
259 	**	We should never get fatal errors (e.g., segmentation
260 	**	violation), so we report those specially.  For other
261 	**	errors, we choose a status message (into statmsg),
262 	**	and if it represents an error, we print it.
263 	*/
264 
265 	while ((i = wait(&st)) > 0 && i != pid)
266 		continue;
267 	if (i < 0)
268 	{
269 		syserr("wait");
270 		return (-1);
271 	}
272 	if ((st & 0377) != 0)
273 	{
274 		syserr("%s: stat %o", pvp[0], st);
275 		ExitStat = EX_UNAVAILABLE;
276 		return (-1);
277 	}
278 	i = (st >> 8) & 0377;
279 	giveresponse(i, TRUE, m);
280 	return (i);
281 }
282 /*
283 **  GIVERESPONSE -- Interpret an error response from a mailer
284 **
285 **	Parameters:
286 **		stat -- the status code from the mailer (high byte
287 **			only; core dumps must have been taken care of
288 **			already).
289 **		force -- if set, force an error message output, even
290 **			if the mailer seems to like to print its own
291 **			messages.
292 **		m -- the mailer descriptor for this mailer.
293 **
294 **	Returns:
295 **		stat.
296 **
297 **	Side Effects:
298 **		Errors may be incremented.
299 **		ExitStat may be set.
300 **
301 **	Called By:
302 **		deliver
303 */
304 
305 giveresponse(stat, force, m)
306 	int stat;
307 	int force;
308 	register struct mailer *m;
309 {
310 	register char *statmsg;
311 	extern char *SysExMsg[];
312 	register int i;
313 	extern int N_SysEx;
314 	extern long MsgSize;
315 	char buf[30];
316 
317 	i = stat - EX__BASE;
318 	if (i < 0 || i > N_SysEx)
319 		statmsg = NULL;
320 	else
321 		statmsg = SysExMsg[i];
322 	if (stat == 0)
323 		statmsg = "ok";
324 	else
325 	{
326 		Errors++;
327 		if (statmsg == NULL && m->m_badstat != 0)
328 		{
329 			stat = m->m_badstat;
330 			i = stat - EX__BASE;
331 # ifdef DEBUG
332 			if (i < 0 || i >= N_SysEx)
333 				syserr("Bad m_badstat %d", stat);
334 			else
335 # endif DEBUG
336 			statmsg = SysExMsg[i];
337 		}
338 		if (statmsg == NULL)
339 			usrerr("unknown mailer response %d", stat);
340 		else if (force || !bitset(M_QUIET, m->m_flags))
341 			usrerr("%s", statmsg);
342 	}
343 
344 	/*
345 	**  Final cleanup.
346 	**	Log a record of the transaction.  Compute the new
347 	**	ExitStat -- if we already had an error, stick with
348 	**	that.
349 	*/
350 
351 	if (statmsg == NULL)
352 	{
353 		sprintf(buf, "error %d", stat);
354 		statmsg = buf;
355 	}
356 
357 # ifdef LOG
358 	syslog(LOG_INFO, "%s->%s: %ld: %s", From.q_paddr, To, MsgSize, statmsg);
359 # endif LOG
360 	setstat(stat);
361 	return (stat);
362 }
363 /*
364 **  PUTMESSAGE -- output a message to the final mailer.
365 **
366 **	This routine takes care of recreating the header from the
367 **	in-core copy, etc.
368 **
369 **	Parameters:
370 **		fp -- file to output onto.
371 **		m -- a mailer descriptor.
372 **
373 **	Returns:
374 **		none.
375 **
376 **	Side Effects:
377 **		The message is written onto fp.
378 */
379 
380 putmessage(fp, m)
381 	FILE *fp;
382 	struct mailer *m;
383 {
384 	char buf[BUFSIZ];
385 	register int i;
386 	HDR *h;
387 	register char *p;
388 	extern char *arpadate();
389 	extern char *hvalue();
390 	bool anyheader = FALSE;
391 	extern char *translate();
392 
393 	/* clear all "used" bits */
394 	for (h = Header; h != NULL; h = h->h_link)
395 		h->h_flags &= ~H_USED;
396 
397 	/* output date if needed by mailer */
398 	p = hvalue("date");
399 	if (bitset(M_NEEDDATE, m->m_flags) && p == NULL)
400 		p = arpadate(Date);
401 	if (p != NULL)
402 	{
403 		fprintf(fp, "Date: %s\n", p);
404 		anyheader = TRUE;
405 	}
406 
407 	/* output from line if needed by mailer */
408 	p = hvalue("from");
409 	if (bitset(M_NEEDFROM, m->m_flags) && p == NULL)
410 	{
411 		char frombuf[MAXLINE];
412 		extern char *FullName;
413 
414 		p = translate("$f", From.q_mailer, From.q_paddr, NULL, NULL);
415 		if (FullName != NULL)
416 			fprintf(fp, "From: %s <%s>\n", FullName, p);
417 		else
418 			fprintf(fp, "From: %s\n", p);
419 		free(p);
420 		anyheader = TRUE;
421 	}
422 	else if (p != NULL)
423 	{
424 		fprintf(fp, "From: %s\n", p);
425 		anyheader = TRUE;
426 	}
427 
428 	/* output message-id field if needed */
429 	p = hvalue("message-id");
430 	if (bitset(M_MSGID, m->m_flags) && p == NULL)
431 		p = MsgId;
432 	if (p != NULL)
433 	{
434 		fprintf(fp, "Message-Id: %s\n", p);
435 		anyheader = TRUE;
436 	}
437 
438 	/* output any other header lines */
439 	for (h = Header; h != NULL; h = h->h_link)
440 	{
441 		if (bitset(H_USED, h->h_flags))
442 			continue;
443 		fprintf(fp, "%s: %s\n", capitalize(h->h_field), h->h_value);
444 		h->h_flags |= H_USED;
445 		anyheader = TRUE;
446 	}
447 
448 	if (anyheader)
449 		fprintf(fp, "\n");
450 
451 	/* output the body of the message */
452 	while (!ferror(fp) && (i = read(0, buf, BUFSIZ)) > 0)
453 		fwrite(buf, 1, i, fp);
454 
455 	if (ferror(fp))
456 	{
457 		syserr("putmessage: write error");
458 		setstat(EX_IOERR);
459 	}
460 }
461 /*
462 **  PIPESIG -- Handle broken pipe signals
463 **
464 **	This just logs an error.
465 **
466 **	Parameters:
467 **		none
468 **
469 **	Returns:
470 **		none
471 **
472 **	Side Effects:
473 **		logs an error message.
474 */
475 
476 pipesig()
477 {
478 	syserr("Broken pipe");
479 	signal(SIGPIPE, SIG_IGN);
480 }
481 /*
482 **  SENDTO -- Designate a send list.
483 **
484 **	The parameter is a comma-separated list of people to send to.
485 **	This routine arranges to send to all of them.
486 **
487 **	Parameters:
488 **		list -- the send list.
489 **		copyf -- the copy flag; passed to parse.
490 **
491 **	Returns:
492 **		none
493 **
494 **	Side Effects:
495 **		none.
496 **
497 **	Called By:
498 **		main
499 **		alias
500 */
501 
502 sendto(list, copyf)
503 	char *list;
504 	int copyf;
505 {
506 	register char *p;
507 	register char *q;
508 	register char c;
509 	ADDRESS *a;
510 	extern ADDRESS *parse();
511 	bool more;
512 
513 	/* more keeps track of what the previous delimiter was */
514 	more = TRUE;
515 	for (p = list; more; )
516 	{
517 		/* find the end of this address */
518 		q = p;
519 		while ((c = *p++) != '\0' && c != ',' && c != '\n')
520 			continue;
521 		more = c != '\0';
522 		*--p = '\0';
523 		if (more)
524 			p++;
525 
526 		/* parse the address */
527 		if ((a = parse(q, (ADDRESS *) NULL, copyf)) == NULL)
528 			continue;
529 
530 		/* arrange to send to this person */
531 		recipient(a, &SendQ);
532 	}
533 	To = NULL;
534 }
535 /*
536 **  RECIPIENT -- Designate a message recipient
537 **
538 **	Saves the named person for future mailing.
539 **
540 **	Designates a person as a recipient.  This routine
541 **	does the initial parsing, and checks to see if
542 **	this person has already received the mail.
543 **	It also supresses local network names and turns them into
544 **	local names.
545 **
546 **	Parameters:
547 **		a -- the (preparsed) address header for the recipient.
548 **		targetq -- the queue to add the name to.
549 **
550 **	Returns:
551 **		none.
552 **
553 **	Side Effects:
554 **		none.
555 **
556 **	Called By:
557 **		sendto
558 **		main
559 */
560 
561 recipient(a, targetq)
562 	register ADDRESS *a;
563 	ADDRESS *targetq;
564 {
565 	register ADDRESS *q;
566 	register struct mailer *m;
567 	register char **pvp;
568 	extern bool forward();
569 	extern int errno;
570 	extern bool sameaddr();
571 
572 	To = a->q_paddr;
573 	m = a->q_mailer;
574 	errno = 0;
575 # ifdef DEBUG
576 	if (Debug)
577 		printf("recipient(%s)\n", To);
578 # endif DEBUG
579 
580 	/*
581 	**  Look up this person in the recipient list.  If they
582 	**  are there already, return, otherwise continue.
583 	*/
584 
585 	if (!ForceMail)
586 	{
587 		for (q = &SendQ; (q = nxtinq(q)) != NULL; )
588 			if (sameaddr(q, a, FALSE))
589 			{
590 # ifdef DEBUG
591 				if (Debug)
592 					printf("(%s in SendQ)\n", a->q_paddr);
593 # endif DEBUG
594 				return;
595 			}
596 		for (q = &AliasQ; (q = nxtinq(q)) != NULL; )
597 			if (sameaddr(q, a, FALSE))
598 			{
599 # ifdef DEBUG
600 				if (Debug)
601 					printf("(%s in AliasQ)\n", a->q_paddr);
602 # endif DEBUG
603 				return;
604 			}
605 	}
606 
607 	/*
608 	**  See if the user wants hir mail forwarded.
609 	**	`Forward' must do the forwarding recursively.
610 	*/
611 
612 	if (m == &Mailer[0] && !NoAlias && targetq == &SendQ && forward(a))
613 		return;
614 
615 	/*
616 	**  Put the user onto the target queue.
617 	*/
618 
619 	if (targetq != NULL)
620 	{
621 		putonq(a, targetq);
622 	}
623 
624 	return;
625 }
626 /*
627 **  BUILDARGV -- Build an argument vector for a mail server.
628 **
629 **	Using a template defined in config.c, an argv is built.
630 **	The format of the template is already a vector.  The
631 **	items of this vector are copied, unless a dollar sign
632 **	is encountered.  In this case, the next character
633 **	specifies something else to copy in.  These can be
634 **		$f	The from address.
635 **		$h	The host.
636 **		$u	The user.
637 **		$c	The hop count.
638 **	The vector is built in a local buffer.  A pointer to
639 **	the static argv is returned.
640 **
641 **	Parameters:
642 **		m -- a pointer to the mailer descriptor.
643 **		host -- the host name to send to.
644 **		user -- the user name to send to.
645 **		from -- the person this mail is from.
646 **
647 **	Returns:
648 **		A pointer to an argv.
649 **
650 **	Side Effects:
651 **		none
652 **
653 **	WARNING:
654 **		Since the argv is staticly allocated, any subsequent
655 **		calls will clobber the old argv.
656 **
657 **	Called By:
658 **		deliver
659 */
660 
661 char **
662 buildargv(m, host, user, from)
663 	struct mailer *m;
664 	char *host;
665 	char *user;
666 	char *from;
667 {
668 	register char *p;
669 	register char *q;
670 	static char *pv[MAXPV+1];
671 	char **pvp;
672 	char **mvp;
673 	static char buf[512];
674 	register char *bp;
675 	extern char *translate();
676 
677 	/*
678 	**  Do initial argv setup.
679 	**	Insert the mailer name.  Notice that $x expansion is
680 	**	NOT done on the mailer name.  Then, if the mailer has
681 	**	a picky -f flag, we insert it as appropriate.  This
682 	**	code does not check for 'pv' overflow; this places a
683 	**	manifest lower limit of 4 for MAXPV.
684 	*/
685 
686 	pvp = pv;
687 	bp = buf;
688 
689 	*pvp++ = m->m_argv[0];
690 
691 	/* insert -f or -r flag as appropriate */
692 	if (bitset(M_FOPT|M_ROPT, m->m_flags) && FromFlag)
693 	{
694 		if (bitset(M_FOPT, m->m_flags))
695 			*pvp++ = "-f";
696 		else
697 			*pvp++ = "-r";
698 		*pvp++ = translate(from, m, from, user, host);
699 	}
700 
701 	/*
702 	**  Build the rest of argv.
703 	**	For each prototype parameter, the prototype is
704 	**	scanned character at a time.  If a dollar-sign is
705 	**	found, 'q' is set to the appropriate expansion,
706 	**	otherwise it is null.  Then either the string
707 	**	pointed to by q, or the original character, is
708 	**	interpolated into the buffer.  Buffer overflow is
709 	**	checked.
710 	*/
711 
712 	for (mvp = m->m_argv; (p = *++mvp) != NULL; )
713 	{
714 		if (pvp >= &pv[MAXPV])
715 		{
716 			syserr("Too many parameters to %s", pv[0]);
717 			return (NULL);
718 		}
719 		*pvp++ = translate(p, m, from, user, host);
720 	}
721 	*pvp = NULL;
722 
723 # ifdef DEBUG
724 	if (Debug)
725 	{
726 		printf("Interpolated argv is:\n");
727 		for (mvp = pv; *mvp != NULL; mvp++)
728 			printf("\t%s\n", *mvp);
729 	}
730 # endif DEBUG
731 
732 	return (pv);
733 }
734 /*
735 **  TRANSLATE -- translate a string using $x escapes.
736 **
737 **	Parameters:
738 **		s -- string to translate.
739 **		m -- pointer to mailer descriptor.
740 **
741 **	Returns:
742 **		pointer to translated string.
743 **
744 **	Side Effects:
745 **		none.
746 */
747 
748 char *
749 translate(s, m, from, user, host)
750 	register char *s;
751 	struct mailer *m;
752 	char *from;
753 	char *user;
754 	char *host;
755 {
756 	register char *q;
757 	char buf[MAXNAME];
758 	register char *bp;
759 	char *stack = NULL;
760 	char pbuf[10];
761 	extern char *newstr();
762 	extern char *Macro[];
763 
764 	bp = buf;
765 restart:
766 
767 # ifdef DEBUG
768 	if (Debug)
769 		printf("translate(%s)\n", s);
770 # endif DEBUG
771 	for (; *s != '\0'; s++)
772 	{
773 		/* q will be the interpolated quantity */
774 		q = NULL;
775 		if (*s == '$')
776 		{
777 			if (isupper(*++s))
778 				q = Macro[*s - 'A'];
779 			else
780 			{
781 				switch (*s)
782 				{
783 				  case 'f':	/* from person */
784 					if (stack == NULL && m != NULL)
785 					{
786 						stack = s;
787 						s = m->m_from;
788 						goto restart;
789 					}
790 					q = from;
791 					break;
792 
793 				  case 'u':	/* user */
794 					q = user;
795 					break;
796 
797 				  case 'h':	/* host */
798 					q = host;
799 					break;
800 
801 				  case 'c':	/* hop count */
802 					sprintf(pbuf, "%d", HopCount);
803 					q = pbuf;
804 					break;
805 				}
806 			}
807 		}
808 
809 		/*
810 		**  Interpolate q or output one character
811 		**	Strip quote bits as we proceed.....
812 		*/
813 
814 		if (q != NULL)
815 		{
816 			while (bp < &buf[sizeof buf - 1] && (*bp++ = *q++) != '\0')
817 				continue;
818 			bp--;
819 		}
820 		else if (bp < &buf[sizeof buf - 1])
821 			*bp++ = *s;
822 	}
823 	if (stack != NULL)
824 	{
825 		s = stack;
826 		s++;
827 		stack = NULL;
828 		goto restart;
829 	}
830 	*bp++ = '\0';
831 	if (bp >= &buf[sizeof buf - 1])
832 		return (NULL);
833 # ifdef DEBUG
834 	if (Debug)
835 		printf("translate ==> '%s'\n", buf);
836 # endif DEBUG
837 	return (newstr(buf));
838 }
839 /*
840 **  MAILFILE -- Send a message to a file.
841 **
842 **	Parameters:
843 **		filename -- the name of the file to send to.
844 **
845 **	Returns:
846 **		The exit code associated with the operation.
847 **
848 **	Side Effects:
849 **		none.
850 **
851 **	Called By:
852 **		deliver
853 */
854 
855 mailfile(filename)
856 	char *filename;
857 {
858 	char buf[MAXLINE];
859 	register FILE *f;
860 	auto long tim;
861 	extern char *ctime();
862 
863 	f = fopen(filename, "a");
864 	if (f == NULL)
865 		return (EX_CANTCREAT);
866 
867 	/* output the timestamp */
868 	time(&tim);
869 	fprintf(f, "From %s %s", From.q_paddr, ctime(&tim));
870 	rewind(stdin);
871 	while (fgets(buf, sizeof buf, stdin) != NULL)
872 	{
873 		fputs(buf, f);
874 		if (ferror(f))
875 		{
876 			fclose(f);
877 			return (EX_IOERR);
878 		}
879 	}
880 	fputs("\n", f);
881 	fclose(f);
882 	return (EX_OK);
883 }
884