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		for (pid = 3; pid < 20; pid++)
1442			(void)close(pid);
1443		(void)xsignal(SIGINT, SIG_DFL);
1444		shellp = getenv("SHELL");
1445		if (shellp == NULL)
1446			shellp = _PATH_BSHELL;
1447		namep = strrchr(shellp, '/');
1448		if (namep == NULL)
1449			namep = shellp;
1450		else
1451			namep++;
1452		(void)strlcpy(shellnam, namep, sizeof(shellnam));
1453		if (ftp_debug) {
1454			fputs(shellp, ttyout);
1455			putc('\n', ttyout);
1456		}
1457		if (argc > 1) {
1458			execl(shellp, shellnam, "-c", altarg, (char *)0);
1459		}
1460		else {
1461			execl(shellp, shellnam, (char *)0);
1462		}
1463		warn("Can't execute `%s'", shellp);
1464		code = -1;
1465		exit(1);
1466	}
1467	if (pid > 0)
1468		while (wait(&wait_status) != pid)
1469			;
1470	(void)xsignal(SIGINT, oldintr);
1471	if (pid == -1) {
1472		warn("Can't fork a subshell; try again later");
1473		code = -1;
1474	} else
1475		code = 0;
1476}
1477
1478/*
1479 * Send new user information (re-login)
1480 */
1481void
1482user(int argc, char *argv[])
1483{
1484	char *password;
1485	char emptypass[] = "";
1486	int n, aflag = 0;
1487
1488	if (argc == 0)
1489		goto usage;
1490	if (argc < 2)
1491		(void)another(&argc, &argv, "username");
1492	if (argc < 2 || argc > 4) {
1493 usage:
1494		UPRINTF("usage: %s username [password [account]]\n",
1495		    argv[0]);
1496		code = -1;
1497		return;
1498	}
1499	n = command("USER %s", argv[1]);
1500	if (n == CONTINUE) {
1501		if (argc < 3) {
1502			password = getpass("Password: ");
1503			if (password == NULL)
1504				password = emptypass;
1505		} else {
1506			password = argv[2];
1507		}
1508		n = command("PASS %s", password);
1509		memset(password, 0, strlen(password));
1510	}
1511	if (n == CONTINUE) {
1512		aflag++;
1513		if (argc < 4) {
1514			password = getpass("Account: ");
1515			if (password == NULL)
1516				password = emptypass;
1517		} else {
1518			password = argv[3];
1519		}
1520		n = command("ACCT %s", password);
1521		memset(password, 0, strlen(password));
1522	}
1523	if (n != COMPLETE) {
1524		fputs("Login failed.\n", ttyout);
1525		return;
1526	}
1527	if (!aflag && argc == 4) {
1528		password = argv[3];
1529		(void)command("ACCT %s", password);
1530		memset(password, 0, strlen(password));
1531	}
1532	connected = -1;
1533	getremoteinfo();
1534}
1535
1536/*
1537 * Print working directory on remote machine.
1538 */
1539/*VARARGS*/
1540void
1541pwd(int argc, char *argv[])
1542{
1543
1544	code = -1;
1545	if (argc != 1) {
1546		UPRINTF("usage: %s\n", argv[0]);
1547		return;
1548	}
1549	if (! remotecwd[0])
1550		updateremotecwd();
1551	if (! remotecwd[0])
1552		fprintf(ttyout, "Unable to determine remote directory\n");
1553	else {
1554		fprintf(ttyout, "Remote directory: %s\n", remotecwd);
1555		code = 0;
1556	}
1557}
1558
1559/*
1560 * Print working directory on local machine.
1561 */
1562void
1563lpwd(int argc, char *argv[])
1564{
1565
1566	code = -1;
1567	if (argc != 1) {
1568		UPRINTF("usage: %s\n", argv[0]);
1569		return;
1570	}
1571	if (! localcwd[0])
1572		updatelocalcwd();
1573	if (! localcwd[0])
1574		fprintf(ttyout, "Unable to determine local directory\n");
1575	else {
1576		fprintf(ttyout, "Local directory: %s\n", localcwd);
1577		code = 0;
1578	}
1579}
1580
1581/*
1582 * Make a directory.
1583 */
1584void
1585makedir(int argc, char *argv[])
1586{
1587	int r;
1588
1589	if (argc == 0 || argc > 2 ||
1590	    (argc == 1 && !another(&argc, &argv, "directory-name"))) {
1591		UPRINTF("usage: %s directory-name\n", argv[0]);
1592		code = -1;
1593		return;
1594	}
1595	r = command("MKD %s", argv[1]);
1596	if (r == ERROR && code == 500) {
1597		if (verbose)
1598			fputs("MKD command not recognized, trying XMKD.\n",
1599			    ttyout);
1600		r = command("XMKD %s", argv[1]);
1601	}
1602	if (r == COMPLETE)
1603		dirchange = 1;
1604}
1605
1606/*
1607 * Remove a directory.
1608 */
1609void
1610removedir(int argc, char *argv[])
1611{
1612	int r;
1613
1614	if (argc == 0 || argc > 2 ||
1615	    (argc == 1 && !another(&argc, &argv, "directory-name"))) {
1616		UPRINTF("usage: %s directory-name\n", argv[0]);
1617		code = -1;
1618		return;
1619	}
1620	r = command("RMD %s", argv[1]);
1621	if (r == ERROR && code == 500) {
1622		if (verbose)
1623			fputs("RMD command not recognized, trying XRMD.\n",
1624			    ttyout);
1625		r = command("XRMD %s", argv[1]);
1626	}
1627	if (r == COMPLETE)
1628		dirchange = 1;
1629}
1630
1631/*
1632 * Send a line, verbatim, to the remote machine.
1633 */
1634void
1635quote(int argc, char *argv[])
1636{
1637
1638	if (argc == 0 ||
1639	    (argc == 1 && !another(&argc, &argv, "command line to send"))) {
1640		UPRINTF("usage: %s line-to-send\n", argv[0]);
1641		code = -1;
1642		return;
1643	}
1644	quote1("", argc, argv);
1645}
1646
1647/*
1648 * Send a SITE command to the remote machine.  The line
1649 * is sent verbatim to the remote machine, except that the
1650 * word "SITE" is added at the front.
1651 */
1652void
1653site(int argc, char *argv[])
1654{
1655
1656	if (argc == 0 ||
1657	    (argc == 1 && !another(&argc, &argv, "arguments to SITE command"))){
1658		UPRINTF("usage: %s line-to-send\n", argv[0]);
1659		code = -1;
1660		return;
1661	}
1662	quote1("SITE ", argc, argv);
1663}
1664
1665/*
1666 * Turn argv[1..argc) into a space-separated string, then prepend initial text.
1667 * Send the result as a one-line command and get response.
1668 */
1669void
1670quote1(const char *initial, int argc, char *argv[])
1671{
1672	int i;
1673	char buf[BUFSIZ];		/* must be >= sizeof(line) */
1674
1675	(void)strlcpy(buf, initial, sizeof(buf));
1676	for (i = 1; i < argc; i++) {
1677		(void)strlcat(buf, argv[i], sizeof(buf));
1678		if (i < (argc - 1))
1679			(void)strlcat(buf, " ", sizeof(buf));
1680	}
1681	if (command("%s", buf) == PRELIM) {
1682		while (getreply(0) == PRELIM)
1683			continue;
1684	}
1685	dirchange = 1;
1686}
1687
1688void
1689do_chmod(int argc, char *argv[])
1690{
1691
1692	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "mode")))
1693		goto usage;
1694	if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
1695 usage:
1696		UPRINTF("usage: %s mode remote-file\n", argv[0]);
1697		code = -1;
1698		return;
1699	}
1700	(void)command("SITE CHMOD %s %s", argv[1], argv[2]);
1701}
1702
1703#define COMMAND_1ARG(argc, argv, cmd)			\
1704	if (argc == 1)					\
1705		command(cmd);				\
1706	else						\
1707		command(cmd " %s", argv[1])
1708
1709void
1710do_umask(int argc, char *argv[])
1711{
1712	int oldverbose = verbose;
1713
1714	if (argc == 0) {
1715		UPRINTF("usage: %s [umask]\n", argv[0]);
1716		code = -1;
1717		return;
1718	}
1719	verbose = 1;
1720	COMMAND_1ARG(argc, argv, "SITE UMASK");
1721	verbose = oldverbose;
1722}
1723
1724void
1725idlecmd(int argc, char *argv[])
1726{
1727	int oldverbose = verbose;
1728
1729	if (argc < 1 || argc > 2) {
1730		UPRINTF("usage: %s [seconds]\n", argv[0]);
1731		code = -1;
1732		return;
1733	}
1734	verbose = 1;
1735	COMMAND_1ARG(argc, argv, "SITE IDLE");
1736	verbose = oldverbose;
1737}
1738
1739/*
1740 * Ask the other side for help.
1741 */
1742void
1743rmthelp(int argc, char *argv[])
1744{
1745	int oldverbose = verbose;
1746
1747	if (argc == 0) {
1748		UPRINTF("usage: %s\n", argv[0]);
1749		code = -1;
1750		return;
1751	}
1752	verbose = 1;
1753	COMMAND_1ARG(argc, argv, "HELP");
1754	verbose = oldverbose;
1755}
1756
1757/*
1758 * Terminate session and exit.
1759 * May be called with 0, NULL.
1760 */
1761/*VARARGS*/
1762void
1763quit(int argc, char *argv[])
1764{
1765
1766			/* this may be called with argc == 0, argv == NULL */
1767	if (argc == 0 && argv != NULL) {
1768		UPRINTF("usage: %s\n", argv[0]);
1769		code = -1;
1770		return;
1771	}
1772	if (connected)
1773		disconnect(0, NULL);
1774	pswitch(1);
1775	if (connected)
1776		disconnect(0, NULL);
1777	exit(0);
1778}
1779
1780/*
1781 * Terminate session, but don't exit.
1782 * May be called with 0, NULL.
1783 */
1784void
1785disconnect(int argc, char *argv[])
1786{
1787
1788			/* this may be called with argc == 0, argv == NULL */
1789	if (argc == 0 && argv != NULL) {
1790		UPRINTF("usage: %s\n", argv[0]);
1791		code = -1;
1792		return;
1793	}
1794	if (!connected)
1795		return;
1796	(void)command("QUIT");
1797	cleanuppeer();
1798}
1799
1800void
1801account(int argc, char *argv[])
1802{
1803	char *ap;
1804	char emptypass[] = "";
1805
1806	if (argc == 0 || argc > 2) {
1807		UPRINTF("usage: %s [password]\n", argv[0]);
1808		code = -1;
1809		return;
1810	}
1811	else if (argc == 2)
1812		ap = argv[1];
1813	else {
1814		ap = getpass("Account:");
1815		if (ap == NULL)
1816			ap = emptypass;
1817	}
1818	(void)command("ACCT %s", ap);
1819	memset(ap, 0, strlen(ap));
1820}
1821
1822sigjmp_buf abortprox;
1823
1824void
1825proxabort(int notused)
1826{
1827
1828	sigint_raised = 1;
1829	alarmtimer(0);
1830	if (!proxy) {
1831		pswitch(1);
1832	}
1833	if (connected) {
1834		proxflag = 1;
1835	}
1836	else {
1837		proxflag = 0;
1838	}
1839	pswitch(0);
1840	siglongjmp(abortprox, 1);
1841}
1842
1843void
1844doproxy(int argc, char *argv[])
1845{
1846	struct cmd *c;
1847	int cmdpos;
1848	sigfunc oldintr;
1849	char cmdbuf[MAX_C_NAME];
1850
1851	if (argc == 0 || (argc == 1 && !another(&argc, &argv, "command"))) {
1852		UPRINTF("usage: %s command\n", argv[0]);
1853		code = -1;
1854		return;
1855	}
1856	c = getcmd(argv[1]);
1857	if (c == (struct cmd *) -1) {
1858		fputs("?Ambiguous command.\n", ttyout);
1859		code = -1;
1860		return;
1861	}
1862	if (c == 0) {
1863		fputs("?Invalid command.\n", ttyout);
1864		code = -1;
1865		return;
1866	}
1867	if (!c->c_proxy) {
1868		fputs("?Invalid proxy command.\n", ttyout);
1869		code = -1;
1870		return;
1871	}
1872	if (sigsetjmp(abortprox, 1)) {
1873		code = -1;
1874		return;
1875	}
1876	oldintr = xsignal(SIGINT, proxabort);
1877	pswitch(1);
1878	if (c->c_conn && !connected) {
1879		fputs("Not connected.\n", ttyout);
1880		pswitch(0);
1881		(void)xsignal(SIGINT, oldintr);
1882		code = -1;
1883		return;
1884	}
1885	cmdpos = strcspn(line, " \t");
1886	if (cmdpos > 0)		/* remove leading "proxy " from input buffer */
1887		memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
1888	(void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf));
1889	argv[1] = cmdbuf;
1890	(*c->c_handler)(argc-1, argv+1);
1891	if (connected) {
1892		proxflag = 1;
1893	}
1894	else {
1895		proxflag = 0;
1896	}
1897	pswitch(0);
1898	(void)xsignal(SIGINT, oldintr);
1899}
1900
1901void
1902setcase(int argc, char *argv[])
1903{
1904
1905	code = togglevar(argc, argv, &mcase, "Case mapping");
1906}
1907
1908/*
1909 * convert the given name to lower case if it's all upper case, into
1910 * a static buffer which is returned to the caller
1911 */
1912static const char *
1913docase(char *dst, size_t dlen, const char *src)
1914{
1915	size_t i;
1916	int dochange = 1;
1917
1918	for (i = 0; src[i] != '\0' && i < dlen - 1; i++) {
1919		dst[i] = src[i];
1920		if (islower((unsigned char)dst[i]))
1921			dochange = 0;
1922	}
1923	dst[i] = '\0';
1924
1925	if (dochange) {
1926		for (i = 0; dst[i] != '\0'; i++)
1927			if (isupper((unsigned char)dst[i]))
1928				dst[i] = tolower((unsigned char)dst[i]);
1929	}
1930	return dst;
1931}
1932
1933void
1934setcr(int argc, char *argv[])
1935{
1936
1937	code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
1938}
1939
1940void
1941setntrans(int argc, char *argv[])
1942{
1943
1944	if (argc == 0 || argc > 3) {
1945		UPRINTF("usage: %s [inchars [outchars]]\n", argv[0]);
1946		code = -1;
1947		return;
1948	}
1949	if (argc == 1) {
1950		ntflag = 0;
1951		fputs("Ntrans off.\n", ttyout);
1952		code = ntflag;
1953		return;
1954	}
1955	ntflag++;
1956	code = ntflag;
1957	(void)strlcpy(ntin, argv[1], sizeof(ntin));
1958	if (argc == 2) {
1959		ntout[0] = '\0';
1960		return;
1961	}
1962	(void)strlcpy(ntout, argv[2], sizeof(ntout));
1963}
1964
1965static const char *
1966dotrans(char *dst, size_t dlen, const char *src)
1967{
1968	const char *cp1;
1969	char *cp2 = dst;
1970	size_t i, ostop;
1971
1972	for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
1973		continue;
1974	for (cp1 = src; *cp1; cp1++) {
1975		int found = 0;
1976		for (i = 0; *(ntin + i) && i < 16; i++) {
1977			if (*cp1 == *(ntin + i)) {
1978				found++;
1979				if (i < ostop) {
1980					*cp2++ = *(ntout + i);
1981					if (cp2 - dst >= (ptrdiff_t)(dlen - 1))
1982						goto out;
1983				}
1984				break;
1985			}
1986		}
1987		if (!found) {
1988			*cp2++ = *cp1;
1989		}
1990	}
1991out:
1992	*cp2 = '\0';
1993	return dst;
1994}
1995
1996void
1997setnmap(int argc, char *argv[])
1998{
1999	char *cp;
2000
2001	if (argc == 1) {
2002		mapflag = 0;
2003		fputs("Nmap off.\n", ttyout);
2004		code = mapflag;
2005		return;
2006	}
2007	if (argc == 0 ||
2008	    (argc < 3 && !another(&argc, &argv, "mapout")) || argc > 3) {
2009		UPRINTF("usage: %s [mapin mapout]\n", argv[0]);
2010		code = -1;
2011		return;
2012	}
2013	mapflag = 1;
2014	code = 1;
2015	cp = strchr(altarg, ' ');
2016	if (proxy) {
2017		while(*++cp == ' ')
2018			continue;
2019		altarg = cp;
2020		cp = strchr(altarg, ' ');
2021	}
2022	*cp = '\0';
2023	(void)strlcpy(mapin, altarg, MAXPATHLEN);
2024	while (*++cp == ' ')
2025		continue;
2026	(void)strlcpy(mapout, cp, MAXPATHLEN);
2027}
2028
2029static const char *
2030domap(char *dst, size_t dlen, const char *src)
2031{
2032	const char *cp1 = src;
2033	char *cp2 = mapin;
2034	const char *tp[9], *te[9];
2035	int i, toks[9], toknum = 0, match = 1;
2036
2037	for (i=0; i < 9; ++i) {
2038		toks[i] = 0;
2039	}
2040	while (match && *cp1 && *cp2) {
2041		switch (*cp2) {
2042			case '\\':
2043				if (*++cp2 != *cp1) {
2044					match = 0;
2045				}
2046				break;
2047			case '$':
2048				if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
2049					if (*cp1 != *(++cp2+1)) {
2050						toks[toknum = *cp2 - '1']++;
2051						tp[toknum] = cp1;
2052						while (*++cp1 && *(cp2+1)
2053							!= *cp1);
2054						te[toknum] = cp1;
2055					}
2056					cp2++;
2057					break;
2058				}
2059				/* FALLTHROUGH */
2060			default:
2061				if (*cp2 != *cp1) {
2062					match = 0;
2063				}
2064				break;
2065		}
2066		if (match && *cp1) {
2067			cp1++;
2068		}
2069		if (match && *cp2) {
2070			cp2++;
2071		}
2072	}
2073	if (!match && *cp1) /* last token mismatch */
2074	{
2075		toks[toknum] = 0;
2076	}
2077	cp2 = dst;
2078	*cp2 = '\0';
2079	cp1 = mapout;
2080	while (*cp1) {
2081		match = 0;
2082		switch (*cp1) {
2083			case '\\':
2084				if (*(cp1 + 1)) {
2085					*cp2++ = *++cp1;
2086				}
2087				break;
2088			case '[':
2089LOOP:
2090				if (*++cp1 == '$' &&
2091				    isdigit((unsigned char)*(cp1+1))) {
2092					if (*++cp1 == '0') {
2093						const char *cp3 = src;
2094
2095						while (*cp3) {
2096							*cp2++ = *cp3++;
2097						}
2098						match = 1;
2099					}
2100					else if (toks[toknum = *cp1 - '1']) {
2101						const char *cp3 = tp[toknum];
2102
2103						while (cp3 != te[toknum]) {
2104							*cp2++ = *cp3++;
2105						}
2106						match = 1;
2107					}
2108				}
2109				else {
2110					while (*cp1 && *cp1 != ',' &&
2111					    *cp1 != ']') {
2112						if (*cp1 == '\\') {
2113							cp1++;
2114						}
2115						else if (*cp1 == '$' &&
2116						    isdigit((unsigned char)*(cp1+1))) {
2117							if (*++cp1 == '0') {
2118							   const char *cp3 = src;
2119
2120							   while (*cp3) {
2121								*cp2++ = *cp3++;
2122							   }
2123							}
2124							else if (toks[toknum =
2125							    *cp1 - '1']) {
2126							   const char *cp3=tp[toknum];
2127
2128							   while (cp3 !=
2129								  te[toknum]) {
2130								*cp2++ = *cp3++;
2131							   }
2132							}
2133						}
2134						else if (*cp1) {
2135							*cp2++ = *cp1++;
2136						}
2137					}
2138					if (!*cp1) {
2139						fputs(
2140						"nmap: unbalanced brackets.\n",
2141						    ttyout);
2142						return (src);
2143					}
2144					match = 1;
2145					cp1--;
2146				}
2147				if (match) {
2148					while (*++cp1 && *cp1 != ']') {
2149					      if (*cp1 == '\\' && *(cp1 + 1)) {
2150							cp1++;
2151					      }
2152					}
2153					if (!*cp1) {
2154						fputs(
2155						"nmap: unbalanced brackets.\n",
2156						    ttyout);
2157						return (src);
2158					}
2159					break;
2160				}
2161				switch (*++cp1) {
2162					case ',':
2163						goto LOOP;
2164					case ']':
2165						break;
2166					default:
2167						cp1--;
2168						goto LOOP;
2169				}
2170				break;
2171			case '$':
2172				if (isdigit((unsigned char)*(cp1 + 1))) {
2173					if (*++cp1 == '0') {
2174						const char *cp3 = src;
2175
2176						while (*cp3) {
2177							*cp2++ = *cp3++;
2178						}
2179					}
2180					else if (toks[toknum = *cp1 - '1']) {
2181						const char *cp3 = tp[toknum];
2182
2183						while (cp3 != te[toknum]) {
2184							*cp2++ = *cp3++;
2185						}
2186					}
2187					break;
2188				}
2189				/* intentional drop through */
2190			default:
2191				*cp2++ = *cp1;
2192				break;
2193		}
2194		cp1++;
2195	}
2196	*cp2 = '\0';
2197	return *dst ? dst : src;
2198}
2199
2200void
2201setpassive(int argc, char *argv[])
2202{
2203
2204	if (argc == 1) {
2205		passivemode = !passivemode;
2206		activefallback = passivemode;
2207	} else if (argc != 2) {
2208 passiveusage:
2209		UPRINTF("usage: %s [ on | off | auto ]\n", argv[0]);
2210		code = -1;
2211		return;
2212	} else if (strcasecmp(argv[1], "on") == 0) {
2213		passivemode = 1;
2214		activefallback = 0;
2215	} else if (strcasecmp(argv[1], "off") == 0) {
2216		passivemode = 0;
2217		activefallback = 0;
2218	} else if (strcasecmp(argv[1], "auto") == 0) {
2219		passivemode = 1;
2220		activefallback = 1;
2221	} else
2222		goto passiveusage;
2223	fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
2224	    onoff(passivemode), onoff(activefallback));
2225	code = passivemode;
2226}
2227
2228
2229void
2230setepsv4(int argc, char *argv[])
2231{
2232	code = togglevar(argc, argv, &epsv4,
2233	    verbose ? "EPSV/EPRT on IPv4" : NULL);
2234	epsv4bad = 0;
2235}
2236
2237void
2238setepsv6(int argc, char *argv[])
2239{
2240	code = togglevar(argc, argv, &epsv6,
2241	    verbose ? "EPSV/EPRT on IPv6" : NULL);
2242	epsv6bad = 0;
2243}
2244
2245void
2246setepsv(int argc, char*argv[])
2247{
2248	setepsv4(argc,argv);
2249	setepsv6(argc,argv);
2250}
2251
2252void
2253setsunique(int argc, char *argv[])
2254{
2255
2256	code = togglevar(argc, argv, &sunique, "Store unique");
2257}
2258
2259void
2260setrunique(int argc, char *argv[])
2261{
2262
2263	code = togglevar(argc, argv, &runique, "Receive unique");
2264}
2265
2266int
2267parserate(int argc, char *argv[], int cmdlineopt)
2268{
2269	int dir, max, incr, showonly;
2270	sigfunc oldusr1, oldusr2;
2271
2272	if (argc > 4 || (argc < (cmdlineopt ? 3 : 2))) {
2273 usage:
2274		if (cmdlineopt)
2275			UPRINTF(
2276	"usage: %s (all|get|put),maximum-bytes[,increment-bytes]]\n",
2277			    argv[0]);
2278		else
2279			UPRINTF(
2280	"usage: %s (all|get|put) [maximum-bytes [increment-bytes]]\n",
2281			    argv[0]);
2282		return -1;
2283	}
2284	dir = max = incr = showonly = 0;
2285#define	RATE_GET	1
2286#define	RATE_PUT	2
2287#define	RATE_ALL	(RATE_GET | RATE_PUT)
2288
2289	if (strcasecmp(argv[1], "all") == 0)
2290		dir = RATE_ALL;
2291	else if (strcasecmp(argv[1], "get") == 0)
2292		dir = RATE_GET;
2293	else if (strcasecmp(argv[1], "put") == 0)
2294		dir = RATE_PUT;
2295	else
2296		goto usage;
2297
2298	if (argc >= 3) {
2299		if ((max = strsuftoi(argv[2])) < 0)
2300			goto usage;
2301	} else
2302		showonly = 1;
2303
2304	if (argc == 4) {
2305		if ((incr = strsuftoi(argv[3])) <= 0)
2306			goto usage;
2307	} else
2308		incr = DEFAULTINCR;
2309
2310	oldusr1 = xsignal(SIGUSR1, SIG_IGN);
2311	oldusr2 = xsignal(SIGUSR2, SIG_IGN);
2312	if (dir & RATE_GET) {
2313		if (!showonly) {
2314			rate_get = max;
2315			rate_get_incr = incr;
2316		}
2317		if (!cmdlineopt || verbose)
2318			fprintf(ttyout,
2319		"Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
2320			    onoff(rate_get), rate_get, rate_get_incr);
2321	}
2322	if (dir & RATE_PUT) {
2323		if (!showonly) {
2324			rate_put = max;
2325			rate_put_incr = incr;
2326		}
2327		if (!cmdlineopt || verbose)
2328			fprintf(ttyout,
2329		"Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
2330			    onoff(rate_put), rate_put, rate_put_incr);
2331	}
2332	(void)xsignal(SIGUSR1, oldusr1);
2333	(void)xsignal(SIGUSR2, oldusr2);
2334	return 0;
2335}
2336
2337void
2338setrate(int argc, char *argv[])
2339{
2340
2341	code = parserate(argc, argv, 0);
2342}
2343
2344/* change directory to parent directory */
2345void
2346cdup(int argc, char *argv[])
2347{
2348	int r;
2349
2350	if (argc == 0) {
2351		UPRINTF("usage: %s\n", argv[0]);
2352		code = -1;
2353		return;
2354	}
2355	r = command("CDUP");
2356	if (r == ERROR && code == 500) {
2357		if (verbose)
2358			fputs("CDUP command not recognized, trying XCUP.\n",
2359			    ttyout);
2360		r = command("XCUP");
2361	}
2362	if (r == COMPLETE) {
2363		dirchange = 1;
2364		updateremotecwd();
2365	}
2366}
2367
2368/*
2369 * Restart transfer at specific point
2370 */
2371void
2372restart(int argc, char *argv[])
2373{
2374
2375	if (argc == 0 || argc > 2) {
2376		UPRINTF("usage: %s [restart-point]\n", argv[0]);
2377		code = -1;
2378		return;
2379	}
2380	if (! features[FEAT_REST_STREAM]) {
2381		fprintf(ttyout,
2382		    "Restart is not supported by the remote server.\n");
2383		return;
2384	}
2385	if (argc == 2) {
2386		off_t rp;
2387		char *ep;
2388
2389		rp = STRTOLL(argv[1], &ep, 10);
2390		if (rp < 0 || *ep != '\0')
2391			fprintf(ttyout, "restart: Invalid offset `%s'\n",
2392			    argv[1]);
2393		else
2394			restart_point = rp;
2395	}
2396	if (restart_point == 0)
2397		fputs("No restart point defined.\n", ttyout);
2398	else
2399		fprintf(ttyout,
2400		    "Restarting at " LLF " for next get, put or append\n",
2401		    (LLT)restart_point);
2402}
2403
2404/*
2405 * Show remote system type
2406 */
2407void
2408syst(int argc, char *argv[])
2409{
2410	int oldverbose = verbose;
2411
2412	if (argc == 0) {
2413		UPRINTF("usage: %s\n", argv[0]);
2414		code = -1;
2415		return;
2416	}
2417	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2418	(void)command("SYST");
2419	verbose = oldverbose;
2420}
2421
2422void
2423macdef(int argc, char *argv[])
2424{
2425	char *tmp;
2426	int c;
2427
2428	if (argc == 0)
2429		goto usage;
2430	if (macnum == 16) {
2431		fputs("Limit of 16 macros have already been defined.\n",
2432		    ttyout);
2433		code = -1;
2434		return;
2435	}
2436	if ((argc < 2 && !another(&argc, &argv, "macro name")) || argc > 2) {
2437 usage:
2438		UPRINTF("usage: %s macro_name\n", argv[0]);
2439		code = -1;
2440		return;
2441	}
2442	if (interactive)
2443		fputs(
2444		"Enter macro line by line, terminating it with a null line.\n",
2445		    ttyout);
2446	(void)strlcpy(macros[macnum].mac_name, argv[1],
2447	    sizeof(macros[macnum].mac_name));
2448	if (macnum == 0)
2449		macros[macnum].mac_start = macbuf;
2450	else
2451		macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
2452	tmp = macros[macnum].mac_start;
2453	while (tmp != macbuf+4096) {
2454		if ((c = getchar()) == EOF) {
2455			fputs("macdef: end of file encountered.\n", ttyout);
2456			code = -1;
2457			return;
2458		}
2459		if ((*tmp = c) == '\n') {
2460			if (tmp == macros[macnum].mac_start) {
2461				macros[macnum++].mac_end = tmp;
2462				code = 0;
2463				return;
2464			}
2465			if (*(tmp-1) == '\0') {
2466				macros[macnum++].mac_end = tmp - 1;
2467				code = 0;
2468				return;
2469			}
2470			*tmp = '\0';
2471		}
2472		tmp++;
2473	}
2474	while (1) {
2475		while ((c = getchar()) != '\n' && c != EOF)
2476			/* LOOP */;
2477		if (c == EOF || getchar() == '\n') {
2478			fputs("Macro not defined - 4K buffer exceeded.\n",
2479			    ttyout);
2480			code = -1;
2481			return;
2482		}
2483	}
2484}
2485
2486/*
2487 * Get size of file on remote machine
2488 */
2489void
2490sizecmd(int argc, char *argv[])
2491{
2492	off_t size;
2493
2494	if (argc == 0 || argc > 2 ||
2495	    (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2496		UPRINTF("usage: %s remote-file\n", argv[0]);
2497		code = -1;
2498		return;
2499	}
2500	size = remotesize(argv[1], 1);
2501	if (size != -1)
2502		fprintf(ttyout,
2503		    "%s\t" LLF "\n", argv[1], (LLT)size);
2504	code = (size > 0);
2505}
2506
2507/*
2508 * Get last modification time of file on remote machine
2509 */
2510void
2511modtime(int argc, char *argv[])
2512{
2513	time_t mtime;
2514
2515	if (argc == 0 || argc > 2 ||
2516	    (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2517		UPRINTF("usage: %s remote-file\n", argv[0]);
2518		code = -1;
2519		return;
2520	}
2521	mtime = remotemodtime(argv[1], 1);
2522	if (mtime != -1)
2523		fprintf(ttyout, "%s\t%s", argv[1],
2524		    rfc2822time(localtime(&mtime)));
2525	code = (mtime > 0);
2526}
2527
2528/*
2529 * Show status on remote machine
2530 */
2531void
2532rmtstatus(int argc, char *argv[])
2533{
2534
2535	if (argc == 0) {
2536		UPRINTF("usage: %s [remote-file]\n", argv[0]);
2537		code = -1;
2538		return;
2539	}
2540	COMMAND_1ARG(argc, argv, "STAT");
2541}
2542
2543/*
2544 * Get file if modtime is more recent than current file
2545 */
2546void
2547newer(int argc, char *argv[])
2548{
2549
2550	if (getit(argc, argv, -1, "w"))
2551		fprintf(ttyout,
2552		    "Local file \"%s\" is newer than remote file \"%s\".\n",
2553		    argv[2], argv[1]);
2554}
2555
2556/*
2557 * Display one local file through $PAGER.
2558 */
2559void
2560lpage(int argc, char *argv[])
2561{
2562	size_t len;
2563	const char *p;
2564	char *pager, *locfile;
2565
2566	if (argc == 0 || argc > 2 ||
2567	    (argc == 1 && !another(&argc, &argv, "local-file"))) {
2568		UPRINTF("usage: %s local-file\n", argv[0]);
2569		code = -1;
2570		return;
2571	}
2572	if ((locfile = globulize(argv[1])) == NULL) {
2573		code = -1;
2574		return;
2575	}
2576	p = getoptionvalue("pager");
2577	if (EMPTYSTRING(p))
2578		p = DEFAULTPAGER;
2579	len = strlen(p) + strlen(locfile) + 2;
2580	pager = ftp_malloc(len);
2581	(void)strlcpy(pager, p,		len);
2582	(void)strlcat(pager, " ",	len);
2583	(void)strlcat(pager, locfile,	len);
2584	system(pager);
2585	code = 0;
2586	(void)free(pager);
2587	(void)free(locfile);
2588}
2589
2590/*
2591 * Display one remote file through $PAGER.
2592 */
2593void
2594page(int argc, char *argv[])
2595{
2596	int ohash, orestart_point, overbose;
2597	size_t len;
2598	const char *p;
2599	char *pager;
2600
2601	if (argc == 0 || argc > 2 ||
2602	    (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2603		UPRINTF("usage: %s remote-file\n", argv[0]);
2604		code = -1;
2605		return;
2606	}
2607	p = getoptionvalue("pager");
2608	if (EMPTYSTRING(p))
2609		p = DEFAULTPAGER;
2610	len = strlen(p) + 2;
2611	pager = ftp_malloc(len);
2612	pager[0] = '|';
2613	(void)strlcpy(pager + 1, p, len - 1);
2614
2615	ohash = hash;
2616	orestart_point = restart_point;
2617	overbose = verbose;
2618	hash = restart_point = verbose = 0;
2619	recvrequest("RETR", pager, argv[1], "r+", 1, 0);
2620	hash = ohash;
2621	restart_point = orestart_point;
2622	verbose = overbose;
2623	(void)free(pager);
2624}
2625
2626/*
2627 * Set the socket send or receive buffer size.
2628 */
2629void
2630setxferbuf(int argc, char *argv[])
2631{
2632	int size, dir;
2633
2634	if (argc != 2) {
2635 usage:
2636		UPRINTF("usage: %s size\n", argv[0]);
2637		code = -1;
2638		return;
2639	}
2640	if (strcasecmp(argv[0], "sndbuf") == 0)
2641		dir = RATE_PUT;
2642	else if (strcasecmp(argv[0], "rcvbuf") == 0)
2643		dir = RATE_GET;
2644	else if (strcasecmp(argv[0], "xferbuf") == 0)
2645		dir = RATE_ALL;
2646	else
2647		goto usage;
2648
2649	if ((size = strsuftoi(argv[1])) == -1)
2650		goto usage;
2651
2652	if (size == 0) {
2653		fprintf(ttyout, "%s: size must be positive.\n", argv[0]);
2654		goto usage;
2655	}
2656
2657	if (dir & RATE_PUT)
2658		sndbuf_size = size;
2659	if (dir & RATE_GET)
2660		rcvbuf_size = size;
2661	fprintf(ttyout, "Socket buffer sizes: send %d, receive %d.\n",
2662	    sndbuf_size, rcvbuf_size);
2663	code = 0;
2664}
2665
2666/*
2667 * Set or display options (defaults are provided by various env vars)
2668 */
2669void
2670setoption(int argc, char *argv[])
2671{
2672	struct option *o;
2673
2674	code = -1;
2675	if (argc == 0 || (argc != 1 && argc != 3)) {
2676		UPRINTF("usage: %s [option value]\n", argv[0]);
2677		return;
2678	}
2679
2680#define	OPTIONINDENT ((int) sizeof("http_proxy"))
2681	if (argc == 1) {
2682		for (o = optiontab; o->name != NULL; o++) {
2683			fprintf(ttyout, "%-*s\t%s\n", OPTIONINDENT,
2684			    o->name, o->value ? o->value : "");
2685		}
2686	} else {
2687		set_option(argv[1], argv[2], 1);
2688	}
2689	code = 0;
2690}
2691
2692void
2693set_option(const char * option, const char * value, int doverbose)
2694{
2695	struct option *o;
2696
2697	o = getoption(option);
2698	if (o == NULL) {
2699		fprintf(ttyout, "No such option `%s'.\n", option);
2700		return;
2701	}
2702	FREEPTR(o->value);
2703	o->value = ftp_strdup(value);
2704	if (verbose && doverbose)
2705		fprintf(ttyout, "Setting `%s' to `%s'.\n",
2706		    o->name, o->value);
2707}
2708
2709/*
2710 * Unset an option
2711 */
2712void
2713unsetoption(int argc, char *argv[])
2714{
2715	struct option *o;
2716
2717	code = -1;
2718	if (argc == 0 || argc != 2) {
2719		UPRINTF("usage: %s option\n", argv[0]);
2720		return;
2721	}
2722
2723	o = getoption(argv[1]);
2724	if (o == NULL) {
2725		fprintf(ttyout, "No such option `%s'.\n", argv[1]);
2726		return;
2727	}
2728	FREEPTR(o->value);
2729	fprintf(ttyout, "Unsetting `%s'.\n", o->name);
2730	code = 0;
2731}
2732
2733/*
2734 * Display features supported by the remote host.
2735 */
2736void
2737feat(int argc, char *argv[])
2738{
2739	int oldverbose = verbose;
2740
2741	if (argc == 0) {
2742		UPRINTF("usage: %s\n", argv[0]);
2743		code = -1;
2744		return;
2745	}
2746	if (! features[FEAT_FEAT]) {
2747		fprintf(ttyout,
2748		    "FEAT is not supported by the remote server.\n");
2749		return;
2750	}
2751	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2752	(void)command("FEAT");
2753	verbose = oldverbose;
2754}
2755
2756void
2757mlst(int argc, char *argv[])
2758{
2759	int oldverbose = verbose;
2760
2761	if (argc < 1 || argc > 2) {
2762		UPRINTF("usage: %s [remote-path]\n", argv[0]);
2763		code = -1;
2764		return;
2765	}
2766	if (! features[FEAT_MLST]) {
2767		fprintf(ttyout,
2768		    "MLST is not supported by the remote server.\n");
2769		return;
2770	}
2771	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2772	COMMAND_1ARG(argc, argv, "MLST");
2773	verbose = oldverbose;
2774}
2775
2776void
2777opts(int argc, char *argv[])
2778{
2779	int oldverbose = verbose;
2780
2781	if (argc < 2 || argc > 3) {
2782		UPRINTF("usage: %s command [options]\n", argv[0]);
2783		code = -1;
2784		return;
2785	}
2786	if (! features[FEAT_FEAT]) {
2787		fprintf(ttyout,
2788		    "OPTS is not supported by the remote server.\n");
2789		return;
2790	}
2791	verbose = 1;	/* If we aren't verbose, this doesn't do anything! */
2792	if (argc == 2)
2793		command("OPTS %s", argv[1]);
2794	else
2795		command("OPTS %s %s", argv[1], argv[2]);
2796	verbose = oldverbose;
2797}
2798