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