xref: /openbsd-src/usr.bin/ftp/cmds.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: cmds.c,v 1.69 2009/04/27 22:51:51 martynas Exp $	*/
2 /*	$NetBSD: cmds.c,v 1.27 1997/08/18 10:20:15 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. Neither the name of the University nor the names of its contributors
46  *    may be used to endorse or promote products derived from this software
47  *    without specific prior written permission.
48  *
49  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59  * SUCH DAMAGE.
60  */
61 
62 /*
63  * FTP User Program -- Command Routines.
64  */
65 #include <sys/types.h>
66 #include <sys/socket.h>
67 #include <sys/stat.h>
68 #include <sys/wait.h>
69 #include <arpa/ftp.h>
70 
71 #include <ctype.h>
72 #include <err.h>
73 #ifndef SMALL
74 #include <fnmatch.h>
75 #endif /* !SMALL */
76 #include <glob.h>
77 #include <netdb.h>
78 #include <stdio.h>
79 #include <stdlib.h>
80 #include <string.h>
81 #include <unistd.h>
82 
83 #include "ftp_var.h"
84 #include "pathnames.h"
85 
86 jmp_buf	jabort;
87 char   *mname;
88 char   *home = "/";
89 
90 struct	types {
91 	char	*t_name;
92 	char	*t_mode;
93 	int	t_type;
94 	char	*t_arg;
95 } types[] = {
96 	{ "ascii",	"A",	TYPE_A,	0 },
97 	{ "binary",	"I",	TYPE_I,	0 },
98 	{ "image",	"I",	TYPE_I,	0 },
99 	{ "ebcdic",	"E",	TYPE_E,	0 },
100 	{ "tenex",	"L",	TYPE_L,	bytename },
101 	{ NULL }
102 };
103 
104 /*
105  * Set transfer type.
106  */
107 void
108 settype(int argc, char *argv[])
109 {
110 	struct types *p;
111 	int comret;
112 
113 	if (argc > 2) {
114 		char *sep;
115 
116 		fprintf(ttyout, "usage: %s [", argv[0]);
117 		sep = "";
118 		for (p = types; p->t_name; p++) {
119 			fprintf(ttyout, "%s%s", sep, p->t_name);
120 			sep = " | ";
121 		}
122 		fputs("]\n", ttyout);
123 		code = -1;
124 		return;
125 	}
126 	if (argc < 2) {
127 		fprintf(ttyout, "Using %s mode to transfer files.\n", typename);
128 		code = 0;
129 		return;
130 	}
131 	for (p = types; p->t_name; p++)
132 		if (strcmp(argv[1], p->t_name) == 0)
133 			break;
134 	if (p->t_name == 0) {
135 		fprintf(ttyout, "%s: unknown mode.\n", argv[1]);
136 		code = -1;
137 		return;
138 	}
139 	if ((p->t_arg != NULL) && (*(p->t_arg) != '\0'))
140 		comret = command("TYPE %s %s", p->t_mode, p->t_arg);
141 	else
142 		comret = command("TYPE %s", p->t_mode);
143 	if (comret == COMPLETE) {
144 		(void)strlcpy(typename, p->t_name, sizeof typename);
145 		curtype = type = p->t_type;
146 	}
147 }
148 
149 /*
150  * Internal form of settype; changes current type in use with server
151  * without changing our notion of the type for data transfers.
152  * Used to change to and from ascii for listings.
153  */
154 void
155 changetype(int newtype, int show)
156 {
157 	struct types *p;
158 	int comret, oldverbose = verbose;
159 
160 	if (newtype == 0)
161 		newtype = TYPE_I;
162 	if (newtype == curtype)
163 		return;
164 	if (
165 #ifndef SMALL
166 	    !debug &&
167 #endif /* !SMALL */
168 	    show == 0)
169 		verbose = 0;
170 	for (p = types; p->t_name; p++)
171 		if (newtype == p->t_type)
172 			break;
173 	if (p->t_name == 0) {
174 		warnx("internal error: unknown type %d.", newtype);
175 		return;
176 	}
177 	if (newtype == TYPE_L && bytename[0] != '\0')
178 		comret = command("TYPE %s %s", p->t_mode, bytename);
179 	else
180 		comret = command("TYPE %s", p->t_mode);
181 	if (comret == COMPLETE)
182 		curtype = newtype;
183 	verbose = oldverbose;
184 }
185 
186 char *stype[] = {
187 	"type",
188 	"",
189 	0
190 };
191 
192 /*
193  * Set binary transfer type.
194  */
195 /*ARGSUSED*/
196 void
197 setbinary(int argc, char *argv[])
198 {
199 
200 	stype[1] = "binary";
201 	settype(2, stype);
202 }
203 
204 /*
205  * Set ascii transfer type.
206  */
207 /*ARGSUSED*/
208 void
209 setascii(int argc, char *argv[])
210 {
211 
212 	stype[1] = "ascii";
213 	settype(2, stype);
214 }
215 
216 /*
217  * Set tenex transfer type.
218  */
219 /*ARGSUSED*/
220 void
221 settenex(int argc, char *argv[])
222 {
223 
224 	stype[1] = "tenex";
225 	settype(2, stype);
226 }
227 
228 /*
229  * Set file transfer mode.
230  */
231 /*ARGSUSED*/
232 void
233 setftmode(int argc, char *argv[])
234 {
235 
236 	fprintf(ttyout, "We only support %s mode, sorry.\n", modename);
237 	code = -1;
238 }
239 
240 /*
241  * Set file transfer format.
242  */
243 /*ARGSUSED*/
244 void
245 setform(int argc, char *argv[])
246 {
247 
248 	fprintf(ttyout, "We only support %s format, sorry.\n", formname);
249 	code = -1;
250 }
251 
252 /*
253  * Set file transfer structure.
254  */
255 /*ARGSUSED*/
256 void
257 setstruct(int argc, char *argv[])
258 {
259 
260 	fprintf(ttyout, "We only support %s structure, sorry.\n", structname);
261 	code = -1;
262 }
263 
264 #ifndef SMALL
265 void
266 reput(int argc, char *argv[])
267 {
268 
269 	(void)putit(argc, argv, 1);
270 }
271 #endif /* !SMALL */
272 
273 #ifndef SMALL
274 void
275 put(int argc, char *argv[])
276 {
277 
278 	(void)putit(argc, argv, 0);
279 }
280 #endif /* !SMALL */
281 
282 /*
283  * Send a single file.
284  */
285 #ifndef SMALL
286 void
287 putit(int argc, char *argv[], int restartit)
288 {
289 	char *cmd;
290 	int loc = 0;
291 	char *oldargv1, *oldargv2;
292 
293 	if (argc == 2) {
294 		argc++;
295 		argv[2] = argv[1];
296 		loc++;
297 	}
298 	if (argc < 2 && !another(&argc, &argv, "local-file"))
299 		goto usage;
300 	if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
301 usage:
302 		fprintf(ttyout, "usage: %s local-file [remote-file]\n",
303 		    argv[0]);
304 		code = -1;
305 		return;
306 	}
307 	oldargv1 = argv[1];
308 	oldargv2 = argv[2];
309 	if (!globulize(&argv[1])) {
310 		code = -1;
311 		return;
312 	}
313 	/*
314 	 * If "globulize" modifies argv[1], and argv[2] is a copy of
315 	 * the old argv[1], make it a copy of the new argv[1].
316 	 */
317 	if (argv[1] != oldargv1 && argv[2] == oldargv1) {
318 		argv[2] = argv[1];
319 	}
320 #ifndef SMALL
321 	if (restartit == 1) {
322 		if (curtype != type)
323 			changetype(type, 0);
324 		restart_point = remotesize(argv[2], 1);
325 		if (restart_point < 0) {
326 			restart_point = 0;
327 			code = -1;
328 			return;
329 		}
330 	}
331 #endif /* !SMALL */
332 	if (strcmp(argv[0], "append") == 0) {
333 		restartit = 1;
334 	}
335 	cmd = restartit ? "APPE" : ((sunique) ? "STOU" : "STOR");
336 	if (loc && ntflag) {
337 		argv[2] = dotrans(argv[2]);
338 	}
339 	if (loc && mapflag) {
340 		argv[2] = domap(argv[2]);
341 	}
342 	sendrequest(cmd, argv[1], argv[2],
343 	    argv[1] != oldargv1 || argv[2] != oldargv2);
344 	restart_point = 0;
345 	if (oldargv1 != argv[1])	/* free up after globulize() */
346 		free(argv[1]);
347 }
348 #endif /* !SMALL */
349 
350 /*
351  * Send multiple files.
352  */
353 #ifndef SMALL
354 void
355 mput(int argc, char *argv[])
356 {
357 	extern int optind, optreset;
358 	int ch, i, restartit = 0;
359 	sig_t oldintr;
360 	char *cmd, *tp;
361 
362 	optind = optreset = 1;
363 
364 #ifndef SMALL
365 	while ((ch = getopt(argc, argv, "c")) != -1) {
366 		switch(ch) {
367 		case 'c':
368 			restartit = 1;
369 			break;
370 		default:
371 			goto usage;
372 		}
373 	}
374 #endif /* !SMALL */
375 
376 	if (argc - optind < 1 && !another(&argc, &argv, "local-files")) {
377 usage:
378 		fprintf(ttyout, "usage: %s [-c] local-files\n", argv[0]);
379 		code = -1;
380 		return;
381 	}
382 
383 #ifndef SMALL
384 	argv[optind - 1] = argv[0];
385 	argc -= optind - 1;
386 	argv += optind - 1;
387 #endif /* !SMALL */
388 
389 	mname = argv[0];
390 	mflag = 1;
391 
392 	oldintr = signal(SIGINT, mabort);
393 	(void)setjmp(jabort);
394 	if (proxy) {
395 		char *cp, *tp2, tmpbuf[MAXPATHLEN];
396 
397 		while ((cp = remglob(argv, 0, NULL)) != NULL) {
398 			if (*cp == '\0') {
399 				mflag = 0;
400 				continue;
401 			}
402 			if (mflag && confirm(argv[0], cp)) {
403 				tp = cp;
404 				if (mcase) {
405 					while (*tp && !islower(*tp)) {
406 						tp++;
407 					}
408 					if (!*tp) {
409 						tp = cp;
410 						tp2 = tmpbuf;
411 						while ((*tp2 = *tp) != '\0') {
412 						     if (isupper(*tp2)) {
413 							    *tp2 =
414 								tolower(*tp2);
415 						     }
416 						     tp++;
417 						     tp2++;
418 						}
419 					}
420 					tp = tmpbuf;
421 				}
422 				if (ntflag) {
423 					tp = dotrans(tp);
424 				}
425 				if (mapflag) {
426 					tp = domap(tp);
427 				}
428 #ifndef SMALL
429 				if (restartit == 1) {
430 					off_t ret;
431 
432 					if (curtype != type)
433 						changetype(type, 0);
434 					ret = remotesize(tp, 0);
435 					restart_point = (ret < 0) ? 0 : ret;
436 				}
437 #endif /* !SMALL */
438 				cmd = restartit ? "APPE" : ((sunique) ?
439 				    "STOU" : "STOR");
440 				sendrequest(cmd, cp, tp,
441 				    cp != tp || !interactive);
442 				restart_point = 0;
443 				if (!mflag && fromatty) {
444 					if (confirm(argv[0], NULL))
445 						mflag = 1;
446 				}
447 			}
448 		}
449 		(void)signal(SIGINT, oldintr);
450 		mflag = 0;
451 		return;
452 	}
453 	for (i = 1; i < argc; i++) {
454 		char **cpp;
455 		glob_t gl;
456 		int flags;
457 
458 		if (!doglob) {
459 			if (mflag && confirm(argv[0], argv[i])) {
460 				tp = (ntflag) ? dotrans(argv[i]) : argv[i];
461 				tp = (mapflag) ? domap(tp) : tp;
462 #ifndef SMALL
463 				if (restartit == 1) {
464 					off_t ret;
465 
466 					if (curtype != type)
467 						changetype(type, 0);
468 					ret = remotesize(tp, 0);
469 					restart_point = (ret < 0) ? 0 : ret;
470 				}
471 #endif /* !SMALL */
472 				cmd = restartit ? "APPE" : ((sunique) ?
473 				    "STOU" : "STOR");
474 				sendrequest(cmd, argv[i], tp,
475 				    tp != argv[i] || !interactive);
476 				restart_point = 0;
477 				if (!mflag && fromatty) {
478 					if (confirm(argv[0], NULL))
479 						mflag = 1;
480 				}
481 			}
482 			continue;
483 		}
484 
485 		memset(&gl, 0, sizeof(gl));
486 		flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
487 		if (glob(argv[i], flags, NULL, &gl) || gl.gl_pathc == 0) {
488 			warnx("%s: not found", argv[i]);
489 			globfree(&gl);
490 			continue;
491 		}
492 		for (cpp = gl.gl_pathv; cpp && *cpp != NULL; cpp++) {
493 			if (mflag && confirm(argv[0], *cpp)) {
494 				tp = (ntflag) ? dotrans(*cpp) : *cpp;
495 				tp = (mapflag) ? domap(tp) : tp;
496 #ifndef SMALL
497 				if (restartit == 1) {
498 					off_t ret;
499 
500 					if (curtype != type)
501 						changetype(type, 0);
502 					ret = remotesize(tp, 0);
503 					restart_point = (ret < 0) ? 0 : ret;
504 				}
505 #endif /* !SMALL */
506 				cmd = restartit ? "APPE" : ((sunique) ?
507 				    "STOU" : "STOR");
508 				sendrequest(cmd, *cpp, tp,
509 				    *cpp != tp || !interactive);
510 				restart_point = 0;
511 				if (!mflag && fromatty) {
512 					if (confirm(argv[0], NULL))
513 						mflag = 1;
514 				}
515 			}
516 		}
517 		globfree(&gl);
518 	}
519 	(void)signal(SIGINT, oldintr);
520 	mflag = 0;
521 }
522 #endif /* !SMALL */
523 
524 #ifndef SMALL
525 void
526 reget(int argc, char *argv[])
527 {
528 
529 	(void)getit(argc, argv, 1, "a+w");
530 }
531 #endif /* !SMALL */
532 
533 void
534 get(int argc, char *argv[])
535 {
536 
537 	(void)getit(argc, argv, 0, restart_point ? "a+w" : "w" );
538 }
539 
540 /*
541  * Receive one file.
542  */
543 int
544 getit(int argc, char *argv[], int restartit, const char *mode)
545 {
546 	int loc = 0;
547 	int rval = 0;
548 	char *oldargv1, *oldargv2, *globargv2;
549 
550 	if (argc == 2) {
551 		argc++;
552 		argv[2] = argv[1];
553 		loc++;
554 	}
555 	if (argc < 2 && !another(&argc, &argv, "remote-file"))
556 		goto usage;
557 	if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) {
558 usage:
559 		fprintf(ttyout, "usage: %s remote-file [local-file]\n",
560 		    argv[0]);
561 		code = -1;
562 		return (0);
563 	}
564 	oldargv1 = argv[1];
565 	oldargv2 = argv[2];
566 	if (!globulize(&argv[2])) {
567 		code = -1;
568 		return (0);
569 	}
570 	globargv2 = argv[2];
571 	if (loc && mcase) {
572 		char *tp = argv[1], *tp2, tmpbuf[MAXPATHLEN];
573 
574 		while (*tp && !islower(*tp)) {
575 			tp++;
576 		}
577 		if (!*tp) {
578 			tp = argv[2];
579 			tp2 = tmpbuf;
580 			while ((*tp2 = *tp) != '\0') {
581 				if (isupper(*tp2)) {
582 					*tp2 = tolower(*tp2);
583 				}
584 				tp++;
585 				tp2++;
586 			}
587 			argv[2] = tmpbuf;
588 		}
589 	}
590 	if (loc && ntflag)
591 		argv[2] = dotrans(argv[2]);
592 	if (loc && mapflag)
593 		argv[2] = domap(argv[2]);
594 #ifndef SMALL
595 	if (restartit) {
596 		struct stat stbuf;
597 		int ret;
598 
599 		ret = stat(argv[2], &stbuf);
600 		if (restartit == 1) {
601 			restart_point = (ret < 0) ? 0 : stbuf.st_size;
602 		} else {
603 			if (ret == 0) {
604 				time_t mtime;
605 
606 				mtime = remotemodtime(argv[1], 0);
607 				if (mtime == -1)
608 					goto freegetit;
609 				if (stbuf.st_mtime >= mtime) {
610 					rval = 1;
611 					goto freegetit;
612 				}
613 			}
614 		}
615 	}
616 #endif /* !SMALL */
617 
618 	recvrequest("RETR", argv[2], argv[1], mode,
619 	    argv[1] != oldargv1 || argv[2] != oldargv2 || !interactive, loc);
620 	restart_point = 0;
621 freegetit:
622 	if (oldargv2 != globargv2)	/* free up after globulize() */
623 		free(globargv2);
624 	return (rval);
625 }
626 
627 /* XXX - Signal race. */
628 /* ARGSUSED */
629 void
630 mabort(int signo)
631 {
632 	alarmtimer(0);
633 	putc('\n', ttyout);
634 	(void)fflush(ttyout);
635 	if (mflag && fromatty)
636 		if (confirm(mname, NULL))
637 			longjmp(jabort, 1);
638 	mflag = 0;
639 	longjmp(jabort, 1);
640 }
641 
642 /*
643  * Get multiple files.
644  */
645 void
646 mget(int argc, char *argv[])
647 {
648 	extern int optind, optreset;
649 	sig_t oldintr;
650 	int ch, xargc = 2;
651 	char *cp, localcwd[MAXPATHLEN], *xargv[] = {argv[0], NULL, NULL};
652 	static int restartit = 0;
653 #ifndef SMALL
654 	extern char *optarg;
655 	const char *errstr;
656 	int i = 1;
657 	char type = NULL, *dummyargv[] = {argv[0], ".", NULL};
658 	FILE *ftemp = NULL;
659 	static int depth = 0, max_depth = 0;
660 #endif /* !SMALL */
661 
662 	optind = optreset = 1;
663 
664 #ifndef SMALL
665 
666 	if (depth)
667 		depth++;
668 
669 	while ((ch = getopt(argc, argv, "cd:nr")) != -1) {
670 		switch(ch) {
671 		case 'c':
672 			restartit = 1;
673 			break;
674 		case 'd':
675 			max_depth = strtonum(optarg, 0, INT_MAX, &errstr);
676 			if (errstr != NULL) {
677 				fprintf(ttyout, "bad depth value, %s: %s\n",
678 				    errstr, optarg);
679 				code = -1;
680 				return;
681 			}
682 			break;
683 		case 'n':
684 			restartit = -1;
685 			break;
686 		case 'r':
687 			depth = 1;
688 			break;
689 		default:
690 			goto usage;
691 		}
692 	}
693 #endif /* !SMALL */
694 
695 	if (argc - optind < 1 && !another(&argc, &argv, "remote-files")) {
696 usage:
697 		fprintf(ttyout, "usage: %s [-cnr] [-d depth] remote-files\n",
698 		    argv[0]);
699 		code = -1;
700 		return;
701 	}
702 
703 #ifndef SMALL
704 	argv[optind - 1] = argv[0];
705 	argc -= optind - 1;
706 	argv += optind - 1;
707 #endif /* !SMALL */
708 
709 	mname = argv[0];
710 	mflag = 1;
711 	if (getcwd(localcwd, sizeof(localcwd)) == NULL)
712 		err(1, "can't get cwd");
713 
714 	oldintr = signal(SIGINT, mabort);
715 	(void)setjmp(jabort);
716 	while ((cp =
717 #ifndef SMALL
718 	    depth ? remglob2(dummyargv, proxy, NULL, &ftemp, &type) :
719 #endif /* !SMALL */
720 	    remglob(argv, proxy, NULL)) != NULL
721 #ifndef SMALL
722 	    || (mflag && depth && ++i < argc)
723 #endif /* !SMALL */
724 	    ) {
725 #ifndef SMALL
726 		if (cp == NULL)
727 			continue;
728 #endif /* !SMALL */
729 		if (*cp == '\0') {
730 			mflag = 0;
731 			continue;
732 		}
733 		if (!mflag)
734 			continue;
735 #ifndef SMALL
736 		if (depth && fnmatch(argv[i], cp, FNM_PATHNAME) != 0)
737 			continue;
738 #endif /* !SMALL */
739 		if (!fileindir(cp, localcwd)) {
740 			fprintf(ttyout, "Skipping non-relative filename `%s'\n",
741 			    cp);
742 			continue;
743 		}
744 #ifndef SMALL
745 		if (type == 'd' && depth == max_depth)
746 			continue;
747 #endif /* !SMALL */
748 		if (confirm(argv[0], cp)) {
749 #ifndef SMALL
750 			if (type == 'd') {
751 				mkdir(cp, 0755);
752 				if (chdir(cp) != 0) {
753 					warn("local: %s", cp);
754 					continue;
755 				}
756 
757 				xargv[1] = cp;
758 				cd(xargc, xargv);
759 				if (dirchange != 1)
760 					goto out;
761 
762 				xargv[1] = "*";
763 				mget(xargc, xargv);
764 
765 				xargv[1] = "..";
766 				cd(xargc, xargv);
767 				if (dirchange != 1) {
768 					mflag = 0;
769 					goto out;
770 				}
771 
772 out:
773 				if (chdir("..") != 0) {
774 					warn("local: %s", cp);
775 					mflag = 0;
776 				}
777 				continue;
778 			}
779 			if (type == 's')
780 				/* Currently ignored. */
781 				continue;
782 #endif /* !SMALL */
783 			xargv[1] = cp;
784 			(void)getit(xargc, xargv, restartit,
785 			    (restartit == 1 || restart_point) ? "a+w" : "w");
786 			if (!mflag && fromatty) {
787 				if (confirm(argv[0], NULL))
788 					mflag = 1;
789 			}
790 		}
791 	}
792 	(void)signal(SIGINT, oldintr);
793 #ifndef SMALL
794 	if (depth)
795 		depth--;
796 	if (depth == 0 || mflag == 0)
797 		depth = max_depth = mflag = restartit = 0;
798 #else /* !SMALL */
799 	mflag = 0;
800 #endif /* !SMALL */
801 }
802 
803 char *
804 onoff(int bool)
805 {
806 
807 	return (bool ? "on" : "off");
808 }
809 
810 /*
811  * Show status.
812  */
813 /*ARGSUSED*/
814 void
815 status(int argc, char *argv[])
816 {
817 	int i;
818 
819 	if (connected)
820 		fprintf(ttyout, "Connected %sto %s.\n",
821 		    connected == -1 ? "and logged in" : "", hostname);
822 	else
823 		fputs("Not connected.\n", ttyout);
824 	if (!proxy) {
825 		pswitch(1);
826 		if (connected) {
827 			fprintf(ttyout, "Connected for proxy commands to %s.\n",
828 			    hostname);
829 		}
830 		else {
831 			fputs("No proxy connection.\n", ttyout);
832 		}
833 		pswitch(0);
834 	}
835 	fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode),
836 	    *gateserver ? gateserver : "(none)", gateport);
837 	fprintf(ttyout, "Passive mode: %s.\n", onoff(passivemode));
838 	fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n",
839 		modename, typename, formname, structname);
840 	fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n",
841 		onoff(verbose), onoff(bell), onoff(interactive),
842 		onoff(doglob));
843 	fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n", onoff(sunique),
844 		onoff(runique));
845 	fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve));
846 	fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase), onoff(crflag));
847 	if (ntflag) {
848 		fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout);
849 	}
850 	else {
851 		fputs("Ntrans: off.\n", ttyout);
852 	}
853 	if (mapflag) {
854 		fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout);
855 	}
856 	else {
857 		fputs("Nmap: off.\n", ttyout);
858 	}
859 	fprintf(ttyout, "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
860 	    onoff(hash), mark, onoff(progress));
861 	fprintf(ttyout, "Use of PORT/LPRT cmds: %s.\n", onoff(sendport));
862 	fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4),
863 	    epsv4bad ? " (disabled for this connection)" : "");
864 #ifndef SMALL
865 	fprintf(ttyout, "Command line editing: %s.\n", onoff(editing));
866 	if (macnum > 0) {
867 		fputs("Macros:\n", ttyout);
868 		for (i=0; i<macnum; i++) {
869 			fprintf(ttyout, "\t%s\n", macros[i].mac_name);
870 		}
871 	}
872 #endif /* !SMALL */
873 	code = 0;
874 }
875 
876 /*
877  * Toggle a variable
878  */
879 int
880 togglevar(int argc, char *argv[], int *var, const char *mesg)
881 {
882 	if (argc < 2) {
883 		*var = !*var;
884 	} else if (argc == 2 && strcasecmp(argv[1], "on") == 0) {
885 		*var = 1;
886 	} else if (argc == 2 && strcasecmp(argv[1], "off") == 0) {
887 		*var = 0;
888 	} else {
889 		fprintf(ttyout, "usage: %s [on | off]\n", argv[0]);
890 		return (-1);
891 	}
892 	if (mesg)
893 		fprintf(ttyout, "%s %s.\n", mesg, onoff(*var));
894 	return (*var);
895 }
896 
897 /*
898  * Set beep on cmd completed mode.
899  */
900 /*ARGSUSED*/
901 void
902 setbell(int argc, char *argv[])
903 {
904 
905 	code = togglevar(argc, argv, &bell, "Bell mode");
906 }
907 
908 /*
909  * Set command line editing
910  */
911 #ifndef SMALL
912 /*ARGSUSED*/
913 void
914 setedit(int argc, char *argv[])
915 {
916 
917 	code = togglevar(argc, argv, &editing, "Editing mode");
918 	controlediting();
919 }
920 #endif /* !SMALL */
921 
922 /*
923  * Toggle use of IPv4 EPSV/EPRT
924  */
925 /*ARGSUSED*/
926 void
927 setepsv4(int argc, char *argv[])
928 {
929 
930 	code = togglevar(argc, argv, &epsv4, "EPSV/EPRT on IPv4");
931 	epsv4bad = 0;
932 }
933 
934 /*
935  * Turn on packet tracing.
936  */
937 /*ARGSUSED*/
938 void
939 settrace(int argc, char *argv[])
940 {
941 
942 	code = togglevar(argc, argv, &trace, "Packet tracing");
943 }
944 
945 /*
946  * Toggle hash mark printing during transfers, or set hash mark bytecount.
947  */
948 /*ARGSUSED*/
949 void
950 sethash(int argc, char *argv[])
951 {
952 	if (argc == 1)
953 		hash = !hash;
954 	else if (argc != 2) {
955 		fprintf(ttyout, "usage: %s [on | off | size]\n", argv[0]);
956 		code = -1;
957 		return;
958 	} else if (strcasecmp(argv[1], "on") == 0)
959 		hash = 1;
960 	else if (strcasecmp(argv[1], "off") == 0)
961 		hash = 0;
962 	else {
963 		int nmark;
964 		const char *errstr;
965 
966 		nmark = strtonum(argv[1], 1, INT_MAX, &errstr);
967 		if (errstr) {
968 			fprintf(ttyout, "bytecount value is %s: %s\n",
969 			    errstr, argv[1]);
970 			code = -1;
971 			return;
972 		}
973 		mark = nmark;
974 		hash = 1;
975 	}
976 	fprintf(ttyout, "Hash mark printing %s", onoff(hash));
977 	if (hash)
978 		fprintf(ttyout, " (%d bytes/hash mark)", mark);
979 	fputs(".\n", ttyout);
980 	code = hash;
981 }
982 
983 /*
984  * Turn on printing of server echo's.
985  */
986 /*ARGSUSED*/
987 void
988 setverbose(int argc, char *argv[])
989 {
990 
991 	code = togglevar(argc, argv, &verbose, "Verbose mode");
992 }
993 
994 /*
995  * Toggle PORT/LPRT cmd use before each data connection.
996  */
997 /*ARGSUSED*/
998 void
999 setport(int argc, char *argv[])
1000 {
1001 
1002 	code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
1003 }
1004 
1005 /*
1006  * Toggle transfer progress bar.
1007  */
1008 /*ARGSUSED*/
1009 void
1010 setprogress(int argc, char *argv[])
1011 {
1012 
1013 	code = togglevar(argc, argv, &progress, "Progress bar");
1014 }
1015 
1016 /*
1017  * Turn on interactive prompting during mget, mput, and mdelete.
1018  */
1019 /*ARGSUSED*/
1020 void
1021 setprompt(int argc, char *argv[])
1022 {
1023 
1024 	code = togglevar(argc, argv, &interactive, "Interactive mode");
1025 }
1026 
1027 /*
1028  * Toggle gate-ftp mode, or set gate-ftp server
1029  */
1030 /*ARGSUSED*/
1031 void
1032 setgate(int argc, char *argv[])
1033 {
1034 	static char gsbuf[MAXHOSTNAMELEN];
1035 
1036 	if (argc > 3) {
1037 		fprintf(ttyout, "usage: %s [on | off | host [port]]\n",
1038 		    argv[0]);
1039 		code = -1;
1040 		return;
1041 	} else if (argc < 2) {
1042 		gatemode = !gatemode;
1043 	} else {
1044 		if (argc == 2 && strcasecmp(argv[1], "on") == 0)
1045 			gatemode = 1;
1046 		else if (argc == 2 && strcasecmp(argv[1], "off") == 0)
1047 			gatemode = 0;
1048 		else {
1049 			if (argc == 3) {
1050 				gateport = strdup(argv[2]);
1051 				if (gateport == NULL)
1052 					err(1, NULL);
1053 			}
1054 			strlcpy(gsbuf, argv[1], sizeof(gsbuf));
1055 			gateserver = gsbuf;
1056 			gatemode = 1;
1057 		}
1058 	}
1059 	if (gatemode && (gateserver == NULL || *gateserver == '\0')) {
1060 		fprintf(ttyout,
1061 		    "Disabling gate-ftp mode - no gate-ftp server defined.\n");
1062 		gatemode = 0;
1063 	} else {
1064 		fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n",
1065 		    onoff(gatemode),
1066 		    *gateserver ? gateserver : "(none)", gateport);
1067 	}
1068 	code = gatemode;
1069 }
1070 
1071 /*
1072  * Toggle metacharacter interpretation on local file names.
1073  */
1074 /*ARGSUSED*/
1075 void
1076 setglob(int argc, char *argv[])
1077 {
1078 
1079 	code = togglevar(argc, argv, &doglob, "Globbing");
1080 }
1081 
1082 /*
1083  * Toggle preserving modification times on retrieved files.
1084  */
1085 /*ARGSUSED*/
1086 void
1087 setpreserve(int argc, char *argv[])
1088 {
1089 
1090 	code = togglevar(argc, argv, &preserve, "Preserve modification times");
1091 }
1092 
1093 /*
1094  * Set debugging mode on/off and/or set level of debugging.
1095  */
1096 #ifndef SMALL
1097 /*ARGSUSED*/
1098 void
1099 setdebug(int argc, char *argv[])
1100 {
1101 	if (argc > 2) {
1102 		fprintf(ttyout, "usage: %s [on | off | debuglevel]\n", argv[0]);
1103 		code = -1;
1104 		return;
1105 	} else if (argc == 2) {
1106 		if (strcasecmp(argv[1], "on") == 0)
1107 			debug = 1;
1108 		else if (strcasecmp(argv[1], "off") == 0)
1109 			debug = 0;
1110 		else {
1111 			const char *errstr;
1112 			int val;
1113 
1114 			val = strtonum(argv[1], 0, INT_MAX, &errstr);
1115 			if (errstr) {
1116 				fprintf(ttyout, "debugging value is %s: %s\n",
1117 				    errstr, argv[1]);
1118 				code = -1;
1119 				return;
1120 			}
1121 			debug = val;
1122 		}
1123 	} else
1124 		debug = !debug;
1125 	if (debug)
1126 		options |= SO_DEBUG;
1127 	else
1128 		options &= ~SO_DEBUG;
1129 	fprintf(ttyout, "Debugging %s (debug=%d).\n", onoff(debug), debug);
1130 	code = debug > 0;
1131 }
1132 #endif /* !SMALL */
1133 
1134 /*
1135  * Set current working directory on remote machine.
1136  */
1137 void
1138 cd(int argc, char *argv[])
1139 {
1140 	int r;
1141 
1142 	if ((argc < 2 && !another(&argc, &argv, "remote-directory")) ||
1143 	    argc > 2) {
1144 		fprintf(ttyout, "usage: %s remote-directory\n", argv[0]);
1145 		code = -1;
1146 		return;
1147 	}
1148 	r = command("CWD %s", argv[1]);
1149 	if (r == ERROR && code == 500) {
1150 		if (verbose)
1151 			fputs("CWD command not recognized, trying XCWD.\n", ttyout);
1152 		r = command("XCWD %s", argv[1]);
1153 	}
1154 	if (r == ERROR && code == 550) {
1155 		dirchange = 0;
1156 		return;
1157 	}
1158 	if (r == COMPLETE)
1159 		dirchange = 1;
1160 }
1161 
1162 /*
1163  * Set current working directory on local machine.
1164  */
1165 void
1166 lcd(int argc, char *argv[])
1167 {
1168 	char buf[MAXPATHLEN];
1169 	char *oldargv1;
1170 
1171 	if (argc < 2)
1172 		argc++, argv[1] = home;
1173 	if (argc != 2) {
1174 		fprintf(ttyout, "usage: %s [local-directory]\n", argv[0]);
1175 		code = -1;
1176 		return;
1177 	}
1178 	oldargv1 = argv[1];
1179 	if (!globulize(&argv[1])) {
1180 		code = -1;
1181 		return;
1182 	}
1183 	if (chdir(argv[1]) < 0) {
1184 		warn("local: %s", argv[1]);
1185 		code = -1;
1186 	} else {
1187 		if (getcwd(buf, sizeof(buf)) != NULL)
1188 			fprintf(ttyout, "Local directory now %s\n", buf);
1189 		else
1190 			warn("getcwd: %s", argv[1]);
1191 		code = 0;
1192 	}
1193 	if (oldargv1 != argv[1])	/* free up after globulize() */
1194 		free(argv[1]);
1195 }
1196 
1197 /*
1198  * Delete a single file.
1199  */
1200 void
1201 deletecmd(int argc, char *argv[])
1202 {
1203 
1204 	if ((argc < 2 && !another(&argc, &argv, "remote-file")) || argc > 2) {
1205 		fprintf(ttyout, "usage: %s remote-file\n", argv[0]);
1206 		code = -1;
1207 		return;
1208 	}
1209 	(void)command("DELE %s", argv[1]);
1210 }
1211 
1212 /*
1213  * Delete multiple files.
1214  */
1215 void
1216 mdelete(int argc, char *argv[])
1217 {
1218 	sig_t oldintr;
1219 	char *cp;
1220 
1221 	if (argc < 2 && !another(&argc, &argv, "remote-files")) {
1222 		fprintf(ttyout, "usage: %s remote-files\n", argv[0]);
1223 		code = -1;
1224 		return;
1225 	}
1226 	mname = argv[0];
1227 	mflag = 1;
1228 	oldintr = signal(SIGINT, mabort);
1229 	(void)setjmp(jabort);
1230 	while ((cp = remglob(argv, 0, NULL)) != NULL) {
1231 		if (*cp == '\0') {
1232 			mflag = 0;
1233 			continue;
1234 		}
1235 		if (mflag && confirm(argv[0], cp)) {
1236 			(void)command("DELE %s", cp);
1237 			if (!mflag && fromatty) {
1238 				if (confirm(argv[0], NULL))
1239 					mflag = 1;
1240 			}
1241 		}
1242 	}
1243 	(void)signal(SIGINT, oldintr);
1244 	mflag = 0;
1245 }
1246 
1247 /*
1248  * Rename a remote file.
1249  */
1250 void
1251 renamefile(int argc, char *argv[])
1252 {
1253 
1254 	if (argc < 2 && !another(&argc, &argv, "from-name"))
1255 		goto usage;
1256 	if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
1257 usage:
1258 		fprintf(ttyout, "usage: %s from-name to-name\n", argv[0]);
1259 		code = -1;
1260 		return;
1261 	}
1262 	if (command("RNFR %s", argv[1]) == CONTINUE)
1263 		(void)command("RNTO %s", argv[2]);
1264 }
1265 
1266 /*
1267  * Get a directory listing of remote files.
1268  */
1269 void
1270 ls(int argc, char *argv[])
1271 {
1272 	const char *cmd;
1273 	char *oldargv2, *globargv2;
1274 
1275 	if (argc < 2)
1276 		argc++, argv[1] = NULL;
1277 	if (argc < 3)
1278 		argc++, argv[2] = "-";
1279 	if (argc > 3) {
1280 		fprintf(ttyout, "usage: %s [remote-directory [local-file]]\n",
1281 		    argv[0]);
1282 		code = -1;
1283 		return;
1284 	}
1285 	cmd = strcmp(argv[0], "nlist") == 0 ? "NLST" : "LIST";
1286 	oldargv2 = argv[2];
1287 	if (strcmp(argv[2], "-") && !globulize(&argv[2])) {
1288 		code = -1;
1289 		return;
1290 	}
1291 	globargv2 = argv[2];
1292 	if (strcmp(argv[2], "-") && *argv[2] != '|' && (!globulize(&argv[2]) ||
1293 	    !confirm("output to local-file:", argv[2]))) {
1294 		code = -1;
1295 		goto freels;
1296 	}
1297 	recvrequest(cmd, argv[2], argv[1], "w", 0, 0);
1298 
1299 	/* flush results in case commands are coming from a pipe */
1300 	fflush(ttyout);
1301 freels:
1302 	if (argv[2] != globargv2)		/* free up after globulize() */
1303 		free(argv[2]);
1304 	if (globargv2 != oldargv2)
1305 		free(globargv2);
1306 }
1307 
1308 /*
1309  * Get a directory listing of multiple remote files.
1310  */
1311 void
1312 mls(int argc, char *argv[])
1313 {
1314 	sig_t oldintr;
1315 	int i;
1316 	char lmode[1], *dest, *odest;
1317 
1318 	if (argc < 2 && !another(&argc, &argv, "remote-files"))
1319 		goto usage;
1320 	if (argc < 3 && !another(&argc, &argv, "local-file")) {
1321 usage:
1322 		fprintf(ttyout, "usage: %s remote-files local-file\n", argv[0]);
1323 		code = -1;
1324 		return;
1325 	}
1326 	odest = dest = argv[argc - 1];
1327 	argv[argc - 1] = NULL;
1328 	if (strcmp(dest, "-") && *dest != '|')
1329 		if (!globulize(&dest) ||
1330 		    !confirm("output to local-file:", dest)) {
1331 			code = -1;
1332 			return;
1333 	}
1334 	mname = argv[0];
1335 	mflag = 1;
1336 	oldintr = signal(SIGINT, mabort);
1337 	(void)setjmp(jabort);
1338 	for (i = 1; mflag && i < argc-1; ++i) {
1339 		*lmode = (i == 1) ? 'w' : 'a';
1340 		recvrequest("LIST", dest, argv[i], lmode, 0, 0);
1341 		if (!mflag && fromatty) {
1342 			if (confirm(argv[0], NULL))
1343 				mflag ++;
1344 		}
1345 	}
1346 	(void)signal(SIGINT, oldintr);
1347 	mflag = 0;
1348 	if (dest != odest)			/* free up after globulize() */
1349 		free(dest);
1350 }
1351 
1352 /*
1353  * Do a shell escape
1354  */
1355 /*ARGSUSED*/
1356 void
1357 shell(int argc, char *argv[])
1358 {
1359 	pid_t pid;
1360 	sig_t old1, old2;
1361 	char shellnam[MAXPATHLEN], *shellp, *namep;
1362 	int wait_status;
1363 
1364 	old1 = signal (SIGINT, SIG_IGN);
1365 	old2 = signal (SIGQUIT, SIG_IGN);
1366 	if ((pid = fork()) == 0) {
1367 		for (pid = 3; pid < 20; pid++)
1368 			(void)close(pid);
1369 		(void)signal(SIGINT, SIG_DFL);
1370 		(void)signal(SIGQUIT, SIG_DFL);
1371 		shellp = getenv("SHELL");
1372 		if (shellp == NULL || *shellp == '\0')
1373 			shellp = _PATH_BSHELL;
1374 		namep = strrchr(shellp, '/');
1375 		if (namep == NULL)
1376 			namep = shellp;
1377 		shellnam[0] = '-';
1378 		(void)strlcpy(shellnam + 1, ++namep, sizeof(shellnam) - 1);
1379 		if (strcmp(namep, "sh") != 0)
1380 			shellnam[0] = '+';
1381 #ifndef SMALL
1382 		if (debug) {
1383 			fputs(shellp, ttyout);
1384 			fputc('\n', ttyout);
1385 			(void)fflush(ttyout);
1386 		}
1387 #endif /* !SMALL */
1388 		if (argc > 1) {
1389 			execl(shellp, shellnam, "-c", altarg, (char *)0);
1390 		}
1391 		else {
1392 			execl(shellp, shellnam, (char *)0);
1393 		}
1394 		warn("%s", shellp);
1395 		code = -1;
1396 		exit(1);
1397 	}
1398 	if (pid > 0)
1399 		while (wait(&wait_status) != pid)
1400 			;
1401 	(void)signal(SIGINT, old1);
1402 	(void)signal(SIGQUIT, old2);
1403 	if (pid == -1) {
1404 		warn("Try again later");
1405 		code = -1;
1406 	}
1407 	else {
1408 		code = 0;
1409 	}
1410 }
1411 
1412 /*
1413  * Send new user information (re-login)
1414  */
1415 void
1416 user(int argc, char *argv[])
1417 {
1418 	char acctname[80];
1419 	int n, aflag = 0;
1420 
1421 	if (argc < 2)
1422 		(void)another(&argc, &argv, "username");
1423 	if (argc < 2 || argc > 4) {
1424 		fprintf(ttyout, "usage: %s username [password [account]]\n",
1425 		    argv[0]);
1426 		code = -1;
1427 		return;
1428 	}
1429 	n = command("USER %s", argv[1]);
1430 	if (n == CONTINUE) {
1431 		if (argc < 3 )
1432 			argv[2] = getpass("Password:"), argc++;
1433 		n = command("PASS %s", argv[2]);
1434 	}
1435 	if (n == CONTINUE) {
1436 		if (argc < 4) {
1437 			(void)fputs("Account: ", ttyout);
1438 			(void)fflush(ttyout);
1439 			if (fgets(acctname, sizeof(acctname), stdin) == NULL) {
1440 				clearerr(stdin);
1441 				goto fail;
1442 			}
1443 
1444 			acctname[strcspn(acctname, "\n")] = '\0';
1445 
1446 			argv[3] = acctname;
1447 			argc++;
1448 		}
1449 		n = command("ACCT %s", argv[3]);
1450 		aflag++;
1451 	}
1452 	if (n != COMPLETE) {
1453  fail:
1454 		fputs("Login failed.\n", ttyout);
1455 		return;
1456 	}
1457 	if (!aflag && argc == 4) {
1458 		(void)command("ACCT %s", argv[3]);
1459 	}
1460 	connected = -1;
1461 }
1462 
1463 /*
1464  * Print working directory on remote machine.
1465  */
1466 /*ARGSUSED*/
1467 void
1468 pwd(int argc, char *argv[])
1469 {
1470 	int oldverbose = verbose;
1471 
1472 	/*
1473 	 * If we aren't verbose, this doesn't do anything!
1474 	 */
1475 	verbose = 1;
1476 	if (command("PWD") == ERROR && code == 500) {
1477 		fputs("PWD command not recognized, trying XPWD.\n", ttyout);
1478 		(void)command("XPWD");
1479 	}
1480 	verbose = oldverbose;
1481 }
1482 
1483 /*
1484  * Print working directory on local machine.
1485  */
1486 /* ARGSUSED */
1487 void
1488 lpwd(int argc, char *argv[])
1489 {
1490 	char buf[MAXPATHLEN];
1491 
1492 	if (getcwd(buf, sizeof(buf)) != NULL)
1493 		fprintf(ttyout, "Local directory %s\n", buf);
1494 	else
1495 		warn("getcwd");
1496 	code = 0;
1497 }
1498 
1499 /*
1500  * Make a directory.
1501  */
1502 void
1503 makedir(int argc, char *argv[])
1504 {
1505 
1506 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1507 	    argc > 2) {
1508 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1509 		code = -1;
1510 		return;
1511 	}
1512 	if (command("MKD %s", argv[1]) == ERROR && code == 500) {
1513 		if (verbose)
1514 			fputs("MKD command not recognized, trying XMKD.\n", ttyout);
1515 		(void)command("XMKD %s", argv[1]);
1516 	}
1517 }
1518 
1519 /*
1520  * Remove a directory.
1521  */
1522 void
1523 removedir(int argc, char *argv[])
1524 {
1525 
1526 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1527 	    argc > 2) {
1528 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1529 		code = -1;
1530 		return;
1531 	}
1532 	if (command("RMD %s", argv[1]) == ERROR && code == 500) {
1533 		if (verbose)
1534 			fputs("RMD command not recognized, trying XRMD.\n", ttyout);
1535 		(void)command("XRMD %s", argv[1]);
1536 	}
1537 }
1538 
1539 /*
1540  * Send a line, verbatim, to the remote machine.
1541  */
1542 void
1543 quote(int argc, char *argv[])
1544 {
1545 
1546 	if (argc < 2 && !another(&argc, &argv, "command line to send")) {
1547 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1548 		code = -1;
1549 		return;
1550 	}
1551 	quote1("", argc, argv);
1552 }
1553 
1554 /*
1555  * Send a SITE command to the remote machine.  The line
1556  * is sent verbatim to the remote machine, except that the
1557  * word "SITE" is added at the front.
1558  */
1559 void
1560 site(int argc, char *argv[])
1561 {
1562 
1563 	if (argc < 2 && !another(&argc, &argv, "arguments to SITE command")) {
1564 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1565 		code = -1;
1566 		return;
1567 	}
1568 	quote1("SITE", argc, argv);
1569 }
1570 
1571 /*
1572  * Turn argv[1..argc) into a space-separated string, then prepend initial text.
1573  * Send the result as a one-line command and get response.
1574  */
1575 void
1576 quote1(const char *initial, int argc, char *argv[])
1577 {
1578 	int i, len;
1579 	char buf[BUFSIZ];		/* must be >= sizeof(line) */
1580 
1581 	(void)strlcpy(buf, initial, sizeof(buf));
1582 	if (argc > 1) {
1583 		for (i = 1, len = strlen(buf); i < argc && len < sizeof(buf)-1; i++) {
1584 			/* Space for next arg */
1585 			if (len > 1)
1586 				buf[len++] = ' ';
1587 
1588 			/* Sanity check */
1589 			if (len >= sizeof(buf) - 1)
1590 				break;
1591 
1592 			/* Copy next argument, NUL terminate always */
1593 			strlcpy(&buf[len], argv[i], sizeof(buf) - len);
1594 
1595 			/* Update string length */
1596 			len = strlen(buf);
1597 		}
1598 	}
1599 
1600 	/* Make double (triple?) sure the sucker is NUL terminated */
1601 	buf[sizeof(buf) - 1] = '\0';
1602 
1603 	if (command("%s", buf) == PRELIM) {
1604 		while (getreply(0) == PRELIM)
1605 			continue;
1606 	}
1607 }
1608 
1609 void
1610 do_chmod(int argc, char *argv[])
1611 {
1612 
1613 	if (argc < 2 && !another(&argc, &argv, "mode"))
1614 		goto usage;
1615 	if ((argc < 3 && !another(&argc, &argv, "file")) || argc > 3) {
1616 usage:
1617 		fprintf(ttyout, "usage: %s mode file\n", argv[0]);
1618 		code = -1;
1619 		return;
1620 	}
1621 	(void)command("SITE CHMOD %s %s", argv[1], argv[2]);
1622 }
1623 
1624 void
1625 do_umask(int argc, char *argv[])
1626 {
1627 	int oldverbose = verbose;
1628 
1629 	verbose = 1;
1630 	(void)command(argc == 1 ? "SITE UMASK" : "SITE UMASK %s", argv[1]);
1631 	verbose = oldverbose;
1632 }
1633 
1634 void
1635 idle(int argc, char *argv[])
1636 {
1637 	int oldverbose = verbose;
1638 
1639 	verbose = 1;
1640 	(void)command(argc == 1 ? "SITE IDLE" : "SITE IDLE %s", argv[1]);
1641 	verbose = oldverbose;
1642 }
1643 
1644 /*
1645  * Ask the other side for help.
1646  */
1647 void
1648 rmthelp(int argc, char *argv[])
1649 {
1650 	int oldverbose = verbose;
1651 
1652 	verbose = 1;
1653 	(void)command(argc == 1 ? "HELP" : "HELP %s", argv[1]);
1654 	verbose = oldverbose;
1655 }
1656 
1657 /*
1658  * Terminate session and exit.
1659  */
1660 /*ARGSUSED*/
1661 void
1662 quit(int argc, char *argv[])
1663 {
1664 
1665 	if (connected)
1666 		disconnect(0, 0);
1667 	pswitch(1);
1668 	if (connected) {
1669 		disconnect(0, 0);
1670 	}
1671 	exit(0);
1672 }
1673 
1674 /*
1675  * Terminate session, but don't exit.
1676  */
1677 /* ARGSUSED */
1678 void
1679 disconnect(int argc, char *argv[])
1680 {
1681 
1682 	if (!connected)
1683 		return;
1684 	(void)command("QUIT");
1685 	if (cout) {
1686 		(void)fclose(cout);
1687 	}
1688 	cout = NULL;
1689 	connected = 0;
1690 	data = -1;
1691 #ifndef SMALL
1692 	if (!proxy) {
1693 		macnum = 0;
1694 	}
1695 #endif /* !SMALL */
1696 }
1697 
1698 void
1699 account(int argc, char *argv[])
1700 {
1701 	char *ap;
1702 
1703 	if (argc > 2) {
1704 		fprintf(ttyout, "usage: %s [password]\n", argv[0]);
1705 		code = -1;
1706 		return;
1707 	}
1708 	else if (argc == 2)
1709 		ap = argv[1];
1710 	else
1711 		ap = getpass("Account:");
1712 	(void)command("ACCT %s", ap);
1713 }
1714 
1715 jmp_buf abortprox;
1716 
1717 /* ARGSUSED */
1718 void
1719 proxabort(int signo)
1720 {
1721 
1722 	alarmtimer(0);
1723 	if (!proxy) {
1724 		pswitch(1);
1725 	}
1726 	if (connected) {
1727 		proxflag = 1;
1728 	}
1729 	else {
1730 		proxflag = 0;
1731 	}
1732 	pswitch(0);
1733 	longjmp(abortprox, 1);
1734 }
1735 
1736 void
1737 doproxy(int argc, char *argv[])
1738 {
1739 	struct cmd *c;
1740 	int cmdpos;
1741 	sig_t oldintr;
1742 
1743 	if (argc < 2 && !another(&argc, &argv, "command")) {
1744 		fprintf(ttyout, "usage: %s command\n", argv[0]);
1745 		code = -1;
1746 		return;
1747 	}
1748 	c = getcmd(argv[1]);
1749 	if (c == (struct cmd *) -1) {
1750 		fputs("?Ambiguous command.\n", ttyout);
1751 		(void)fflush(ttyout);
1752 		code = -1;
1753 		return;
1754 	}
1755 	if (c == 0) {
1756 		fputs("?Invalid command.\n", ttyout);
1757 		(void)fflush(ttyout);
1758 		code = -1;
1759 		return;
1760 	}
1761 	if (!c->c_proxy) {
1762 		fputs("?Invalid proxy command.\n", ttyout);
1763 		(void)fflush(ttyout);
1764 		code = -1;
1765 		return;
1766 	}
1767 	if (setjmp(abortprox)) {
1768 		code = -1;
1769 		return;
1770 	}
1771 	oldintr = signal(SIGINT, proxabort);
1772 	pswitch(1);
1773 	if (c->c_conn && !connected) {
1774 		fputs("Not connected.\n", ttyout);
1775 		(void)fflush(ttyout);
1776 		pswitch(0);
1777 		(void)signal(SIGINT, oldintr);
1778 		code = -1;
1779 		return;
1780 	}
1781 	cmdpos = strcspn(line, " \t");
1782 	if (cmdpos > 0)		/* remove leading "proxy " from input buffer */
1783 		memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
1784 	(*c->c_handler)(argc-1, argv+1);
1785 	if (connected) {
1786 		proxflag = 1;
1787 	}
1788 	else {
1789 		proxflag = 0;
1790 	}
1791 	pswitch(0);
1792 	(void)signal(SIGINT, oldintr);
1793 }
1794 
1795 void
1796 setcase(int argc, char *argv[])
1797 {
1798 
1799 	code = togglevar(argc, argv, &mcase, "Case mapping");
1800 }
1801 
1802 void
1803 setcr(int argc, char *argv[])
1804 {
1805 
1806 	code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
1807 }
1808 
1809 void
1810 setntrans(int argc, char *argv[])
1811 {
1812 	if (argc == 1) {
1813 		ntflag = 0;
1814 		fputs("Ntrans off.\n", ttyout);
1815 		code = ntflag;
1816 		return;
1817 	}
1818 	ntflag++;
1819 	code = ntflag;
1820 	(void)strlcpy(ntin, argv[1], sizeof(ntin));
1821 	if (argc == 2) {
1822 		ntout[0] = '\0';
1823 		return;
1824 	}
1825 	(void)strlcpy(ntout, argv[2], sizeof(ntout));
1826 }
1827 
1828 char *
1829 dotrans(char *name)
1830 {
1831 	static char new[MAXPATHLEN];
1832 	char *cp1, *cp2 = new;
1833 	int i, ostop, found;
1834 
1835 	for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
1836 		continue;
1837 	for (cp1 = name; *cp1; cp1++) {
1838 		found = 0;
1839 		for (i = 0; *(ntin + i) && i < 16; i++) {
1840 			if (*cp1 == *(ntin + i)) {
1841 				found++;
1842 				if (i < ostop) {
1843 					*cp2++ = *(ntout + i);
1844 				}
1845 				break;
1846 			}
1847 		}
1848 		if (!found) {
1849 			*cp2++ = *cp1;
1850 		}
1851 	}
1852 	*cp2 = '\0';
1853 	return (new);
1854 }
1855 
1856 void
1857 setnmap(int argc, char *argv[])
1858 {
1859 	char *cp;
1860 
1861 	if (argc == 1) {
1862 		mapflag = 0;
1863 		fputs("Nmap off.\n", ttyout);
1864 		code = mapflag;
1865 		return;
1866 	}
1867 	if ((argc < 3 && !another(&argc, &argv, "outpattern")) || argc > 3) {
1868 		fprintf(ttyout, "usage: %s [inpattern outpattern]\n", argv[0]);
1869 		code = -1;
1870 		return;
1871 	}
1872 	mapflag = 1;
1873 	code = 1;
1874 	cp = strchr(altarg, ' ');
1875 	if (proxy) {
1876 		while(*++cp == ' ')
1877 			continue;
1878 		altarg = cp;
1879 		cp = strchr(altarg, ' ');
1880 	}
1881 	*cp = '\0';
1882 	(void)strncpy(mapin, altarg, MAXPATHLEN - 1);
1883 	while (*++cp == ' ')
1884 		continue;
1885 	(void)strncpy(mapout, cp, MAXPATHLEN - 1);
1886 }
1887 
1888 char *
1889 domap(char *name)
1890 {
1891 	static char new[MAXPATHLEN];
1892 	char *cp1 = name, *cp2 = mapin;
1893 	char *tp[9], *te[9];
1894 	int i, toks[9], toknum = 0, match = 1;
1895 
1896 	for (i=0; i < 9; ++i) {
1897 		toks[i] = 0;
1898 	}
1899 	while (match && *cp1 && *cp2) {
1900 		switch (*cp2) {
1901 			case '\\':
1902 				if (*++cp2 != *cp1) {
1903 					match = 0;
1904 				}
1905 				break;
1906 			case '$':
1907 				if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
1908 					if (*cp1 != *(++cp2+1)) {
1909 						toks[toknum = *cp2 - '1']++;
1910 						tp[toknum] = cp1;
1911 						while (*++cp1 && *(cp2+1)
1912 							!= *cp1);
1913 						te[toknum] = cp1;
1914 					}
1915 					cp2++;
1916 					break;
1917 				}
1918 				/* FALLTHROUGH */
1919 			default:
1920 				if (*cp2 != *cp1) {
1921 					match = 0;
1922 				}
1923 				break;
1924 		}
1925 		if (match && *cp1) {
1926 			cp1++;
1927 		}
1928 		if (match && *cp2) {
1929 			cp2++;
1930 		}
1931 	}
1932 	if (!match && *cp1) /* last token mismatch */
1933 	{
1934 		toks[toknum] = 0;
1935 	}
1936 	cp1 = new;
1937 	*cp1 = '\0';
1938 	cp2 = mapout;
1939 	while (*cp2) {
1940 		match = 0;
1941 		switch (*cp2) {
1942 			case '\\':
1943 				if (*(cp2 + 1)) {
1944 					*cp1++ = *++cp2;
1945 				}
1946 				break;
1947 			case '[':
1948 LOOP:
1949 				if (*++cp2 == '$' && isdigit(*(cp2+1))) {
1950 					if (*++cp2 == '0') {
1951 						char *cp3 = name;
1952 
1953 						while (*cp3) {
1954 							*cp1++ = *cp3++;
1955 						}
1956 						match = 1;
1957 					}
1958 					else if (toks[toknum = *cp2 - '1']) {
1959 						char *cp3 = tp[toknum];
1960 
1961 						while (cp3 != te[toknum]) {
1962 							*cp1++ = *cp3++;
1963 						}
1964 						match = 1;
1965 					}
1966 				}
1967 				else {
1968 					while (*cp2 && *cp2 != ',' &&
1969 					    *cp2 != ']') {
1970 						if (*cp2 == '\\') {
1971 							cp2++;
1972 						}
1973 						else if (*cp2 == '$' &&
1974    						        isdigit(*(cp2+1))) {
1975 							if (*++cp2 == '0') {
1976 							   char *cp3 = name;
1977 
1978 							   while (*cp3) {
1979 								*cp1++ = *cp3++;
1980 							   }
1981 							}
1982 							else if (toks[toknum =
1983 							    *cp2 - '1']) {
1984 							   char *cp3=tp[toknum];
1985 
1986 							   while (cp3 !=
1987 								  te[toknum]) {
1988 								*cp1++ = *cp3++;
1989 							   }
1990 							}
1991 						}
1992 						else if (*cp2) {
1993 							*cp1++ = *cp2++;
1994 						}
1995 					}
1996 					if (!*cp2) {
1997 						fputs(
1998 "nmap: unbalanced brackets.\n", ttyout);
1999 						return (name);
2000 					}
2001 					match = 1;
2002 					cp2--;
2003 				}
2004 				if (match) {
2005 					while (*++cp2 && *cp2 != ']') {
2006 					      if (*cp2 == '\\' && *(cp2 + 1)) {
2007 							cp2++;
2008 					      }
2009 					}
2010 					if (!*cp2) {
2011 						fputs(
2012 "nmap: unbalanced brackets.\n", ttyout);
2013 						return (name);
2014 					}
2015 					break;
2016 				}
2017 				switch (*++cp2) {
2018 					case ',':
2019 						goto LOOP;
2020 					case ']':
2021 						break;
2022 					default:
2023 						cp2--;
2024 						goto LOOP;
2025 				}
2026 				break;
2027 			case '$':
2028 				if (isdigit(*(cp2 + 1))) {
2029 					if (*++cp2 == '0') {
2030 						char *cp3 = name;
2031 
2032 						while (*cp3) {
2033 							*cp1++ = *cp3++;
2034 						}
2035 					}
2036 					else if (toks[toknum = *cp2 - '1']) {
2037 						char *cp3 = tp[toknum];
2038 
2039 						while (cp3 != te[toknum]) {
2040 							*cp1++ = *cp3++;
2041 						}
2042 					}
2043 					break;
2044 				}
2045 				/* FALLTHROUGH */
2046 			default:
2047 				*cp1++ = *cp2;
2048 				break;
2049 		}
2050 		cp2++;
2051 	}
2052 	*cp1 = '\0';
2053 	if (!*new) {
2054 		return (name);
2055 	}
2056 	return (new);
2057 }
2058 
2059 void
2060 setpassive(int argc, char *argv[])
2061 {
2062 
2063 	code = togglevar(argc, argv, &passivemode,
2064 	    verbose ? "Passive mode" : NULL);
2065 }
2066 
2067 void
2068 setsunique(int argc, char *argv[])
2069 {
2070 
2071 	code = togglevar(argc, argv, &sunique, "Store unique");
2072 }
2073 
2074 void
2075 setrunique(int argc, char *argv[])
2076 {
2077 
2078 	code = togglevar(argc, argv, &runique, "Receive unique");
2079 }
2080 
2081 /* change directory to parent directory */
2082 /* ARGSUSED */
2083 void
2084 cdup(int argc, char *argv[])
2085 {
2086 	int r;
2087 
2088 	r = command("CDUP");
2089 	if (r == ERROR && code == 500) {
2090 		if (verbose)
2091 			fputs("CDUP command not recognized, trying XCUP.\n", ttyout);
2092 		r = command("XCUP");
2093 	}
2094 	if (r == COMPLETE)
2095 		dirchange = 1;
2096 }
2097 
2098 /*
2099  * Restart transfer at specific point
2100  */
2101 void
2102 restart(int argc, char *argv[])
2103 {
2104 	quad_t nrestart_point;
2105 	char *ep;
2106 
2107 	if (argc != 2)
2108 		fputs("restart: offset not specified.\n", ttyout);
2109 	else {
2110 		nrestart_point = strtoq(argv[1], &ep, 10);
2111 		if (nrestart_point == QUAD_MAX || *ep != '\0')
2112 			fputs("restart: invalid offset.\n", ttyout);
2113 		else {
2114 			fprintf(ttyout, "Restarting at %lld. Execute get, put "
2115 				"or append to initiate transfer\n",
2116 				(long long)nrestart_point);
2117 			restart_point = nrestart_point;
2118 		}
2119 	}
2120 }
2121 
2122 /*
2123  * Show remote system type
2124  */
2125 /* ARGSUSED */
2126 void
2127 syst(int argc, char *argv[])
2128 {
2129 
2130 	(void)command("SYST");
2131 }
2132 
2133 #ifndef SMALL
2134 void
2135 macdef(int argc, char *argv[])
2136 {
2137 	char *tmp;
2138 	int c;
2139 
2140 	if (macnum == 16) {
2141 		fputs("Limit of 16 macros have already been defined.\n", ttyout);
2142 		code = -1;
2143 		return;
2144 	}
2145 	if ((argc < 2 && !another(&argc, &argv, "macro-name")) || argc > 2) {
2146 		fprintf(ttyout, "usage: %s macro-name\n", argv[0]);
2147 		code = -1;
2148 		return;
2149 	}
2150 	if (interactive)
2151 		fputs(
2152 "Enter macro line by line, terminating it with a null line.\n", ttyout);
2153 	(void)strlcpy(macros[macnum].mac_name, argv[1],
2154 	    sizeof(macros[macnum].mac_name));
2155 	if (macnum == 0)
2156 		macros[macnum].mac_start = macbuf;
2157 	else
2158 		macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
2159 	tmp = macros[macnum].mac_start;
2160 	while (tmp != macbuf+4096) {
2161 		if ((c = getchar()) == EOF) {
2162 			fputs("macdef: end of file encountered.\n", ttyout);
2163 			code = -1;
2164 			return;
2165 		}
2166 		if ((*tmp = c) == '\n') {
2167 			if (tmp == macros[macnum].mac_start) {
2168 				macros[macnum++].mac_end = tmp;
2169 				code = 0;
2170 				return;
2171 			}
2172 			if (*(tmp-1) == '\0') {
2173 				macros[macnum++].mac_end = tmp - 1;
2174 				code = 0;
2175 				return;
2176 			}
2177 			*tmp = '\0';
2178 		}
2179 		tmp++;
2180 	}
2181 	while (1) {
2182 		while ((c = getchar()) != '\n' && c != EOF)
2183 			/* LOOP */;
2184 		if (c == EOF || getchar() == '\n') {
2185 			fputs("Macro not defined - 4K buffer exceeded.\n", ttyout);
2186 			code = -1;
2187 			return;
2188 		}
2189 	}
2190 }
2191 #endif /* !SMALL */
2192 
2193 /*
2194  * Get size of file on remote machine
2195  */
2196 void
2197 sizecmd(int argc, char *argv[])
2198 {
2199 	off_t size;
2200 
2201 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
2202 		fprintf(ttyout, "usage: %s file\n", argv[0]);
2203 		code = -1;
2204 		return;
2205 	}
2206 	size = remotesize(argv[1], 1);
2207 	if (size != -1)
2208 		fprintf(ttyout, "%s\t%lld\n", argv[1], (long long)size);
2209 	code = size;
2210 }
2211 
2212 /*
2213  * Get last modification time of file on remote machine
2214  */
2215 void
2216 modtime(int argc, char *argv[])
2217 {
2218 	time_t mtime;
2219 
2220 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
2221 		fprintf(ttyout, "usage: %s file\n", argv[0]);
2222 		code = -1;
2223 		return;
2224 	}
2225 	mtime = remotemodtime(argv[1], 1);
2226 	if (mtime != -1)
2227 		fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime)));
2228 	code = mtime;
2229 }
2230 
2231 /*
2232  * Show status on remote machine
2233  */
2234 void
2235 rmtstatus(int argc, char *argv[])
2236 {
2237 
2238 	(void)command(argc > 1 ? "STAT %s" : "STAT" , argv[1]);
2239 }
2240 
2241 /*
2242  * Get file if modtime is more recent than current file
2243  */
2244 void
2245 newer(int argc, char *argv[])
2246 {
2247 
2248 	if (getit(argc, argv, -1, "w"))
2249 		fprintf(ttyout, "Local file \"%s\" is newer than remote file \"%s\".\n",
2250 			argv[2], argv[1]);
2251 }
2252 
2253 /*
2254  * Display one file through $PAGER (defaults to "more").
2255  */
2256 void
2257 page(int argc, char *argv[])
2258 {
2259 	int orestart_point, ohash, overbose;
2260 	char *p, *pager, *oldargv1;
2261 
2262 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
2263 		fprintf(ttyout, "usage: %s file\n", argv[0]);
2264 		code = -1;
2265 		return;
2266 	}
2267 	oldargv1 = argv[1];
2268 	if (!globulize(&argv[1])) {
2269 		code = -1;
2270 		return;
2271 	}
2272 	p = getenv("PAGER");
2273 	if (p == NULL || (*p == '\0'))
2274 		p = PAGER;
2275 	if (asprintf(&pager, "|%s", p) == -1)
2276 		errx(1, "Can't allocate memory for $PAGER");
2277 
2278 	orestart_point = restart_point;
2279 	ohash = hash;
2280 	overbose = verbose;
2281 	restart_point = hash = verbose = 0;
2282 	recvrequest("RETR", pager, argv[1], "r+w", 1, 0);
2283 	(void)free(pager);
2284 	restart_point = orestart_point;
2285 	hash = ohash;
2286 	verbose = overbose;
2287 	if (oldargv1 != argv[1])	/* free up after globulize() */
2288 		free(argv[1]);
2289 }
2290