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