1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  *	Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  *	Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	All Rights Reserved  	*/
29 
30 /*
31  *	University Copyright- Copyright (c) 1982, 1986, 1988
32  *	The Regents of the University of California
33  *	All Rights Reserved
34  *
35  *	University Acknowledgment- Portions of this document are derived from
36  *	software developed by the University of California, Berkeley, and its
37  *	contributors.
38  */
39 
40 #pragma ident	"%Z%%M%	%I%	%E% SMI"
41 
42 /*
43  * FTP User Program -- Command Interface.
44  */
45 #define	EXTERN
46 #include	"ftp_var.h"
47 #include	<deflt.h>	/* macros that make using libcmd easier */
48 
49 static void usage(void);
50 static void timeout_sig(int sig);
51 static void cmdscanner(int top);
52 static void intr(int sig);
53 static char *slurpstring(void);
54 extern	int use_eprt;
55 
56 boolean_t ls_invokes_NLST = B_TRUE;
57 
58 #include <gssapi/gssapi.h>
59 #include <gssapi/gssapi_ext.h>
60 #define	GETOPT_STR	"dginptvET:axfm:"
61 #define	USAGE_STR	"[-adfginptvx] [-m mech] [-T timeout] " \
62 			"[hostname [port]]"
63 
64 int
65 main(int argc, char *argv[])
66 {
67 	char *cp;
68 	int c, top;
69 	struct passwd *pw = NULL;
70 	char homedir[MAXPATHLEN];
71 	char *temp_string = NULL;
72 
73 	(void) setlocale(LC_ALL, "");
74 
75 	buf = (char *)memalign(getpagesize(), FTPBUFSIZ);
76 	if (buf == NULL) {
77 		(void) fprintf(stderr, "ftp: memory allocation failed\n");
78 		return (1);
79 	}
80 
81 	timeoutms = timeout = 0;
82 	doglob = 1;
83 	interactive = 1;
84 	autologin = 1;
85 
86 	autoauth = 0;
87 	fflag = 0;
88 	autoencrypt = 0;
89 	goteof = 0;
90 	mechstr[0] = '\0';
91 
92 	sendport = -1;	/* tri-state variable. start out in "automatic" mode. */
93 	passivemode = 0;
94 
95 	while ((c = getopt(argc, argv, GETOPT_STR)) != EOF) {
96 		switch (c) {
97 		case 'd':
98 			options |= SO_DEBUG;
99 			debug++;
100 			break;
101 
102 		case 'g':
103 			doglob = 0;
104 			break;
105 
106 		case 'i':
107 			interactive = 0;
108 			break;
109 
110 		case 'n':
111 			autologin = 0;
112 			break;
113 
114 		case 'p':
115 			passivemode = 1;
116 			break;
117 
118 		case 't':
119 			trace++;
120 			break;
121 
122 		case 'v':
123 			verbose++;
124 			break;
125 
126 		/* undocumented option: allows testing of EPRT */
127 		case 'E':
128 			use_eprt = 1;
129 			break;
130 
131 		case 'T':
132 			if (!isdigit(*optarg)) {
133 				(void) fprintf(stderr,
134 					"ftp: bad timeout: \"%s\"\n", optarg);
135 				break;
136 			}
137 			timeout = atoi(optarg);
138 			timeoutms = timeout * MILLISEC;
139 			break;
140 
141 		case 'a':
142 			autoauth = 1;
143 			break;
144 
145 		case 'f':
146 			autoauth = 1;
147 			fflag = 1;
148 			break;
149 
150 		case 'm':
151 			autoauth = 1;
152 			call(setmech, "ftp", optarg, 0);
153 			if (code != 0)
154 				exit(1);
155 			break;
156 
157 		case 'x':
158 			autoauth = 1;
159 			autoencrypt = 1;
160 			break;
161 
162 		case '?':
163 		default:
164 			usage();
165 		}
166 	}
167 	argc -= optind;
168 	argv += optind;
169 
170 	if (argc > 2)
171 		usage();
172 
173 	fromatty = isatty(fileno(stdin));
174 	/*
175 	 * Scan env, then DEFAULTFTPFILE
176 	 * for FTP_LS_SENDS_NLST
177 	 */
178 	temp_string = getenv("FTP_LS_SENDS_NLST");
179 	if (temp_string == NULL) {	/* env var not set */
180 		if (defopen(DEFAULTFTPFILE) == 0) {
181 			/*
182 			 * turn off case sensitivity
183 			 */
184 			int flags = defcntl(DC_GETFLAGS, 0);
185 
186 			TURNOFF(flags, DC_CASE);
187 			(void) defcntl(DC_SETFLAGS, flags);
188 
189 			temp_string = defread("FTP_LS_SENDS_NLST=");
190 			(void) defopen(NULL);	/* close default file */
191 		}
192 	}
193 	if (temp_string != NULL &&
194 	    strncasecmp(temp_string, "n", 1) == 0)
195 		ls_invokes_NLST = B_FALSE;
196 
197 	/*
198 	 * Set up defaults for FTP.
199 	 */
200 	(void) strcpy(typename, "ascii"), type = TYPE_A;
201 	(void) strcpy(formname, "non-print"), form = FORM_N;
202 	(void) strcpy(modename, "stream"), mode = MODE_S;
203 	(void) strcpy(structname, "file"), stru = STRU_F;
204 	(void) strcpy(bytename, "8"), bytesize = 8;
205 	if (fromatty)
206 		verbose++;
207 	cpend = 0;	/* no pending replies */
208 	proxy = 0;	/* proxy not active */
209 	crflag = 1;	/* strip c.r. on ascii gets */
210 
211 	if (mechstr[0] == '\0') {
212 		strlcpy(mechstr, FTP_DEF_MECH, MECH_SZ);
213 	}
214 
215 	/*
216 	 * Set up the home directory in case we're globbing.
217 	 */
218 	cp = getlogin();
219 	if (cp != NULL) {
220 		pw = getpwnam(cp);
221 	}
222 	if (pw == NULL)
223 		pw = getpwuid(getuid());
224 	if (pw != NULL) {
225 		home = homedir;
226 		(void) strcpy(home, pw->pw_dir);
227 	}
228 	if (setjmp(timeralarm)) {
229 		(void) fflush(stdout);
230 		(void) printf("Connection timeout\n");
231 		exit(1);
232 	}
233 	(void) signal(SIGALRM, timeout_sig);
234 	reset_timer();
235 	if (argc > 0) {
236 		int nargc = 0;
237 		char *nargv[4];
238 
239 		if (setjmp(toplevel))
240 			return (0);
241 		(void) signal(SIGINT, intr);
242 		(void) signal(SIGPIPE, lostpeer);
243 		nargv[nargc++] = "ftp";
244 		nargv[nargc++] = argv[0];		/* hostname */
245 		if (argc > 1)
246 			nargv[nargc++] = argv[1];	/* port */
247 		nargv[nargc] = NULL;
248 		setpeer(nargc, nargv);
249 	}
250 	top = setjmp(toplevel) == 0;
251 	if (top) {
252 		(void) signal(SIGINT, intr);
253 		(void) signal(SIGPIPE, lostpeer);
254 	}
255 
256 	for (;;) {
257 		cmdscanner(top);
258 		top = 1;
259 	}
260 }
261 
262 static void
263 usage(void)
264 {
265 	(void) fprintf(stderr, "usage: ftp %s\n", USAGE_STR);
266 	exit(1);
267 }
268 
269 void
270 reset_timer()
271 {
272 	/* The test is just to reduce syscalls if timeouts aren't used */
273 	if (timeout)
274 		alarm(timeout);
275 }
276 
277 void
278 stop_timer()
279 {
280 	if (timeout)
281 		alarm(0);
282 }
283 
284 /*ARGSUSED*/
285 static void
286 timeout_sig(int sig)
287 {
288 	longjmp(timeralarm, 1);
289 }
290 
291 /*ARGSUSED*/
292 static void
293 intr(int sig)
294 {
295 	longjmp(toplevel, 1);
296 }
297 
298 /*ARGSUSED*/
299 void
300 lostpeer(int sig)
301 {
302 	extern FILE *ctrl_out;
303 	extern int data;
304 
305 	if (connected) {
306 		if (ctrl_out != NULL) {
307 			(void) shutdown(fileno(ctrl_out), 1+1);
308 			(void) fclose(ctrl_out);
309 			ctrl_out = NULL;
310 		}
311 		if (data >= 0) {
312 			(void) shutdown(data, 1+1);
313 			(void) close(data);
314 			data = -1;
315 		}
316 		connected = 0;
317 
318 		auth_type = AUTHTYPE_NONE;
319 		clevel = dlevel = PROT_C;
320 		goteof = 0;
321 	}
322 	pswitch(1);
323 	if (connected) {
324 		if (ctrl_out != NULL) {
325 			(void) shutdown(fileno(ctrl_out), 1+1);
326 			(void) fclose(ctrl_out);
327 			ctrl_out = NULL;
328 		}
329 		connected = 0;
330 
331 		auth_type = AUTHTYPE_NONE;
332 		clevel = dlevel = PROT_C;
333 		goteof = 0;
334 	}
335 	proxflag = 0;
336 	pswitch(0);
337 }
338 
339 /*
340  * Command parser.
341  */
342 static void
343 cmdscanner(int top)
344 {
345 	struct cmd *c;
346 
347 	if (!top)
348 		(void) putchar('\n');
349 	for (;;) {
350 		stop_timer();
351 		if (fromatty) {
352 			(void) printf("ftp> ");
353 			(void) fflush(stdout);
354 		}
355 		if (fgets(line, sizeof (line), stdin) == 0) {
356 			if (feof(stdin) || ferror(stdin))
357 				quit(0, NULL);
358 			break;
359 		}
360 		if (line[0] == 0)
361 			break;
362 		/* If not all, just discard rest of line */
363 		if (line[strlen(line)-1] != '\n') {
364 			while (fgetc(stdin) != '\n' && !feof(stdin) &&
365 			    !ferror(stdin))
366 				;
367 			(void) printf("Line too long\n");
368 			continue;
369 		} else
370 			line[strlen(line)-1] = 0;
371 
372 		makeargv();
373 		if (margc == 0) {
374 			continue;
375 		}
376 		c = getcmd(margv[0]);
377 		if (c == (struct cmd *)-1) {
378 			(void) printf("?Ambiguous command\n");
379 			continue;
380 		}
381 		if (c == 0) {
382 			(void) printf("?Invalid command\n");
383 			continue;
384 		}
385 		if (c->c_conn && !connected) {
386 			(void) printf("Not connected.\n");
387 			continue;
388 		}
389 		reset_timer();
390 		(*c->c_handler)(margc, margv);
391 #ifndef CTRL
392 #define	CTRL(c) ((c)&037)
393 #endif
394 		stop_timer();
395 		if (bell && c->c_bell)
396 			(void) putchar(CTRL('g'));
397 		if (c->c_handler != help)
398 			break;
399 	}
400 	(void) signal(SIGINT, intr);
401 	(void) signal(SIGPIPE, lostpeer);
402 }
403 
404 struct cmd *
405 getcmd(char *name)
406 {
407 	char *p, *q;
408 	struct cmd *c, *found;
409 	int nmatches, longest;
410 	extern struct cmd cmdtab[];
411 
412 	if (name == NULL)
413 		return (0);
414 
415 	longest = 0;
416 	nmatches = 0;
417 	found = 0;
418 	for (c = cmdtab; (p = c->c_name) != NULL; c++) {
419 		for (q = name; *q == *p++; q++)
420 			if (*q == 0)		/* exact match? */
421 				return (c);
422 		if (!*q) {			/* the name was a prefix */
423 			if (q - name > longest) {
424 				longest = q - name;
425 				nmatches = 1;
426 				found = c;
427 			} else if (q - name == longest)
428 				nmatches++;
429 		}
430 	}
431 	if (nmatches > 1)
432 		return ((struct cmd *)-1);
433 	return (found);
434 }
435 
436 /*
437  * Slice a string up into argc/argv.
438  */
439 
440 static int slrflag;
441 #define	MARGV_INC	20
442 
443 void
444 makeargv(void)
445 {
446 	char **argp;
447 	static int margv_size;
448 
449 	margc = 0;
450 	stringbase = line;		/* scan from first of buffer */
451 	argbase = argbuf;		/* store from first of buffer */
452 	slrflag = 0;
453 
454 	if (!margv) {
455 		margv_size = MARGV_INC;
456 		if ((margv = malloc(margv_size * sizeof (char *))) == NULL)
457 			fatal("Out of memory");
458 	}
459 	argp = margv;
460 	while (*argp++ = slurpstring()) {
461 		margc++;
462 		if (margc == margv_size) {
463 			margv_size += MARGV_INC;
464 			if ((margv = realloc(margv,
465 			    margv_size * sizeof (char *))) == NULL)
466 				fatal("Out of memory");
467 			argp = margv + margc;
468 		}
469 	}
470 }
471 
472 /*
473  * Parse string into argbuf;
474  * implemented with FSM to
475  * handle quoting and strings
476  */
477 static char *
478 slurpstring(void)
479 {
480 	int got_one = 0;
481 	char *sb = stringbase;
482 	char *ap = argbase;
483 	char *tmp = argbase;		/* will return this if token found */
484 	int	len;
485 
486 	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
487 		switch (slrflag) {	/* and $ as token for macro invoke */
488 			case 0:
489 				slrflag++;
490 				stringbase++;
491 				return ((*sb == '!') ? "!" : "$");
492 			case 1:
493 				slrflag++;
494 				altarg = stringbase;
495 				break;
496 			default:
497 				break;
498 		}
499 	}
500 
501 S0:
502 	switch (*sb) {
503 
504 	case '\0':
505 		goto OUT;
506 
507 	case ' ':
508 	case '\t':
509 		sb++; goto S0;
510 
511 	default:
512 		switch (slrflag) {
513 			case 0:
514 				slrflag++;
515 				break;
516 			case 1:
517 				slrflag++;
518 				altarg = sb;
519 				break;
520 			default:
521 				break;
522 		}
523 		goto S1;
524 	}
525 
526 S1:
527 	switch (*sb) {
528 
529 	case ' ':
530 	case '\t':
531 	case '\0':
532 		goto OUT;	/* end of token */
533 
534 	case '\\':
535 		sb++; goto S2;	/* slurp next character */
536 
537 	case '"':
538 		sb++; goto S3;	/* slurp quoted string */
539 
540 	default:
541 		if ((len = mblen(sb, MB_CUR_MAX)) <= 0)
542 			len = 1;
543 		memcpy(ap, sb, len);
544 		ap += len;
545 		sb += len;
546 		got_one = 1;
547 		goto S1;
548 	}
549 
550 S2:
551 	switch (*sb) {
552 
553 	case '\0':
554 		goto OUT;
555 
556 	default:
557 		if ((len = mblen(sb, MB_CUR_MAX)) <= 0)
558 			len = 1;
559 		memcpy(ap, sb, len);
560 		ap += len;
561 		sb += len;
562 		got_one = 1;
563 		goto S1;
564 	}
565 
566 S3:
567 	switch (*sb) {
568 
569 	case '\0':
570 		goto OUT;
571 
572 	case '"':
573 		sb++; goto S1;
574 
575 	default:
576 		if ((len = mblen(sb, MB_CUR_MAX)) <= 0)
577 			len = 1;
578 		memcpy(ap, sb, len);
579 		ap += len;
580 		sb += len;
581 		got_one = 1;
582 		goto S3;
583 	}
584 
585 OUT:
586 	if (got_one)
587 		*ap++ = '\0';
588 	argbase = ap;			/* update storage pointer */
589 	stringbase = sb;		/* update scan pointer */
590 	if (got_one) {
591 		return (tmp);
592 	}
593 	switch (slrflag) {
594 		case 0:
595 			slrflag++;
596 			break;
597 		case 1:
598 			slrflag++;
599 			altarg = (char *)0;
600 			break;
601 		default:
602 			break;
603 	}
604 	return ((char *)0);
605 }
606 
607 #define	HELPINDENT (sizeof ("directory"))
608 
609 /*
610  * Help command.
611  * Call each command handler with argc == 0 and argv[0] == name.
612  */
613 void
614 help(int argc, char *argv[])
615 {
616 	struct cmd *c;
617 	extern struct cmd cmdtab[];
618 
619 	if (argc == 1) {
620 		int i, j, w, k;
621 		int columns, width = 0, lines;
622 		extern int NCMDS;
623 
624 		(void) printf(
625 			"Commands may be abbreviated.  Commands are:\n\n");
626 		for (c = cmdtab; c < &cmdtab[NCMDS]; c++) {
627 			int len = strlen(c->c_name);
628 
629 			if (len > width)
630 				width = len;
631 		}
632 		width = (width + 8) &~ 7;
633 		columns = 80 / width;
634 		if (columns == 0)
635 			columns = 1;
636 		lines = (NCMDS + columns - 1) / columns;
637 		for (i = 0; i < lines; i++) {
638 			for (j = 0; j < columns; j++) {
639 				c = cmdtab + j * lines + i;
640 				if (c->c_name && (!proxy || c->c_proxy)) {
641 					(void) printf("%s", c->c_name);
642 				} else if (c->c_name) {
643 					for (k = 0; k < strlen(c->c_name);
644 					    k++) {
645 						(void) putchar(' ');
646 					}
647 				}
648 				if (c + lines >= &cmdtab[NCMDS]) {
649 					(void) printf("\n");
650 					break;
651 				}
652 				w = strlen(c->c_name);
653 				while (w < width) {
654 					w = (w + 8) &~ 7;
655 					(void) putchar('\t');
656 				}
657 			}
658 		}
659 		return;
660 	}
661 	while (--argc > 0) {
662 		char *arg;
663 		arg = *++argv;
664 		c = getcmd(arg);
665 		if (c == (struct cmd *)-1)
666 			(void) printf("?Ambiguous help command %s\n", arg);
667 		else if (c == (struct cmd *)0)
668 			(void) printf("?Invalid help command %s\n", arg);
669 		else
670 			(void) printf("%-*s\t%s\n", HELPINDENT,
671 				c->c_name, c->c_help);
672 	}
673 }
674 
675 /*
676  * Call routine with argc, argv set from args (terminated by 0).
677  */
678 void
679 call(void (*routine)(int argc, char *argv[]), ...)
680 {
681 	va_list ap;
682 	char *argv[10];
683 	int argc = 0;
684 
685 	va_start(ap, routine);
686 	while ((argv[argc] = va_arg(ap, char *)) != (char *)0)
687 		argc++;
688 	va_end(ap);
689 	(*routine)(argc, argv);
690 }
691