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