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