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