xref: /openbsd-src/usr.bin/ftp/main.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /*	$OpenBSD: main.c,v 1.80 2009/08/09 18:36:11 sobrado Exp $	*/
2 /*	$NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $	*/
3 
4 /*
5  * Copyright (C) 1997 and 1998 WIDE Project.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the project nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  * Copyright (c) 1985, 1989, 1993, 1994
35  *	The Regents of the University of California.  All rights reserved.
36  *
37  * Redistribution and use in source and binary forms, with or without
38  * modification, are permitted provided that the following conditions
39  * are met:
40  * 1. Redistributions of source code must retain the above copyright
41  *    notice, this list of conditions and the following disclaimer.
42  * 2. Redistributions in binary form must reproduce the above copyright
43  *    notice, this list of conditions and the following disclaimer in the
44  *    documentation and/or other materials provided with the distribution.
45  * 3. Neither the name of the University nor the names of its contributors
46  *    may be used to endorse or promote products derived from this software
47  *    without specific prior written permission.
48  *
49  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59  * SUCH DAMAGE.
60  */
61 
62 /*
63  * FTP User Program -- Command Interface.
64  */
65 #include <sys/types.h>
66 #include <sys/socket.h>
67 
68 #include <ctype.h>
69 #include <err.h>
70 #include <netdb.h>
71 #include <pwd.h>
72 #include <stdio.h>
73 #include <errno.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include <unistd.h>
77 
78 #include "ftp_var.h"
79 #include "cmds.h"
80 
81 int family = PF_UNSPEC;
82 
83 int
84 main(volatile int argc, char *argv[])
85 {
86 	int ch, top, rval;
87 	struct passwd *pw = NULL;
88 	char *cp, homedir[MAXPATHLEN];
89 	char *outfile = NULL;
90 	const char *errstr;
91 	int dumb_terminal = 0;
92 
93 	ftpport = "ftp";
94 	httpport = "http";
95 #ifndef SMALL
96 	httpsport = "https";
97 #endif /* !SMALL */
98 	gateport = getenv("FTPSERVERPORT");
99 	if (gateport == NULL || *gateport == '\0')
100 		gateport = "ftpgate";
101 	doglob = 1;
102 	interactive = 1;
103 	autologin = 1;
104 	passivemode = 1;
105 	activefallback = 1;
106 	preserve = 1;
107 	verbose = 0;
108 	progress = 0;
109 	gatemode = 0;
110 #ifndef SMALL
111 	editing = 0;
112 	el = NULL;
113 	hist = NULL;
114 	cookiefile = NULL;
115 	resume = 0;
116 	marg_sl = sl_init();
117 #endif /* !SMALL */
118 	mark = HASHBYTES;
119 #ifdef INET6
120 	epsv4 = 1;
121 #else
122 	epsv4 = 0;
123 #endif
124 	epsv4bad = 0;
125 
126 	/* Set default operation mode based on FTPMODE environment variable */
127 	if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') {
128 		if (strcmp(cp, "passive") == 0) {
129 			passivemode = 1;
130 			activefallback = 0;
131 		} else if (strcmp(cp, "active") == 0) {
132 			passivemode = 0;
133 			activefallback = 0;
134 		} else if (strcmp(cp, "gate") == 0) {
135 			gatemode = 1;
136 		} else if (strcmp(cp, "auto") == 0) {
137 			passivemode = 1;
138 			activefallback = 1;
139 		} else
140 			warnx("unknown FTPMODE: %s.  Using defaults", cp);
141 	}
142 
143 	if (strcmp(__progname, "gate-ftp") == 0)
144 		gatemode = 1;
145 	gateserver = getenv("FTPSERVER");
146 	if (gateserver == NULL || *gateserver == '\0')
147 		gateserver = GATE_SERVER;
148 	if (gatemode) {
149 		if (*gateserver == '\0') {
150 			warnx(
151 "Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp");
152 			gatemode = 0;
153 		}
154 	}
155 
156 	cp = getenv("TERM");
157 	dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") ||
158 	    !strcmp(cp, "emacs") || !strcmp(cp, "su"));
159 	fromatty = isatty(fileno(stdin));
160 	if (fromatty) {
161 		verbose = 1;		/* verbose if from a tty */
162 #ifndef SMALL
163 		if (!dumb_terminal)
164 			editing = 1;	/* editing mode on if tty is usable */
165 #endif /* !SMALL */
166 	}
167 
168 	ttyout = stdout;
169 	if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc())
170 		progress = 1;		/* progress bar on if tty is usable */
171 
172 #ifndef SMALL
173 	cookiefile = getenv("http_cookies");
174 #endif /* !SMALL */
175 
176 	while ((ch = getopt(argc, argv, "46AaCc:dEegik:mno:pP:r:tvV")) != -1) {
177 		switch (ch) {
178 		case '4':
179 			family = PF_INET;
180 			break;
181 		case '6':
182 			family = PF_INET6;
183 			break;
184 		case 'A':
185 			activefallback = 0;
186 			passivemode = 0;
187 			break;
188 
189 		case 'a':
190 			anonftp = 1;
191 			break;
192 
193 		case 'C':
194 #ifndef SMALL
195 			resume = 1;
196 #endif /* !SMALL */
197 			break;
198 
199 		case 'c':
200 #ifndef SMALL
201 			cookiefile = optarg;
202 #endif /* !SMALL */
203 			break;
204 
205 		case 'd':
206 #ifndef SMALL
207 			options |= SO_DEBUG;
208 			debug++;
209 #endif /* !SMALL */
210 			break;
211 
212 		case 'E':
213 			epsv4 = 0;
214 			break;
215 
216 		case 'e':
217 #ifndef SMALL
218 			editing = 0;
219 #endif /* !SMALL */
220 			break;
221 
222 		case 'g':
223 			doglob = 0;
224 			break;
225 
226 		case 'i':
227 			interactive = 0;
228 			break;
229 
230 		case 'k':
231 			keep_alive_timeout = strtonum(optarg, 0, INT_MAX,
232 			    &errstr);
233 			if (errstr != NULL) {
234 				warnx("keep alive amount is %s: %s", errstr,
235 					optarg);
236 				usage();
237 			}
238 			break;
239 		case 'm':
240 			progress = -1;
241 			break;
242 
243 		case 'n':
244 			autologin = 0;
245 			break;
246 
247 		case 'o':
248 			outfile = optarg;
249 			if (strcmp(outfile, "-") == 0)
250 				ttyout = stderr;
251 			break;
252 
253 		case 'p':
254 			passivemode = 1;
255 			activefallback = 0;
256 			break;
257 
258 		case 'P':
259 			ftpport = optarg;
260 			break;
261 
262 		case 'r':
263 			retry_connect = strtonum(optarg, 0, INT_MAX, &errstr);
264 			if (errstr != NULL) {
265 				warnx("retry amount is %s: %s", errstr,
266 					optarg);
267 				usage();
268 			}
269 			break;
270 
271 		case 't':
272 			trace = 1;
273 			break;
274 
275 		case 'v':
276 			verbose = 1;
277 			break;
278 
279 		case 'V':
280 			verbose = 0;
281 			break;
282 
283 		default:
284 			usage();
285 		}
286 	}
287 	argc -= optind;
288 	argv += optind;
289 
290 #ifndef SMALL
291 	cookie_load();
292 #endif /* !SMALL */
293 
294 	cpend = 0;	/* no pending replies */
295 	proxy = 0;	/* proxy not active */
296 	crflag = 1;	/* strip c.r. on ascii gets */
297 	sendport = -1;	/* not using ports */
298 	/*
299 	 * Set up the home directory in case we're globbing.
300 	 */
301 	cp = getlogin();
302 	if (cp != NULL) {
303 		pw = getpwnam(cp);
304 	}
305 	if (pw == NULL)
306 		pw = getpwuid(getuid());
307 	if (pw != NULL) {
308 		(void)strlcpy(homedir, pw->pw_dir, sizeof homedir);
309 		home = homedir;
310 	}
311 
312 	setttywidth(0);
313 	(void)signal(SIGWINCH, setttywidth);
314 
315 	if (argc > 0) {
316 		if (isurl(argv[0])) {
317 			rval = auto_fetch(argc, argv, outfile);
318 			if (rval >= 0)		/* -1 == connected and cd-ed */
319 				exit(rval);
320 		} else {
321 #ifndef SMALL
322 			char *xargv[5];
323 
324 			if (setjmp(toplevel))
325 				exit(0);
326 			(void)signal(SIGINT, (sig_t)intr);
327 			(void)signal(SIGPIPE, (sig_t)lostpeer);
328 			xargv[0] = __progname;
329 			xargv[1] = argv[0];
330 			xargv[2] = argv[1];
331 			xargv[3] = argv[2];
332 			xargv[4] = NULL;
333 			do {
334 				setpeer(argc+1, xargv);
335 				if (!retry_connect)
336 					break;
337 				if (!connected) {
338 					macnum = 0;
339 					fputs("Retrying...\n", ttyout);
340 					sleep(retry_connect);
341 				}
342 			} while (!connected);
343 			retry_connect = 0; /* connected, stop hiding msgs */
344 #endif /* !SMALL */
345 		}
346 	}
347 #ifndef SMALL
348 	controlediting();
349 	top = setjmp(toplevel) == 0;
350 	if (top) {
351 		(void)signal(SIGINT, (sig_t)intr);
352 		(void)signal(SIGPIPE, (sig_t)lostpeer);
353 	}
354 	for (;;) {
355 		cmdscanner(top);
356 		top = 1;
357 	}
358 #else /* !SMALL */
359 	usage();
360 #endif /* !SMALL */
361 }
362 
363 void
364 intr(void)
365 {
366 
367 	alarmtimer(0);
368 	longjmp(toplevel, 1);
369 }
370 
371 void
372 lostpeer(void)
373 {
374 	int save_errno = errno;
375 
376 	alarmtimer(0);
377 	if (connected) {
378 		if (cout != NULL) {
379 			(void)shutdown(fileno(cout), SHUT_RDWR);
380 			(void)fclose(cout);
381 			cout = NULL;
382 		}
383 		if (data >= 0) {
384 			(void)shutdown(data, SHUT_RDWR);
385 			(void)close(data);
386 			data = -1;
387 		}
388 		connected = 0;
389 	}
390 	pswitch(1);
391 	if (connected) {
392 		if (cout != NULL) {
393 			(void)shutdown(fileno(cout), SHUT_RDWR);
394 			(void)fclose(cout);
395 			cout = NULL;
396 		}
397 		connected = 0;
398 	}
399 	proxflag = 0;
400 	pswitch(0);
401 	errno = save_errno;
402 }
403 
404 #ifndef SMALL
405 /*
406  * Generate a prompt
407  */
408 char *
409 prompt(void)
410 {
411 	return ("ftp> ");
412 }
413 
414 /*
415  * Command parser.
416  */
417 void
418 cmdscanner(int top)
419 {
420 	struct cmd *c;
421 	int num;
422 	HistEvent hev;
423 
424 	if (!top && !editing)
425 		(void)putc('\n', ttyout);
426 	for (;;) {
427 		if (!editing) {
428 			if (fromatty) {
429 				fputs(prompt(), ttyout);
430 				(void)fflush(ttyout);
431 			}
432 			if (fgets(line, sizeof(line), stdin) == NULL)
433 				quit(0, 0);
434 			num = strlen(line);
435 			if (num == 0)
436 				break;
437 			if (line[--num] == '\n') {
438 				if (num == 0)
439 					break;
440 				line[num] = '\0';
441 			} else if (num == sizeof(line) - 2) {
442 				fputs("sorry, input line too long.\n", ttyout);
443 				while ((num = getchar()) != '\n' && num != EOF)
444 					/* void */;
445 				break;
446 			} /* else it was a line without a newline */
447 		} else {
448 			const char *buf;
449 			cursor_pos = NULL;
450 
451 			if ((buf = el_gets(el, &num)) == NULL || num == 0)
452 				quit(0, 0);
453 			if (buf[--num] == '\n') {
454 				if (num == 0)
455 					break;
456 			}
457 			if (num >= sizeof(line)) {
458 				fputs("sorry, input line too long.\n", ttyout);
459 				break;
460 			}
461 			memcpy(line, buf, (size_t)num);
462 			line[num] = '\0';
463 			history(hist, &hev, H_ENTER, buf);
464 		}
465 
466 		makeargv();
467 		if (margc == 0)
468 			continue;
469 		c = getcmd(margv[0]);
470 		if (c == (struct cmd *)-1) {
471 			fputs("?Ambiguous command.\n", ttyout);
472 			continue;
473 		}
474 		if (c == 0) {
475 			/*
476 			 * Give editline(3) a shot at unknown commands.
477 			 * XXX - bogus commands with a colon in
478 			 *       them will not elicit an error.
479 			 */
480 			if (editing &&
481 			    el_parse(el, margc, (const char **)margv) != 0)
482 				fputs("?Invalid command.\n", ttyout);
483 			continue;
484 		}
485 		if (c->c_conn && !connected) {
486 			fputs("Not connected.\n", ttyout);
487 			continue;
488 		}
489 		confirmrest = 0;
490 		(*c->c_handler)(margc, margv);
491 		if (bell && c->c_bell)
492 			(void)putc('\007', ttyout);
493 		if (c->c_handler != help)
494 			break;
495 	}
496 	(void)signal(SIGINT, (sig_t)intr);
497 	(void)signal(SIGPIPE, (sig_t)lostpeer);
498 }
499 
500 struct cmd *
501 getcmd(const char *name)
502 {
503 	const char *p, *q;
504 	struct cmd *c, *found;
505 	int nmatches, longest;
506 
507 	if (name == NULL)
508 		return (0);
509 
510 	longest = 0;
511 	nmatches = 0;
512 	found = 0;
513 	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
514 		for (q = name; *q == *p++; q++)
515 			if (*q == 0)		/* exact match? */
516 				return (c);
517 		if (!*q) {			/* the name was a prefix */
518 			if (q - name > longest) {
519 				longest = q - name;
520 				nmatches = 1;
521 				found = c;
522 			} else if (q - name == longest)
523 				nmatches++;
524 		}
525 	}
526 	if (nmatches > 1)
527 		return ((struct cmd *)-1);
528 	return (found);
529 }
530 
531 /*
532  * Slice a string up into argc/argv.
533  */
534 
535 int slrflag;
536 
537 void
538 makeargv(void)
539 {
540 	char *argp;
541 
542 	stringbase = line;		/* scan from first of buffer */
543 	argbase = argbuf;		/* store from first of buffer */
544 	slrflag = 0;
545 	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
546 	for (margc = 0; ; margc++) {
547 		argp = slurpstring();
548 		sl_add(marg_sl, argp);
549 		if (argp == NULL)
550 			break;
551 	}
552 	if (cursor_pos == line) {
553 		cursor_argc = 0;
554 		cursor_argo = 0;
555 	} else if (cursor_pos != NULL) {
556 		cursor_argc = margc;
557 		cursor_argo = strlen(margv[margc-1]);
558 	}
559 }
560 
561 #define INC_CHKCURSOR(x)	{ (x)++ ; \
562 				if (x == cursor_pos) { \
563 					cursor_argc = margc; \
564 					cursor_argo = ap-argbase; \
565 					cursor_pos = NULL; \
566 				} }
567 
568 /*
569  * Parse string into argbuf;
570  * implemented with FSM to
571  * handle quoting and strings
572  */
573 char *
574 slurpstring(void)
575 {
576 	int got_one = 0;
577 	char *sb = stringbase;
578 	char *ap = argbase;
579 	char *tmp = argbase;		/* will return this if token found */
580 
581 	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
582 		switch (slrflag) {	/* and $ as token for macro invoke */
583 			case 0:
584 				slrflag++;
585 				INC_CHKCURSOR(stringbase);
586 				return ((*sb == '!') ? "!" : "$");
587 				/* NOTREACHED */
588 			case 1:
589 				slrflag++;
590 				altarg = stringbase;
591 				break;
592 			default:
593 				break;
594 		}
595 	}
596 
597 S0:
598 	switch (*sb) {
599 
600 	case '\0':
601 		goto OUT;
602 
603 	case ' ':
604 	case '\t':
605 		INC_CHKCURSOR(sb);
606 		goto S0;
607 
608 	default:
609 		switch (slrflag) {
610 			case 0:
611 				slrflag++;
612 				break;
613 			case 1:
614 				slrflag++;
615 				altarg = sb;
616 				break;
617 			default:
618 				break;
619 		}
620 		goto S1;
621 	}
622 
623 S1:
624 	switch (*sb) {
625 
626 	case ' ':
627 	case '\t':
628 	case '\0':
629 		goto OUT;	/* end of token */
630 
631 	case '\\':
632 		INC_CHKCURSOR(sb);
633 		goto S2;	/* slurp next character */
634 
635 	case '"':
636 		INC_CHKCURSOR(sb);
637 		goto S3;	/* slurp quoted string */
638 
639 	default:
640 		*ap = *sb;	/* add character to token */
641 		ap++;
642 		INC_CHKCURSOR(sb);
643 		got_one = 1;
644 		goto S1;
645 	}
646 
647 S2:
648 	switch (*sb) {
649 
650 	case '\0':
651 		goto OUT;
652 
653 	default:
654 		*ap = *sb;
655 		ap++;
656 		INC_CHKCURSOR(sb);
657 		got_one = 1;
658 		goto S1;
659 	}
660 
661 S3:
662 	switch (*sb) {
663 
664 	case '\0':
665 		goto OUT;
666 
667 	case '"':
668 		INC_CHKCURSOR(sb);
669 		goto S1;
670 
671 	default:
672 		*ap = *sb;
673 		ap++;
674 		INC_CHKCURSOR(sb);
675 		got_one = 1;
676 		goto S3;
677 	}
678 
679 OUT:
680 	if (got_one)
681 		*ap++ = '\0';
682 	argbase = ap;			/* update storage pointer */
683 	stringbase = sb;		/* update scan pointer */
684 	if (got_one) {
685 		return (tmp);
686 	}
687 	switch (slrflag) {
688 		case 0:
689 			slrflag++;
690 			break;
691 		case 1:
692 			slrflag++;
693 			altarg = (char *) 0;
694 			break;
695 		default:
696 			break;
697 	}
698 	return ((char *)0);
699 }
700 
701 /*
702  * Help command.
703  * Call each command handler with argc == 0 and argv[0] == name.
704  */
705 void
706 help(int argc, char *argv[])
707 {
708 	struct cmd *c;
709 
710 	if (argc == 1) {
711 		StringList *buf;
712 
713 		buf = sl_init();
714 		fprintf(ttyout, "%sommands may be abbreviated.  Commands are:\n\n",
715 		    proxy ? "Proxy c" : "C");
716 		for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
717 			if (c->c_name && (!proxy || c->c_proxy))
718 				sl_add(buf, c->c_name);
719 		list_vertical(buf);
720 		sl_free(buf, 0);
721 		return;
722 	}
723 
724 #define HELPINDENT ((int) sizeof("disconnect"))
725 
726 	while (--argc > 0) {
727 		char *arg;
728 
729 		arg = *++argv;
730 		c = getcmd(arg);
731 		if (c == (struct cmd *)-1)
732 			fprintf(ttyout, "?Ambiguous help command %s\n", arg);
733 		else if (c == (struct cmd *)0)
734 			fprintf(ttyout, "?Invalid help command %s\n", arg);
735 		else
736 			fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
737 				c->c_name, c->c_help);
738 	}
739 }
740 #endif /* !SMALL */
741 
742 void
743 usage(void)
744 {
745 	(void)fprintf(stderr, "usage: %s "
746 #ifndef SMALL
747 	    "[-46AadEegimnptVv] [-k seconds] [-P port] "
748 	    "[-r seconds] [host [port]]\n"
749 	    "       %s [-C] "
750 #endif /* !SMALL */
751 	    "[-o output] "
752 	    "ftp://[user:password@]host[:port]/file[/] ...\n"
753 	    "       %s "
754 #ifndef SMALL
755 	    "[-C] [-c cookie] "
756 #endif /* !SMALL */
757 	    "[-o output] "
758 	    "http://host[:port]/file ...\n"
759 #ifndef SMALL
760 	    "       %s [-C] [-c cookie] [-o output] "
761 	    "https://host[:port]/file ...\n"
762 #endif /* !SMALL */
763 	    "       %s "
764 #ifndef SMALL
765 	    "[-C] "
766 #endif /* !SMALL */
767 	    "[-o output] "
768 	    "file:file ...\n"
769 	    "       %s "
770 #ifndef SMALL
771 	    "[-C] "
772 #endif /* !SMALL */
773 	    "[-o output] host:/file[/] ...\n",
774 #ifndef SMALL
775 	    __progname, __progname, __progname, __progname, __progname,
776 	    __progname);
777 #else /* !SMALL */
778 	    __progname, __progname, __progname, __progname);
779 #endif /* !SMALL */
780 	exit(1);
781 }
782 
783