xref: /openbsd/usr.bin/ftp/main.c (revision f2dfb0a4)
1 /*	$OpenBSD: main.c,v 1.39 1998/05/13 08:59:08 deraadt 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.39 1998/05/13 08:59:08 deraadt 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, "Aadegino:pPr: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 'n':
205 			autologin = 0;
206 			break;
207 
208 		case 'o':
209 			outfile = optarg;
210 			if (strcmp(outfile, "-") == 0)
211 				ttyout = stderr;
212 			break;
213 
214 		case 'p':
215 			passivemode = 1;
216 			activefallback = 0;
217 			break;
218 
219 		case 'P':
220 			port = strtol(optarg, &ep, 10);
221 			if (port < 1 || port > USHRT_MAX || *ep != '\0')
222 				warnx("bad port number: %s (ignored)", optarg);
223 			else
224 				ftpport = htons((in_port_t)port);
225 			break;
226 
227 		case 'r':
228 			if (isdigit(*optarg))
229 				retry_connect = atoi(optarg);
230 			else
231 				errx(1, "-r requires numeric argument");
232 			break;
233 
234 		case 't':
235 			trace = 1;
236 			break;
237 
238 		case 'v':
239 			verbose = 1;
240 			break;
241 
242 		case 'V':
243 			verbose = 0;
244 			break;
245 
246 		default:
247 			usage();
248 		}
249 	}
250 	argc -= optind;
251 	argv += optind;
252 
253 	cpend = 0;	/* no pending replies */
254 	proxy = 0;	/* proxy not active */
255 	crflag = 1;	/* strip c.r. on ascii gets */
256 	sendport = -1;	/* not using ports */
257 	/*
258 	 * Set up the home directory in case we're globbing.
259 	 */
260 	cp = getlogin();
261 	if (cp != NULL) {
262 		pw = getpwnam(cp);
263 	}
264 	if (pw == NULL)
265 		pw = getpwuid(getuid());
266 	if (pw != NULL) {
267 		home = homedir;
268 		(void)strcpy(home, pw->pw_dir);
269 	}
270 
271 	setttywidth(0);
272 	(void)signal(SIGWINCH, setttywidth);
273 
274 #ifdef __GNUC__				/* XXX: to shut up gcc warnings */
275 	(void)&argc;
276 	(void)&argv;
277 #endif
278 
279 	if (argc > 0) {
280 		if (strchr(argv[0], ':') != NULL) {
281 			anonftp = 1;	/* Handle "automatic" transfers. */
282 			rval = auto_fetch(argc, argv, outfile);
283 			if (rval >= 0)		/* -1 == connected and cd-ed */
284 				exit(rval);
285 		} else {
286 			char *xargv[5];
287 
288 			if (setjmp(toplevel))
289 				exit(0);
290 			(void)signal(SIGINT, (sig_t)intr);
291 			(void)signal(SIGPIPE, (sig_t)lostpeer);
292 			xargv[0] = __progname;
293 			xargv[1] = argv[0];
294 			xargv[2] = argv[1];
295 			xargv[3] = argv[2];
296 			xargv[4] = NULL;
297 			do {
298 				setpeer(argc+1, xargv);
299 				if (!retry_connect)
300 					break;
301 				if (!connected) {
302 					macnum = 0;
303 					fputs("Retrying...\n", ttyout);
304 					sleep(retry_connect);
305 				}
306 			} while (!connected);
307 			retry_connect = 0; /* connected, stop hiding msgs */
308 		}
309 	}
310 #ifndef SMALL
311 	controlediting();
312 #endif /* !SMALL */
313 	top = setjmp(toplevel) == 0;
314 	if (top) {
315 		(void)signal(SIGINT, (sig_t)intr);
316 		(void)signal(SIGPIPE, (sig_t)lostpeer);
317 	}
318 	for (;;) {
319 		cmdscanner(top);
320 		top = 1;
321 	}
322 }
323 
324 void
325 intr()
326 {
327 
328 	alarmtimer(0);
329 	longjmp(toplevel, 1);
330 }
331 
332 void
333 lostpeer()
334 {
335 	int save_errno = errno;
336 
337 	alarmtimer(0);
338 	if (connected) {
339 		if (cout != NULL) {
340 			(void)shutdown(fileno(cout), 1+1);
341 			(void)fclose(cout);
342 			cout = NULL;
343 		}
344 		if (data >= 0) {
345 			(void)shutdown(data, 1+1);
346 			(void)close(data);
347 			data = -1;
348 		}
349 		connected = 0;
350 	}
351 	pswitch(1);
352 	if (connected) {
353 		if (cout != NULL) {
354 			(void)shutdown(fileno(cout), 1+1);
355 			(void)fclose(cout);
356 			cout = NULL;
357 		}
358 		connected = 0;
359 	}
360 	proxflag = 0;
361 	pswitch(0);
362 	errno = save_errno;
363 }
364 
365 /*
366  * Generate a prompt
367  */
368 char *
369 prompt()
370 {
371 	return ("ftp> ");
372 }
373 
374 /*
375  * Command parser.
376  */
377 void
378 cmdscanner(top)
379 	int top;
380 {
381 	struct cmd *c;
382 	int num;
383 
384 	if (!top
385 #ifndef SMALL
386 	    && !editing
387 #endif /* !SMALL */
388 	    )
389 		(void)putc('\n', ttyout);
390 	for (;;) {
391 #ifndef SMALL
392 		if (!editing) {
393 #endif /* !SMALL */
394 			if (fromatty) {
395 				fputs(prompt(), ttyout);
396 				(void)fflush(ttyout);
397 			}
398 			if (fgets(line, sizeof(line), stdin) == NULL)
399 				quit(0, 0);
400 			num = strlen(line);
401 			if (num == 0)
402 				break;
403 			if (line[--num] == '\n') {
404 				if (num == 0)
405 					break;
406 				line[num] = '\0';
407 			} else if (num == sizeof(line) - 2) {
408 				fputs("sorry, input line too long.\n", ttyout);
409 				while ((num = getchar()) != '\n' && num != EOF)
410 					/* void */;
411 				break;
412 			} /* else it was a line without a newline */
413 #ifndef SMALL
414 		} else {
415 			const char *buf;
416 			cursor_pos = NULL;
417 
418 			if ((buf = el_gets(el, &num)) == NULL || num == 0)
419 				quit(0, 0);
420 			if (line[--num] == '\n') {
421 				if (num == 0)
422 					break;
423 			} else if (num >= sizeof(line)) {
424 				fputs("sorry, input line too long.\n", ttyout);
425 				break;
426 			}
427 			memcpy(line, buf, (size_t)num);
428 			line[num] = '\0';
429 			history(hist, H_ENTER, buf);
430 		}
431 #endif /* !SMALL */
432 
433 		makeargv();
434 		if (margc == 0)
435 			continue;
436 		c = getcmd(margv[0]);
437 		if (c == (struct cmd *)-1) {
438 			fputs("?Ambiguous command.\n", ttyout);
439 			continue;
440 		}
441 		if (c == 0) {
442 #ifndef SMALL
443 			/*
444 			 * Give editline(3) a shot at unknown commands.
445 			 * XXX - bogus commands with a colon in
446 			 *       them will not elicit an error.
447 			 */
448 			if (el_parse(el, margc, margv) != 0)
449 #endif /* !SMALL */
450 				fputs("?Invalid command.\n", ttyout);
451 			continue;
452 		}
453 		if (c->c_conn && !connected) {
454 			fputs("Not connected.\n", ttyout);
455 			continue;
456 		}
457 		confirmrest = 0;
458 		(*c->c_handler)(margc, margv);
459 		if (bell && c->c_bell)
460 			(void)putc('\007', ttyout);
461 		if (c->c_handler != help)
462 			break;
463 	}
464 	(void)signal(SIGINT, (sig_t)intr);
465 	(void)signal(SIGPIPE, (sig_t)lostpeer);
466 }
467 
468 struct cmd *
469 getcmd(name)
470 	const char *name;
471 {
472 	const char *p, *q;
473 	struct cmd *c, *found;
474 	int nmatches, longest;
475 
476 	if (name == NULL)
477 		return (0);
478 
479 	longest = 0;
480 	nmatches = 0;
481 	found = 0;
482 	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
483 		for (q = name; *q == *p++; q++)
484 			if (*q == 0)		/* exact match? */
485 				return (c);
486 		if (!*q) {			/* the name was a prefix */
487 			if (q - name > longest) {
488 				longest = q - name;
489 				nmatches = 1;
490 				found = c;
491 			} else if (q - name == longest)
492 				nmatches++;
493 		}
494 	}
495 	if (nmatches > 1)
496 		return ((struct cmd *)-1);
497 	return (found);
498 }
499 
500 /*
501  * Slice a string up into argc/argv.
502  */
503 
504 int slrflag;
505 
506 void
507 makeargv()
508 {
509 	char *argp;
510 
511 	stringbase = line;		/* scan from first of buffer */
512 	argbase = argbuf;		/* store from first of buffer */
513 	slrflag = 0;
514 	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
515 	for (margc = 0; ; margc++) {
516 		argp = slurpstring();
517 		sl_add(marg_sl, argp);
518 		if (argp == NULL)
519 			break;
520 	}
521 #ifndef SMALL
522 	if (cursor_pos == line) {
523 		cursor_argc = 0;
524 		cursor_argo = 0;
525 	} else if (cursor_pos != NULL) {
526 		cursor_argc = margc;
527 		cursor_argo = strlen(margv[margc-1]);
528 	}
529 #endif /* !SMALL */
530 }
531 
532 #ifdef SMALL
533 #define INC_CHKCURSOR(x)	(x)++
534 #else  /* !SMALL */
535 #define INC_CHKCURSOR(x)	{ (x)++ ; \
536 				if (x == cursor_pos) { \
537 					cursor_argc = margc; \
538 					cursor_argo = ap-argbase; \
539 					cursor_pos = NULL; \
540 				} }
541 
542 #endif /* !SMALL */
543 
544 /*
545  * Parse string into argbuf;
546  * implemented with FSM to
547  * handle quoting and strings
548  */
549 char *
550 slurpstring()
551 {
552 	int got_one = 0;
553 	char *sb = stringbase;
554 	char *ap = argbase;
555 	char *tmp = argbase;		/* will return this if token found */
556 
557 	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
558 		switch (slrflag) {	/* and $ as token for macro invoke */
559 			case 0:
560 				slrflag++;
561 				INC_CHKCURSOR(stringbase);
562 				return ((*sb == '!') ? "!" : "$");
563 				/* NOTREACHED */
564 			case 1:
565 				slrflag++;
566 				altarg = stringbase;
567 				break;
568 			default:
569 				break;
570 		}
571 	}
572 
573 S0:
574 	switch (*sb) {
575 
576 	case '\0':
577 		goto OUT;
578 
579 	case ' ':
580 	case '\t':
581 		INC_CHKCURSOR(sb);
582 		goto S0;
583 
584 	default:
585 		switch (slrflag) {
586 			case 0:
587 				slrflag++;
588 				break;
589 			case 1:
590 				slrflag++;
591 				altarg = sb;
592 				break;
593 			default:
594 				break;
595 		}
596 		goto S1;
597 	}
598 
599 S1:
600 	switch (*sb) {
601 
602 	case ' ':
603 	case '\t':
604 	case '\0':
605 		goto OUT;	/* end of token */
606 
607 	case '\\':
608 		INC_CHKCURSOR(sb);
609 		goto S2;	/* slurp next character */
610 
611 	case '"':
612 		INC_CHKCURSOR(sb);
613 		goto S3;	/* slurp quoted string */
614 
615 	default:
616 		*ap = *sb;	/* add character to token */
617 		ap++;
618 		INC_CHKCURSOR(sb);
619 		got_one = 1;
620 		goto S1;
621 	}
622 
623 S2:
624 	switch (*sb) {
625 
626 	case '\0':
627 		goto OUT;
628 
629 	default:
630 		*ap = *sb;
631 		ap++;
632 		INC_CHKCURSOR(sb);
633 		got_one = 1;
634 		goto S1;
635 	}
636 
637 S3:
638 	switch (*sb) {
639 
640 	case '\0':
641 		goto OUT;
642 
643 	case '"':
644 		INC_CHKCURSOR(sb);
645 		goto S1;
646 
647 	default:
648 		*ap = *sb;
649 		ap++;
650 		INC_CHKCURSOR(sb);
651 		got_one = 1;
652 		goto S3;
653 	}
654 
655 OUT:
656 	if (got_one)
657 		*ap++ = '\0';
658 	argbase = ap;			/* update storage pointer */
659 	stringbase = sb;		/* update scan pointer */
660 	if (got_one) {
661 		return (tmp);
662 	}
663 	switch (slrflag) {
664 		case 0:
665 			slrflag++;
666 			break;
667 		case 1:
668 			slrflag++;
669 			altarg = (char *) 0;
670 			break;
671 		default:
672 			break;
673 	}
674 	return ((char *)0);
675 }
676 
677 /*
678  * Help command.
679  * Call each command handler with argc == 0 and argv[0] == name.
680  */
681 void
682 help(argc, argv)
683 	int argc;
684 	char *argv[];
685 {
686 	struct cmd *c;
687 
688 	if (argc == 1) {
689 		StringList *buf;
690 
691 		buf = sl_init();
692 		fprintf(ttyout, "%sommands may be abbreviated.  Commands are:\n\n",
693 		    proxy ? "Proxy c" : "C");
694 		for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
695 			if (c->c_name && (!proxy || c->c_proxy))
696 				sl_add(buf, c->c_name);
697 		list_vertical(buf);
698 		sl_free(buf, 0);
699 		return;
700 	}
701 
702 #define HELPINDENT ((int) sizeof("disconnect"))
703 
704 	while (--argc > 0) {
705 		char *arg;
706 
707 		arg = *++argv;
708 		c = getcmd(arg);
709 		if (c == (struct cmd *)-1)
710 			fprintf(ttyout, "?Ambiguous help command %s\n", arg);
711 		else if (c == (struct cmd *)0)
712 			fprintf(ttyout, "?Invalid help command %s\n", arg);
713 		else
714 			fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
715 				c->c_name, c->c_help);
716 	}
717 }
718 
719 void
720 usage()
721 {
722 	(void)fprintf(stderr,
723 	    "usage: %s [-adeginptvV] [-r <seconds>] [host [port]]\n"
724 	    "       %s host:path[/]\n"
725 	    "       %s ftp://host[:port]/path[/]\n"
726 	    "       %s http://host[:port]/file\n",
727 	    __progname, __progname, __progname, __progname);
728 	exit(1);
729 }
730