xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/tftp/tftp.c (revision cb76cc6610fb7124c6e081f2303201e65d11b972)
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  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1983, 1984, 1985, 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 /*
40  * TFTP User Program -- Protocol Machines
41  */
42 #include <sys/types.h>
43 #include <sys/socket.h>
44 #include <sys/time.h>
45 #include <sys/stat.h>
46 
47 #include <signal.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <stdbool.h>
51 #include <unistd.h>
52 #include <errno.h>
53 #include <string.h>
54 #include <stddef.h>
55 #include <inttypes.h>
56 
57 #include "tftpcommon.h"
58 #include "tftpprivate.h"
59 
60 static char	*blksize_str(void);
61 static char	*timeout_str(void);
62 static char	*tsize_str(void);
63 static int	blksize_handler(char *);
64 static int	timeout_handler(char *);
65 static int	tsize_handler(char *);
66 static int	add_options(char *, char *);
67 static int	process_oack(tftpbuf *, int);
68 static void	nak(int);
69 static void	startclock(void);
70 static void	stopclock(void);
71 static void	printstats(char *, off_t);
72 static int	makerequest(int, char *, struct tftphdr *, char *);
73 static void	tpacket(char *, struct tftphdr *, int);
74 
75 static struct options {
76 	char	*opt_name;
77 	char	*(*opt_str)(void);
78 	int	(*opt_handler)(char *);
79 } options[] = {
80 	{ "blksize",	blksize_str, blksize_handler },
81 	{ "timeout",	timeout_str, timeout_handler },
82 	{ "tsize",	tsize_str, tsize_handler },
83 	{ NULL }
84 };
85 
86 static char	optbuf[MAX_OPTVAL_LEN];
87 static bool	tsize_set;
88 
89 static tftpbuf	ackbuf;
90 static int	timeout;
91 static off_t	tsize;
92 static jmp_buf	timeoutbuf;
93 
94 int	blocksize = SEGSIZE;	/* Number of data bytes in a DATA packet */
95 
96 /*ARGSUSED*/
97 static void
timer(int signum)98 timer(int signum)
99 {
100 	timeout += rexmtval;
101 	if (timeout >= maxtimeout) {
102 		(void) fputs("Transfer timed out.\n", stderr);
103 		longjmp(toplevel, -1);
104 	}
105 	(void) signal(SIGALRM, timer);
106 	longjmp(timeoutbuf, 1);
107 }
108 
109 /*
110  * Send the requested file.
111  */
112 void
tftp_sendfile(int fd,char * name,char * mode)113 tftp_sendfile(int fd, char *name, char *mode)
114 {
115 	struct tftphdr *ap;	/* data and ack packets */
116 	struct tftphdr *dp;
117 	int n;
118 	volatile int count = 0, size;
119 	volatile ushort_t block = 0;
120 	volatile off_t amount = 0;
121 	struct sockaddr_in6 from;
122 	socklen_t fromlen;
123 	int convert;	/* true if doing nl->crlf conversion */
124 	FILE *file;
125 	struct stat statb;
126 	int errcode;
127 
128 	startclock();	/* start stat's clock */
129 	dp = r_init();	/* reset fillbuf/read-ahead code */
130 	ap = &ackbuf.tb_hdr;
131 	file = fdopen(fd, "r");
132 	convert = (strcmp(mode, "netascii") == 0);
133 
134 	tsize_set = ((tsize_opt != 0) && !convert && (fstat(fd, &statb) == 0));
135 	if (tsize_set)
136 		tsize = statb.st_size;
137 
138 	do {
139 		(void) signal(SIGALRM, timer);
140 		if (count == 0) {
141 			if ((size = makerequest(WRQ, name, dp, mode)) == -1) {
142 				(void) fprintf(stderr,
143 				    "tftp: Error: Write request packet too "
144 				    "big\n");
145 				(void) fclose(file);
146 				return;
147 			}
148 			size -= 4;
149 		} else {
150 			size = readit(file, &dp, convert);
151 			if (size < 0) {
152 				nak(errno + 100);
153 				break;
154 			}
155 			dp->th_opcode = htons((ushort_t)DATA);
156 			dp->th_block = htons((ushort_t)block);
157 		}
158 		timeout = 0;
159 		(void) setjmp(timeoutbuf);
160 		if (trace)
161 			tpacket("sent", dp, size + 4);
162 		n = sendto(f, dp, size + 4, 0,
163 		    (struct sockaddr *)&sin6, sizeof (sin6));
164 		if (n != size + 4) {
165 			perror("tftp: sendto");
166 			goto abort;
167 		}
168 		/* Can't read-ahead first block as OACK may change blocksize */
169 		if (count != 0)
170 			read_ahead(file, convert);
171 		(void) alarm(rexmtval);
172 		for (; ; ) {
173 			(void) sigrelse(SIGALRM);
174 			do {
175 				fromlen = (socklen_t)sizeof (from);
176 				n = recvfrom(f, ackbuf.tb_data,
177 				    sizeof (ackbuf.tb_data), 0,
178 				    (struct sockaddr *)&from, &fromlen);
179 				if (n < 0) {
180 					perror("tftp: recvfrom");
181 					goto abort;
182 				}
183 			} while (n < offsetof(struct tftphdr, th_data));
184 			(void) sighold(SIGALRM);
185 			sin6.sin6_port = from.sin6_port;   /* added */
186 			if (trace)
187 				tpacket("received", ap, n);
188 			/* should verify packet came from server */
189 			ap->th_opcode = ntohs(ap->th_opcode);
190 			if (ap->th_opcode == ERROR) {
191 				ap->th_code = ntohs(ap->th_code);
192 				(void) fprintf(stderr,
193 				    "Error code %d", ap->th_code);
194 				if (n > offsetof(struct tftphdr, th_data))
195 					(void) fprintf(stderr, ": %.*s", n -
196 					    offsetof(struct tftphdr, th_data),
197 					    ap->th_msg);
198 				(void) fputc('\n', stderr);
199 				goto abort;
200 			}
201 			if ((count == 0) && (ap->th_opcode == OACK)) {
202 				errcode = process_oack(&ackbuf, n);
203 				if (errcode >= 0) {
204 					nak(errcode);
205 					(void) fputs("Rejected OACK\n",
206 					    stderr);
207 					goto abort;
208 				}
209 				break;
210 			}
211 			if (ap->th_opcode == ACK) {
212 				ap->th_block = ntohs(ap->th_block);
213 				if (ap->th_block == block) {
214 					break;
215 				}
216 				/*
217 				 * Never resend the current DATA packet on
218 				 * receipt of a duplicate ACK, doing so would
219 				 * cause the "Sorcerer's Apprentice Syndrome".
220 				 */
221 			}
222 		}
223 		cancel_alarm();
224 		if (count > 0)
225 			amount += size;
226 		block++;
227 		count++;
228 	} while (size == blocksize || count == 1);
229 abort:
230 	cancel_alarm();
231 	(void) fclose(file);
232 	stopclock();
233 	if (amount > 0)
234 		printstats("Sent", amount);
235 }
236 
237 /*
238  * Receive a file.
239  */
240 void
tftp_recvfile(int fd,char * name,char * mode)241 tftp_recvfile(int fd, char *name, char *mode)
242 {
243 	struct tftphdr *ap;
244 	struct tftphdr *dp;
245 	volatile ushort_t block = 1;
246 	int n;
247 	volatile int size;
248 	volatile unsigned long amount = 0;
249 	struct sockaddr_in6 from;
250 	socklen_t fromlen;
251 	volatile bool firsttrip = true;
252 	FILE *file;
253 	int convert;	/* true if converting crlf -> lf */
254 	int errcode;
255 
256 	startclock();
257 	dp = w_init();
258 	ap = &ackbuf.tb_hdr;
259 	file = fdopen(fd, "w");
260 	convert = (strcmp(mode, "netascii") == 0);
261 
262 	tsize_set = (tsize_opt != 0);
263 	if (tsize_set)
264 		tsize = 0;
265 
266 	if ((size = makerequest(RRQ, name, ap, mode)) == -1) {
267 		(void) fprintf(stderr,
268 		    "tftp: Error: Read request packet too big\n");
269 		(void) fclose(file);
270 		return;
271 	}
272 
273 	do {
274 		(void) signal(SIGALRM, timer);
275 		if (firsttrip) {
276 			firsttrip = false;
277 		} else {
278 			ap->th_opcode = htons((ushort_t)ACK);
279 			ap->th_block = htons((ushort_t)(block));
280 			size = 4;
281 			block++;
282 		}
283 
284 send_oack_ack:
285 		timeout = 0;
286 		(void) setjmp(timeoutbuf);
287 send_ack:
288 		if (trace)
289 			tpacket("sent", ap, size);
290 		if (sendto(f, ackbuf.tb_data, size, 0, (struct sockaddr *)&sin6,
291 		    sizeof (sin6)) != size) {
292 			(void) alarm(0);
293 			perror("tftp: sendto");
294 			goto abort;
295 		}
296 		if (write_behind(file, convert) < 0) {
297 			nak(errno + 100);
298 			goto abort;
299 		}
300 		(void) alarm(rexmtval);
301 		for (; ; ) {
302 			(void) sigrelse(SIGALRM);
303 			do  {
304 				fromlen = (socklen_t)sizeof (from);
305 				n = recvfrom(f, dp, blocksize + 4, 0,
306 				    (struct sockaddr *)&from, &fromlen);
307 				if (n < 0) {
308 					perror("tftp: recvfrom");
309 					goto abort;
310 				}
311 			} while (n < offsetof(struct tftphdr, th_data));
312 			(void) sighold(SIGALRM);
313 			sin6.sin6_port = from.sin6_port;   /* added */
314 			if (trace)
315 				tpacket("received", dp, n);
316 			/* should verify client address */
317 			dp->th_opcode = ntohs(dp->th_opcode);
318 			if (dp->th_opcode == ERROR) {
319 				dp->th_code = ntohs(dp->th_code);
320 				(void) fprintf(stderr, "Error code %d",
321 				    dp->th_code);
322 				if (n > offsetof(struct tftphdr, th_data))
323 					(void) fprintf(stderr, ": %.*s", n -
324 					    offsetof(struct tftphdr, th_data),
325 					    dp->th_msg);
326 				(void) fputc('\n', stderr);
327 				goto abort;
328 			}
329 			if ((block == 1) && (dp->th_opcode == OACK)) {
330 				errcode = process_oack((tftpbuf *)dp, n);
331 				if (errcode >= 0) {
332 					cancel_alarm();
333 					nak(errcode);
334 					(void) fputs("Rejected OACK\n",
335 					    stderr);
336 					(void) fclose(file);
337 					return;
338 				}
339 				ap->th_opcode = htons((ushort_t)ACK);
340 				ap->th_block = htons(0);
341 				size = 4;
342 				goto send_oack_ack;
343 			}
344 			if (dp->th_opcode == DATA) {
345 				int j;
346 
347 				dp->th_block = ntohs(dp->th_block);
348 				if (dp->th_block == block) {
349 					break;	/* have next packet */
350 				}
351 				/*
352 				 * On an error, try to synchronize
353 				 * both sides.
354 				 */
355 				j = synchnet(f);
356 				if (j < 0) {
357 					perror("tftp: recvfrom");
358 					goto abort;
359 				}
360 				if ((j > 0) && trace) {
361 					(void) printf("discarded %d packets\n",
362 					    j);
363 				}
364 				if (dp->th_block == (block-1)) {
365 					goto send_ack;  /* resend ack */
366 				}
367 			}
368 		}
369 		cancel_alarm();
370 		size = writeit(file, &dp, n - 4, convert);
371 		if (size < 0) {
372 			nak(errno + 100);
373 			goto abort;
374 		}
375 		amount += size;
376 	} while (size == blocksize);
377 
378 	cancel_alarm();
379 	if (write_behind(file, convert) < 0) {	/* flush last buffer */
380 		nak(errno + 100);
381 		goto abort;
382 	}
383 	n = fclose(file);
384 	file = NULL;
385 	if (n == EOF) {
386 		nak(errno + 100);
387 		goto abort;
388 	}
389 
390 	/* ok to ack, since user has seen err msg */
391 	ap->th_opcode = htons((ushort_t)ACK);
392 	ap->th_block = htons((ushort_t)block);
393 	if (trace)
394 		tpacket("sent", ap, 4);
395 	if (sendto(f, ackbuf.tb_data, 4, 0,
396 	    (struct sockaddr *)&sin6, sizeof (sin6)) != 4)
397 		perror("tftp: sendto");
398 
399 abort:
400 	cancel_alarm();
401 	if (file != NULL)
402 		(void) fclose(file);
403 	stopclock();
404 	if (amount > 0)
405 		printstats("Received", amount);
406 }
407 
408 static int
makerequest(int request,char * name,struct tftphdr * tp,char * mode)409 makerequest(int request, char *name, struct tftphdr *tp, char *mode)
410 {
411 	char *cp, *cpend;
412 	int len;
413 
414 	tp->th_opcode = htons((ushort_t)request);
415 	cp = (char *)&tp->th_stuff;
416 
417 	/* Maximum size of a request packet is 512 bytes (RFC 2347) */
418 	cpend = (char *)tp + SEGSIZE;
419 
420 	len = strlcpy(cp, name, cpend - cp) + 1;
421 	cp += len;
422 	if (cp > cpend)
423 		return (-1);
424 
425 	len = strlcpy(cp, mode, cpend - cp) + 1;
426 	cp += len;
427 	if (cp > cpend)
428 		return (-1);
429 
430 	len = add_options(cp, cpend);
431 	if (len == -1)
432 		return (-1);
433 	cp += len;
434 
435 	return (cp - (char *)tp);
436 }
437 
438 /*
439  * Return the blksize option value string to include in the request packet.
440  */
441 static char *
blksize_str(void)442 blksize_str(void)
443 {
444 	blocksize = SEGSIZE;
445 	if (blksize == 0)
446 		return (NULL);
447 
448 	(void) snprintf(optbuf, sizeof (optbuf), "%d", blksize);
449 	return (optbuf);
450 }
451 
452 /*
453  * Return the timeout option value string to include in the request packet.
454  */
455 static char *
timeout_str(void)456 timeout_str(void)
457 {
458 	if (srexmtval == 0)
459 		return (NULL);
460 
461 	(void) snprintf(optbuf, sizeof (optbuf), "%d", srexmtval);
462 	return (optbuf);
463 }
464 
465 /*
466  * Return the tsize option value string to include in the request packet.
467  */
468 static char *
tsize_str(void)469 tsize_str(void)
470 {
471 	if (tsize_set == false)
472 		return (NULL);
473 
474 	(void) snprintf(optbuf, sizeof (optbuf), OFF_T_FMT, tsize);
475 	return (optbuf);
476 }
477 
478 /*
479  * Validate and action the blksize option value string from the OACK packet.
480  * Returns -1 on success or an error code on failure.
481  */
482 static int
blksize_handler(char * optstr)483 blksize_handler(char *optstr)
484 {
485 	char *endp;
486 	int value;
487 
488 	/* Make sure the option was requested */
489 	if (blksize == 0)
490 		return (EOPTNEG);
491 	errno = 0;
492 	value = (int)strtol(optstr, &endp, 10);
493 	if (errno != 0 || value < MIN_BLKSIZE || value > blksize ||
494 	    *endp != '\0')
495 		return (EOPTNEG);
496 	blocksize = value;
497 	return (-1);
498 }
499 
500 /*
501  * Validate and action the timeout option value string from the OACK packet.
502  * Returns -1 on success or an error code on failure.
503  */
504 static int
timeout_handler(char * optstr)505 timeout_handler(char *optstr)
506 {
507 	char *endp;
508 	int value;
509 
510 	/* Make sure the option was requested */
511 	if (srexmtval == 0)
512 		return (EOPTNEG);
513 	errno = 0;
514 	value = (int)strtol(optstr, &endp, 10);
515 	if (errno != 0 || value != srexmtval || *endp != '\0')
516 		return (EOPTNEG);
517 	/*
518 	 * Nothing to set, client and server retransmission intervals are
519 	 * set separately in the client.
520 	 */
521 	return (-1);
522 }
523 
524 /*
525  * Validate and action the tsize option value string from the OACK packet.
526  * Returns -1 on success or an error code on failure.
527  */
528 static int
tsize_handler(char * optstr)529 tsize_handler(char *optstr)
530 {
531 	char *endp;
532 	longlong_t value;
533 
534 	/* Make sure the option was requested */
535 	if (tsize_set == false)
536 		return (EOPTNEG);
537 	errno = 0;
538 	value = strtoll(optstr, &endp, 10);
539 	if (errno != 0 || value < 0 || *endp != '\0')
540 		return (EOPTNEG);
541 #if _FILE_OFFSET_BITS == 32
542 	if (value > MAXOFF_T)
543 		return (ENOSPACE);
544 #endif
545 	/*
546 	 * Don't bother checking the tsize value we specified in a write
547 	 * request is echoed back in the OACK.
548 	 */
549 	if (tsize == 0)
550 		tsize = value;
551 	return (-1);
552 }
553 
554 /*
555  * Add TFTP options to a request packet.
556  */
557 static int
add_options(char * obuf,char * obufend)558 add_options(char *obuf, char *obufend)
559 {
560 	int i;
561 	char *cp, *ostr;
562 
563 	cp = obuf;
564 	for (i = 0; options[i].opt_name != NULL; i++) {
565 		ostr = options[i].opt_str();
566 		if (ostr != NULL) {
567 			cp += strlcpy(cp, options[i].opt_name, obufend - cp)
568 			    + 1;
569 			if (cp > obufend)
570 				return (-1);
571 
572 			cp += strlcpy(cp, ostr, obufend - cp) + 1;
573 			if (cp > obufend)
574 				return (-1);
575 		}
576 	}
577 	return (cp - obuf);
578 }
579 
580 /*
581  * Process OACK packet sent by server in response to options in the request
582  * packet. Returns -1 on success or an error code on failure.
583  */
584 static int
process_oack(tftpbuf * oackbuf,int n)585 process_oack(tftpbuf *oackbuf, int n)
586 {
587 	char *cp, *oackend, *optname, *optval;
588 	struct tftphdr *oackp;
589 	int i, errcode;
590 
591 	oackp = &oackbuf->tb_hdr;
592 	cp = (char *)&oackp->th_stuff;
593 	oackend = (char *)oackbuf + n;
594 
595 	while (cp < oackend) {
596 		optname = cp;
597 		if ((optval = next_field(optname, oackend)) == NULL)
598 			return (EOPTNEG);
599 		if ((cp = next_field(optval, oackend)) == NULL)
600 			return (EOPTNEG);
601 		for (i = 0; options[i].opt_name != NULL; i++) {
602 			if (strcasecmp(optname, options[i].opt_name) == 0)
603 				break;
604 		}
605 		if (options[i].opt_name == NULL)
606 			return (EOPTNEG);
607 		errcode = options[i].opt_handler(optval);
608 		if (errcode >= 0)
609 			return (errcode);
610 	}
611 	return (-1);
612 }
613 
614 /*
615  * Send a nak packet (error message).
616  * Error code passed in is one of the
617  * standard TFTP codes, or a UNIX errno
618  * offset by 100.
619  */
620 static void
nak(int error)621 nak(int error)
622 {
623 	struct tftphdr *tp;
624 	int length;
625 	struct errmsg *pe;
626 
627 	tp = &ackbuf.tb_hdr;
628 	tp->th_opcode = htons((ushort_t)ERROR);
629 	tp->th_code = htons((ushort_t)error);
630 	for (pe = errmsgs; pe->e_code >= 0; pe++)
631 		if (pe->e_code == error)
632 			break;
633 	if (pe->e_code < 0) {
634 		pe->e_msg = strerror(error - 100);
635 		tp->th_code = EUNDEF;
636 	}
637 	(void) strlcpy(tp->th_msg, pe->e_msg,
638 	    sizeof (ackbuf) - sizeof (struct tftphdr));
639 	length = strlen(pe->e_msg) + 4;
640 	if (trace)
641 		tpacket("sent", tp, length);
642 	if (sendto(f, ackbuf.tb_data, length, 0,
643 	    (struct sockaddr *)&sin6, sizeof (sin6)) != length)
644 		perror("nak");
645 }
646 
647 static void
tpacket(char * s,struct tftphdr * tp,int n)648 tpacket(char *s, struct tftphdr *tp, int n)
649 {
650 	static char *opcodes[] = {
651 	    "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK"
652 	};
653 	char *cp, *file, *mode;
654 	ushort_t op = ntohs(tp->th_opcode);
655 	char *tpend;
656 
657 	if (op < RRQ || op > OACK)
658 		(void) printf("%s opcode=%x ", s, op);
659 	else
660 		(void) printf("%s %s ", s, opcodes[op]);
661 
662 	switch (op) {
663 	case RRQ:
664 	case WRQ:
665 		tpend = (char *)tp + n;
666 		n -= sizeof (tp->th_opcode);
667 		file = (char *)&tp->th_stuff;
668 		if ((mode = next_field(file, tpend)) == NULL) {
669 			(void) printf("<file=%.*s>\n", n, file);
670 			break;
671 		}
672 		n -= mode - file;
673 		if ((cp = next_field(mode, tpend)) == NULL) {
674 			(void) printf("<file=%s, mode=%.*s>\n", file, n, mode);
675 			break;
676 		}
677 		(void) printf("<file=%s, mode=%s", file, mode);
678 		n -= cp - mode;
679 		if (n > 0) {
680 			(void) printf(", options: ");
681 			print_options(stdout, cp, n);
682 		}
683 		(void) puts(">");
684 		break;
685 
686 	case DATA:
687 		(void) printf("<block=%d, %d bytes>\n", ntohs(tp->th_block),
688 		    n - sizeof (tp->th_opcode) - sizeof (tp->th_block));
689 		break;
690 
691 	case ACK:
692 		(void) printf("<block=%d>\n", ntohs(tp->th_block));
693 		break;
694 
695 	case OACK:
696 		(void) printf("<options: ");
697 		print_options(stdout, (char *)&tp->th_stuff,
698 		    n - sizeof (tp->th_opcode));
699 		(void) puts(">");
700 		break;
701 
702 	case ERROR:
703 		(void) printf("<code=%d", ntohs(tp->th_code));
704 		n = n - sizeof (tp->th_opcode) - sizeof (tp->th_code);
705 		if (n > 0)
706 			(void) printf(", msg=%.*s", n, tp->th_msg);
707 		(void) puts(">");
708 		break;
709 	}
710 }
711 
712 static hrtime_t	tstart, tstop;
713 
714 static void
startclock(void)715 startclock(void)
716 {
717 	tstart = gethrtime();
718 }
719 
720 static void
stopclock(void)721 stopclock(void)
722 {
723 	tstop = gethrtime();
724 }
725 
726 static void
printstats(char * direction,off_t amount)727 printstats(char *direction, off_t amount)
728 {
729 	hrtime_t	delta, tenths;
730 
731 	delta = tstop - tstart;
732 	tenths = delta / (NANOSEC / 10);
733 	(void) printf("%s " OFF_T_FMT " bytes in %" PRId64 ".%" PRId64
734 	    " seconds", direction, amount, tenths / 10, tenths % 10);
735 	if (verbose)
736 		(void) printf(" [%" PRId64 " bits/sec]\n",
737 		    ((hrtime_t)amount * 8 * NANOSEC) / delta);
738 	else
739 		(void) putchar('\n');
740 }
741