xref: /netbsd-src/usr.bin/ftp/main.c (revision d0fed6c87ddc40a8bffa6f99e7433ddfc864dd83)
1 /*	$NetBSD: main.c,v 1.21 1997/04/05 03:27:39 lukem Exp $	*/
2 
3 /*
4  * Copyright (c) 1985, 1989, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 static char copyright[] =
38 "@(#) Copyright (c) 1985, 1989, 1993, 1994\n\
39 	The Regents of the University of California.  All rights reserved.\n";
40 #endif /* not lint */
41 
42 #ifndef lint
43 #if 0
44 static char sccsid[] = "@(#)main.c	8.6 (Berkeley) 10/9/94";
45 #else
46 static char rcsid[] = "$NetBSD: main.c,v 1.21 1997/04/05 03:27:39 lukem Exp $";
47 #endif
48 #endif /* not lint */
49 
50 /*
51  * FTP User Program -- Command Interface.
52  */
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 
56 #include <err.h>
57 #include <netdb.h>
58 #include <pwd.h>
59 #include <stdio.h>
60 #include <string.h>
61 #include <unistd.h>
62 
63 #include "ftp_var.h"
64 
65 int
66 main(argc, argv)
67 	int argc;
68 	char *argv[];
69 {
70 	struct servent *sp;
71 	int ch, top, port, rval;
72 	struct passwd *pw = NULL;
73 	char *cp, homedir[MAXPATHLEN];
74 
75 	sp = getservbyname("ftp", "tcp");
76 	if (sp == 0)
77 		ftpport = htons(FTP_PORT);	/* good fallback */
78 	else
79 		ftpport = sp->s_port;
80 	sp = getservbyname("http", "tcp");
81 	if (sp == 0)
82 		httpport = htons(HTTP_PORT);	/* good fallback */
83 	else
84 		httpport = sp->s_port;
85 	doglob = 1;
86 	interactive = 1;
87 	autologin = 1;
88 	passivemode = 0;
89 	preserve = 1;
90 	verbose = 0;
91 	progress = 0;
92 #ifndef SMALL
93 	editing = 0;
94 	el = NULL;
95 	hist = NULL;
96 #endif
97 	mark = HASHBYTES;
98 	marg_sl = sl_init();
99 
100 	cp = strrchr(argv[0], '/');
101 	cp = (cp == NULL) ? argv[0] : cp + 1;
102 	if (strcmp(cp, "pftp") == 0)
103 		passivemode = 1;
104 
105 	fromatty = isatty(fileno(stdin));
106 	if (fromatty) {
107 		verbose = 1;		/* verbose if from a tty */
108 #ifndef SMALL
109 		editing = 1;		/* editing mode on if from a tty */
110 #endif
111 	}
112 	if (isatty(fileno(stdout)))
113 		progress = 1;		/* progress bar on if going to a tty */
114 
115 	while ((ch = getopt(argc, argv, "adeginpP:tvV")) != -1) {
116 		switch (ch) {
117 		case 'a':
118 			anonftp = 1;
119 			break;
120 
121 		case 'd':
122 			options |= SO_DEBUG;
123 			debug++;
124 			break;
125 
126 		case 'e':
127 #ifndef SMALL
128 			editing = 0;
129 #endif
130 			break;
131 
132 		case 'g':
133 			doglob = 0;
134 			break;
135 
136 		case 'i':
137 			interactive = 0;
138 			break;
139 
140 		case 'n':
141 			autologin = 0;
142 			break;
143 
144 		case 'p':
145 			passivemode = 1;
146 			break;
147 
148 		case 'P':
149 			port = atoi(optarg);
150 			if (port <= 0)
151 				warnx("bad port number: %s (ignored)", optarg);
152 			else
153 				ftpport = htons(port);
154 			break;
155 
156 		case 't':
157 			trace = 1;
158 			break;
159 
160 		case 'v':
161 			verbose = 1;
162 			break;
163 
164 		case 'V':
165 			verbose = 0;
166 			break;
167 
168 		default:
169 			usage();
170 		}
171 	}
172 	argc -= optind;
173 	argv += optind;
174 
175 	cpend = 0;	/* no pending replies */
176 	proxy = 0;	/* proxy not active */
177 	crflag = 1;	/* strip c.r. on ascii gets */
178 	sendport = -1;	/* not using ports */
179 	/*
180 	 * Set up the home directory in case we're globbing.
181 	 */
182 	cp = getlogin();
183 	if (cp != NULL) {
184 		pw = getpwnam(cp);
185 	}
186 	if (pw == NULL)
187 		pw = getpwuid(getuid());
188 	if (pw != NULL) {
189 		home = homedir;
190 		(void)strcpy(home, pw->pw_dir);
191 	}
192 
193 	setttywidth(0);
194 	(void)signal(SIGWINCH, setttywidth);
195 
196 	if (argc > 0) {
197 		if (strchr(argv[0], ':') != NULL) {
198 			anonftp = 1;	/* Handle "automatic" transfers. */
199 			rval = auto_fetch(argc, argv);
200 			if (rval >= 0)		/* -1 == connected and cd-ed */
201 				exit(rval);
202 		} else {
203 			char *xargv[5];
204 
205 			if (setjmp(toplevel))
206 				exit(0);
207 			(void)signal(SIGINT, (sig_t)intr);
208 			(void)signal(SIGPIPE, (sig_t)lostpeer);
209 			xargv[0] = __progname;
210 			xargv[1] = argv[0];
211 			xargv[2] = argv[1];
212 			xargv[3] = argv[2];
213 			xargv[4] = NULL;
214 			setpeer(argc+1, xargv);
215 		}
216 	}
217 #ifndef SMALL
218 	controlediting();
219 #endif /* !SMALL */
220 	top = setjmp(toplevel) == 0;
221 	if (top) {
222 		(void)signal(SIGINT, (sig_t)intr);
223 		(void)signal(SIGPIPE, (sig_t)lostpeer);
224 	}
225 	for (;;) {
226 		cmdscanner(top);
227 		top = 1;
228 	}
229 }
230 
231 void
232 intr()
233 {
234 
235 	alarmtimer(0);
236 	longjmp(toplevel, 1);
237 }
238 
239 void
240 lostpeer()
241 {
242 
243 	alarmtimer(0);
244 	if (connected) {
245 		if (cout != NULL) {
246 			(void)shutdown(fileno(cout), 1+1);
247 			(void)fclose(cout);
248 			cout = NULL;
249 		}
250 		if (data >= 0) {
251 			(void)shutdown(data, 1+1);
252 			(void)close(data);
253 			data = -1;
254 		}
255 		connected = 0;
256 	}
257 	pswitch(1);
258 	if (connected) {
259 		if (cout != NULL) {
260 			(void)shutdown(fileno(cout), 1+1);
261 			(void)fclose(cout);
262 			cout = NULL;
263 		}
264 		connected = 0;
265 	}
266 	proxflag = 0;
267 	pswitch(0);
268 }
269 
270 /*
271  * Generate a prompt
272  */
273 char *
274 prompt()
275 {
276 	return ("ftp> ");
277 }
278 
279 /*
280  * Command parser.
281  */
282 void
283 cmdscanner(top)
284 	int top;
285 {
286 	struct cmd *c;
287 	int num;
288 
289 	if (!top
290 #ifndef SMALL
291 	    && !editing
292 #endif /* !SMALL */
293 	    )
294 		(void)putchar('\n');
295 	for (;;) {
296 #ifndef SMALL
297 		if (!editing) {
298 #endif /* !SMALL */
299 			if (fromatty) {
300 				fputs(prompt(), stdout);
301 				(void)fflush(stdout);
302 			}
303 			if (fgets(line, sizeof(line), stdin) == NULL)
304 				quit(0, 0);
305 			num = strlen(line);
306 			if (num == 0)
307 				break;
308 			if (line[--num] == '\n') {
309 				if (num == 0)
310 					break;
311 				line[num] = '\0';
312 			} else if (num == sizeof(line) - 2) {
313 				puts("sorry, input line too long.");
314 				while ((num = getchar()) != '\n' && num != EOF)
315 					/* void */;
316 				break;
317 			} /* else it was a line without a newline */
318 #ifndef SMALL
319 		} else {
320 			const char *buf;
321 			cursor_pos = NULL;
322 
323 			if ((buf = el_gets(el, &num)) == NULL || num == 0)
324 				quit(0, 0);
325 			if (line[--num] == '\n') {
326 				if (num == 0)
327 					break;
328 			} else if (num >= sizeof(line)) {
329 				puts("sorry, input line too long.");
330 				break;
331 			}
332 			memcpy(line, buf, num);
333 			line[num] = '\0';
334 			history(hist, H_ENTER, buf);
335 		}
336 #endif /* !SMALL */
337 
338 		makeargv();
339 		if (margc == 0)
340 			continue;
341 #if 0 && !defined(SMALL)	/* XXX: don't want el_parse */
342 		/*
343 		 * el_parse returns -1 to signal that it's not been handled
344 		 * internally.
345 		 */
346 		if (el_parse(el, margc, margv) != -1)
347 			continue;
348 #endif /* !SMALL */
349 		c = getcmd(margv[0]);
350 		if (c == (struct cmd *)-1) {
351 			puts("?Ambiguous command.");
352 			continue;
353 		}
354 		if (c == 0) {
355 			puts("?Invalid command.");
356 			continue;
357 		}
358 		if (c->c_conn && !connected) {
359 			puts("Not connected.");
360 			continue;
361 		}
362 		confirmrest = 0;
363 		(*c->c_handler)(margc, margv);
364 		if (bell && c->c_bell)
365 			(void)putchar('\007');
366 		if (c->c_handler != help)
367 			break;
368 	}
369 	(void)signal(SIGINT, (sig_t)intr);
370 	(void)signal(SIGPIPE, (sig_t)lostpeer);
371 }
372 
373 struct cmd *
374 getcmd(name)
375 	const char *name;
376 {
377 	const char *p, *q;
378 	struct cmd *c, *found;
379 	int nmatches, longest;
380 
381 	if (name == NULL)
382 		return (0);
383 
384 	longest = 0;
385 	nmatches = 0;
386 	found = 0;
387 	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
388 		for (q = name; *q == *p++; q++)
389 			if (*q == 0)		/* exact match? */
390 				return (c);
391 		if (!*q) {			/* the name was a prefix */
392 			if (q - name > longest) {
393 				longest = q - name;
394 				nmatches = 1;
395 				found = c;
396 			} else if (q - name == longest)
397 				nmatches++;
398 		}
399 	}
400 	if (nmatches > 1)
401 		return ((struct cmd *)-1);
402 	return (found);
403 }
404 
405 /*
406  * Slice a string up into argc/argv.
407  */
408 
409 int slrflag;
410 
411 void
412 makeargv()
413 {
414 	char *argp;
415 
416 	stringbase = line;		/* scan from first of buffer */
417 	argbase = argbuf;		/* store from first of buffer */
418 	slrflag = 0;
419 	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
420 	for (margc = 0; ; margc++) {
421 		argp = slurpstring();
422 		sl_add(marg_sl, argp);
423 		if (argp == NULL)
424 			break;
425 	}
426 #ifndef SMALL
427 	if (cursor_pos == line) {
428 		cursor_argc = 0;
429 		cursor_argo = 0;
430 	} else if (cursor_pos != NULL) {
431 		cursor_argc = margc;
432 		cursor_argo = strlen(margv[margc-1]);
433 	}
434 #endif /* !SMALL */
435 }
436 
437 #ifdef SMALL
438 #define INC_CHKCURSOR(x)	(x)++
439 #else  /* !SMALL */
440 #define INC_CHKCURSOR(x)	{ (x)++ ; \
441 				if (x == cursor_pos) { \
442 					cursor_argc = margc; \
443 					cursor_argo = ap-argbase; \
444 					cursor_pos = NULL; \
445 				} }
446 
447 #endif /* !SMALL */
448 
449 /*
450  * Parse string into argbuf;
451  * implemented with FSM to
452  * handle quoting and strings
453  */
454 char *
455 slurpstring()
456 {
457 	int got_one = 0;
458 	char *sb = stringbase;
459 	char *ap = argbase;
460 	char *tmp = argbase;		/* will return this if token found */
461 
462 	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
463 		switch (slrflag) {	/* and $ as token for macro invoke */
464 			case 0:
465 				slrflag++;
466 				INC_CHKCURSOR(stringbase);
467 				return ((*sb == '!') ? "!" : "$");
468 				/* NOTREACHED */
469 			case 1:
470 				slrflag++;
471 				altarg = stringbase;
472 				break;
473 			default:
474 				break;
475 		}
476 	}
477 
478 S0:
479 	switch (*sb) {
480 
481 	case '\0':
482 		goto OUT;
483 
484 	case ' ':
485 	case '\t':
486 		INC_CHKCURSOR(sb);
487 		goto S0;
488 
489 	default:
490 		switch (slrflag) {
491 			case 0:
492 				slrflag++;
493 				break;
494 			case 1:
495 				slrflag++;
496 				altarg = sb;
497 				break;
498 			default:
499 				break;
500 		}
501 		goto S1;
502 	}
503 
504 S1:
505 	switch (*sb) {
506 
507 	case ' ':
508 	case '\t':
509 	case '\0':
510 		goto OUT;	/* end of token */
511 
512 	case '\\':
513 		INC_CHKCURSOR(sb);
514 		goto S2;	/* slurp next character */
515 
516 	case '"':
517 		INC_CHKCURSOR(sb);
518 		goto S3;	/* slurp quoted string */
519 
520 	default:
521 		*ap = *sb;	/* add character to token */
522 		ap++;
523 		INC_CHKCURSOR(sb);
524 		got_one = 1;
525 		goto S1;
526 	}
527 
528 S2:
529 	switch (*sb) {
530 
531 	case '\0':
532 		goto OUT;
533 
534 	default:
535 		*ap = *sb;
536 		ap++;
537 		INC_CHKCURSOR(sb);
538 		got_one = 1;
539 		goto S1;
540 	}
541 
542 S3:
543 	switch (*sb) {
544 
545 	case '\0':
546 		goto OUT;
547 
548 	case '"':
549 		INC_CHKCURSOR(sb);
550 		goto S1;
551 
552 	default:
553 		*ap = *sb;
554 		ap++;
555 		INC_CHKCURSOR(sb);
556 		got_one = 1;
557 		goto S3;
558 	}
559 
560 OUT:
561 	if (got_one)
562 		*ap++ = '\0';
563 	argbase = ap;			/* update storage pointer */
564 	stringbase = sb;		/* update scan pointer */
565 	if (got_one) {
566 		return (tmp);
567 	}
568 	switch (slrflag) {
569 		case 0:
570 			slrflag++;
571 			break;
572 		case 1:
573 			slrflag++;
574 			altarg = (char *) 0;
575 			break;
576 		default:
577 			break;
578 	}
579 	return ((char *)0);
580 }
581 
582 /*
583  * Help command.
584  * Call each command handler with argc == 0 and argv[0] == name.
585  */
586 void
587 help(argc, argv)
588 	int argc;
589 	char *argv[];
590 {
591 	struct cmd *c;
592 
593 	if (argc == 1) {
594 		StringList *buf;
595 
596 		buf = sl_init();
597 		printf("%sommands may be abbreviated.  Commands are:\n\n",
598 		    proxy ? "Proxy c" : "C");
599 		for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
600 			if (c->c_name && (!proxy || c->c_proxy))
601 				sl_add(buf, c->c_name);
602 		list_vertical(buf);
603 		sl_free(buf, 0);
604 		return;
605 	}
606 
607 #define HELPINDENT ((int) sizeof("disconnect"))
608 
609 	while (--argc > 0) {
610 		char *arg;
611 
612 		arg = *++argv;
613 		c = getcmd(arg);
614 		if (c == (struct cmd *)-1)
615 			printf("?Ambiguous help command %s\n", arg);
616 		else if (c == (struct cmd *)0)
617 			printf("?Invalid help command %s\n", arg);
618 		else
619 			printf("%-*s\t%s\n", HELPINDENT,
620 				c->c_name, c->c_help);
621 	}
622 }
623 
624 void
625 usage()
626 {
627 	(void)fprintf(stderr,
628 	    "usage: %s [-adeginptvV] [host [port]]\n"
629 	    "       %s host:path[/]\n"
630 	    "       %s ftp://host[:port]/path[/]\n"
631 	    "       %s http://host[:port]/file\n",
632 	    __progname, __progname, __progname, __progname);
633 	exit(1);
634 }
635