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