1/*	$NetBSD: cmds.c,v 1.17 2010/01/12 06:55:47 lukem Exp $	*/
2/*	from	NetBSD: cmds.c,v 1.130 2009/07/13 19:05:41 roy Exp	*/
3
4/*-
5 * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Luke Mewburn.
10 *
11 * This code is derived from software contributed to The NetBSD Foundation
12 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
13 * NASA Ames Research Center.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 *    notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 *    notice, this list of conditions and the following disclaimer in the
22 *    documentation and/or other materials provided with the distribution.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
25 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
28 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37/*
38 * Copyright (c) 1985, 1989, 1993, 1994
39 *	The Regents of the University of California.  All rights reserved.
40 *
41 * Redistribution and use in source and binary forms, with or without
42 * modification, are permitted provided that the following conditions
43 * are met:
44 * 1. Redistributions of source code must retain the above copyright
45 *    notice, this list of conditions and the following disclaimer.
46 * 2. Redistributions in binary form must reproduce the above copyright
47 *    notice, this list of conditions and the following disclaimer in the
48 *    documentation and/or other materials provided with the distribution.
49 * 3. Neither the name of the University nor the names of its contributors
50 *    may be used to endorse or promote products derived from this software
51 *    without specific prior written permission.
52 *
53 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
54 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
55 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
56 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
57 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
58 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
59 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
60 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
61 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
62 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
63 * SUCH DAMAGE.
64 */
65
66/*
67 * Copyright (C) 1997 and 1998 WIDE Project.
68 * All rights reserved.
69 *
70 * Redistribution and use in source and binary forms, with or without
71 * modification, are permitted provided that the following conditions
72 * are met:
73 * 1. Redistributions of source code must retain the above copyright
74 *    notice, this list of conditions and the following disclaimer.
75 * 2. Redistributions in binary form must reproduce the above copyright
76 *    notice, this list of conditions and the following disclaimer in the
77 *    documentation and/or other materials provided with the distribution.
78 * 3. Neither the name of the project nor the names of its contributors
79 *    may be used to endorse or promote products derived from this software
80 *    without specific prior written permission.
81 *
82 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
83 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
84 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
85 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
86 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
87 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
88 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
89 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
90 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
91 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
92 * SUCH DAMAGE.
93 */
94
95#include "tnftp.h"
96
97#if 0	/* tnftp */
98
99#include <sys/cdefs.h>
100#ifndef lint
101#if 0
102static char sccsid[] = "@(#)cmds.c	8.6 (Berkeley) 10/9/94";
103#else
104__RCSID(" NetBSD: cmds.c,v 1.130 2009/07/13 19:05:41 roy Exp  ");
105#endif
106#endif /* not lint */
107
108/*
109 * FTP User Program -- Command Routines.
110 */
111#include <sys/types.h>
112#include <sys/socket.h>
113#include <sys/stat.h>
114#include <sys/wait.h>
115#include <arpa/ftp.h>
116
117#include <ctype.h>
118#include <err.h>
119#include <glob.h>
120#include <limits.h>
121#include <netdb.h>
122#include <paths.h>
123#include <stddef.h>
124#include <stdio.h>
125#include <stdlib.h>
126#include <string.h>
127#include <time.h>
128#include <unistd.h>
129
130#endif	/* tnftp */
131
132#include "ftp_var.h"
133#include "version.h"
134
135static struct types {
136	const char	*t_name;
137	const char	*t_mode;
138	int		t_type;
139	const char	*t_arg;
140} types[] = {
141	{ "ascii",	"A",	TYPE_A,	0 },
142	{ "binary",	"I",	TYPE_I,	0 },
143	{ "image",	"I",	TYPE_I,	0 },
144	{ "ebcdic",	"E",	TYPE_E,	0 },
145	{ "tenex",	"L",	TYPE_L,	bytename },
146	{ NULL,		NULL,	0, NULL }
147};
148
149static sigjmp_buf	 jabort;
150
151static int	confirm(const char *, const char *);
152static void	mintr(int);
153static void	mabort(const char *);
154static void	set_type(const char *);
155
156static const char *doprocess(char *, size_t, const char *, int, int, int);
157static const char *domap(char *, size_t, const char *);
158static const char *docase(char *, size_t, const char *);
159static const char *dotrans(char *, size_t, const char *);
160
161/*
162 * Confirm if "cmd" is to be performed upon "file".
163 * If "file" is NULL, generate a "Continue with" prompt instead.
164 */
165static int
166confirm(const char *cmd, const char *file)
167{
168	const char *errormsg;
169	char cline[BUFSIZ];
170	const char *promptleft, *promptright;
171
172	if (!interactive || confirmrest)
173		return (1);
174	if (file == NULL) {
175		promptleft = "Continue with";
176		promptright = cmd;
177	} else {
178		promptleft = cmd;
179		promptright = file;
180	}
181	while (1) {
182		fprintf(ttyout, "%s %s [anpqy?]? ", promptleft, promptright);
183		(void)fflush(ttyout);
184		if (get_line(stdin, cline, sizeof(cline), &errormsg) < 0) {
185			mflag = 0;
186			fprintf(ttyout, "%s; %s aborted\n", errormsg, cmd);
187			return (0);
188		}
189		switch (tolower((unsigned char)*cline)) {
190			case 'a':
191				confirmrest = 1;
192				fprintf(ttyout,
193				    "Prompting off for duration of %s.\n", cmd);
194				break;
195			case 'p':
196				interactive = 0;
197				fputs("Interactive mode: off.\n", ttyout);
198				break;
199			case 'q':
200				mflag = 0;
201				fprintf(ttyout, "%s aborted.\n", cmd);
202				/* FALLTHROUGH */
203			case 'n':
204				return (0);
205			case '?':
206				fprintf(ttyout,
207				    "  confirmation options:\n"
208				    "\ta  answer `yes' for the duration of %s\n"
209				    "\tn  answer `no' for this file\n"
210				    "\tp  turn off `prompt' mode\n"
211				    "\tq  stop the current %s\n"
212				    "\ty  answer `yes' for this file\n"
213				    "\t?  this help list\n",
214				    cmd, cmd);
215				continue;	/* back to while(1) */
216		}
217		return (1);
218	}
219	/* NOTREACHED */
220}
221
222/*
223 * Set transfer type.
224 */
225void
226settype(int argc, char *argv[])
227{
228	struct types *p;
229
230	if (argc == 0 || argc > 2) {
231		const char *sep;
232
233		UPRINTF("usage: %s [", argv[0]);
234		sep = " ";
235		for (p = types; p->t_name; p++) {
236			fprintf(ttyout, "%s%s", sep, p->t_name);
237			sep = " | ";
238		}
239		fputs(" ]\n", ttyout);
240		code = -1;
241		return;
242	}
243	if (argc < 2) {
244		fprintf(ttyout, "Using %s mode to transfer files.\n", typename);
245		code = 0;
246		return;
247	}
248	set_type(argv[1]);
249}
250
251void
252set_type(const char *ttype)
253{
254	struct types *p;
255	int comret;
256
257	for (p = types; p->t_name; p++)
258		if (strcmp(ttype, p->t_name) == 0)
259			break;
260	if (p->t_name == 0) {
261		fprintf(ttyout, "%s: unknown mode.\n", ttype);
262		code = -1;
263		return;
264	}
265	if ((p->t_arg != NULL) && (*(p->t_arg) != '\0'))
266		comret = command("TYPE %s %s", p->t_mode, p->t_arg);
267	else
268		comret = command("TYPE %s", p->t_mode);
269	if (comret == COMPLETE) {
270		(void)strlcpy(typename, p->t_name, sizeof(typename));
271		curtype = type = p->t_type;
272	}
273}
274
275/*
276 * Internal form of settype; changes current type in use with server
277 * without changing our notion of the type for data transfers.
278 * Used to change to and from ascii for listings.
279 */
280void
281changetype(int newtype, int show)
282{
283	struct types *p;
284	int comret, oldverbose = verbose;
285
286	if (newtype == 0)
287		newtype = TYPE_I;
288	if (newtype == curtype)
289		return;
290	if (ftp_debug == 0 && show == 0)
291		verbose = 0;
292	for (p = types; p->t_name; p++)
293		if (newtype == p->t_type)
294			break;
295	if (p->t_name == 0) {
296		errx(1, "changetype: unknown type %d", newtype);
297	}
298	if (newtype == TYPE_L && bytename[0] != '\0')
299		comret = command("TYPE %s %s", p->t_mode, bytename);
300	else
301		comret = command("TYPE %s", p->t_mode);
302	if (comret == COMPLETE)
303		curtype = newtype;
304	verbose = oldverbose;
305}
306
307/*
308 * Set binary transfer type.
309 */
310/*VARARGS*/
311void
312setbinary(int argc, char *argv[])
313{
314
315	if (argc == 0) {
316		UPRINTF("usage: %s\n", argv[0]);
317		code = -1;
318		return;
319	}
320	set_type("binary");
321}
322
323/*
324 * Set ascii transfer type.
325 */
326/*VARARGS*/
327void
328setascii(int argc, char *argv[])
329{
330
331	if (argc == 0) {
332		UPRINTF("usage: %s\n", argv[0]);
333		code = -1;
334		return;
335	}
336	set_type("ascii");
337}
338
339/*
340 * Set tenex transfer type.
341 */
342/*VARARGS*/
343void
344settenex(int argc, char *argv[])
345{
346
347	if (argc == 0) {
348		UPRINTF("usage: %s\n", argv[0]);
349		code = -1;
350		return;
351	}
352	set_type("tenex");
353}
354
355/*
356 * Set file transfer mode.
357 */
358/*ARGSUSED*/
359void
360setftmode(int argc, char *argv[])
361{
362
363	if (argc != 2) {
364		UPRINTF("usage: %s mode-name\n", argv[0]);
365		code = -1;
366		return;
367	}
368	fprintf(ttyout, "We only support %s mode, sorry.\n", modename);
369	code = -1;
370}
371
372/*
373 * Set file transfer format.
374 */
375/*ARGSUSED*/
376void
377setform(int argc, char *argv[])
378{
379
380	if (argc != 2) {
381		UPRINTF("usage: %s format\n", argv[0]);
382		code = -1;
383		return;
384	}
385	fprintf(ttyout, "We only support %s format, sorry.\n", formname);
386	code = -1;
387}
388
389/*
390 * Set file transfer structure.
391 */
392/*ARGSUSED*/
393void
394setstruct(int argc, char *argv[])
395{
396
397	if (argc != 2) {
398		UPRINTF("usage: %s struct-mode\n", argv[0]);
399		code = -1;
400		return;
401	}
402	fprintf(ttyout, "We only support %s structure, sorry.\n", structname);
403	code = -1;
404}
405
406/*
407 * Send a single file.
408 */
409void
410put(int argc, char *argv[])
411{
412	char buf[MAXPATHLEN];
413	const char *cmd;
414	int loc = 0;
415	char *locfile;
416	const char *remfile;
417
418	if (argc == 2) {
419		argc++;
420		argv[2] = argv[1];
421		loc++;
422	}
423	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "local-file")))
424		goto usage;
425	if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
426 usage:
427		UPRINTF("usage: %s local-file [remote-file]\n", argv[0]);
428		code = -1;
429		return;
430	}
431	if ((locfile = globulize(argv[1])) == NULL) {
432		code = -1;
433		return;
434	}
435	remfile = argv[2];
436	if (loc)	/* If argv[2] is a copy of the old argv[1], update it */
437		remfile = locfile;
438	cmd = (argv[0][0] == 'a') ? "APPE" : ((sunique) ? "STOU" : "STOR");
439	remfile = doprocess(buf, sizeof(buf), remfile,
440		0, loc && ntflag, loc && mapflag);
441	sendrequest(cmd, locfile, remfile,
442	    locfile != argv[1] || remfile != argv[2]);
443	free(locfile);
444}
445
446static const char *
447doprocess(char *dst, size_t dlen, const char *src,
448    int casef, int transf, int mapf)
449{
450	if (casef)
451		src = docase(dst, dlen, src);
452	if (transf)
453		src = dotrans(dst, dlen, src);
454	if (mapf)
455		src = domap(dst, dlen, src);
456	return src;
457}
458
459/*
460 * Send multiple files.
461 */
462void
463mput(int argc, char *argv[])
464{
465	int i;
466	sigfunc oldintr;
467	int ointer;
468	const char *tp;
469
470	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "local-files"))) {
471		UPRINTF("usage: %s local-files\n", argv[0]);
472		code = -1;
473		return;
474	}
475	mflag = 1;
476	oldintr = xsignal(SIGINT, mintr);
477	if (sigsetjmp(jabort, 1))
478		mabort(argv[0]);
479	if (proxy) {
480		char *cp;
481
482		while ((cp = remglob(argv, 0, NULL)) != NULL) {
483			if (*cp == '\0' || !connected) {
484				mflag = 0;
485				continue;
486			}
487			if (mflag && confirm(argv[0], cp)) {
488				char buf[MAXPATHLEN];
489				tp = doprocess(buf, sizeof(buf), cp,
490				    mcase, ntflag, mapflag);
491				sendrequest((sunique) ? "STOU" : "STOR",
492				    cp, tp, cp != tp || !interactive);
493				if (!mflag && fromatty) {
494					ointer = interactive;
495					interactive = 1;
496					if (confirm(argv[0], NULL)) {
497						mflag++;
498					}
499					interactive = ointer;
500				}
501			}
502		}
503		goto cleanupmput;
504	}
505	for (i = 1; i < argc && connected; i++) {
506		char **cpp;
507		glob_t gl;
508		int flags;
509
510		if (!doglob) {
511			if (mflag && confirm(argv[0], argv[i])) {
512				char buf[MAXPATHLEN];
513				tp = doprocess(buf, sizeof(buf), argv[i],
514					0, ntflag, mapflag);
515				sendrequest((sunique) ? "STOU" : "STOR",
516				    argv[i], tp, tp != argv[i] || !interactive);
517				if (!mflag && fromatty) {
518					ointer = interactive;
519					interactive = 1;
520					if (confirm(argv[0], NULL)) {
521						mflag++;
522					}
523					interactive = ointer;
524				}
525			}
526			continue;
527		}
528
529		memset(&gl, 0, sizeof(gl));
530		flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
531		if (glob(argv[i], flags, NULL, &gl) || gl.gl_pathc == 0) {
532			warnx("Glob pattern `%s' not found", argv[i]);
533			globfree(&gl);
534			continue;
535		}
536		for (cpp = gl.gl_pathv; cpp && *cpp != NULL && connected;
537		    cpp++) {
538			if (mflag && confirm(argv[0], *cpp)) {
539				char buf[MAXPATHLEN];
540				tp = *cpp;
541				tp = doprocess(buf, sizeof(buf), *cpp,
542				    0, ntflag, mapflag);
543				sendrequest((sunique) ? "STOU" : "STOR",
544				    *cpp, tp, *cpp != tp || !interactive);
545				if (!mflag && fromatty) {
546					ointer = interactive;
547					interactive = 1;
548					if (confirm(argv[0], NULL)) {
549						mflag++;
550					}
551					interactive = ointer;
552				}
553			}
554		}
555		globfree(&gl);
556	}
557 cleanupmput:
558	(void)xsignal(SIGINT, oldintr);
559	mflag = 0;
560}
561
562void
563reget(int argc, char *argv[])
564{
565
566	(void)getit(argc, argv, 1, "r+");
567}
568
569void
570get(int argc, char *argv[])
571{
572
573	(void)getit(argc, argv, 0, restart_point ? "r+" : "w" );
574}
575
576/*
577 * Receive one file.
578 * If restartit is  1, restart the xfer always.
579 * If restartit is -1, restart the xfer only if the remote file is newer.
580 */
581int
582getit(int argc, char *argv[], int restartit, const char *gmode)
583{
584	int	loc, rval;
585	char	*remfile, *olocfile;
586	const char *locfile;
587	char	buf[MAXPATHLEN];
588
589	loc = rval = 0;
590	if (argc == 2) {
591		argc++;
592		argv[2] = argv[1];
593		loc++;
594	}
595	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "remote-file")))
596		goto usage;
597	if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) {
598 usage:
599		UPRINTF("usage: %s remote-file [local-file]\n", argv[0]);
600		code = -1;
601		return (0);
602	}
603	remfile = argv[1];
604	if ((olocfile = globulize(argv[2])) == NULL) {
605		code = -1;
606		return (0);
607	}
608	locfile = doprocess(buf, sizeof(buf), olocfile,
609		loc && mcase, loc && ntflag, loc && mapflag);
610	if (restartit) {
611		struct stat stbuf;
612		int ret;
613
614		if (! features[FEAT_REST_STREAM]) {
615			fprintf(ttyout,
616			    "Restart is not supported by the remote server.\n");
617			return (0);
618		}
619		ret = stat(locfile, &stbuf);
620		if (restartit == 1) {
621			if (ret < 0) {
622				warn("Can't stat `%s'", locfile);
623				goto freegetit;
624			}
625			restart_point = stbuf.st_size;
626		} else {
627			if (ret == 0) {
628				time_t mtime;
629
630				mtime = remotemodtime(argv[1], 0);
631				if (mtime == -1)
632					goto freegetit;
633				if (stbuf.st_mtime >= mtime) {
634					rval = 1;
635					goto freegetit;
636				}
637			}
638		}
639	}
640
641	recvrequest("RETR", locfile, remfile, gmode,
642	    remfile != argv[1] || locfile != argv[2], loc);
643	restart_point = 0;
644 freegetit:
645	(void)free(olocfile);
646	return (rval);
647}
648
649/* ARGSUSED */
650static void
651mintr(int signo)
652{
653
654	alarmtimer(0);
655	if (fromatty)
656		write(fileno(ttyout), "\n", 1);
657	siglongjmp(jabort, 1);
658}
659
660static void
661mabort(const char *cmd)
662{
663	int ointer, oconf;
664
665	if (mflag && fromatty) {
666		ointer = interactive;
667		oconf = confirmrest;
668		interactive = 1;
669		confirmrest = 0;
670		if (confirm(cmd, NULL)) {
671			interactive = ointer;
672			confirmrest = oconf;
673			return;
674		}
675		interactive = ointer;
676		confirmrest = oconf;
677	}
678	mflag = 0;
679}
680
681/*
682 * Get multiple files.
683 */
684void
685mget(int argc, char *argv[])
686{
687	sigfunc oldintr;
688	int ointer;
689	char *cp;
690	const char *tp;
691	int volatile restartit;
692
693	if (argc == 0 ||
694	    (argc == 1 && !another(&argc, &argv, "remote-files"))) {
695		UPRINTF("usage: %s remote-files\n", argv[0]);
696		code = -1;
697		return;
698	}
699	mflag = 1;
700	restart_point = 0;
701	restartit = 0;
702	if (strcmp(argv[0], "mreget") == 0) {
703		if (! features[FEAT_REST_STREAM]) {
704			fprintf(ttyout,
705		    "Restart is not supported by the remote server.\n");
706			return;
707		}
708		restartit = 1;
709	}
710	oldintr = xsignal(SIGINT, mintr);
711	if (sigsetjmp(jabort, 1))
712		mabort(argv[0]);
713	while ((cp = remglob(argv, proxy, NULL)) != NULL) {
714		char buf[MAXPATHLEN];
715		if (*cp == '\0' || !connected) {
716			mflag = 0;
717			continue;
718		}
719		if (! mflag)
720			continue;
721		if (! fileindir(cp, localcwd)) {
722			fprintf(ttyout, "Skipping non-relative filename `%s'\n",
723			    cp);
724			continue;
725		}
726		if (!confirm(argv[0], cp))
727			continue;
728		tp = doprocess(buf, sizeof(buf), cp, mcase, ntflag, mapflag);
729		if (restartit) {
730			struct stat stbuf;
731
732			if (stat(tp, &stbuf) == 0)
733				restart_point = stbuf.st_size;
734			else
735				warn("Can't stat `%s'", tp);
736		}
737		recvrequest("RETR", tp, cp, restart_point ? "r+" : "w",
738		    tp != cp || !interactive, 1);
739		restart_point = 0;
740		if (!mflag && fromatty) {
741			ointer = interactive;
742			interactive = 1;
743			if (confirm(argv[0], NULL))
744				mflag++;
745			interactive = ointer;
746		}
747	}
748	(void)xsignal(SIGINT, oldintr);
749	mflag = 0;
750}
751
752/*
753 * Read list of filenames from a local file and get those
754 */
755void
756fget(int argc, char *argv[])
757{
758	const char *gmode;
759	FILE	*fp;
760	char	buf[MAXPATHLEN], cmdbuf[MAX_C_NAME];
761
762	if (argc != 2) {
763		UPRINTF("usage: %s localfile\n", argv[0]);
764		code = -1;
765		return;
766	}
767
768	fp = fopen(argv[1], "r");
769	if (fp == NULL) {
770		fprintf(ttyout, "Can't open source file %s\n", argv[1]);
771		code = -1;
772		return;
773	}
774
775	(void)strlcpy(cmdbuf, "get", sizeof(cmdbuf));
776	argv[0] = cmdbuf;
777	gmode = restart_point ? "r+" : "w";
778
779	while (get_line(fp, buf, sizeof(buf), NULL) >= 0) {
780		if (buf[0] == '\0')
781			continue;
782		argv[1] = buf;
783		(void)getit(argc, argv, 0, gmode);
784	}
785	fclose(fp);
786}
787
788const char *
789onoff(int val)
790{
791
792	return (val ? "on" : "off");
793}
794
795/*
796 * Show status.
797 */
798/*ARGSUSED*/
799void
800status(int argc, char *argv[])
801{
802
803	if (argc == 0) {
804		UPRINTF("usage: %s\n", argv[0]);
805		code = -1;
806		return;
807	}
808#ifndef NO_STATUS
809	if (connected)
810		fprintf(ttyout, "Connected %sto %s.\n",
811		    connected == -1 ? "and logged in" : "", hostname);
812	else
813		fputs("Not connected.\n", ttyout);
814	if (!proxy) {
815		pswitch(1);
816		if (connected) {
817			fprintf(ttyout, "Connected for proxy commands to %s.\n",
818			    hostname);
819		}
820		else {
821			fputs("No proxy connection.\n", ttyout);
822		}
823		pswitch(0);
824	}
825	fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode),
826	    *gateserver ? gateserver : "(none)", gateport);
827	fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
828	    onoff(passivemode), onoff(activefallback));
829	fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n",
830	    modename, typename, formname, structname);
831	fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n",
832	    onoff(verbose), onoff(bell), onoff(interactive), onoff(doglob));
833	fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n",
834	    onoff(sunique), onoff(runique));
835	fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve));
836	fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase),
837	    onoff(crflag));
838	if (ntflag) {
839		fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout);
840	}
841	else {
842		fputs("Ntrans: off.\n", ttyout);
843	}
844	if (mapflag) {
845		fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout);
846	}
847	else {
848		fputs("Nmap: off.\n", ttyout);
849	}
850	fprintf(ttyout,
851	    "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
852	    onoff(hash), mark, onoff(progress));
853	fprintf(ttyout,
854	    "Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
855	    onoff(rate_get), rate_get, rate_get_incr);
856	fprintf(ttyout,
857	    "Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
858	    onoff(rate_put), rate_put, rate_put_incr);
859	fprintf(ttyout,
860	    "Socket buffer sizes: send %d, receive %d.\n",
861	    sndbuf_size, rcvbuf_size);
862	fprintf(ttyout, "Use of PORT cmds: %s.\n", onoff(sendport));
863	fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4),
864	    epsv4bad ? " (disabled for this connection)" : "");
865	fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv6: %s%s.\n", onoff(epsv6),
866	    epsv6bad ? " (disabled for this connection)" : "");
867	fprintf(ttyout, "Command line editing: %s.\n",
868#ifdef NO_EDITCOMPLETE
869	    "support not compiled in"
870#else	/* !def NO_EDITCOMPLETE */
871	    onoff(editing)
872#endif	/* !def NO_EDITCOMPLETE */
873	    );
874	if (macnum > 0) {
875		int i;
876
877		fputs("Macros:\n", ttyout);
878		for (i=0; i<macnum; i++) {
879			fprintf(ttyout, "\t%s\n", macros[i].mac_name);
880		}
881	}
882#endif /* !def NO_STATUS */
883	fprintf(ttyout, "Version: %s %s\n", FTP_PRODUCT, FTP_VERSION);
884	code = 0;
885}
886
887/*
888 * Toggle a variable
889 */
890int
891togglevar(int argc, char *argv[], int *var, const char *mesg)
892{
893	if (argc == 1) {
894		*var = !*var;
895	} else if (argc == 2 && strcasecmp(argv[1], "on") == 0) {
896		*var = 1;
897	} else if (argc == 2 && strcasecmp(argv[1], "off") == 0) {
898		*var = 0;
899	} else {
900		UPRINTF("usage: %s [ on | off ]\n", argv[0]);
901		return (-1);
902	}
903	if (mesg)
904		fprintf(ttyout, "%s %s.\n", mesg, onoff(*var));
905	return (*var);
906}
907
908/*
909 * Set beep on cmd completed mode.
910 */
911/*VARARGS*/
912void
913setbell(int argc, char *argv[])
914{
915
916	code = togglevar(argc, argv, &bell, "Bell mode");
917}
918
919/*
920 * Set command line editing
921 */
922/*VARARGS*/
923void
924setedit(int argc, char *argv[])
925{
926
927#ifdef NO_EDITCOMPLETE
928	if (argc == 0) {
929		UPRINTF("usage: %s\n", argv[0]);
930		code = -1;
931		return;
932	}
933	if (verbose)
934		fputs("Editing support not compiled in; ignoring command.\n",
935		    ttyout);
936#else	/* !def NO_EDITCOMPLETE */
937	code = togglevar(argc, argv, &editing, "Editing mode");
938	controlediting();
939#endif	/* !def NO_EDITCOMPLETE */
940}
941
942/*
943 * Turn on packet tracing.
944 */
945/*VARARGS*/
946void
947settrace(int argc, char *argv[])
948{
949
950	code = togglevar(argc, argv, &trace, "Packet tracing");
951}
952
953/*
954 * Toggle hash mark printing during transfers, or set hash mark bytecount.
955 */
956/*VARARGS*/
957void
958sethash(int argc, char *argv[])
959{
960	if (argc == 1)
961		hash = !hash;
962	else if (argc != 2) {
963		UPRINTF("usage: %s [ on | off | bytecount ]\n",
964		    argv[0]);
965		code = -1;
966		return;
967	} else if (strcasecmp(argv[1], "on") == 0)
968		hash = 1;
969	else if (strcasecmp(argv[1], "off") == 0)
970		hash = 0;
971	else {
972		int nmark;
973
974		nmark = strsuftoi(argv[1]);
975		if (nmark < 1) {
976			fprintf(ttyout, "mark: bad bytecount value `%s'.\n",
977			    argv[1]);
978			code = -1;
979			return;
980		}
981		mark = nmark;
982		hash = 1;
983	}
984	fprintf(ttyout, "Hash mark printing %s", onoff(hash));
985	if (hash)
986		fprintf(ttyout, " (%d bytes/hash mark)", mark);
987	fputs(".\n", ttyout);
988	if (hash)
989		progress = 0;
990	code = hash;
991}
992
993/*
994 * Turn on printing of server echo's.
995 */
996/*VARARGS*/
997void
998setverbose(int argc, char *argv[])
999{
1000
1001	code = togglevar(argc, argv, &verbose, "Verbose mode");
1002}
1003
1004/*
1005 * Toggle PORT/LPRT cmd use before each data connection.
1006 */
1007/*VARARGS*/
1008void
1009setport(int argc, char *argv[])
1010{
1011
1012	code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
1013}
1014
1015/*
1016 * Toggle transfer progress bar.
1017 */
1018/*VARARGS*/
1019void
1020setprogress(int argc, char *argv[])
1021{
1022
1023	code = togglevar(argc, argv, &progress, "Progress bar");
1024	if (progress)
1025		hash = 0;
1026}
1027
1028/*
1029 * Turn on interactive prompting during mget, mput, and mdelete.
1030 */
1031/*VARARGS*/
1032void
1033setprompt(int argc, char *argv[])
1034{
1035
1036	code = togglevar(argc, argv, &interactive, "Interactive mode");
1037}
1038
1039/*
1040 * Toggle gate-ftp mode, or set gate-ftp server
1041 */
1042/*VARARGS*/
1043void
1044setgate(int argc, char *argv[])
1045{
1046	static char gsbuf[MAXHOSTNAMELEN];
1047
1048	if (argc == 0 || argc > 3) {
1049		UPRINTF(
1050		    "usage: %s [ on | off | gateserver [port] ]\n", argv[0]);
1051		code = -1;
1052		return;
1053	} else if (argc < 2) {
1054		gatemode = !gatemode;
1055	} else {
1056		if (argc == 2 && strcasecmp(argv[1], "on") == 0)
1057			gatemode = 1;
1058		else if (argc == 2 && strcasecmp(argv[1], "off") == 0)
1059			gatemode = 0;
1060		else {
1061			if (argc == 3)
1062				gateport = ftp_strdup(argv[2]);
1063			(void)strlcpy(gsbuf, argv[1], sizeof(gsbuf));
1064			gateserver = gsbuf;
1065			gatemode = 1;
1066		}
1067	}
1068	if (gatemode && (gateserver == NULL || *gateserver == '\0')) {
1069		fprintf(ttyout,
1070		    "Disabling gate-ftp mode - no gate-ftp server defined.\n");
1071		gatemode = 0;
1072	} else {
1073		fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n",
1074		    onoff(gatemode), *gateserver ? gateserver : "(none)",
1075		    gateport);
1076	}
1077	code = gatemode;
1078}
1079
1080/*
1081 * Toggle metacharacter interpretation on local file names.
1082 */
1083/*VARARGS*/
1084void
1085setglob(int argc, char *argv[])
1086{
1087
1088	code = togglevar(argc, argv, &doglob, "Globbing");
1089}
1090
1091/*
1092 * Toggle preserving modification times on retrieved files.
1093 */
1094/*VARARGS*/
1095void
1096setpreserve(int argc, char *argv[])
1097{
1098
1099	code = togglevar(argc, argv, &preserve, "Preserve modification times");
1100}
1101
1102/*
1103 * Set debugging mode on/off and/or set level of debugging.
1104 */
1105/*VARARGS*/
1106void
1107setdebug(int argc, char *argv[])
1108{
1109	if (argc == 0 || argc > 2) {
1110		UPRINTF("usage: %s [ on | off | debuglevel ]\n", argv[0]);
1111		code = -1;
1112		return;
1113	} else if (argc == 2) {
1114		if (strcasecmp(argv[1], "on") == 0)
1115			ftp_debug = 1;
1116		else if (strcasecmp(argv[1], "off") == 0)
1117			ftp_debug = 0;
1118		else {
1119			int val;
1120
1121			val = strsuftoi(argv[1]);
1122			if (val < 0) {
1123				fprintf(ttyout, "%s: bad debugging value.\n",
1124				    argv[1]);
1125				code = -1;
1126				return;
1127			}
1128			ftp_debug = val;
1129		}
1130	} else
1131		ftp_debug = !ftp_debug;
1132	if (ftp_debug)
1133		options |= SO_DEBUG;
1134	else
1135		options &= ~SO_DEBUG;
1136	fprintf(ttyout, "Debugging %s (ftp_debug=%d).\n", onoff(ftp_debug), ftp_debug);
1137	code = ftp_debug > 0;
1138}
1139
1140/*
1141 * Set current working directory on remote machine.
1142 */
1143void
1144cd(int argc, char *argv[])
1145{
1146	int r;
1147
1148	if (argc == 0 || argc > 2 ||
1149	    (argc == 1 && !another(&argc, &argv, "remote-directory"))) {
1150		UPRINTF("usage: %s remote-directory\n", argv[0]);
1151		code = -1;
1152		return;
1153	}
1154	r = command("CWD %s", argv[1]);
1155	if (r == ERROR && code == 500) {
1156		if (verbose)
1157			fputs("CWD command not recognized, trying XCWD.\n",
1158			    ttyout);
1159		r = command("XCWD %s", argv[1]);
1160	}
1161	if (r == COMPLETE) {
1162		dirchange = 1;
1163		updateremotecwd();
1164	}
1165}
1166
1167/*
1168 * Set current working directory on local machine.
1169 */
1170void
1171lcd(int argc, char *argv[])
1172{
1173	char *locdir;
1174
1175	code = -1;
1176	if (argc == 1) {
1177		argc++;
1178		argv[1] = localhome;
1179	}
1180	if (argc != 2) {
1181		UPRINTF("usage: %s [local-directory]\n", argv[0]);
1182		return;
1183	}
1184	if ((locdir = globulize(argv[1])) == NULL)
1185		return;
1186	if (chdir(locdir) == -1)
1187		warn("Can't chdir `%s'", locdir);
1188	else {
1189		updatelocalcwd();
1190		if (localcwd[0]) {
1191			fprintf(ttyout, "Local directory now: %s\n", localcwd);
1192			code = 0;
1193		} else {
1194			fprintf(ttyout, "Unable to determine local directory\n");
1195		}
1196	}
1197	(void)free(locdir);
1198}
1199
1200/*
1201 * Delete a single file.
1202 */
1203void
1204delete(int argc, char *argv[])
1205{
1206
1207	if (argc == 0 || argc > 2 ||
1208	    (argc == 1 && !another(&argc, &argv, "remote-file"))) {
1209		UPRINTF("usage: %s remote-file\n", argv[0]);
1210		code = -1;
1211		return;
1212	}
1213	if (command("DELE %s", argv[1]) == COMPLETE)
1214		dirchange = 1;
1215}
1216
1217/*
1218 * Delete multiple files.
1219 */
1220void
1221mdelete(int argc, char *argv[])
1222{
1223	sigfunc oldintr;
1224	int ointer;
1225	char *cp;
1226
1227	if (argc == 0 ||
1228	    (argc == 1 && !another(&argc, &argv, "remote-files"))) {
1229		UPRINTF("usage: %s [remote-files]\n", argv[0]);
1230		code = -1;
1231		return;
1232	}
1233	mflag = 1;
1234	oldintr = xsignal(SIGINT, mintr);
1235	if (sigsetjmp(jabort, 1))
1236		mabort(argv[0]);
1237	while ((cp = remglob(argv, 0, NULL)) != NULL) {
1238		if (*cp == '\0') {
1239			mflag = 0;
1240			continue;
1241		}
1242		if (mflag && confirm(argv[0], cp)) {
1243			if (command("DELE %s", cp) == COMPLETE)
1244				dirchange = 1;
1245			if (!mflag && fromatty) {
1246				ointer = interactive;
1247				interactive = 1;
1248				if (confirm(argv[0], NULL)) {
1249					mflag++;
1250				}
1251				interactive = ointer;
1252			}
1253		}
1254	}
1255	(void)xsignal(SIGINT, oldintr);
1256	mflag = 0;
1257}
1258
1259/*
1260 * Rename a remote file.
1261 */
1262void
1263renamefile(int argc, char *argv[])
1264{
1265
1266	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "from-name")))
1267		goto usage;
1268	if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
1269 usage:
1270		UPRINTF("usage: %s from-name to-name\n", argv[0]);
1271		code = -1;
1272		return;
1273	}
1274	if (command("RNFR %s", argv[1]) == CONTINUE &&
1275	    command("RNTO %s", argv[2]) == COMPLETE)
1276		dirchange = 1;
1277}
1278
1279/*
1280 * Get a directory listing of remote files.
1281 * Supports being invoked as:
1282 *	cmd		runs
1283 *	---		----
1284 *	dir, ls		LIST
1285 *	mlsd		MLSD
1286 *	nlist		NLST
1287 *	pdir, pls	LIST |$PAGER
1288 *	pmlsd		MLSD |$PAGER
1289 */
1290void
1291ls(int argc, char *argv[])
1292{
1293	const char *cmd;
1294	char *remdir, *locbuf;
1295	const char *locfile;
1296	int pagecmd, mlsdcmd;
1297
1298	remdir = NULL;
1299	locbuf = NULL;
1300	locfile = "-";
1301	pagecmd = mlsdcmd = 0;
1302			/*
1303			 * the only commands that start with `p' are
1304			 * the `pager' versions.
1305			 */
1306	if (argv[0][0] == 'p')
1307		pagecmd = 1;
1308	if (strcmp(argv[0] + pagecmd , "mlsd") == 0) {
1309		if (! features[FEAT_MLST]) {
1310			fprintf(ttyout,
1311			   "MLSD is not supported by the remote server.\n");
1312			return;
1313		}
1314		mlsdcmd = 1;
1315	}
1316	if (argc == 0)
1317		goto usage;
1318
1319	if (mlsdcmd)
1320		cmd = "MLSD";
1321	else if (strcmp(argv[0] + pagecmd, "nlist") == 0)
1322		cmd = "NLST";
1323	else
1324		cmd = "LIST";
1325
1326	if (argc > 1)
1327		remdir = argv[1];
1328	if (argc > 2)
1329		locfile = argv[2];
1330	if (argc > 3 || ((pagecmd | mlsdcmd) && argc > 2)) {
1331 usage:
1332		if (pagecmd || mlsdcmd)
1333			UPRINTF("usage: %s [remote-path]\n", argv[0]);
1334		else
1335			UPRINTF("usage: %s [remote-path [local-file]]\n",
1336			    argv[0]);
1337		code = -1;
1338		goto freels;
1339	}
1340
1341	if (pagecmd) {
1342		const char *p;
1343		size_t len;
1344
1345		p = getoptionvalue("pager");
1346		if (EMPTYSTRING(p))
1347			p = DEFAULTPAGER;
1348		len = strlen(p) + 2;
1349		locbuf = ftp_malloc(len);
1350		locbuf[0] = '|';
1351		(void)strlcpy(locbuf + 1, p, len - 1);
1352		locfile = locbuf;
1353	} else if ((strcmp(locfile, "-") != 0) && *locfile != '|') {
1354		if ((locbuf = globulize(locfile)) == NULL ||
1355		    !confirm("output to local-file:", locbuf)) {
1356			code = -1;
1357			goto freels;
1358		}
1359		locfile = locbuf;
1360	}
1361	recvrequest(cmd, locfile, remdir, "w", 0, 0);
1362 freels:
1363	if (locbuf)
1364		(void)free(locbuf);
1365}
1366
1367/*
1368 * Get a directory listing of multiple remote files.
1369 */
1370void
1371mls(int argc, char *argv[])
1372{
1373	sigfunc oldintr;
1374	int ointer, i;
1375	int volatile dolist;
1376	char * volatile dest, *odest;
1377	const char *lmode;
1378
1379	if (argc == 0)
1380		goto usage;
1381	if (argc < 2 && !another(&argc, &argv, "remote-files"))
1382		goto usage;
1383	if (argc < 3 && !another(&argc, &argv, "local-file")) {
1384 usage:
1385		UPRINTF("usage: %s remote-files local-file\n", argv[0]);
1386		code = -1;
1387		return;
1388	}
1389	odest = dest = argv[argc - 1];
1390	argv[argc - 1] = NULL;
1391	if (strcmp(dest, "-") && *dest != '|')
1392		if (((dest = globulize(dest)) == NULL) ||
1393		    !confirm("output to local-file:", dest)) {
1394			code = -1;
1395			return;
1396	}
1397	dolist = strcmp(argv[0], "mls");
1398	mflag = 1;
1399	oldintr = xsignal(SIGINT, mintr);
1400	if (sigsetjmp(jabort, 1))
1401		mabort(argv[0]);
1402	for (i = 1; mflag && i < argc-1 && connected; i++) {
1403		lmode = (i == 1) ? "w" : "a";
1404		recvrequest(dolist ? "LIST" : "NLST", dest, argv[i], lmode,
1405		    0, 0);
1406		if (!mflag && fromatty) {
1407			ointer = interactive;
1408			interactive = 1;
1409			if (confirm(argv[0], NULL)) {
1410				mflag++;
1411			}
1412			interactive = ointer;
1413		}
1414	}
1415	(void)xsignal(SIGINT, oldintr);
1416	mflag = 0;
1417	if (dest != odest)			/* free up after globulize() */
1418		free(dest);
1419}
1420
1421/*
1422 * Do a shell escape
1423 */
1424/*ARGSUSED*/
1425void
1426shell(int argc, char *argv[])
1427{
1428	pid_t pid;
1429	sigfunc oldintr;
1430	char shellnam[MAXPATHLEN];
1431	const char *shellp, *namep;
1432	int wait_status;
1433
1434	if (argc == 0) {
1435		UPRINTF("usage: %s [command [args]]\n", argv[0]);
1436		code = -1;
1437		return;
1438	}
1439	oldintr = xsignal(SIGINT, SIG_IGN);
1440	if ((pid = fork()) == 0) {
1441		(void)closefrom(3);
1442		(void)xsignal(SIGINT, SIG_DFL);
1443		shellp = getenv("SHELL");
1444		if (shellp == NULL)
1445			shellp = _PATH_BSHELL;
1446		namep = strrchr(shellp, '/');
1447		if (namep == NULL)
1448			namep = shellp;
1449		else
1450			namep++;
1451		(void)strlcpy(shellnam, namep, sizeof(shellnam));
1452		if (ftp_debug) {
1453			fputs(shellp, ttyout);
1454			putc('\n', ttyout);
1455		}
1456		if (argc > 1) {
1457			execl(shellp, shellnam, "-c", altarg, (char *)0);
1458		}
1459		else {
1460			execl(shellp, shellnam, (char *)0);
1461		}
1462		warn("Can't execute `%s'", shellp);
1463		code = -1;
1464		exit(1);
1465	}
1466	if (pid > 0)
1467		while (wait(&wait_status) != pid)
1468			;
1469	(void)xsignal(SIGINT, oldintr);
1470	if (pid == -1) {
1471		warn("Can't fork a subshell; try again later");
1472		code = -1;
1473	} else
1474		code = 0;
1475}
1476
1477/*
1478 * Send new user information (re-login)
1479 */
1480void
1481user(int argc, char *argv[])
1482{
1483	char *password;
1484	char emptypass[] = "";
1485	int n, aflag = 0;
1486
1487	if (argc == 0)
1488		goto usage;
1489	if (argc < 2)
1490		(void)another(&argc, &argv, "username");
1491	if (argc < 2 || argc > 4) {
1492 usage:
1493		UPRINTF("usage: %s username [password [account]]\n",
1494		    argv[0]);
1495		code = -1;
1496		return;
1497	}
1498	n = command("USER %s", argv[1]);
1499	if (n == CONTINUE) {
1500		if (argc < 3) {
1501			password = getpass("Password: ");
1502			if (password == NULL)
1503				password = emptypass;
1504		} else {
1505			password = argv[2];
1506		}
1507		n = command("PASS %s", password);
1508		memset(password, 0, strlen(password));
1509	}
1510	if (n == CONTINUE) {
1511		aflag++;
1512		if (argc < 4) {
1513			password = getpass("Account: ");
1514			if (password == NULL)
1515				password = emptypass;
1516		} else {
1517			password = argv[3];
1518		}
1519		n = command("ACCT %s", password);
1520		memset(password, 0, strlen(password));
1521	}
1522	if (n != COMPLETE) {
1523		fputs("Login failed.\n", ttyout);
1524		return;
1525	}
1526	if (!aflag && argc == 4) {
1527		password = argv[3];
1528		(void)command("ACCT %s", password);
1529		memset(password, 0, strlen(password));
1530	}
1531	connected = -1;
1532	getremoteinfo();
1533}
1534
1535/*
1536 * Print working directory on remote machine.
1537 */
1538/*VARARGS*/
1539void
1540pwd(int argc, char *argv[])
1541{
1542
1543	code = -1;
1544	if (argc != 1) {
1545		UPRINTF("usage: %s\n", argv[0]);
1546		return;
1547	}
1548	if (! remotecwd[0])
1549		updateremotecwd();
1550	if (! remotecwd[0])
1551		fprintf(ttyout, "Unable to determine remote directory\n");
1552	else {
1553		fprintf(ttyout, "Remote directory: %s\n", remotecwd);
1554		code = 0;
1555	}
1556}
1557
1558/*
1559 * Print working directory on local machine.
1560 */
1561void
1562lpwd(int argc, char *argv[])
1563{
1564
1565	code = -1;
1566	if (argc != 1) {
1567		UPRINTF("usage: %s\n", argv[0]);
1568		return;
1569	}
1570	if (! localcwd[0])
1571		updatelocalcwd();
1572	if (! localcwd[0])
1573		fprintf(ttyout, "Unable to determine local directory\n");
1574	else {
1575		fprintf(ttyout, "Local directory: %s\n", localcwd);
1576		code = 0;
1577	}
1578}
1579
1580/*
1581 * Make a directory.
1582 */
1583void
1584makedir(int argc, char *argv[])
1585{
1586	int r;
1587
1588	if (argc == 0 || argc > 2 ||
1589	    (argc == 1 && !another(&argc, &argv, "directory-name"))) {
1590		UPRINTF("usage: %s directory-name\n", argv[0]);
1591		code = -1;
1592		return;
1593	}
1594	r = command("MKD %s", argv[1]);
1595	if (r == ERROR && code == 500) {
1596		if (verbose)
1597			fputs("MKD command not recognized, trying XMKD.\n",
1598			    ttyout);
1599		r = command("XMKD %s", argv[1]);
1600	}
1601	if (r == COMPLETE)
1602		dirchange = 1;
1603}
1604
1605/*
1606 * Remove a directory.
1607 */
1608void
1609removedir(int argc, char *argv[])
1610{
1611	int r;
1612
1613	if (argc == 0 || argc > 2 ||
1614	    (argc == 1 && !another(&argc, &argv, "directory-name"))) {
1615		UPRINTF("usage: %s directory-name\n", argv[0]);
1616		code = -1;
1617		return;
1618	}
1619	r = command("RMD %s", argv[1]);
1620	if (r == ERROR && code == 500) {
1621		if (verbose)
1622			fputs("RMD command not recognized, trying XRMD.\n",
1623			    ttyout);
1624		r = command("XRMD %s", argv[1]);
1625	}
1626	if (r == COMPLETE)
1627		dirchange = 1;
1628}
1629
1630/*
1631 * Send a line, verbatim, to the remote machine.
1632 */
1633void
1634quote(int argc, char *argv[])
1635{
1636
1637	if (argc == 0 ||
1638	    (argc == 1 && !another(&argc, &argv, "command line to send"))) {
1639		UPRINTF("usage: %s line-to-send\n", argv[0]);
1640		code = -1;
1641		return;
1642	}
1643	quote1("", argc, argv);
1644}
1645
1646/*
1647 * Send a SITE command to the remote machine.  The line
1648 * is sent verbatim to the remote machine, except that the
1649 * word "SITE" is added at the front.
1650 */
1651void
1652site(int argc, char *argv[])
1653{
1654
1655	if (argc == 0 ||
1656	    (argc == 1 && !another(&argc, &argv, "arguments to SITE command"))){
1657		UPRINTF("usage: %s line-to-send\n", argv[0]);
1658		code = -1;
1659		return;
1660	}
1661	quote1("SITE ", argc, argv);
1662}
1663
1664/*
1665 * Turn argv[1..argc) into a space-separated string, then prepend initial text.
1666 * Send the result as a one-line command and get response.
1667 */
1668void
1669quote1(const char *initial, int argc, char *argv[])
1670{
1671	int i;
1672	char buf[BUFSIZ];		/* must be >= sizeof(line) */
1673
1674	(void)strlcpy(buf, initial, sizeof(buf));
1675	for (i = 1; i < argc; i++) {
1676		(void)strlcat(buf, argv[i], sizeof(buf));
1677		if (i < (argc - 1))
1678			(void)strlcat(buf, " ", sizeof(buf));
1679	}
1680	if (command("%s", buf) == PRELIM) {
1681		while (getreply(0) == PRELIM)
1682			continue;
1683	}
1684	dirchange = 1;
1685}
1686
1687void
1688do_chmod(int argc, char *argv[])
1689{
1690
1691	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "mode")))
1692		goto usage;
1693	if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
1694 usage:
1695		UPRINTF("usage: %s mode remote-file\n", argv[0]);
1696		code = -1;
1697		return;
1698	}
1699	(void)command("SITE CHMOD %s %s", argv[1], argv[2]);
1700}
1701
1702#define COMMAND_1ARG(argc, argv, cmd)			\
1703	if (argc == 1)					\
1704		command(cmd);				\
1705	else						\
1706		command(cmd " %s", argv[1])
1707
1708void
1709do_umask(int argc, char *argv[])
1710{
1711	int oldverbose = verbose;
1712
1713	if (argc == 0) {
1714		UPRINTF("usage: %s [umask]\n", argv[0]);
1715		code = -1;
1716		return;
1717	}
1718	verbose = 1;
1719	COMMAND_1ARG(argc, argv, "SITE UMASK");
1720	verbose = oldverbose;
1721}
1722
1723void
1724idlecmd(int argc, char *argv[])
1725{
1726	int oldverbose = verbose;
1727
1728	if (argc < 1 || argc > 2) {
1729		UPRINTF("usage: %s [seconds]\n", argv[0]);
1730		code = -1;
1731		return;
1732	}
1733	verbose = 1;
1734	COMMAND_1ARG(argc, argv, "SITE IDLE");
1735	verbose = oldverbose;
1736}
1737
1738/*
1739 * Ask the other side for help.
1740 */
1741void
1742rmthelp(int argc, char *argv[])
1743{
1744	int oldverbose = verbose;
1745
1746	if (argc == 0) {
1747		UPRINTF("usage: %s\n", argv[0]);
1748		code = -1;
1749		return;
1750	}
1751	verbose = 1;
1752	COMMAND_1ARG(argc, argv, "HELP");
1753	verbose = oldverbose;
1754}
1755
1756/*
1757 * Terminate session and exit.
1758 * May be called with 0, NULL.
1759 */
1760/*VARARGS*/
1761void
1762quit(int argc, char *argv[])
1763{
1764
1765			/* this may be called with argc == 0, argv == NULL */
1766	if (argc == 0 && argv != NULL) {
1767		UPRINTF("usage: %s\n", argv[0]);
1768		code = -1;
1769		return;
1770	}
1771	if (connected)
1772		disconnect(0, NULL);
1773	pswitch(1);
1774	if (connected)
1775		disconnect(0, NULL);
1776	exit(0);
1777}
1778
1779/*
1780 * Terminate session, but don't exit.
1781 * May be called with 0, NULL.
1782 */
1783void
1784disconnect(int argc, char *argv[])
1785{
1786
1787			/* this may be called with argc == 0, argv == NULL */
1788	if (argc == 0 && argv != NULL) {
1789		UPRINTF("usage: %s\n", argv[0]);
1790		code = -1;
1791		return;
1792	}
1793	if (!connected)
1794		return;
1795	(void)command("QUIT");
1796	cleanuppeer();
1797}
1798
1799void
1800account(int argc, char *argv[])
1801{
1802	char *ap;
1803	char emptypass[] = "";
1804
1805	if (argc == 0 || argc > 2) {
1806		UPRINTF("usage: %s [password]\n", argv[0]);
1807		code = -1;
1808		return;
1809	}
1810	else if (argc == 2)
1811		ap = argv[1];
1812	else {
1813		ap = getpass("Account:");
1814		if (ap == NULL)
1815			ap = emptypass;
1816	}
1817	(void)command("ACCT %s", ap);
1818	memset(ap, 0, strlen(ap));
1819}
1820
1821sigjmp_buf abortprox;
1822
1823void
1824proxabort(int notused)
1825{
1826
1827	sigint_raised = 1;
1828	alarmtimer(0);
1829	if (!proxy) {
1830		pswitch(1);
1831	}
1832	if (connected) {
1833		proxflag = 1;
1834	}
1835	else {
1836		proxflag = 0;
1837	}
1838	pswitch(0);
1839	siglongjmp(abortprox, 1);
1840}
1841
1842void
1843doproxy(int argc, char *argv[])
1844{
1845	struct cmd *c;
1846	int cmdpos;
1847	sigfunc oldintr;
1848	char cmdbuf[MAX_C_NAME];
1849
1850	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "command"))) {
1851		UPRINTF("usage: %s command\n", argv[0]);
1852		code = -1;
1853		return;
1854	}
1855	c = getcmd(argv[1]);
1856	if (c == (struct cmd *) -1) {
1857		fputs("?Ambiguous command.\n", ttyout);
1858		code = -1;
1859		return;
1860	}
1861	if (c == 0) {
1862		fputs("?Invalid command.\n", ttyout);
1863		code = -1;
1864		return;
1865	}
1866	if (!c->c_proxy) {
1867		fputs("?Invalid proxy command.\n", ttyout);
1868		code = -1;
1869		return;
1870	}
1871	if (sigsetjmp(abortprox, 1)) {
1872		code = -1;
1873		return;
1874	}
1875	oldintr = xsignal(SIGINT, proxabort);
1876	pswitch(1);
1877	if (c->c_conn && !connected) {
1878		fputs("Not connected.\n", ttyout);
1879		pswitch(0);
1880		(void)xsignal(SIGINT, oldintr);
1881		code = -1;
1882		return;
1883	}
1884	cmdpos = strcspn(line, " \t");
1885	if (cmdpos > 0)		/* remove leading "proxy " from input buffer */
1886		memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
1887	(void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf));
1888	argv[1] = cmdbuf;
1889	(*c->c_handler)(argc-1, argv+1);
1890	if (connected) {
1891		proxflag = 1;
1892	}
1893	else {
1894		proxflag = 0;
1895	}
1896	pswitch(0);
1897	(void)xsignal(SIGINT, oldintr);
1898}
1899
1900void
1901setcase(int argc, char *argv[])
1902{
1903
1904	code = togglevar(argc, argv, &mcase, "Case mapping");
1905}
1906
1907/*
1908 * convert the given name to lower case if it's all upper case, into
1909 * a static buffer which is returned to the caller
1910 */
1911static const char *
1912docase(char *dst, size_t dlen, const char *src)
1913{
1914	size_t i;
1915	int dochange = 1;
1916
1917	for (i = 0; src[i] != '\0' && i < dlen - 1; i++) {
1918		dst[i] = src[i];
1919		if (islower((unsigned char)dst[i]))
1920			dochange = 0;
1921	}
1922	dst[i] = '\0';
1923
1924	if (dochange) {
1925		for (i = 0; dst[i] != '\0'; i++)
1926			if (isupper((unsigned char)dst[i]))
1927				dst[i] = tolower((unsigned char)dst[i]);
1928	}
1929	return dst;
1930}
1931
1932void
1933setcr(int argc, char *argv[])
1934{
1935
1936	code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
1937}
1938
1939void
1940setntrans(int argc, char *argv[])
1941{
1942
1943	if (argc == 0 || argc > 3) {
1944		UPRINTF("usage: %s [inchars [outchars]]\n", argv[0]);
1945		code = -1;
1946		return;
1947	}
1948	if (argc == 1) {
1949		ntflag = 0;
1950		fputs("Ntrans off.\n", ttyout);
1951		code = ntflag;
1952		return;
1953	}
1954	ntflag++;
1955	code = ntflag;
1956	(void)strlcpy(ntin, argv[1], sizeof(ntin));
1957	if (argc == 2) {
1958		ntout[0] = '\0';
1959		return;
1960	}
1961	(void)strlcpy(ntout, argv[2], sizeof(ntout));
1962}
1963
1964static const char *
1965dotrans(char *dst, size_t dlen, const char *src)
1966{
1967	const char *cp1;
1968	char *cp2 = dst;
1969	size_t i, ostop;
1970
1971	for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
1972		continue;
1973	for (cp1 = src; *cp1; cp1++) {
1974		int found = 0;
1975		for (i = 0; *(ntin + i) && i < 16; i++) {
1976			if (*cp1 == *(ntin + i)) {
1977				found++;
1978				if (i < ostop) {
1979					*cp2++ = *(ntout + i);
1980					if (cp2 - dst >= (ptrdiff_t)(dlen - 1))
1981						goto out;
1982				}
1983				break;
1984			}
1985		}
1986		if (!found) {
1987			*cp2++ = *cp1;
1988		}
1989	}
1990out:
1991	*cp2 = '\0';
1992	return dst;
1993}
1994
1995void
1996setnmap(int argc, char *argv[])
1997{
1998	char *cp;
1999
2000	if (argc == 1) {
2001		mapflag = 0;
2002		fputs("Nmap off.\n", ttyout);
2003		code = mapflag;
2004		return;
2005	}
2006	if (argc == 0 ||
2007	    (argc < 3 && !another(&argc, &argv, "mapout")) || argc > 3) {
2008		UPRINTF("usage: %s [mapin mapout]\n", argv[0]);
2009		code = -1;
2010		return;
2011	}
2012	mapflag = 1;
2013	code = 1;
2014	cp = strchr(altarg, ' ');
2015	if (proxy) {
2016		while(*++cp == ' ')
2017			continue;
2018		altarg = cp;
2019		cp = strchr(altarg, ' ');
2020	}
2021	*cp = '\0';
2022	(void)strlcpy(mapin, altarg, MAXPATHLEN);
2023	while (*++cp == ' ')
2024		continue;
2025	(void)strlcpy(mapout, cp, MAXPATHLEN);
2026}
2027
2028static const char *
2029domap(char *dst, size_t dlen, const char *src)
2030{
2031	const char *cp1 = src;
2032	char *cp2 = mapin;
2033	const char *tp[9], *te[9];
2034	int i, toks[9], toknum = 0, match = 1;
2035
2036	for (i=0; i < 9; ++i) {
2037		toks[i] = 0;
2038	}
2039	while (match && *cp1 && *cp2) {
2040		switch (*cp2) {
2041			case '\\':
2042				if (*++cp2 != *cp1) {
2043					match = 0;
2044				}
2045				break;
2046			case '$':
2047				if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
2048					if (*cp1 != *(++cp2+1)) {
2049						toks[toknum = *cp2 - '1']++;
2050						tp[toknum] = cp1;
2051						while (*++cp1 && *(cp2+1)
2052							!= *cp1);
2053						te[toknum] = cp1;
2054					}
2055					cp2++;
2056					break;
2057				}
2058				/* FALLTHROUGH */
2059			default:
2060				if (*cp2 != *cp1) {
2061					match = 0;
2062				}
2063				break;
2064		}
2065		if (match && *cp1) {
2066			cp1++;
2067		}
2068		if (match && *cp2) {
2069			cp2++;
2070		}
2071	}
2072	if (!match && *cp1) /* last token mismatch */
2073	{
2074		toks[toknum] = 0;
2075	}
2076	cp2 = dst;
2077	*cp2 = '\0';
2078	cp1 = mapout;
2079	while (*cp1) {
2080		match = 0;
2081		switch (*cp1) {
2082			case '\\':
2083				if (*(cp1 + 1)) {
2084					*cp2++ = *++cp1;
2085				}
2086				break;
2087			case '[':
2088LOOP:
2089				if (*++cp1 == '$' &&
2090				    isdigit((unsigned char)*(cp1+1))) {
2091					if (*++cp1 == '0') {
2092						const char *cp3 = src;
2093
2094						while (*cp3) {
2095							*cp2++ = *cp3++;
2096						}
2097						match = 1;
2098					}
2099					else if (toks[toknum = *cp1 - '1']) {
2100						const char *cp3 = tp[toknum];
2101
2102						while (cp3 != te[toknum]) {
2103							*cp2++ = *cp3++;
2104						}
2105						match = 1;
2106					}
2107				}
2108				else {
2109					while (*cp1 && *cp1 != ',' &&
2110					    *cp1 != ']') {
2111						if (*cp1 == '\\') {
2112							cp1++;
2113						}
2114						else if (*cp1 == '$' &&
2115						    isdigit((unsigned char)*(cp1+1))) {
2116							if (*++cp1 == '0') {
2117							   const char *cp3 = src;
2118
2119							   while (*cp3) {
2120								*cp2++ = *cp3++;
2121							   }
2122							}
2123							else if (toks[toknum =
2124							    *cp1 - '1']) {
2125							   const char *cp3=tp[toknum];
2126
2127							   while (cp3 !=
2128								  te[toknum]) {
2129								*cp2++ = *cp3++;
2130							   }
2131							}
2132						}
2133						else if (*cp1) {
2134							*cp2++ = *cp1++;
2135						}
2136					}
2137					if (!*cp1) {
2138						fputs(
2139						"nmap: unbalanced brackets.\n",
2140						    ttyout);
2141						return (src);
2142					}
2143					match = 1;
2144					cp1--;
2145				}
2146				if (match) {
2147					while (*++cp1 && *cp1 != ']') {
2148					      if (*cp1 == '\\' && *(cp1 + 1)) {
2149							cp1++;
2150					      }
2151					}
2152					if (!*cp1) {
2153						fputs(
2154						"nmap: unbalanced brackets.\n",
2155						    ttyout);
2156						return (src);
2157					}
2158					break;
2159				}
2160				switch (*++cp1) {
2161					case ',':
2162						goto LOOP;
2163					case ']':
2164						break;
2165					default:
2166						cp1--;
2167						goto LOOP;
2168				}
2169				break;
2170			case '$':
2171				if (isdigit((unsigned char)*(cp1 + 1))) {
2172					if (*++cp1 == '0') {
2173						const char *cp3 = src;
2174
2175						while (*cp3) {
2176							*cp2++ = *cp3++;
2177						}
2178					}
2179					else if (toks[toknum = *cp1 - '1']) {
2180						const char *cp3 = tp[toknum];
2181
2182						while (cp3 != te[toknum]) {
2183							*cp2++ = *cp3++;
2184						}
2185					}
2186					break;
2187				}
2188				/* intentional drop through */
2189			default:
2190				*cp2++ = *cp1;
2191				break;
2192		}
2193		cp1++;
2194	}
2195	*cp2 = '\0';
2196	return *dst ? dst : src;
2197}
2198
2199void
2200setpassive(int argc, char *argv[])
2201{
2202
2203	if (argc == 1) {
2204		passivemode = !passivemode;
2205		activefallback = passivemode;
2206	} else if (argc != 2) {
2207 passiveusage:
2208		UPRINTF("usage: %s [ on | off | auto ]\n", argv[0]);
2209		code = -1;
2210		return;
2211	} else if (strcasecmp(argv[1], "on") == 0) {
2212		passivemode = 1;
2213		activefallback = 0;
2214	} else if (strcasecmp(argv[1], "off") == 0) {
2215		passivemode = 0;
2216		activefallback = 0;
2217	} else if (strcasecmp(argv[1], "auto") == 0) {
2218		passivemode = 1;
2219		activefallback = 1;
2220	} else
2221		goto passiveusage;
2222	fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
2223	    onoff(passivemode), onoff(activefallback));
2224	code = passivemode;
2225}
2226
2227
2228void
2229setepsv4(int argc, char *argv[])
2230{
2231	code = togglevar(argc, argv, &epsv4,
2232	    verbose ? "EPSV/EPRT on IPv4" : NULL);
2233	epsv4bad = 0;
2234}
2235
2236void
2237setepsv6(int argc, char *argv[])
2238{
2239	code = togglevar(argc, argv, &epsv6,
2240	    verbose ? "EPSV/EPRT on IPv6" : NULL);
2241	epsv6bad = 0;
2242}
2243
2244void
2245setepsv(int argc, char*argv[])
2246{
2247	setepsv4(argc,argv);
2248	setepsv6(argc,argv);
2249}
2250
2251void
2252setsunique(int argc, char *argv[])
2253{
2254
2255	code = togglevar(argc, argv, &sunique, "Store unique");
2256}
2257
2258void
2259setrunique(int argc, char *argv[])
2260{
2261
2262	code = togglevar(argc, argv, &runique, "Receive unique");
2263}
2264
2265int
2266parserate(int argc, char *argv[], int cmdlineopt)
2267{
2268	int dir, max, incr, showonly;
2269	sigfunc oldusr1, oldusr2;
2270
2271	if (argc > 4 || (argc < (cmdlineopt ? 3 : 2))) {
2272 usage:
2273		if (cmdlineopt)
2274			UPRINTF(
2275	"usage: %s (all|get|put),maximum-bytes[,increment-bytes]]\n",
2276			    argv[0]);
2277		else
2278			UPRINTF(
2279	"usage: %s (all|get|put) [maximum-bytes [increment-bytes]]\n",
2280			    argv[0]);
2281		return -1;
2282	}
2283	dir = max = incr = showonly = 0;
2284#define	RATE_GET	1
2285#define	RATE_PUT	2
2286#define	RATE_ALL	(RATE_GET | RATE_PUT)
2287
2288	if (strcasecmp(argv[1], "all") == 0)
2289		dir = RATE_ALL;
2290	else if (strcasecmp(argv[1], "get") == 0)
2291		dir = RATE_GET;
2292	else if (strcasecmp(argv[1], "put") == 0)
2293		dir = RATE_PUT;
2294	else
2295		goto usage;
2296
2297	if (argc >= 3) {
2298		if ((max = strsuftoi(argv[2])) < 0)
2299			goto usage;
2300	} else
2301		showonly = 1;
2302
2303	if (argc == 4) {
2304		if ((incr = strsuftoi(argv[3])) <= 0)
2305			goto usage;
2306	} else
2307		incr = DEFAULTINCR;
2308
2309	oldusr1 = xsignal(SIGUSR1, SIG_IGN);
2310	oldusr2 = xsignal(SIGUSR2, SIG_IGN);
2311	if (dir & RATE_GET) {
2312		if (!showonly) {
2313			rate_get = max;
2314			rate_get_incr = incr;
2315		}
2316		if (!cmdlineopt || verbose)
2317			fprintf(ttyout,
2318		"Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
2319			    onoff(rate_get), rate_get, rate_get_incr);
2320	}
2321	if (dir & RATE_PUT) {
2322		if (!showonly) {
2323			rate_put = max;
2324			rate_put_incr = incr;
2325		}
2326		if (!cmdlineopt || verbose)
2327			fprintf(ttyout,
2328		"Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
2329			    onoff(rate_put), rate_put, rate_put_incr);
2330	}
2331	(void)xsignal(SIGUSR1, oldusr1);
2332	(void)xsignal(SIGUSR2, oldusr2);
2333	return 0;
2334}
2335
2336void
2337setrate(int argc, char *argv[])
2338{
2339
2340	code = parserate(argc, argv, 0);
2341}
2342
2343/* change directory to parent directory */
2344void
2345cdup(int argc, char *argv[])
2346{
2347	int r;
2348
2349	if (argc == 0) {
2350		UPRINTF("usage: %s\n", argv[0]);
2351		code = -1;
2352		return;
2353	}
2354	r = command("CDUP");
2355	if (r == ERROR && code == 500) {
2356		if (verbose)
2357			fputs("CDUP command not recognized, trying XCUP.\n",
2358			    ttyout);
2359		r = command("XCUP");
2360	}
2361	if (r == COMPLETE) {
2362		dirchange = 1;
2363		updateremotecwd();
2364	}
2365}
2366
2367/*
2368 * Restart transfer at specific point
2369 */
2370void
2371restart(int argc, char *argv[])
2372{
2373
2374	if (argc == 0 || argc > 2) {
2375		UPRINTF("usage: %s [restart-point]\n", argv[0]);
2376		code = -1;
2377		return;
2378	}
2379	if (! features[FEAT_REST_STREAM]) {
2380		fprintf(ttyout,
2381		    "Restart is not supported by the remote server.\n");
2382		return;
2383	}
2384	if (argc == 2) {
2385		off_t rp;
2386		char *ep;
2387
2388		rp = STRTOLL(argv[1], &ep, 10);
2389		if (rp < 0 || *ep != '\0')
2390			fprintf(ttyout, "restart: Invalid offset `%s'\n",
2391			    argv[1]);
2392		else
2393			restart_point = rp;
2394	}
2395	if (restart_point == 0)
2396		fputs("No restart point defined.\n", ttyout);
2397	else
2398		fprintf(ttyout,
2399		    "Restarting at " LLF " for next get, put or append\n",
2400		    (LLT)restart_point);
2401}
2402
2403/*
2404 * Show remote system type
2405 */
2406void
2407syst(int argc, char *argv[])
2408{
2409	int oldverbose = verbose;
2410
2411	if (argc == 0) {
2412		UPRINTF("usage: %s\n", argv[0]);
2413		code = -1;
2414		return;
2415	}
2416	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2417	(void)command("SYST");
2418	verbose = oldverbose;
2419}
2420
2421void
2422macdef(int argc, char *argv[])
2423{
2424	char *tmp;
2425	int c;
2426
2427	if (argc == 0)
2428		goto usage;
2429	if (macnum == 16) {
2430		fputs("Limit of 16 macros have already been defined.\n",
2431		    ttyout);
2432		code = -1;
2433		return;
2434	}
2435	if ((argc < 2 && !another(&argc, &argv, "macro name")) || argc > 2) {
2436 usage:
2437		UPRINTF("usage: %s macro_name\n", argv[0]);
2438		code = -1;
2439		return;
2440	}
2441	if (interactive)
2442		fputs(
2443		"Enter macro line by line, terminating it with a null line.\n",
2444		    ttyout);
2445	(void)strlcpy(macros[macnum].mac_name, argv[1],
2446	    sizeof(macros[macnum].mac_name));
2447	if (macnum == 0)
2448		macros[macnum].mac_start = macbuf;
2449	else
2450		macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
2451	tmp = macros[macnum].mac_start;
2452	while (tmp != macbuf+4096) {
2453		if ((c = getchar()) == EOF) {
2454			fputs("macdef: end of file encountered.\n", ttyout);
2455			code = -1;
2456			return;
2457		}
2458		if ((*tmp = c) == '\n') {
2459			if (tmp == macros[macnum].mac_start) {
2460				macros[macnum++].mac_end = tmp;
2461				code = 0;
2462				return;
2463			}
2464			if (*(tmp-1) == '\0') {
2465				macros[macnum++].mac_end = tmp - 1;
2466				code = 0;
2467				return;
2468			}
2469			*tmp = '\0';
2470		}
2471		tmp++;
2472	}
2473	while (1) {
2474		while ((c = getchar()) != '\n' && c != EOF)
2475			/* LOOP */;
2476		if (c == EOF || getchar() == '\n') {
2477			fputs("Macro not defined - 4K buffer exceeded.\n",
2478			    ttyout);
2479			code = -1;
2480			return;
2481		}
2482	}
2483}
2484
2485/*
2486 * Get size of file on remote machine
2487 */
2488void
2489sizecmd(int argc, char *argv[])
2490{
2491	off_t size;
2492
2493	if (argc == 0 || argc > 2 ||
2494	    (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2495		UPRINTF("usage: %s remote-file\n", argv[0]);
2496		code = -1;
2497		return;
2498	}
2499	size = remotesize(argv[1], 1);
2500	if (size != -1)
2501		fprintf(ttyout,
2502		    "%s\t" LLF "\n", argv[1], (LLT)size);
2503	code = (size > 0);
2504}
2505
2506/*
2507 * Get last modification time of file on remote machine
2508 */
2509void
2510modtime(int argc, char *argv[])
2511{
2512	time_t mtime;
2513
2514	if (argc == 0 || argc > 2 ||
2515	    (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2516		UPRINTF("usage: %s remote-file\n", argv[0]);
2517		code = -1;
2518		return;
2519	}
2520	mtime = remotemodtime(argv[1], 1);
2521	if (mtime != -1)
2522		fprintf(ttyout, "%s\t%s", argv[1],
2523		    rfc2822time(localtime(&mtime)));
2524	code = (mtime > 0);
2525}
2526
2527/*
2528 * Show status on remote machine
2529 */
2530void
2531rmtstatus(int argc, char *argv[])
2532{
2533
2534	if (argc == 0) {
2535		UPRINTF("usage: %s [remote-file]\n", argv[0]);
2536		code = -1;
2537		return;
2538	}
2539	COMMAND_1ARG(argc, argv, "STAT");
2540}
2541
2542/*
2543 * Get file if modtime is more recent than current file
2544 */
2545void
2546newer(int argc, char *argv[])
2547{
2548
2549	if (getit(argc, argv, -1, "w"))
2550		fprintf(ttyout,
2551		    "Local file \"%s\" is newer than remote file \"%s\".\n",
2552		    argv[2], argv[1]);
2553}
2554
2555/*
2556 * Display one local file through $PAGER.
2557 */
2558void
2559lpage(int argc, char *argv[])
2560{
2561	size_t len;
2562	const char *p;
2563	char *pager, *locfile;
2564
2565	if (argc == 0 || argc > 2 ||
2566	    (argc == 1 && !another(&argc, &argv, "local-file"))) {
2567		UPRINTF("usage: %s local-file\n", argv[0]);
2568		code = -1;
2569		return;
2570	}
2571	if ((locfile = globulize(argv[1])) == NULL) {
2572		code = -1;
2573		return;
2574	}
2575	p = getoptionvalue("pager");
2576	if (EMPTYSTRING(p))
2577		p = DEFAULTPAGER;
2578	len = strlen(p) + strlen(locfile) + 2;
2579	pager = ftp_malloc(len);
2580	(void)strlcpy(pager, p,		len);
2581	(void)strlcat(pager, " ",	len);
2582	(void)strlcat(pager, locfile,	len);
2583	system(pager);
2584	code = 0;
2585	(void)free(pager);
2586	(void)free(locfile);
2587}
2588
2589/*
2590 * Display one remote file through $PAGER.
2591 */
2592void
2593page(int argc, char *argv[])
2594{
2595	int ohash, orestart_point, overbose;
2596	size_t len;
2597	const char *p;
2598	char *pager;
2599
2600	if (argc == 0 || argc > 2 ||
2601	    (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2602		UPRINTF("usage: %s remote-file\n", argv[0]);
2603		code = -1;
2604		return;
2605	}
2606	p = getoptionvalue("pager");
2607	if (EMPTYSTRING(p))
2608		p = DEFAULTPAGER;
2609	len = strlen(p) + 2;
2610	pager = ftp_malloc(len);
2611	pager[0] = '|';
2612	(void)strlcpy(pager + 1, p, len - 1);
2613
2614	ohash = hash;
2615	orestart_point = restart_point;
2616	overbose = verbose;
2617	hash = restart_point = verbose = 0;
2618	recvrequest("RETR", pager, argv[1], "r+", 1, 0);
2619	hash = ohash;
2620	restart_point = orestart_point;
2621	verbose = overbose;
2622	(void)free(pager);
2623}
2624
2625/*
2626 * Set the socket send or receive buffer size.
2627 */
2628void
2629setxferbuf(int argc, char *argv[])
2630{
2631	int size, dir;
2632
2633	if (argc != 2) {
2634 usage:
2635		UPRINTF("usage: %s size\n", argv[0]);
2636		code = -1;
2637		return;
2638	}
2639	if (strcasecmp(argv[0], "sndbuf") == 0)
2640		dir = RATE_PUT;
2641	else if (strcasecmp(argv[0], "rcvbuf") == 0)
2642		dir = RATE_GET;
2643	else if (strcasecmp(argv[0], "xferbuf") == 0)
2644		dir = RATE_ALL;
2645	else
2646		goto usage;
2647
2648	if ((size = strsuftoi(argv[1])) == -1)
2649		goto usage;
2650
2651	if (size == 0) {
2652		fprintf(ttyout, "%s: size must be positive.\n", argv[0]);
2653		goto usage;
2654	}
2655
2656	if (dir & RATE_PUT) {
2657		sndbuf_size = size;
2658		auto_sndbuf = 0;
2659	}
2660	if (dir & RATE_GET) {
2661		rcvbuf_size = size;
2662		auto_rcvbuf = 0;
2663	}
2664	fprintf(ttyout, "Socket buffer sizes: send %d, receive %d.\n",
2665	    sndbuf_size, rcvbuf_size);
2666	code = 0;
2667}
2668
2669/*
2670 * Set or display options (defaults are provided by various env vars)
2671 */
2672void
2673setoption(int argc, char *argv[])
2674{
2675	struct option *o;
2676
2677	code = -1;
2678	if (argc == 0 || (argc != 1 && argc != 3)) {
2679		UPRINTF("usage: %s [option value]\n", argv[0]);
2680		return;
2681	}
2682
2683#define	OPTIONINDENT ((int) sizeof("http_proxy"))
2684	if (argc == 1) {
2685		for (o = optiontab; o->name != NULL; o++) {
2686			fprintf(ttyout, "%-*s\t%s\n", OPTIONINDENT,
2687			    o->name, o->value ? o->value : "");
2688		}
2689	} else {
2690		set_option(argv[1], argv[2], 1);
2691	}
2692	code = 0;
2693}
2694
2695void
2696set_option(const char * option, const char * value, int doverbose)
2697{
2698	struct option *o;
2699
2700	o = getoption(option);
2701	if (o == NULL) {
2702		fprintf(ttyout, "No such option `%s'.\n", option);
2703		return;
2704	}
2705	FREEPTR(o->value);
2706	o->value = ftp_strdup(value);
2707	if (verbose && doverbose)
2708		fprintf(ttyout, "Setting `%s' to `%s'.\n",
2709		    o->name, o->value);
2710}
2711
2712/*
2713 * Unset an option
2714 */
2715void
2716unsetoption(int argc, char *argv[])
2717{
2718	struct option *o;
2719
2720	code = -1;
2721	if (argc == 0 || argc != 2) {
2722		UPRINTF("usage: %s option\n", argv[0]);
2723		return;
2724	}
2725
2726	o = getoption(argv[1]);
2727	if (o == NULL) {
2728		fprintf(ttyout, "No such option `%s'.\n", argv[1]);
2729		return;
2730	}
2731	FREEPTR(o->value);
2732	fprintf(ttyout, "Unsetting `%s'.\n", o->name);
2733	code = 0;
2734}
2735
2736/*
2737 * Display features supported by the remote host.
2738 */
2739void
2740feat(int argc, char *argv[])
2741{
2742	int oldverbose = verbose;
2743
2744	if (argc == 0) {
2745		UPRINTF("usage: %s\n", argv[0]);
2746		code = -1;
2747		return;
2748	}
2749	if (! features[FEAT_FEAT]) {
2750		fprintf(ttyout,
2751		    "FEAT is not supported by the remote server.\n");
2752		return;
2753	}
2754	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2755	(void)command("FEAT");
2756	verbose = oldverbose;
2757}
2758
2759void
2760mlst(int argc, char *argv[])
2761{
2762	int oldverbose = verbose;
2763
2764	if (argc < 1 || argc > 2) {
2765		UPRINTF("usage: %s [remote-path]\n", argv[0]);
2766		code = -1;
2767		return;
2768	}
2769	if (! features[FEAT_MLST]) {
2770		fprintf(ttyout,
2771		    "MLST is not supported by the remote server.\n");
2772		return;
2773	}
2774	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2775	COMMAND_1ARG(argc, argv, "MLST");
2776	verbose = oldverbose;
2777}
2778
2779void
2780opts(int argc, char *argv[])
2781{
2782	int oldverbose = verbose;
2783
2784	if (argc < 2 || argc > 3) {
2785		UPRINTF("usage: %s command [options]\n", argv[0]);
2786		code = -1;
2787		return;
2788	}
2789	if (! features[FEAT_FEAT]) {
2790		fprintf(ttyout,
2791		    "OPTS is not supported by the remote server.\n");
2792		return;
2793	}
2794	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2795	if (argc == 2)
2796		command("OPTS %s", argv[1]);
2797	else
2798		command("OPTS %s %s", argv[1], argv[2]);
2799	verbose = oldverbose;
2800}
2801