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