xref: /illumos-gate/usr/src/cmd/mailx/send.c (revision 7c478bd9)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 /*
32  * University Copyright- Copyright (c) 1982, 1986, 1988
33  * The Regents of the University of California
34  * All Rights Reserved
35  *
36  * University Acknowledgment- Portions of this document are derived from
37  * software developed by the University of California, Berkeley, and its
38  * contributors.
39  */
40 
41 #pragma ident	"%Z%%M%	%I%	%E% SMI"
42 
43 #include "rcv.h"
44 #include <locale.h>
45 
46 /*
47  * mailx -- a modified version of a University of California at Berkeley
48  *	mail program
49  *
50  * Mail to others.
51  */
52 
53 static void		fmt(register char *str, register FILE *fo);
54 static FILE		*infix(struct header *hp, FILE *fi);
55 static void		statusput(register struct message *mp, register FILE *obuf, int doign, int (*fp)(const char *, FILE *));
56 static int		savemail(char name[], struct header *hp, FILE *fi);
57 static int		sendmail(char *str);
58 static int		Sendmail(char *str);
59 
60 static off_t textpos;
61 
62 /*
63  * Send message described by the passed pointer to the
64  * passed output buffer.  Return -1 on error, but normally
65  * the number of lines written.  Adjust the status: field
66  * if need be.  If doign is set, suppress ignored header fields.
67  * Call (*fp)(line, obuf) to print the line.
68  */
69 long
70 msend(
71 	struct message *mailp,
72 	FILE *obuf,
73 	int flag,
74 	int (*fp)(const char *, FILE *))
75 {
76 	register struct message *mp;
77 	long clen, n, c;
78 	FILE *ibuf;
79 	char line[LINESIZE], field[BUFSIZ];
80 	int ishead, infld, fline, dostat, doclen, nread, unused;
81 	char *cp, *cp2;
82 	int doign = flag & M_IGNORE;
83 	int oldign = 0;	/* previous line was ignored */
84 	long lc;
85 
86 	mp = mailp;
87 	if (mp->m_clen == 0)
88 		setclen(mp);
89 	ibuf = setinput(mp);
90 	c = mp->m_size;
91 	ishead = 1;
92 	dostat = 1;
93 	doclen = 1;
94 	infld = 0;
95 	fline = 1;
96 	lc = 0;
97 	clearerr(obuf);
98 	while (c > 0L) {
99 		nread = getline(line, LINESIZE, ibuf, &unused);
100 		c -= nread;
101 		lc++;
102 		if (ishead) {
103 			/*
104 			 * First line is the From line, so no headers
105 			 * there to worry about
106 			 */
107 			if (fline) {
108 				fline = 0;
109 				goto writeit;
110 			}
111 			/*
112 			 * If line is blank, we've reached end of
113 			 * headers, so force out status: field
114 			 * and note that we are no longer in header
115 			 * fields.  Also force out Content-Length: field.
116 			 */
117 			if (line[0] == '\n') {
118 				if (dostat) {
119 					statusput(mailp, obuf, doign, fp);
120 					dostat = 0;
121 				}
122 				if (doclen &&
123 				    !isign("content-length", flag&M_SAVING)) {
124 					snprintf(field, sizeof (field),
125 						"Content-Length: %ld\n",
126 						mp->m_clen - 1);
127 					(*fp)(field, obuf);
128 					if (ferror(obuf))
129 						return(-1);
130 					doclen = 0;
131 				}
132 				ishead = 0;
133 				goto writeit;
134 			}
135 			/*
136 			 * If this line is a continuation
137 			 * of a previous header field, just echo it.
138 			 */
139 			if (isspace(line[0]) && infld)
140 				if (oldign)
141 					continue;
142 				else
143 					goto writeit;
144 			infld = 0;
145 			/*
146 			 * If we are no longer looking at real
147 			 * header lines, force out status:
148 			 * This happens in uucp style mail where
149 			 * there are no headers at all.
150 			 */
151 			if (!headerp(line)) {
152 				if (dostat) {
153 					statusput(mailp, obuf, doign, fp);
154 					dostat = 0;
155 				}
156 				(*fp)("\n", obuf);
157 				ishead = 0;
158 				goto writeit;
159 			}
160 			infld++;
161 			/*
162 			 * Pick up the header field.
163 			 * If it is an ignored field and
164 			 * we care about such things, skip it.
165 			 */
166 			cp = line;
167 			cp2 = field;
168 			while (*cp && *cp != ':' && !isspace(*cp))
169 				*cp2++ = *cp++;
170 			*cp2 = 0;
171 			oldign = doign && isign(field, flag&M_SAVING);
172 			if (oldign)
173 				continue;
174 			/*
175 			 * If the field is "status," go compute and print the
176 			 * real Status: field
177 			 */
178 			if (icequal(field, "status")) {
179 				if (dostat) {
180 					statusput(mailp, obuf, doign, fp);
181 					dostat = 0;
182 				}
183 				continue;
184 			}
185 			if (icequal(field, "content-length")) {
186 				if (doclen) {
187 					snprintf(line, sizeof (line),
188 						"Content-Length: %ld\n",
189 						mp->m_clen - 1);
190 					(*fp)(line, obuf);
191 					if (ferror(obuf))
192 						return(-1);
193 					doclen = 0;
194 				}
195 				continue;
196 			}
197 		}
198 writeit:
199 		if (!ishead && !mp->m_text && mp->m_clen != 0) {
200 			if (line[0] == '\n')
201 				putc('\n', obuf);
202 			clen = mp->m_clen-1;
203 			for (;;) {
204 				n = clen < sizeof line ? clen : sizeof line;
205 				if ((n = fread(line, 1, (int)n, ibuf)) <= 0) {
206 					fprintf(stderr, gettext(
207 					    "\t(Unexpected end-of-file).\n"));
208 					clen = 0;
209 				} else {
210 					if (fwrite(line, 1, (int)n, obuf) != n) {
211 						fprintf(stderr, gettext(
212 					"\tError writing to the new file.\n"));
213 						fflush(obuf);
214 						if (fferror(obuf))
215 							return (-1);
216 					}
217 				}
218 				clen -= n;
219 				if (clen <= 0) {
220 					break;
221 				}
222 			}
223 			c = 0L;
224 		} else {
225 			(*fp)(line, obuf);
226 			if (ferror(obuf))
227 				return(-1);
228 		}
229 	}
230 	fflush(obuf);
231 	if (ferror(obuf))
232 		return(-1);
233 	if (ishead && (mailp->m_flag & MSTATUS))
234 		printf(gettext("failed to fix up status field\n"));
235 	return(lc);
236 }
237 
238 /*
239  * Test if the passed line is a header line, RFC 822 style.
240  */
241 int
242 headerp(register char *line)
243 {
244 	register char *cp = line;
245 
246 	while (*cp && *cp != ' ' && *cp != '\t' && *cp != ':')
247 		cp++;
248 	return(*cp == ':');
249 }
250 
251 /*
252  * Output a reasonable looking status field.
253  * But if "status" is ignored and doign, forget it.
254  */
255 static void
256 statusput(
257 	register struct message *mp,
258 	register FILE *obuf,
259 	int doign,
260 	int (*fp)(const char *, FILE *))
261 {
262 	char statout[12];
263 
264 	if (doign && isign("status", 0))
265 		return;
266 	if ((mp->m_flag & (MNEW|MREAD)) == MNEW)
267 		return;
268 	strcpy(statout, "Status: ");
269 	if (mp->m_flag & MREAD)
270 		strcat(statout, "R");
271 	if ((mp->m_flag & MNEW) == 0)
272 		strcat(statout, "O");
273 	strcat(statout, "\n");
274 	(*fp)(statout, obuf);
275 }
276 
277 /*
278  * Interface between the argument list and the mail1 routine
279  * which does all the dirty work.
280  */
281 
282 int
283 mail(char **people)
284 {
285 	register char *cp2, *cp3;
286 	register int s;
287 	char *buf, **ap;
288 	struct header head;
289 
290 	for (s = 0, ap = people; *ap; ap++)
291 		s += strlen(*ap) + 2;
292 	buf = (char *)salloc((unsigned)(s+1));
293 	cp2 = buf;
294 	for (ap = people; *ap; ap++) {
295 		for (cp3 = *ap; *cp3; ) {
296 			if (*cp3 == ' ' || *cp3 == '\t') {
297 				*cp3++ = ',';
298 				while (*cp3 == ' ' || *cp3 == '\t')
299 					cp3++;
300 			} else
301 				cp3++;
302 		}
303 		cp2 = copy(*ap, cp2);
304 		*cp2++ = ',';
305 		*cp2++ = ' ';
306 	}
307 	*cp2 = '\0';
308 	head.h_to = buf;
309 	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
310 	head.h_others = NOSTRPTR;
311 	head.h_seq = 0;
312 	mail1(&head, Fflag, NOSTR);
313 	return(0);
314 }
315 
316 int
317 sendm(char *str)
318 {
319 	if (value("flipm") != NOSTR)
320 		return(Sendmail(str));
321 	else return(sendmail(str));
322 }
323 
324 int
325 Sendm(char *str)
326 {
327 	if (value("flipm") != NOSTR)
328 		return(sendmail(str));
329 	else return(Sendmail(str));
330 }
331 
332 /*
333  * Interface to the mail1 routine for the -t flag
334  * (read headers from text).
335  */
336 int
337 tmail(void)
338 {
339 	struct header head;
340 
341 	head.h_to = NOSTR;
342 	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
343 	head.h_others = NOSTRPTR;
344 	head.h_seq = 0;
345 	mail1(&head, Fflag, NOSTR);
346 	return(0);
347 }
348 
349 /*
350  * Send mail to a bunch of user names.  The interface is through
351  * the mail routine below.
352  */
353 static int
354 sendmail(char *str)
355 {
356 	struct header head;
357 
358 	if (blankline(str))
359 		head.h_to = NOSTR;
360 	else
361 		head.h_to = addto(NOSTR, str);
362 	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
363 	head.h_others = NOSTRPTR;
364 	head.h_seq = 0;
365 	mail1(&head, 0, NOSTR);
366 	return(0);
367 }
368 
369 /*
370  * Send mail to a bunch of user names.  The interface is through
371  * the mail routine below.
372  * save a copy of the letter
373  */
374 static int
375 Sendmail(char *str)
376 {
377 	struct header head;
378 
379 	if (blankline(str))
380 		head.h_to = NOSTR;
381 	else
382 		head.h_to = addto(NOSTR, str);
383 	head.h_subject = head.h_cc = head.h_bcc = head.h_defopt = NOSTR;
384 	head.h_others = NOSTRPTR;
385 	head.h_seq = 0;
386 	mail1(&head, 1, NOSTR);
387 	return(0);
388 }
389 
390 /*
391  * Walk the list of fds, closing all but one.
392  */
393 static int
394 closefd_walk(void *special_fd, int fd)
395 {
396 	if (fd > STDERR_FILENO && fd != *(int *)special_fd)
397 		(void) close(fd);
398 	return (0);
399 }
400 
401 /*
402  * Mail a message on standard input to the people indicated
403  * in the passed header.  (Internal interface).
404  */
405 void
406 mail1(struct header *hp, int use_to, char *orig_to)
407 {
408 	pid_t p, pid;
409 	int i, s, gotcha;
410 	char **namelist, *deliver;
411 	struct name *to, *np;
412 	FILE *mtf, *fp;
413 	int remote = rflag != NOSTR || rmail;
414 	char **t;
415 	char *deadletter;
416 	char recfile[PATHSIZE];
417 
418 	/*
419 	 * Collect user's mail from standard input.
420 	 * Get the result as mtf.
421 	 */
422 
423 	pid = (pid_t)-1;
424 	if ((mtf = collect(hp)) == NULL)
425 		return;
426 	hp->h_seq = 1;
427 	if (hp->h_subject == NOSTR)
428 		hp->h_subject = sflag;
429 	if (fsize(mtf) == 0 && hp->h_subject == NOSTR) {
430 		printf(gettext("No message !?!\n"));
431 		goto out;
432 	}
433 	if (intty) {
434 		printf(gettext("EOT\n"));
435 		flush();
436 	}
437 
438 	/*
439 	 * If we need to use the To: line to determine the record
440 	 * file, save a copy of it before it's sorted below.
441 	 */
442 
443 	if (use_to && orig_to == NOSTR && hp->h_to != NOSTR)
444 		orig_to = strcpy((char *)salloc(strlen(hp->h_to)+1), hp->h_to);
445 	else if (orig_to == NOSTR)
446 		orig_to = "";
447 
448 	/*
449 	 * Now, take the user names from the combined
450 	 * to and cc lists and do all the alias
451 	 * processing.
452 	 */
453 
454 	senderr = 0;
455 	to = cat(extract(hp->h_bcc, GBCC),
456 	     cat(extract(hp->h_to, GTO),
457 	     extract(hp->h_cc, GCC)));
458 	to = translate(outpre(elide(usermap(to))));
459 	if (!senderr)
460 		mapf(to, myname);
461 	mechk(to);
462 	for (gotcha = 0, np = to; np != NIL; np = np->n_flink)
463 		if ((np->n_type & GDEL) == 0)
464 			gotcha++;
465 	hp->h_to = detract(to, GTO);
466 	hp->h_cc = detract(to, GCC);
467 	hp->h_bcc = detract(to, GBCC);
468 	if ((mtf = infix(hp, mtf)) == NULL) {
469 		fprintf(stderr, gettext(". . . message lost, sorry.\n"));
470 		return;
471 	}
472 	rewind(mtf);
473 	if (askme && isatty(0)) {
474 		char ans[64];
475 		puthead(hp, stdout, GTO|GCC|GBCC, 0);
476 		printf(gettext("Send? "));
477 		printf("[yes] ");
478 		if (fgets(ans, sizeof(ans), stdin) && ans[0] &&
479 				(tolower(ans[0]) != 'y' && ans[0] != '\n'))
480 			goto dead;
481 	}
482 	if (senderr)
483 		goto dead;
484 	/*
485 	 * Look through the recipient list for names with /'s
486 	 * in them which we write to as files directly.
487 	 */
488 	i = outof(to, mtf);
489 	rewind(mtf);
490 	if (!gotcha && !i) {
491 		printf(gettext("No recipients specified\n"));
492 		goto dead;
493 	}
494 	if (senderr)
495 		goto dead;
496 
497 	getrecf(orig_to, recfile, use_to, sizeof (recfile));
498 	if (recfile != NOSTR && *recfile)
499 		savemail(safeexpand(recfile), hp, mtf);
500 	if (!gotcha)
501 		goto out;
502 	namelist = unpack(to);
503 	if (debug) {
504 		fprintf(stderr, "Recipients of message:\n");
505 		for (t = namelist; *t != NOSTR; t++)
506 			fprintf(stderr, " \"%s\"", *t);
507 		fprintf(stderr, "\n");
508 		return;
509 	}
510 
511 	/*
512 	 * Wait, to absorb a potential zombie, then
513 	 * fork, set up the temporary mail file as standard
514 	 * input for "mail" and exec with the user list we generated
515 	 * far above. Return the process id to caller in case he
516 	 * wants to await the completion of mail.
517 	 */
518 
519 #ifdef VMUNIX
520 	while (wait3((int *)0, WNOHANG, (struct rusage *)0) > 0)
521 		;
522 #else
523 #ifdef preSVr4
524 	wait((int *)0);
525 #else
526 	while (waitpid((pid_t)-1, (int *)0, WNOHANG) > 0)
527 		;
528 #endif
529 #endif
530 	rewind(mtf);
531 	pid = fork();
532 	if (pid == (pid_t)-1) {
533 		perror("fork");
534 dead:
535 		deadletter = Getf("DEAD");
536 		if (fp = fopen(deadletter,
537 		    value("appenddeadletter") == NOSTR ? "w" : "a")) {
538 			chmod(deadletter, DEADPERM);
539 			puthead(hp, fp, GMASK|GCLEN, fsize(mtf) - textpos);
540 			fseek(mtf, textpos, 0);
541 			lcwrite(deadletter, mtf, fp,
542 			    value("appenddeadletter") != NOSTR);
543 			fclose(fp);
544 		} else
545 			perror(deadletter);
546 		goto out;
547 	}
548 	if (pid == 0) {
549 		sigchild();
550 #ifdef SIGTSTP
551 		if (remote == 0) {
552 			sigset(SIGTSTP, SIG_IGN);
553 			sigset(SIGTTIN, SIG_IGN);
554 			sigset(SIGTTOU, SIG_IGN);
555 		}
556 #endif
557 		sigset(SIGHUP, SIG_IGN);
558 		sigset(SIGINT, SIG_IGN);
559 		sigset(SIGQUIT, SIG_IGN);
560 		s = fileno(mtf);
561 		(void) fdwalk(closefd_walk, &s);
562 		close(0);
563 		dup(s);
564 		close(s);
565 #ifdef CC
566 		submit(getpid());
567 #endif /* CC */
568 		if ((deliver = value("sendmail")) == NOSTR)
569 #ifdef SENDMAIL
570 			deliver = SENDMAIL;
571 #else
572 			deliver = MAIL;
573 #endif
574 		execvp(safeexpand(deliver), namelist);
575 		perror(deliver);
576 		exit(1);
577 	}
578 
579 	if (value("sendwait")!=NOSTR)
580 		remote++;
581 out:
582 	if (remote) {
583 		while ((p = wait(&s)) != pid && p != (pid_t)-1)
584 			;
585 		if (s != 0)
586 			senderr++;
587 		pid = 0;
588 	}
589 	fclose(mtf);
590 	return;
591 }
592 
593 /*
594  * Prepend a header in front of the collected stuff
595  * and return the new file.
596  */
597 
598 static FILE *
599 infix(struct header *hp, FILE *fi)
600 {
601 	register FILE *nfo, *nfi;
602 	register int c;
603 	char *postmark, *returnaddr;
604 	int fd = -1;
605 
606 	rewind(fi);
607 	if ((fd = open(tempMail, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0 ||
608 	(nfo = fdopen(fd, "w")) == NULL) {
609 		perror(tempMail);
610 		return(fi);
611 	}
612 	if ((nfi = fopen(tempMail, "r")) == NULL) {
613 		perror(tempMail);
614 		fclose(nfo);
615 		return(fi);
616 	}
617 	removefile(tempMail);
618 	postmark = value("postmark");
619 	returnaddr = value("returnaddr");
620 	if ((postmark != NOSTR) || (returnaddr != NOSTR)) {
621 		if (returnaddr && *returnaddr)
622 			fprintf(nfo, "From: %s", returnaddr);
623 		else
624 			fprintf(nfo, "From: %s@%s", myname, host);
625 		if (postmark && *postmark)
626 			fprintf(nfo, " (%s)", postmark);
627 		putc('\n', nfo);
628 	}
629 	puthead(hp, nfo, (GMASK & ~GBCC) | GCLEN, fsize(fi));
630 	textpos = ftell(nfo);
631 	while ((c = getc(fi)) != EOF)
632 		putc(c, nfo);
633 	if (ferror(fi)) {
634 		perror("read");
635 		return(fi);
636 	}
637 	fflush(nfo);
638 	if (fferror(nfo)) {
639 		perror(tempMail);
640 		fclose(nfo);
641 		fclose(nfi);
642 		return(fi);
643 	}
644 	fclose(nfo);
645 	fclose(fi);
646 	rewind(nfi);
647 	return(nfi);
648 }
649 
650 /*
651  * Dump the message header on the
652  * passed file buffer.
653  */
654 
655 puthead(struct header *hp, FILE *fo, int w, long clen)
656 {
657 	register int gotcha;
658 
659 	gotcha = 0;
660 	if (hp->h_to != NOSTR && (w & GTO))
661 		fprintf(fo, "To: "), fmt(hp->h_to, fo), gotcha++;
662 	if ((w & GSUBJECT) && (int)value("bsdcompat"))
663 		if (hp->h_subject != NOSTR && *hp->h_subject)
664 			fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
665 		else
666 			if (sflag && *sflag)
667 				fprintf(fo, "Subject: %s\n", sflag), gotcha++;
668 	if (hp->h_cc != NOSTR && (w & GCC))
669 		fprintf(fo, "Cc: "), fmt(hp->h_cc, fo), gotcha++;
670 	if (hp->h_bcc != NOSTR && (w & GBCC))
671 		fprintf(fo, "Bcc: "), fmt(hp->h_bcc, fo), gotcha++;
672 	if (hp->h_defopt != NOSTR && (w & GDEFOPT))
673 		if (receipt_flg)
674 			fprintf(fo, "Return-Receipt-To: %s\n",
675 				hp->h_defopt), gotcha++;
676 		else
677 		fprintf(fo, "Default-Options: %s\n", hp->h_defopt), gotcha++;
678 	if ((w & GSUBJECT) && !(int)value("bsdcompat"))
679 		if (hp->h_subject != NOSTR && *hp->h_subject)
680 			fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
681 		else
682 			if (sflag && *sflag)
683 				fprintf(fo, "Subject: %s\n", sflag), gotcha++;
684 	if (hp->h_others != NOSTRPTR && (w & GOTHER)) {
685 		char **p;
686 		for (p = hp->h_others; *p; p++)
687 			fprintf(fo, "%s\n", *p);
688 		gotcha++;
689 	}
690 #ifndef preSVr4
691 	if (w & GCLEN)
692 		fprintf(fo, "Content-Length: %ld\n", clen), gotcha++;
693 #endif
694 	if (gotcha && (w & GNL))
695 		putc('\n', fo);
696 	return(0);
697 }
698 
699 /*
700  * Format the given text to not exceed 78 characters.
701  */
702 static void
703 fmt(register char *str, register FILE *fo)
704 {
705 	register int col = 4;
706 	char name[256];
707 	int len;
708 
709 	str = strcpy((char *)salloc(strlen(str)+1), str);
710 	while (str = yankword(str, name, sizeof (name), 1)) {
711 		len = strlen(name);
712 		if (col > 4) {
713 			if (col + len > 76) {
714 				fputs(",\n    ", fo);
715 				col = 4;
716 			} else {
717 				fputs(", ", fo);
718 				col += 2;
719 			}
720 		}
721 		fputs(name, fo);
722 		col += len;
723 	}
724 	putc('\n', fo);
725 }
726 
727 /*
728  * Save the outgoing mail on the passed file.
729  */
730 static int
731 savemail(char name[], struct header *hp, FILE *fi)
732 {
733 	register FILE *fo;
734 	time_t now;
735 	char *n;
736 #ifdef preSVr4
737 	char line[BUFSIZ];
738 #else
739 	int c;
740 #endif
741 
742 	if (debug)
743 		fprintf(stderr, gettext("save in '%s'\n"), name);
744 	if ((fo = fopen(name, "a")) == NULL) {
745 		perror(name);
746 		return(-1);
747 	}
748 	time(&now);
749 	n = rflag;
750 	if (n == NOSTR)
751 		n = myname;
752 	fprintf(fo, "From %s %s", n, ctime(&now));
753 	puthead(hp, fo, GMASK|GCLEN, fsize(fi) - textpos);
754 	fseek(fi, textpos, 0);
755 #ifdef preSVr4
756 	while (fgets(line, sizeof line, fi)) {
757 		if (!strncmp(line, "From ", 5))
758 			putc('>', fo);
759 		fputs(line, fo);
760 	}
761 #else
762 	while ((c = getc(fi)) != EOF)
763 		putc(c, fo);
764 #endif
765 	putc('\n', fo);
766 	fflush(fo);
767 	if (fferror(fo))
768 		perror(name);
769 	fclose(fo);
770 	return(0);
771 }
772