xref: /openbsd-src/usr.bin/ftp/cmds.c (revision 850e275390052b330d93020bf619a739a3c277ac)
1 /*	$OpenBSD: cmds.c,v 1.66 2008/09/18 14:45:36 todd 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.66 2008/09/18 14:45:36 todd 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, "r+w");
528 }
529 #endif /* !SMALL */
530 
531 void
532 get(int argc, char *argv[])
533 {
534 
535 	(void)getit(argc, argv, 0, restart_point ? "r+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 			if (ret < 0) {
600 				warn("local: %s", argv[2]);
601 				goto freegetit;
602 			}
603 			restart_point = stbuf.st_size;
604 		} else {
605 			if (ret == 0) {
606 				time_t mtime;
607 
608 				mtime = remotemodtime(argv[1], 0);
609 				if (mtime == -1)
610 					goto freegetit;
611 				if (stbuf.st_mtime >= mtime) {
612 					rval = 1;
613 					goto freegetit;
614 				}
615 			}
616 		}
617 	}
618 #endif /* !SMALL */
619 
620 	recvrequest("RETR", argv[2], argv[1], mode,
621 	    argv[1] != oldargv1 || argv[2] != oldargv2 || !interactive, loc);
622 	restart_point = 0;
623 freegetit:
624 	if (oldargv2 != globargv2)	/* free up after globulize() */
625 		free(globargv2);
626 	return (rval);
627 }
628 
629 /* XXX - Signal race. */
630 /* ARGSUSED */
631 void
632 mabort(int signo)
633 {
634 	alarmtimer(0);
635 	putc('\n', ttyout);
636 	(void)fflush(ttyout);
637 	if (mflag && fromatty)
638 		if (confirm(mname, NULL))
639 			longjmp(jabort, 1);
640 	mflag = 0;
641 	longjmp(jabort, 1);
642 }
643 
644 /*
645  * Get multiple files.
646  */
647 void
648 mget(int argc, char *argv[])
649 {
650 	extern int optind, optreset;
651 	sig_t oldintr;
652 	int ch, xargc = 2;
653 	char *cp, localcwd[MAXPATHLEN], *xargv[] = {argv[0], NULL, NULL};
654 	static int restartit = 0;
655 #ifndef SMALL
656 	extern char *optarg;
657 	const char *errstr;
658 	int i = 1;
659 	char type = NULL, *dummyargv[] = {argv[0], ".", NULL};
660 	FILE *ftemp = NULL;
661 	static int depth = 0, max_depth = 0;
662 #endif /* !SMALL */
663 
664 	optind = optreset = 1;
665 
666 #ifndef SMALL
667 
668 	if (depth)
669 		depth++;
670 
671 	while ((ch = getopt(argc, argv, "cd:nr")) != -1) {
672 		switch(ch) {
673 		case 'c':
674 			restartit = 1;
675 			break;
676 		case 'd':
677 			max_depth = strtonum(optarg, 0, INT_MAX, &errstr);
678 			if (errstr != NULL) {
679 				fprintf(ttyout, "bad depth value, %s: %s\n",
680 				    errstr, optarg);
681 				code = -1;
682 				return;
683 			}
684 			break;
685 		case 'n':
686 			restartit = -1;
687 			break;
688 		case 'r':
689 			depth = 1;
690 			break;
691 		default:
692 			goto usage;
693 		}
694 	}
695 #endif /* !SMALL */
696 
697 	if (argc - optind < 1 && !another(&argc, &argv, "remote-files")) {
698 usage:
699 		fprintf(ttyout, "usage: %s [-cnr] [-d depth] remote-files\n",
700 		    argv[0]);
701 		code = -1;
702 		return;
703 	}
704 
705 #ifndef SMALL
706 	argv[optind - 1] = argv[0];
707 	argc -= optind - 1;
708 	argv += optind - 1;
709 #endif /* !SMALL */
710 
711 	mname = argv[0];
712 	mflag = 1;
713 	if (getcwd(localcwd, sizeof(localcwd)) == NULL)
714 		err(1, "can't get cwd");
715 
716 	oldintr = signal(SIGINT, mabort);
717 	(void)setjmp(jabort);
718 	while ((cp =
719 #ifndef SMALL
720 	    depth ? remglob2(dummyargv, proxy, NULL, &ftemp, &type) :
721 #endif /* !SMALL */
722 	    remglob(argv, proxy, NULL)) != NULL
723 #ifndef SMALL
724 	    || (mflag && depth && ++i < argc)
725 #endif /* !SMALL */
726 	    ) {
727 #ifndef SMALL
728 		if (cp == NULL)
729 			continue;
730 #endif /* !SMALL */
731 		if (*cp == '\0') {
732 			mflag = 0;
733 			continue;
734 		}
735 		if (!mflag)
736 			continue;
737 #ifndef SMALL
738 		if (depth && fnmatch(argv[i], cp, FNM_PATHNAME) != 0)
739 			continue;
740 #endif /* !SMALL */
741 		if (!fileindir(cp, localcwd)) {
742 			fprintf(ttyout, "Skipping non-relative filename `%s'\n",
743 			    cp);
744 			continue;
745 		}
746 #ifndef SMALL
747 		if (type == 'd' && depth == max_depth)
748 			continue;
749 #endif /* !SMALL */
750 		if (confirm(argv[0], cp)) {
751 #ifndef SMALL
752 			if (type == 'd') {
753 				mkdir(cp, 0755);
754 				if (chdir(cp) != 0) {
755 					warn("local: %s", cp);
756 					continue;
757 				}
758 
759 				xargv[1] = cp;
760 				cd(xargc, xargv);
761 				if (dirchange != 1)
762 					goto out;
763 
764 				xargv[1] = "*";
765 				mget(xargc, xargv);
766 
767 				xargv[1] = "..";
768 				cd(xargc, xargv);
769 				if (dirchange != 1) {
770 					mflag = 0;
771 					goto out;
772 				}
773 
774 out:
775 				if (chdir("..") != 0) {
776 					warn("local: %s", cp);
777 					mflag = 0;
778 				}
779 				continue;
780 			}
781 			if (type == 's')
782 				/* Currently ignored. */
783 				continue;
784 #endif /* !SMALL */
785 			xargv[1] = cp;
786 			(void)getit(xargc, xargv, restartit,
787 			    (restartit == 1 || restart_point) ? "r+w" : "w");
788 			if (!mflag && fromatty) {
789 				if (confirm(argv[0], NULL))
790 					mflag = 1;
791 			}
792 		}
793 	}
794 	(void)signal(SIGINT, oldintr);
795 #ifndef SMALL
796 	if (depth)
797 		depth--;
798 	if (depth == 0 || mflag == 0)
799 		depth = max_depth = mflag = restartit = 0;
800 #else /* !SMALL */
801 	mflag = 0;
802 #endif /* !SMALL */
803 }
804 
805 char *
806 onoff(int bool)
807 {
808 
809 	return (bool ? "on" : "off");
810 }
811 
812 /*
813  * Show status.
814  */
815 /*ARGSUSED*/
816 void
817 status(int argc, char *argv[])
818 {
819 	int i;
820 
821 	if (connected)
822 		fprintf(ttyout, "Connected %sto %s.\n",
823 		    connected == -1 ? "and logged in" : "", hostname);
824 	else
825 		fputs("Not connected.\n", ttyout);
826 	if (!proxy) {
827 		pswitch(1);
828 		if (connected) {
829 			fprintf(ttyout, "Connected for proxy commands to %s.\n",
830 			    hostname);
831 		}
832 		else {
833 			fputs("No proxy connection.\n", ttyout);
834 		}
835 		pswitch(0);
836 	}
837 	fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode),
838 	    *gateserver ? gateserver : "(none)", gateport);
839 	fprintf(ttyout, "Passive mode: %s.\n", onoff(passivemode));
840 	fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n",
841 		modename, typename, formname, structname);
842 	fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n",
843 		onoff(verbose), onoff(bell), onoff(interactive),
844 		onoff(doglob));
845 	fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n", onoff(sunique),
846 		onoff(runique));
847 	fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve));
848 	fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase), onoff(crflag));
849 	if (ntflag) {
850 		fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout);
851 	}
852 	else {
853 		fputs("Ntrans: off.\n", ttyout);
854 	}
855 	if (mapflag) {
856 		fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout);
857 	}
858 	else {
859 		fputs("Nmap: off.\n", ttyout);
860 	}
861 	fprintf(ttyout, "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
862 	    onoff(hash), mark, onoff(progress));
863 	fprintf(ttyout, "Use of PORT/LPRT cmds: %s.\n", onoff(sendport));
864 	fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4),
865 	    epsv4bad ? " (disabled for this connection)" : "");
866 #ifndef SMALL
867 	fprintf(ttyout, "Command line editing: %s.\n", onoff(editing));
868 #endif /* !SMALL */
869 	if (macnum > 0) {
870 		fputs("Macros:\n", ttyout);
871 		for (i=0; i<macnum; i++) {
872 			fprintf(ttyout, "\t%s\n", macros[i].mac_name);
873 		}
874 	}
875 	code = 0;
876 }
877 
878 /*
879  * Toggle a variable
880  */
881 int
882 togglevar(int argc, char *argv[], int *var, const char *mesg)
883 {
884 	if (argc < 2) {
885 		*var = !*var;
886 	} else if (argc == 2 && strcasecmp(argv[1], "on") == 0) {
887 		*var = 1;
888 	} else if (argc == 2 && strcasecmp(argv[1], "off") == 0) {
889 		*var = 0;
890 	} else {
891 		fprintf(ttyout, "usage: %s [on | off]\n", argv[0]);
892 		return (-1);
893 	}
894 	if (mesg)
895 		fprintf(ttyout, "%s %s.\n", mesg, onoff(*var));
896 	return (*var);
897 }
898 
899 /*
900  * Set beep on cmd completed mode.
901  */
902 /*ARGSUSED*/
903 void
904 setbell(int argc, char *argv[])
905 {
906 
907 	code = togglevar(argc, argv, &bell, "Bell mode");
908 }
909 
910 /*
911  * Set command line editing
912  */
913 #ifndef SMALL
914 /*ARGSUSED*/
915 void
916 setedit(int argc, char *argv[])
917 {
918 
919 	code = togglevar(argc, argv, &editing, "Editing mode");
920 	controlediting();
921 }
922 #endif /* !SMALL */
923 
924 /*
925  * Toggle use of IPv4 EPSV/EPRT
926  */
927 /*ARGSUSED*/
928 void
929 setepsv4(int argc, char *argv[])
930 {
931 
932 	code = togglevar(argc, argv, &epsv4, "EPSV/EPRT on IPv4");
933 	epsv4bad = 0;
934 }
935 
936 /*
937  * Turn on packet tracing.
938  */
939 /*ARGSUSED*/
940 void
941 settrace(int argc, char *argv[])
942 {
943 
944 	code = togglevar(argc, argv, &trace, "Packet tracing");
945 }
946 
947 /*
948  * Toggle hash mark printing during transfers, or set hash mark bytecount.
949  */
950 /*ARGSUSED*/
951 void
952 sethash(int argc, char *argv[])
953 {
954 	if (argc == 1)
955 		hash = !hash;
956 	else if (argc != 2) {
957 		fprintf(ttyout, "usage: %s [on | off | size]\n", argv[0]);
958 		code = -1;
959 		return;
960 	} else if (strcasecmp(argv[1], "on") == 0)
961 		hash = 1;
962 	else if (strcasecmp(argv[1], "off") == 0)
963 		hash = 0;
964 	else {
965 		int nmark;
966 		const char *errstr;
967 
968 		nmark = strtonum(argv[1], 1, INT_MAX, &errstr);
969 		if (errstr) {
970 			fprintf(ttyout, "bytecount value is %s: %s\n",
971 			    errstr, argv[1]);
972 			code = -1;
973 			return;
974 		}
975 		mark = nmark;
976 		hash = 1;
977 	}
978 	fprintf(ttyout, "Hash mark printing %s", onoff(hash));
979 	if (hash)
980 		fprintf(ttyout, " (%d bytes/hash mark)", mark);
981 	fputs(".\n", ttyout);
982 	code = hash;
983 }
984 
985 /*
986  * Turn on printing of server echo's.
987  */
988 /*ARGSUSED*/
989 void
990 setverbose(int argc, char *argv[])
991 {
992 
993 	code = togglevar(argc, argv, &verbose, "Verbose mode");
994 }
995 
996 /*
997  * Toggle PORT/LPRT cmd use before each data connection.
998  */
999 /*ARGSUSED*/
1000 void
1001 setport(int argc, char *argv[])
1002 {
1003 
1004 	code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
1005 }
1006 
1007 /*
1008  * Toggle transfer progress bar.
1009  */
1010 /*ARGSUSED*/
1011 void
1012 setprogress(int argc, char *argv[])
1013 {
1014 
1015 	code = togglevar(argc, argv, &progress, "Progress bar");
1016 }
1017 
1018 /*
1019  * Turn on interactive prompting during mget, mput, and mdelete.
1020  */
1021 /*ARGSUSED*/
1022 void
1023 setprompt(int argc, char *argv[])
1024 {
1025 
1026 	code = togglevar(argc, argv, &interactive, "Interactive mode");
1027 }
1028 
1029 /*
1030  * Toggle gate-ftp mode, or set gate-ftp server
1031  */
1032 /*ARGSUSED*/
1033 void
1034 setgate(int argc, char *argv[])
1035 {
1036 	static char gsbuf[MAXHOSTNAMELEN];
1037 
1038 	if (argc > 3) {
1039 		fprintf(ttyout, "usage: %s [on | off | host [port]]\n",
1040 		    argv[0]);
1041 		code = -1;
1042 		return;
1043 	} else if (argc < 2) {
1044 		gatemode = !gatemode;
1045 	} else {
1046 		if (argc == 2 && strcasecmp(argv[1], "on") == 0)
1047 			gatemode = 1;
1048 		else if (argc == 2 && strcasecmp(argv[1], "off") == 0)
1049 			gatemode = 0;
1050 		else {
1051 			if (argc == 3) {
1052 				gateport = strdup(argv[2]);
1053 				if (gateport == NULL)
1054 					err(1, NULL);
1055 			}
1056 			strlcpy(gsbuf, argv[1], sizeof(gsbuf));
1057 			gateserver = gsbuf;
1058 			gatemode = 1;
1059 		}
1060 	}
1061 	if (gatemode && (gateserver == NULL || *gateserver == '\0')) {
1062 		fprintf(ttyout,
1063 		    "Disabling gate-ftp mode - no gate-ftp server defined.\n");
1064 		gatemode = 0;
1065 	} else {
1066 		fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n",
1067 		    onoff(gatemode),
1068 		    *gateserver ? gateserver : "(none)", gateport);
1069 	}
1070 	code = gatemode;
1071 }
1072 
1073 /*
1074  * Toggle metacharacter interpretation on local file names.
1075  */
1076 /*ARGSUSED*/
1077 void
1078 setglob(int argc, char *argv[])
1079 {
1080 
1081 	code = togglevar(argc, argv, &doglob, "Globbing");
1082 }
1083 
1084 /*
1085  * Toggle preserving modification times on retrieved files.
1086  */
1087 /*ARGSUSED*/
1088 void
1089 setpreserve(int argc, char *argv[])
1090 {
1091 
1092 	code = togglevar(argc, argv, &preserve, "Preserve modification times");
1093 }
1094 
1095 /*
1096  * Set debugging mode on/off and/or set level of debugging.
1097  */
1098 #ifndef SMALL
1099 /*ARGSUSED*/
1100 void
1101 setdebug(int argc, char *argv[])
1102 {
1103 	if (argc > 2) {
1104 		fprintf(ttyout, "usage: %s [on | off | debuglevel]\n", argv[0]);
1105 		code = -1;
1106 		return;
1107 	} else if (argc == 2) {
1108 		if (strcasecmp(argv[1], "on") == 0)
1109 			debug = 1;
1110 		else if (strcasecmp(argv[1], "off") == 0)
1111 			debug = 0;
1112 		else {
1113 			const char *errstr;
1114 			int val;
1115 
1116 			val = strtonum(argv[1], 0, INT_MAX, &errstr);
1117 			if (errstr) {
1118 				fprintf(ttyout, "debugging value is %s: %s\n",
1119 				    errstr, argv[1]);
1120 				code = -1;
1121 				return;
1122 			}
1123 			debug = val;
1124 		}
1125 	} else
1126 		debug = !debug;
1127 	if (debug)
1128 		options |= SO_DEBUG;
1129 	else
1130 		options &= ~SO_DEBUG;
1131 	fprintf(ttyout, "Debugging %s (debug=%d).\n", onoff(debug), debug);
1132 	code = debug > 0;
1133 }
1134 #endif /* !SMALL */
1135 
1136 /*
1137  * Set current working directory on remote machine.
1138  */
1139 void
1140 cd(int argc, char *argv[])
1141 {
1142 	int r;
1143 
1144 	if ((argc < 2 && !another(&argc, &argv, "remote-directory")) ||
1145 	    argc > 2) {
1146 		fprintf(ttyout, "usage: %s remote-directory\n", argv[0]);
1147 		code = -1;
1148 		return;
1149 	}
1150 	r = command("CWD %s", argv[1]);
1151 	if (r == ERROR && code == 500) {
1152 		if (verbose)
1153 			fputs("CWD command not recognized, trying XCWD.\n", ttyout);
1154 		r = command("XCWD %s", argv[1]);
1155 	}
1156 	if (r == ERROR && code == 550) {
1157 		dirchange = 0;
1158 		return;
1159 	}
1160 	if (r == COMPLETE)
1161 		dirchange = 1;
1162 }
1163 
1164 /*
1165  * Set current working directory on local machine.
1166  */
1167 void
1168 lcd(int argc, char *argv[])
1169 {
1170 	char buf[MAXPATHLEN];
1171 	char *oldargv1;
1172 
1173 	if (argc < 2)
1174 		argc++, argv[1] = home;
1175 	if (argc != 2) {
1176 		fprintf(ttyout, "usage: %s [local-directory]\n", argv[0]);
1177 		code = -1;
1178 		return;
1179 	}
1180 	oldargv1 = argv[1];
1181 	if (!globulize(&argv[1])) {
1182 		code = -1;
1183 		return;
1184 	}
1185 	if (chdir(argv[1]) < 0) {
1186 		warn("local: %s", argv[1]);
1187 		code = -1;
1188 	} else {
1189 		if (getcwd(buf, sizeof(buf)) != NULL)
1190 			fprintf(ttyout, "Local directory now %s\n", buf);
1191 		else
1192 			warn("getcwd: %s", argv[1]);
1193 		code = 0;
1194 	}
1195 	if (oldargv1 != argv[1])	/* free up after globulize() */
1196 		free(argv[1]);
1197 }
1198 
1199 /*
1200  * Delete a single file.
1201  */
1202 void
1203 deletecmd(int argc, char *argv[])
1204 {
1205 
1206 	if ((argc < 2 && !another(&argc, &argv, "remote-file")) || argc > 2) {
1207 		fprintf(ttyout, "usage: %s remote-file\n", argv[0]);
1208 		code = -1;
1209 		return;
1210 	}
1211 	(void)command("DELE %s", argv[1]);
1212 }
1213 
1214 /*
1215  * Delete multiple files.
1216  */
1217 void
1218 mdelete(int argc, char *argv[])
1219 {
1220 	sig_t oldintr;
1221 	char *cp;
1222 
1223 	if (argc < 2 && !another(&argc, &argv, "remote-files")) {
1224 		fprintf(ttyout, "usage: %s remote-files\n", argv[0]);
1225 		code = -1;
1226 		return;
1227 	}
1228 	mname = argv[0];
1229 	mflag = 1;
1230 	oldintr = signal(SIGINT, mabort);
1231 	(void)setjmp(jabort);
1232 	while ((cp = remglob(argv, 0, NULL)) != NULL) {
1233 		if (*cp == '\0') {
1234 			mflag = 0;
1235 			continue;
1236 		}
1237 		if (mflag && confirm(argv[0], cp)) {
1238 			(void)command("DELE %s", cp);
1239 			if (!mflag && fromatty) {
1240 				if (confirm(argv[0], NULL))
1241 					mflag = 1;
1242 			}
1243 		}
1244 	}
1245 	(void)signal(SIGINT, oldintr);
1246 	mflag = 0;
1247 }
1248 
1249 /*
1250  * Rename a remote file.
1251  */
1252 void
1253 renamefile(int argc, char *argv[])
1254 {
1255 
1256 	if (argc < 2 && !another(&argc, &argv, "from-name"))
1257 		goto usage;
1258 	if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
1259 usage:
1260 		fprintf(ttyout, "usage: %s from-name to-name\n", argv[0]);
1261 		code = -1;
1262 		return;
1263 	}
1264 	if (command("RNFR %s", argv[1]) == CONTINUE)
1265 		(void)command("RNTO %s", argv[2]);
1266 }
1267 
1268 /*
1269  * Get a directory listing of remote files.
1270  */
1271 void
1272 ls(int argc, char *argv[])
1273 {
1274 	const char *cmd;
1275 	char *oldargv2, *globargv2;
1276 
1277 	if (argc < 2)
1278 		argc++, argv[1] = NULL;
1279 	if (argc < 3)
1280 		argc++, argv[2] = "-";
1281 	if (argc > 3) {
1282 		fprintf(ttyout, "usage: %s [remote-directory [local-file]]\n",
1283 		    argv[0]);
1284 		code = -1;
1285 		return;
1286 	}
1287 	cmd = strcmp(argv[0], "nlist") == 0 ? "NLST" : "LIST";
1288 	oldargv2 = argv[2];
1289 	if (strcmp(argv[2], "-") && !globulize(&argv[2])) {
1290 		code = -1;
1291 		return;
1292 	}
1293 	globargv2 = argv[2];
1294 	if (strcmp(argv[2], "-") && *argv[2] != '|' && (!globulize(&argv[2]) ||
1295 	    !confirm("output to local-file:", argv[2]))) {
1296 		code = -1;
1297 		goto freels;
1298 	}
1299 	recvrequest(cmd, argv[2], argv[1], "w", 0, 0);
1300 
1301 	/* flush results in case commands are coming from a pipe */
1302 	fflush(ttyout);
1303 freels:
1304 	if (argv[2] != globargv2)		/* free up after globulize() */
1305 		free(argv[2]);
1306 	if (globargv2 != oldargv2)
1307 		free(globargv2);
1308 }
1309 
1310 /*
1311  * Get a directory listing of multiple remote files.
1312  */
1313 void
1314 mls(int argc, char *argv[])
1315 {
1316 	sig_t oldintr;
1317 	int i;
1318 	char lmode[1], *dest, *odest;
1319 
1320 	if (argc < 2 && !another(&argc, &argv, "remote-files"))
1321 		goto usage;
1322 	if (argc < 3 && !another(&argc, &argv, "local-file")) {
1323 usage:
1324 		fprintf(ttyout, "usage: %s remote-files local-file\n", argv[0]);
1325 		code = -1;
1326 		return;
1327 	}
1328 	odest = dest = argv[argc - 1];
1329 	argv[argc - 1] = NULL;
1330 	if (strcmp(dest, "-") && *dest != '|')
1331 		if (!globulize(&dest) ||
1332 		    !confirm("output to local-file:", dest)) {
1333 			code = -1;
1334 			return;
1335 	}
1336 	mname = argv[0];
1337 	mflag = 1;
1338 	oldintr = signal(SIGINT, mabort);
1339 	(void)setjmp(jabort);
1340 	for (i = 1; mflag && i < argc-1; ++i) {
1341 		*lmode = (i == 1) ? 'w' : 'a';
1342 		recvrequest("LIST", dest, argv[i], lmode, 0, 0);
1343 		if (!mflag && fromatty) {
1344 			if (confirm(argv[0], NULL))
1345 				mflag ++;
1346 		}
1347 	}
1348 	(void)signal(SIGINT, oldintr);
1349 	mflag = 0;
1350 	if (dest != odest)			/* free up after globulize() */
1351 		free(dest);
1352 }
1353 
1354 /*
1355  * Do a shell escape
1356  */
1357 /*ARGSUSED*/
1358 void
1359 shell(int argc, char *argv[])
1360 {
1361 	pid_t pid;
1362 	sig_t old1, old2;
1363 	char shellnam[MAXPATHLEN], *shellp, *namep;
1364 	int wait_status;
1365 
1366 	old1 = signal (SIGINT, SIG_IGN);
1367 	old2 = signal (SIGQUIT, SIG_IGN);
1368 	if ((pid = fork()) == 0) {
1369 		for (pid = 3; pid < 20; pid++)
1370 			(void)close(pid);
1371 		(void)signal(SIGINT, SIG_DFL);
1372 		(void)signal(SIGQUIT, SIG_DFL);
1373 		shellp = getenv("SHELL");
1374 		if (shellp == NULL || *shellp == '\0')
1375 			shellp = _PATH_BSHELL;
1376 		namep = strrchr(shellp, '/');
1377 		if (namep == NULL)
1378 			namep = shellp;
1379 		shellnam[0] = '-';
1380 		(void)strlcpy(shellnam + 1, ++namep, sizeof(shellnam) - 1);
1381 		if (strcmp(namep, "sh") != 0)
1382 			shellnam[0] = '+';
1383 #ifndef SMALL
1384 		if (debug) {
1385 			fputs(shellp, ttyout);
1386 			fputc('\n', ttyout);
1387 			(void)fflush(ttyout);
1388 		}
1389 #endif /* !SMALL */
1390 		if (argc > 1) {
1391 			execl(shellp, shellnam, "-c", altarg, (char *)0);
1392 		}
1393 		else {
1394 			execl(shellp, shellnam, (char *)0);
1395 		}
1396 		warn("%s", shellp);
1397 		code = -1;
1398 		exit(1);
1399 	}
1400 	if (pid > 0)
1401 		while (wait(&wait_status) != pid)
1402 			;
1403 	(void)signal(SIGINT, old1);
1404 	(void)signal(SIGQUIT, old2);
1405 	if (pid == -1) {
1406 		warn("Try again later");
1407 		code = -1;
1408 	}
1409 	else {
1410 		code = 0;
1411 	}
1412 }
1413 
1414 /*
1415  * Send new user information (re-login)
1416  */
1417 void
1418 user(int argc, char *argv[])
1419 {
1420 	char acctname[80];
1421 	int n, aflag = 0;
1422 
1423 	if (argc < 2)
1424 		(void)another(&argc, &argv, "username");
1425 	if (argc < 2 || argc > 4) {
1426 		fprintf(ttyout, "usage: %s username [password [account]]\n",
1427 		    argv[0]);
1428 		code = -1;
1429 		return;
1430 	}
1431 	n = command("USER %s", argv[1]);
1432 	if (n == CONTINUE) {
1433 		if (argc < 3 )
1434 			argv[2] = getpass("Password:"), argc++;
1435 		n = command("PASS %s", argv[2]);
1436 	}
1437 	if (n == CONTINUE) {
1438 		if (argc < 4) {
1439 			(void)fputs("Account: ", ttyout);
1440 			(void)fflush(ttyout);
1441 			if (fgets(acctname, sizeof(acctname), stdin) == NULL) {
1442 				clearerr(stdin);
1443 				goto fail;
1444 			}
1445 
1446 			acctname[strcspn(acctname, "\n")] = '\0';
1447 
1448 			argv[3] = acctname;
1449 			argc++;
1450 		}
1451 		n = command("ACCT %s", argv[3]);
1452 		aflag++;
1453 	}
1454 	if (n != COMPLETE) {
1455  fail:
1456 		fputs("Login failed.\n", ttyout);
1457 		return;
1458 	}
1459 	if (!aflag && argc == 4) {
1460 		(void)command("ACCT %s", argv[3]);
1461 	}
1462 	connected = -1;
1463 }
1464 
1465 /*
1466  * Print working directory on remote machine.
1467  */
1468 /*ARGSUSED*/
1469 void
1470 pwd(int argc, char *argv[])
1471 {
1472 	int oldverbose = verbose;
1473 
1474 	/*
1475 	 * If we aren't verbose, this doesn't do anything!
1476 	 */
1477 	verbose = 1;
1478 	if (command("PWD") == ERROR && code == 500) {
1479 		fputs("PWD command not recognized, trying XPWD.\n", ttyout);
1480 		(void)command("XPWD");
1481 	}
1482 	verbose = oldverbose;
1483 }
1484 
1485 /*
1486  * Print working directory on local machine.
1487  */
1488 /* ARGSUSED */
1489 void
1490 lpwd(int argc, char *argv[])
1491 {
1492 	char buf[MAXPATHLEN];
1493 
1494 	if (getcwd(buf, sizeof(buf)) != NULL)
1495 		fprintf(ttyout, "Local directory %s\n", buf);
1496 	else
1497 		warn("getcwd");
1498 	code = 0;
1499 }
1500 
1501 /*
1502  * Make a directory.
1503  */
1504 void
1505 makedir(int argc, char *argv[])
1506 {
1507 
1508 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1509 	    argc > 2) {
1510 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1511 		code = -1;
1512 		return;
1513 	}
1514 	if (command("MKD %s", argv[1]) == ERROR && code == 500) {
1515 		if (verbose)
1516 			fputs("MKD command not recognized, trying XMKD.\n", ttyout);
1517 		(void)command("XMKD %s", argv[1]);
1518 	}
1519 }
1520 
1521 /*
1522  * Remove a directory.
1523  */
1524 void
1525 removedir(int argc, char *argv[])
1526 {
1527 
1528 	if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
1529 	    argc > 2) {
1530 		fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
1531 		code = -1;
1532 		return;
1533 	}
1534 	if (command("RMD %s", argv[1]) == ERROR && code == 500) {
1535 		if (verbose)
1536 			fputs("RMD command not recognized, trying XRMD.\n", ttyout);
1537 		(void)command("XRMD %s", argv[1]);
1538 	}
1539 }
1540 
1541 /*
1542  * Send a line, verbatim, to the remote machine.
1543  */
1544 void
1545 quote(int argc, char *argv[])
1546 {
1547 
1548 	if (argc < 2 && !another(&argc, &argv, "command line to send")) {
1549 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1550 		code = -1;
1551 		return;
1552 	}
1553 	quote1("", argc, argv);
1554 }
1555 
1556 /*
1557  * Send a SITE command to the remote machine.  The line
1558  * is sent verbatim to the remote machine, except that the
1559  * word "SITE" is added at the front.
1560  */
1561 void
1562 site(int argc, char *argv[])
1563 {
1564 
1565 	if (argc < 2 && !another(&argc, &argv, "arguments to SITE command")) {
1566 		fprintf(ttyout, "usage: %s arg ...\n", argv[0]);
1567 		code = -1;
1568 		return;
1569 	}
1570 	quote1("SITE", argc, argv);
1571 }
1572 
1573 /*
1574  * Turn argv[1..argc) into a space-separated string, then prepend initial text.
1575  * Send the result as a one-line command and get response.
1576  */
1577 void
1578 quote1(const char *initial, int argc, char *argv[])
1579 {
1580 	int i, len;
1581 	char buf[BUFSIZ];		/* must be >= sizeof(line) */
1582 
1583 	(void)strlcpy(buf, initial, sizeof(buf));
1584 	if (argc > 1) {
1585 		for (i = 1, len = strlen(buf); i < argc && len < sizeof(buf)-1; i++) {
1586 			/* Space for next arg */
1587 			if (len > 1)
1588 				buf[len++] = ' ';
1589 
1590 			/* Sanity check */
1591 			if (len >= sizeof(buf) - 1)
1592 				break;
1593 
1594 			/* Copy next argument, NUL terminate always */
1595 			strlcpy(&buf[len], argv[i], sizeof(buf) - len);
1596 
1597 			/* Update string length */
1598 			len = strlen(buf);
1599 		}
1600 	}
1601 
1602 	/* Make double (triple?) sure the sucker is NUL terminated */
1603 	buf[sizeof(buf) - 1] = '\0';
1604 
1605 	if (command("%s", buf) == PRELIM) {
1606 		while (getreply(0) == PRELIM)
1607 			continue;
1608 	}
1609 }
1610 
1611 void
1612 do_chmod(int argc, char *argv[])
1613 {
1614 
1615 	if (argc < 2 && !another(&argc, &argv, "mode"))
1616 		goto usage;
1617 	if ((argc < 3 && !another(&argc, &argv, "file")) || argc > 3) {
1618 usage:
1619 		fprintf(ttyout, "usage: %s mode file\n", argv[0]);
1620 		code = -1;
1621 		return;
1622 	}
1623 	(void)command("SITE CHMOD %s %s", argv[1], argv[2]);
1624 }
1625 
1626 void
1627 do_umask(int argc, char *argv[])
1628 {
1629 	int oldverbose = verbose;
1630 
1631 	verbose = 1;
1632 	(void)command(argc == 1 ? "SITE UMASK" : "SITE UMASK %s", argv[1]);
1633 	verbose = oldverbose;
1634 }
1635 
1636 void
1637 idle(int argc, char *argv[])
1638 {
1639 	int oldverbose = verbose;
1640 
1641 	verbose = 1;
1642 	(void)command(argc == 1 ? "SITE IDLE" : "SITE IDLE %s", argv[1]);
1643 	verbose = oldverbose;
1644 }
1645 
1646 /*
1647  * Ask the other side for help.
1648  */
1649 void
1650 rmthelp(int argc, char *argv[])
1651 {
1652 	int oldverbose = verbose;
1653 
1654 	verbose = 1;
1655 	(void)command(argc == 1 ? "HELP" : "HELP %s", argv[1]);
1656 	verbose = oldverbose;
1657 }
1658 
1659 /*
1660  * Terminate session and exit.
1661  */
1662 /*ARGSUSED*/
1663 void
1664 quit(int argc, char *argv[])
1665 {
1666 
1667 	if (connected)
1668 		disconnect(0, 0);
1669 	pswitch(1);
1670 	if (connected) {
1671 		disconnect(0, 0);
1672 	}
1673 	exit(0);
1674 }
1675 
1676 /*
1677  * Terminate session, but don't exit.
1678  */
1679 /* ARGSUSED */
1680 void
1681 disconnect(int argc, char *argv[])
1682 {
1683 
1684 	if (!connected)
1685 		return;
1686 	(void)command("QUIT");
1687 	if (cout) {
1688 		(void)fclose(cout);
1689 	}
1690 	cout = NULL;
1691 	connected = 0;
1692 	data = -1;
1693 	if (!proxy) {
1694 		macnum = 0;
1695 	}
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 void
2134 macdef(int argc, char *argv[])
2135 {
2136 	char *tmp;
2137 	int c;
2138 
2139 	if (macnum == 16) {
2140 		fputs("Limit of 16 macros have already been defined.\n", ttyout);
2141 		code = -1;
2142 		return;
2143 	}
2144 	if ((argc < 2 && !another(&argc, &argv, "macro-name")) || argc > 2) {
2145 		fprintf(ttyout, "usage: %s macro-name\n", argv[0]);
2146 		code = -1;
2147 		return;
2148 	}
2149 	if (interactive)
2150 		fputs(
2151 "Enter macro line by line, terminating it with a null line.\n", ttyout);
2152 	(void)strlcpy(macros[macnum].mac_name, argv[1],
2153 	    sizeof(macros[macnum].mac_name));
2154 	if (macnum == 0)
2155 		macros[macnum].mac_start = macbuf;
2156 	else
2157 		macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
2158 	tmp = macros[macnum].mac_start;
2159 	while (tmp != macbuf+4096) {
2160 		if ((c = getchar()) == EOF) {
2161 			fputs("macdef: end of file encountered.\n", ttyout);
2162 			code = -1;
2163 			return;
2164 		}
2165 		if ((*tmp = c) == '\n') {
2166 			if (tmp == macros[macnum].mac_start) {
2167 				macros[macnum++].mac_end = tmp;
2168 				code = 0;
2169 				return;
2170 			}
2171 			if (*(tmp-1) == '\0') {
2172 				macros[macnum++].mac_end = tmp - 1;
2173 				code = 0;
2174 				return;
2175 			}
2176 			*tmp = '\0';
2177 		}
2178 		tmp++;
2179 	}
2180 	while (1) {
2181 		while ((c = getchar()) != '\n' && c != EOF)
2182 			/* LOOP */;
2183 		if (c == EOF || getchar() == '\n') {
2184 			fputs("Macro not defined - 4K buffer exceeded.\n", ttyout);
2185 			code = -1;
2186 			return;
2187 		}
2188 	}
2189 }
2190 
2191 /*
2192  * Get size of file on remote machine
2193  */
2194 void
2195 sizecmd(int argc, char *argv[])
2196 {
2197 	off_t size;
2198 
2199 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
2200 		fprintf(ttyout, "usage: %s file\n", argv[0]);
2201 		code = -1;
2202 		return;
2203 	}
2204 	size = remotesize(argv[1], 1);
2205 	if (size != -1)
2206 		fprintf(ttyout, "%s\t%lld\n", argv[1], (long long)size);
2207 	code = size;
2208 }
2209 
2210 /*
2211  * Get last modification time of file on remote machine
2212  */
2213 void
2214 modtime(int argc, char *argv[])
2215 {
2216 	time_t mtime;
2217 
2218 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
2219 		fprintf(ttyout, "usage: %s file\n", argv[0]);
2220 		code = -1;
2221 		return;
2222 	}
2223 	mtime = remotemodtime(argv[1], 1);
2224 	if (mtime != -1)
2225 		fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime)));
2226 	code = mtime;
2227 }
2228 
2229 /*
2230  * Show status on remote machine
2231  */
2232 void
2233 rmtstatus(int argc, char *argv[])
2234 {
2235 
2236 	(void)command(argc > 1 ? "STAT %s" : "STAT" , argv[1]);
2237 }
2238 
2239 /*
2240  * Get file if modtime is more recent than current file
2241  */
2242 void
2243 newer(int argc, char *argv[])
2244 {
2245 
2246 	if (getit(argc, argv, -1, "w"))
2247 		fprintf(ttyout, "Local file \"%s\" is newer than remote file \"%s\".\n",
2248 			argv[2], argv[1]);
2249 }
2250 
2251 /*
2252  * Display one file through $PAGER (defaults to "more").
2253  */
2254 void
2255 page(int argc, char *argv[])
2256 {
2257 	int orestart_point, ohash, overbose;
2258 	char *p, *pager, *oldargv1;
2259 
2260 	if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
2261 		fprintf(ttyout, "usage: %s file\n", argv[0]);
2262 		code = -1;
2263 		return;
2264 	}
2265 	oldargv1 = argv[1];
2266 	if (!globulize(&argv[1])) {
2267 		code = -1;
2268 		return;
2269 	}
2270 	p = getenv("PAGER");
2271 	if (p == NULL || (*p == '\0'))
2272 		p = PAGER;
2273 	if (asprintf(&pager, "|%s", p) == -1)
2274 		errx(1, "Can't allocate memory for $PAGER");
2275 
2276 	orestart_point = restart_point;
2277 	ohash = hash;
2278 	overbose = verbose;
2279 	restart_point = hash = verbose = 0;
2280 	recvrequest("RETR", pager, argv[1], "r+w", 1, 0);
2281 	(void)free(pager);
2282 	restart_point = orestart_point;
2283 	hash = ohash;
2284 	verbose = overbose;
2285 	if (oldargv1 != argv[1])	/* free up after globulize() */
2286 		free(argv[1]);
2287 }
2288