Sync with NetBSD. In addition to some bug fixes, this brings in -s srcaddr
[dragonfly.git] / contrib / tnftp / cmds.c
1 /*      $NetBSD: cmds.c,v 1.121 2007/04/18 01:39:04 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.121 2007/04/18 01:39:04 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    *buf, *mode;
758         FILE    *fp;
759
760         if (argc != 2) {
761                 UPRINTF("usage: %s localfile\n", argv[0]);
762                 code = -1;
763                 return;
764         }
765
766         fp = fopen(argv[1], "r");
767         if (fp == NULL) {
768                 fprintf(ttyout, "Can't open source file %s\n", argv[1]);
769                 code = -1;
770                 return;
771         }
772
773         argv[0] = "get";
774         mode = restart_point ? "r+" : "w";
775
776         for (;
777             (buf = fparseln(fp, NULL, NULL, "\0\0\0", 0)) != NULL;
778             free(buf)) {
779                 if (buf[0] == '\0')
780                         continue;
781                 argv[1] = buf;
782                 (void)getit(argc, argv, 0, mode);
783         }
784         fclose(fp);
785 }
786
787 const char *
788 onoff(int bool)
789 {
790
791         return (bool ? "on" : "off");
792 }
793
794 /*
795  * Show status.
796  */
797 /*ARGSUSED*/
798 void
799 status(int argc, char *argv[])
800 {
801
802         if (argc == 0) {
803                 UPRINTF("usage: %s\n", argv[0]);
804                 code = -1;
805                 return;
806         }
807 #ifndef NO_STATUS
808         if (connected)
809                 fprintf(ttyout, "Connected %sto %s.\n",
810                     connected == -1 ? "and logged in" : "", hostname);
811         else
812                 fputs("Not connected.\n", ttyout);
813         if (!proxy) {
814                 pswitch(1);
815                 if (connected) {
816                         fprintf(ttyout, "Connected for proxy commands to %s.\n",
817                             hostname);
818                 }
819                 else {
820                         fputs("No proxy connection.\n", ttyout);
821                 }
822                 pswitch(0);
823         }
824         fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode),
825             *gateserver ? gateserver : "(none)", gateport);
826         fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
827             onoff(passivemode), onoff(activefallback));
828         fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n",
829             modename, typename, formname, structname);
830         fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n",
831             onoff(verbose), onoff(bell), onoff(interactive), onoff(doglob));
832         fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n",
833             onoff(sunique), onoff(runique));
834         fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve));
835         fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase),
836             onoff(crflag));
837         if (ntflag) {
838                 fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout);
839         }
840         else {
841                 fputs("Ntrans: off.\n", ttyout);
842         }
843         if (mapflag) {
844                 fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout);
845         }
846         else {
847                 fputs("Nmap: off.\n", ttyout);
848         }
849         fprintf(ttyout,
850             "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
851             onoff(hash), mark, onoff(progress));
852         fprintf(ttyout,
853             "Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
854             onoff(rate_get), rate_get, rate_get_incr);
855         fprintf(ttyout,
856             "Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
857             onoff(rate_put), rate_put, rate_put_incr);
858         fprintf(ttyout,
859             "Socket buffer sizes: send %d, receive %d.\n",
860             sndbuf_size, rcvbuf_size);
861         fprintf(ttyout, "Use of PORT cmds: %s.\n", onoff(sendport));
862         fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4),
863             epsv4bad ? " (disabled for this connection)" : "");
864         fprintf(ttyout, "Command line editing: %s.\n",
865 #ifdef NO_EDITCOMPLETE
866             "support not compiled in"
867 #else   /* !def NO_EDITCOMPLETE */
868             onoff(editing)
869 #endif  /* !def NO_EDITCOMPLETE */
870             );
871         if (macnum > 0) {
872                 int i;
873
874                 fputs("Macros:\n", ttyout);
875                 for (i=0; i<macnum; i++) {
876                         fprintf(ttyout, "\t%s\n", macros[i].mac_name);
877                 }
878         }
879 #endif /* !def NO_STATUS */
880         fprintf(ttyout, "Version: %s %s\n", FTP_PRODUCT, FTP_VERSION);
881         code = 0;
882 }
883
884 /*
885  * Toggle a variable
886  */
887 int
888 togglevar(int argc, char *argv[], int *var, const char *mesg)
889 {
890         if (argc == 1) {
891                 *var = !*var;
892         } else if (argc == 2 && strcasecmp(argv[1], "on") == 0) {
893                 *var = 1;
894         } else if (argc == 2 && strcasecmp(argv[1], "off") == 0) {
895                 *var = 0;
896         } else {
897                 UPRINTF("usage: %s [ on | off ]\n", argv[0]);
898                 return (-1);
899         }
900         if (mesg)
901                 fprintf(ttyout, "%s %s.\n", mesg, onoff(*var));
902         return (*var);
903 }
904
905 /*
906  * Set beep on cmd completed mode.
907  */
908 /*VARARGS*/
909 void
910 setbell(int argc, char *argv[])
911 {
912
913         code = togglevar(argc, argv, &bell, "Bell mode");
914 }
915
916 /*
917  * Set command line editing
918  */
919 /*VARARGS*/
920 void
921 setedit(int argc, char *argv[])
922 {
923
924 #ifdef NO_EDITCOMPLETE
925         if (argc == 0) {
926                 UPRINTF("usage: %s\n", argv[0]);
927                 code = -1;
928                 return;
929         }
930         if (verbose)
931                 fputs("Editing support not compiled in; ignoring command.\n",
932                     ttyout);
933 #else   /* !def NO_EDITCOMPLETE */
934         code = togglevar(argc, argv, &editing, "Editing mode");
935         controlediting();
936 #endif  /* !def NO_EDITCOMPLETE */
937 }
938
939 /*
940  * Turn on packet tracing.
941  */
942 /*VARARGS*/
943 void
944 settrace(int argc, char *argv[])
945 {
946
947         code = togglevar(argc, argv, &trace, "Packet tracing");
948 }
949
950 /*
951  * Toggle hash mark printing during transfers, or set hash mark bytecount.
952  */
953 /*VARARGS*/
954 void
955 sethash(int argc, char *argv[])
956 {
957         if (argc == 1)
958                 hash = !hash;
959         else if (argc != 2) {
960                 UPRINTF("usage: %s [ on | off | bytecount ]\n",
961                     argv[0]);
962                 code = -1;
963                 return;
964         } else if (strcasecmp(argv[1], "on") == 0)
965                 hash = 1;
966         else if (strcasecmp(argv[1], "off") == 0)
967                 hash = 0;
968         else {
969                 int nmark;
970
971                 nmark = strsuftoi(argv[1]);
972                 if (nmark < 1) {
973                         fprintf(ttyout, "mark: bad bytecount value `%s'.\n",
974                             argv[1]);
975                         code = -1;
976                         return;
977                 }
978                 mark = nmark;
979                 hash = 1;
980         }
981         fprintf(ttyout, "Hash mark printing %s", onoff(hash));
982         if (hash)
983                 fprintf(ttyout, " (%d bytes/hash mark)", mark);
984         fputs(".\n", ttyout);
985         if (hash)
986                 progress = 0;
987         code = hash;
988 }
989
990 /*
991  * Turn on printing of server echo's.
992  */
993 /*VARARGS*/
994 void
995 setverbose(int argc, char *argv[])
996 {
997
998         code = togglevar(argc, argv, &verbose, "Verbose mode");
999 }
1000
1001 /*
1002  * Toggle PORT/LPRT cmd use before each data connection.
1003  */
1004 /*VARARGS*/
1005 void
1006 setport(int argc, char *argv[])
1007 {
1008
1009         code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
1010 }
1011
1012 /*
1013  * Toggle transfer progress bar.
1014  */
1015 /*VARARGS*/
1016 void
1017 setprogress(int argc, char *argv[])
1018 {
1019
1020         code = togglevar(argc, argv, &progress, "Progress bar");
1021         if (progress)
1022                 hash = 0;
1023 }
1024
1025 /*
1026  * Turn on interactive prompting during mget, mput, and mdelete.
1027  */
1028 /*VARARGS*/
1029 void
1030 setprompt(int argc, char *argv[])
1031 {
1032
1033         code = togglevar(argc, argv, &interactive, "Interactive mode");
1034 }
1035
1036 /*
1037  * Toggle gate-ftp mode, or set gate-ftp server
1038  */
1039 /*VARARGS*/
1040 void
1041 setgate(int argc, char *argv[])
1042 {
1043         static char gsbuf[MAXHOSTNAMELEN];
1044
1045         if (argc == 0 || argc > 3) {
1046                 UPRINTF(
1047                     "usage: %s [ on | off | gateserver [port] ]\n", argv[0]);
1048                 code = -1;
1049                 return;
1050         } else if (argc < 2) {
1051                 gatemode = !gatemode;
1052         } else {
1053                 if (argc == 2 && strcasecmp(argv[1], "on") == 0)
1054                         gatemode = 1;
1055                 else if (argc == 2 && strcasecmp(argv[1], "off") == 0)
1056                         gatemode = 0;
1057                 else {
1058                         if (argc == 3)
1059                                 gateport = ftp_strdup(argv[2]);
1060                         (void)strlcpy(gsbuf, argv[1], sizeof(gsbuf));
1061                         gateserver = gsbuf;
1062                         gatemode = 1;
1063                 }
1064         }
1065         if (gatemode && (gateserver == NULL || *gateserver == '\0')) {
1066                 fprintf(ttyout,
1067                     "Disabling gate-ftp mode - no gate-ftp server defined.\n");
1068                 gatemode = 0;
1069         } else {
1070                 fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n",
1071                     onoff(gatemode), *gateserver ? gateserver : "(none)",
1072                     gateport);
1073         }
1074         code = gatemode;
1075 }
1076
1077 /*
1078  * Toggle metacharacter interpretation on local file names.
1079  */
1080 /*VARARGS*/
1081 void
1082 setglob(int argc, char *argv[])
1083 {
1084
1085         code = togglevar(argc, argv, &doglob, "Globbing");
1086 }
1087
1088 /*
1089  * Toggle preserving modification times on retrieved files.
1090  */
1091 /*VARARGS*/
1092 void
1093 setpreserve(int argc, char *argv[])
1094 {
1095
1096         code = togglevar(argc, argv, &preserve, "Preserve modification times");
1097 }
1098
1099 /*
1100  * Set debugging mode on/off and/or set level of debugging.
1101  */
1102 /*VARARGS*/
1103 void
1104 setdebug(int argc, char *argv[])
1105 {
1106         if (argc == 0 || argc > 2) {
1107                 UPRINTF("usage: %s [ on | off | debuglevel ]\n", argv[0]);
1108                 code = -1;
1109                 return;
1110         } else if (argc == 2) {
1111                 if (strcasecmp(argv[1], "on") == 0)
1112                         ftp_debug = 1;
1113                 else if (strcasecmp(argv[1], "off") == 0)
1114                         ftp_debug = 0;
1115                 else {
1116                         int val;
1117
1118                         val = strsuftoi(argv[1]);
1119                         if (val < 0) {
1120                                 fprintf(ttyout, "%s: bad debugging value.\n",
1121                                     argv[1]);
1122                                 code = -1;
1123                                 return;
1124                         }
1125                         ftp_debug = val;
1126                 }
1127         } else
1128                 ftp_debug = !ftp_debug;
1129         if (ftp_debug)
1130                 options |= SO_DEBUG;
1131         else
1132                 options &= ~SO_DEBUG;
1133         fprintf(ttyout, "Debugging %s (ftp_debug=%d).\n", onoff(ftp_debug), ftp_debug);
1134         code = ftp_debug > 0;
1135 }
1136
1137 /*
1138  * Set current working directory on remote machine.
1139  */
1140 void
1141 cd(int argc, char *argv[])
1142 {
1143         int r;
1144
1145         if (argc == 0 || argc > 2 ||
1146             (argc == 1 && !another(&argc, &argv, "remote-directory"))) {
1147                 UPRINTF("usage: %s remote-directory\n", argv[0]);
1148                 code = -1;
1149                 return;
1150         }
1151         r = command("CWD %s", argv[1]);
1152         if (r == ERROR && code == 500) {
1153                 if (verbose)
1154                         fputs("CWD command not recognized, trying XCWD.\n",
1155                             ttyout);
1156                 r = command("XCWD %s", argv[1]);
1157         }
1158         if (r == COMPLETE) {
1159                 dirchange = 1;
1160                 updateremotecwd();
1161         }
1162 }
1163
1164 /*
1165  * Set current working directory on local machine.
1166  */
1167 void
1168 lcd(int argc, char *argv[])
1169 {
1170         char *locdir;
1171
1172         code = -1;
1173         if (argc == 1) {
1174                 argc++;
1175                 argv[1] = localhome;
1176         }
1177         if (argc != 2) {
1178                 UPRINTF("usage: %s [local-directory]\n", argv[0]);
1179                 return;
1180         }
1181         if ((locdir = globulize(argv[1])) == NULL)
1182                 return;
1183         if (chdir(locdir) == -1)
1184                 warn("Can't chdir `%s'", locdir);
1185         else {
1186                 updatelocalcwd();
1187                 if (localcwd[0]) {
1188                         fprintf(ttyout, "Local directory now: %s\n", localcwd);
1189                         code = 0;
1190                 } else {
1191                         fprintf(ttyout, "Unable to determine local directory\n");
1192                 }
1193         }
1194         (void)free(locdir);
1195 }
1196
1197 /*
1198  * Delete a single file.
1199  */
1200 void
1201 delete(int argc, char *argv[])
1202 {
1203
1204         if (argc == 0 || argc > 2 ||
1205             (argc == 1 && !another(&argc, &argv, "remote-file"))) {
1206                 UPRINTF("usage: %s remote-file\n", argv[0]);
1207                 code = -1;
1208                 return;
1209         }
1210         if (command("DELE %s", argv[1]) == COMPLETE)
1211                 dirchange = 1;
1212 }
1213
1214 /*
1215  * Delete multiple files.
1216  */
1217 void
1218 mdelete(int argc, char *argv[])
1219 {
1220         sigfunc oldintr;
1221         int ointer;
1222         char *cp;
1223
1224         if (argc == 0 ||
1225             (argc == 1 && !another(&argc, &argv, "remote-files"))) {
1226                 UPRINTF("usage: %s [remote-files]\n", argv[0]);
1227                 code = -1;
1228                 return;
1229         }
1230         mflag = 1;
1231         oldintr = xsignal(SIGINT, mintr);
1232         if (sigsetjmp(jabort, 1))
1233                 mabort(argv[0]);
1234         while ((cp = remglob(argv, 0, NULL)) != NULL) {
1235                 if (*cp == '\0') {
1236                         mflag = 0;
1237                         continue;
1238                 }
1239                 if (mflag && confirm(argv[0], cp)) {
1240                         if (command("DELE %s", cp) == COMPLETE)
1241                                 dirchange = 1;
1242                         if (!mflag && fromatty) {
1243                                 ointer = interactive;
1244                                 interactive = 1;
1245                                 if (confirm(argv[0], NULL)) {
1246                                         mflag++;
1247                                 }
1248                                 interactive = ointer;
1249                         }
1250                 }
1251         }
1252         (void)xsignal(SIGINT, oldintr);
1253         mflag = 0;
1254 }
1255
1256 /*
1257  * Rename a remote file.
1258  */
1259 void
1260 renamefile(int argc, char *argv[])
1261 {
1262
1263         if (argc == 0 || (argc == 1 && !another(&argc, &argv, "from-name")))
1264                 goto usage;
1265         if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
1266  usage:
1267                 UPRINTF("usage: %s from-name to-name\n", argv[0]);
1268                 code = -1;
1269                 return;
1270         }
1271         if (command("RNFR %s", argv[1]) == CONTINUE &&
1272             command("RNTO %s", argv[2]) == COMPLETE)
1273                 dirchange = 1;
1274 }
1275
1276 /*
1277  * Get a directory listing of remote files.
1278  * Supports being invoked as:
1279  *      cmd             runs
1280  *      ---             ----
1281  *      dir, ls         LIST
1282  *      mlsd            MLSD
1283  *      nlist           NLST
1284  *      pdir, pls       LIST |$PAGER
1285  *      mmlsd           MLSD |$PAGER
1286  */
1287 void
1288 ls(int argc, char *argv[])
1289 {
1290         const char *cmd;
1291         char *remdir, *locfile;
1292         int freelocfile, pagecmd, mlsdcmd;
1293
1294         remdir = NULL;
1295         locfile = "-";
1296         freelocfile = pagecmd = mlsdcmd = 0;
1297                         /*
1298                          * the only commands that start with `p' are
1299                          * the `pager' versions.
1300                          */
1301         if (argv[0][0] == 'p')
1302                 pagecmd = 1;
1303         if (strcmp(argv[0] + pagecmd , "mlsd") == 0) {
1304                 if (! features[FEAT_MLST]) {
1305                         fprintf(ttyout,
1306                            "MLSD is not supported by the remote server.\n");
1307                         return;
1308                 }
1309                 mlsdcmd = 1;
1310         }
1311         if (argc == 0)
1312                 goto usage;
1313
1314         if (mlsdcmd)
1315                 cmd = "MLSD";
1316         else if (strcmp(argv[0] + pagecmd, "nlist") == 0)
1317                 cmd = "NLST";
1318         else
1319                 cmd = "LIST";
1320
1321         if (argc > 1)
1322                 remdir = argv[1];
1323         if (argc > 2)
1324                 locfile = argv[2];
1325         if (argc > 3 || ((pagecmd | mlsdcmd) && argc > 2)) {
1326  usage:
1327                 if (pagecmd || mlsdcmd)
1328                         UPRINTF("usage: %s [remote-path]\n", argv[0]);
1329                 else
1330                         UPRINTF("usage: %s [remote-path [local-file]]\n",
1331                             argv[0]);
1332                 code = -1;
1333                 goto freels;
1334         }
1335
1336         if (pagecmd) {
1337                 char *p;
1338                 size_t len;
1339
1340                 p = getoptionvalue("pager");
1341                 if (EMPTYSTRING(p))
1342                         p = DEFAULTPAGER;
1343                 len = strlen(p) + 2;
1344                 locfile = ftp_malloc(len);
1345                 locfile[0] = '|';
1346                 (void)strlcpy(locfile + 1, p, len - 1);
1347                 freelocfile = 1;
1348         } else if ((strcmp(locfile, "-") != 0) && *locfile != '|') {
1349                 if ((locfile = globulize(locfile)) == NULL ||
1350                     !confirm("output to local-file:", locfile)) {
1351                         code = -1;
1352                         goto freels;
1353                 }
1354                 freelocfile = 1;
1355         }
1356         recvrequest(cmd, locfile, remdir, "w", 0, 0);
1357  freels:
1358         if (freelocfile && locfile)
1359                 (void)free(locfile);
1360 }
1361
1362 /*
1363  * Get a directory listing of multiple remote files.
1364  */
1365 void
1366 mls(int argc, char *argv[])
1367 {
1368         sigfunc oldintr;
1369         int ointer, i;
1370         int dolist;
1371         char *mode, *dest, *odest;
1372
1373         if (argc == 0)
1374                 goto usage;
1375         if (argc < 2 && !another(&argc, &argv, "remote-files"))
1376                 goto usage;
1377         if (argc < 3 && !another(&argc, &argv, "local-file")) {
1378  usage:
1379                 UPRINTF("usage: %s remote-files local-file\n", argv[0]);
1380                 code = -1;
1381                 return;
1382         }
1383         odest = dest = argv[argc - 1];
1384         argv[argc - 1] = NULL;
1385         if (strcmp(dest, "-") && *dest != '|')
1386                 if (((dest = globulize(dest)) == NULL) ||
1387                     !confirm("output to local-file:", dest)) {
1388                         code = -1;
1389                         return;
1390         }
1391         dolist = strcmp(argv[0], "mls");
1392         mflag = 1;
1393         oldintr = xsignal(SIGINT, mintr);
1394         if (sigsetjmp(jabort, 1))
1395                 mabort(argv[0]);
1396         for (i = 1; mflag && i < argc-1 && connected; i++) {
1397                 mode = (i == 1) ? "w" : "a";
1398                 recvrequest(dolist ? "LIST" : "NLST", dest, argv[i], mode,
1399                     0, 0);
1400                 if (!mflag && fromatty) {
1401                         ointer = interactive;
1402                         interactive = 1;
1403                         if (confirm(argv[0], NULL)) {
1404                                 mflag++;
1405                         }
1406                         interactive = ointer;
1407                 }
1408         }
1409         (void)xsignal(SIGINT, oldintr);
1410         mflag = 0;
1411         if (dest != odest)                      /* free up after globulize() */
1412                 free(dest);
1413 }
1414
1415 /*
1416  * Do a shell escape
1417  */
1418 /*ARGSUSED*/
1419 void
1420 shell(int argc, char *argv[])
1421 {
1422         pid_t pid;
1423         sigfunc oldintr;
1424         char shellnam[MAXPATHLEN], *shell, *namep;
1425         int wait_status;
1426
1427         if (argc == 0) {
1428                 UPRINTF("usage: %s [command [args]]\n", argv[0]);
1429                 code = -1;
1430                 return;
1431         }
1432         oldintr = xsignal(SIGINT, SIG_IGN);
1433         if ((pid = fork()) == 0) {
1434                 for (pid = 3; pid < 20; pid++)
1435                         (void)close(pid);
1436                 (void)xsignal(SIGINT, SIG_DFL);
1437                 shell = getenv("SHELL");
1438                 if (shell == NULL)
1439                         shell = _PATH_BSHELL;
1440                 namep = strrchr(shell, '/');
1441                 if (namep == NULL)
1442                         namep = shell;
1443                 else
1444                         namep++;
1445                 (void)strlcpy(shellnam, namep, sizeof(shellnam));
1446                 if (ftp_debug) {
1447                         fputs(shell, ttyout);
1448                         putc('\n', ttyout);
1449                 }
1450                 if (argc > 1) {
1451                         execl(shell, shellnam, "-c", altarg, (char *)0);
1452                 }
1453                 else {
1454                         execl(shell, shellnam, (char *)0);
1455                 }
1456                 warn("Can't execute `%s'", shell);
1457                 code = -1;
1458                 exit(1);
1459         }
1460         if (pid > 0)
1461                 while (wait(&wait_status) != pid)
1462                         ;
1463         (void)xsignal(SIGINT, oldintr);
1464         if (pid == -1) {
1465                 warn("Can't fork a subshell; try again later");
1466                 code = -1;
1467         } else
1468                 code = 0;
1469 }
1470
1471 /*
1472  * Send new user information (re-login)
1473  */
1474 void
1475 user(int argc, char *argv[])
1476 {
1477         char *password;
1478         char emptypass[] = "";
1479         int n, aflag = 0;
1480
1481         if (argc == 0)
1482                 goto usage;
1483         if (argc < 2)
1484                 (void)another(&argc, &argv, "username");
1485         if (argc < 2 || argc > 4) {
1486  usage:
1487                 UPRINTF("usage: %s username [password [account]]\n",
1488                     argv[0]);
1489                 code = -1;
1490                 return;
1491         }
1492         n = command("USER %s", argv[1]);
1493         if (n == CONTINUE) {
1494                 if (argc < 3) {
1495                         password = getpass("Password: ");
1496                         if (password == NULL)
1497                                 password = emptypass;
1498                 } else {
1499                         password = argv[2];
1500                 }
1501                 n = command("PASS %s", password);
1502                 memset(password, 0, strlen(password));
1503         }
1504         if (n == CONTINUE) {
1505                 aflag++;
1506                 if (argc < 4) {
1507                         password = getpass("Account: ");
1508                         if (password == NULL)
1509                                 password = emptypass;
1510                 } else {
1511                         password = argv[3];
1512                 }
1513                 n = command("ACCT %s", password);
1514                 memset(password, 0, strlen(password));
1515         }
1516         if (n != COMPLETE) {
1517                 fputs("Login failed.\n", ttyout);
1518                 return;
1519         }
1520         if (!aflag && argc == 4) {
1521                 password = argv[3];
1522                 (void)command("ACCT %s", password);
1523                 memset(password, 0, strlen(password));
1524         }
1525         connected = -1;
1526         getremoteinfo();
1527 }
1528
1529 /*
1530  * Print working directory on remote machine.
1531  */
1532 /*VARARGS*/
1533 void
1534 pwd(int argc, char *argv[])
1535 {
1536
1537         code = -1;
1538         if (argc != 1) {
1539                 UPRINTF("usage: %s\n", argv[0]);
1540                 return;
1541         }
1542         if (! remotecwd[0])
1543                 updateremotecwd();
1544         if (! remotecwd[0])
1545                 fprintf(ttyout, "Unable to determine remote directory\n");
1546         else {
1547                 fprintf(ttyout, "Remote directory: %s\n", remotecwd);
1548                 code = 0;
1549         }
1550 }
1551
1552 /*
1553  * Print working directory on local machine.
1554  */
1555 void
1556 lpwd(int argc, char *argv[])
1557 {
1558
1559         code = -1;
1560         if (argc != 1) {
1561                 UPRINTF("usage: %s\n", argv[0]);
1562                 return;
1563         }
1564         if (! localcwd[0])
1565                 updatelocalcwd();
1566         if (! localcwd[0])
1567                 fprintf(ttyout, "Unable to determine local directory\n");
1568         else {
1569                 fprintf(ttyout, "Local directory: %s\n", localcwd);
1570                 code = 0;
1571         }
1572 }
1573
1574 /*
1575  * Make a directory.
1576  */
1577 void
1578 makedir(int argc, char *argv[])
1579 {
1580         int r;
1581
1582         if (argc == 0 || argc > 2 ||
1583             (argc == 1 && !another(&argc, &argv, "directory-name"))) {
1584                 UPRINTF("usage: %s directory-name\n", argv[0]);
1585                 code = -1;
1586                 return;
1587         }
1588         r = command("MKD %s", argv[1]);
1589         if (r == ERROR && code == 500) {
1590                 if (verbose)
1591                         fputs("MKD command not recognized, trying XMKD.\n",
1592                             ttyout);
1593                 r = command("XMKD %s", argv[1]);
1594         }
1595         if (r == COMPLETE)
1596                 dirchange = 1;
1597 }
1598
1599 /*
1600  * Remove a directory.
1601  */
1602 void
1603 removedir(int argc, char *argv[])
1604 {
1605         int r;
1606
1607         if (argc == 0 || argc > 2 ||
1608             (argc == 1 && !another(&argc, &argv, "directory-name"))) {
1609                 UPRINTF("usage: %s directory-name\n", argv[0]);
1610                 code = -1;
1611                 return;
1612         }
1613         r = command("RMD %s", argv[1]);
1614         if (r == ERROR && code == 500) {
1615                 if (verbose)
1616                         fputs("RMD command not recognized, trying XRMD.\n",
1617                             ttyout);
1618                 r = command("XRMD %s", argv[1]);
1619         }
1620         if (r == COMPLETE)
1621                 dirchange = 1;
1622 }
1623
1624 /*
1625  * Send a line, verbatim, to the remote machine.
1626  */
1627 void
1628 quote(int argc, char *argv[])
1629 {
1630
1631         if (argc == 0 ||
1632             (argc == 1 && !another(&argc, &argv, "command line to send"))) {
1633                 UPRINTF("usage: %s line-to-send\n", argv[0]);
1634                 code = -1;
1635                 return;
1636         }
1637         quote1("", argc, argv);
1638 }
1639
1640 /*
1641  * Send a SITE command to the remote machine.  The line
1642  * is sent verbatim to the remote machine, except that the
1643  * word "SITE" is added at the front.
1644  */
1645 void
1646 site(int argc, char *argv[])
1647 {
1648
1649         if (argc == 0 ||
1650             (argc == 1 && !another(&argc, &argv, "arguments to SITE command"))){
1651                 UPRINTF("usage: %s line-to-send\n", argv[0]);
1652                 code = -1;
1653                 return;
1654         }
1655         quote1("SITE ", argc, argv);
1656 }
1657
1658 /*
1659  * Turn argv[1..argc) into a space-separated string, then prepend initial text.
1660  * Send the result as a one-line command and get response.
1661  */
1662 void
1663 quote1(const char *initial, int argc, char *argv[])
1664 {
1665         int i;
1666         char buf[BUFSIZ];               /* must be >= sizeof(line) */
1667
1668         (void)strlcpy(buf, initial, sizeof(buf));
1669         for (i = 1; i < argc; i++) {
1670                 (void)strlcat(buf, argv[i], sizeof(buf));
1671                 if (i < (argc - 1))
1672                         (void)strlcat(buf, " ", sizeof(buf));
1673         }
1674         if (command("%s", buf) == PRELIM) {
1675                 while (getreply(0) == PRELIM)
1676                         continue;
1677         }
1678         dirchange = 1;
1679 }
1680
1681 void
1682 do_chmod(int argc, char *argv[])
1683 {
1684
1685         if (argc == 0 || (argc == 1 && !another(&argc, &argv, "mode")))
1686                 goto usage;
1687         if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
1688  usage:
1689                 UPRINTF("usage: %s mode remote-file\n", argv[0]);
1690                 code = -1;
1691                 return;
1692         }
1693         (void)command("SITE CHMOD %s %s", argv[1], argv[2]);
1694 }
1695
1696 #define COMMAND_1ARG(argc, argv, cmd)                   \
1697         if (argc == 1)                                  \
1698                 command(cmd);                           \
1699         else                                            \
1700                 command(cmd " %s", argv[1])
1701
1702 void
1703 do_umask(int argc, char *argv[])
1704 {
1705         int oldverbose = verbose;
1706
1707         if (argc == 0) {
1708                 UPRINTF("usage: %s [umask]\n", argv[0]);
1709                 code = -1;
1710                 return;
1711         }
1712         verbose = 1;
1713         COMMAND_1ARG(argc, argv, "SITE UMASK");
1714         verbose = oldverbose;
1715 }
1716
1717 void
1718 idlecmd(int argc, char *argv[])
1719 {
1720         int oldverbose = verbose;
1721
1722         if (argc < 1 || argc > 2) {
1723                 UPRINTF("usage: %s [seconds]\n", argv[0]);
1724                 code = -1;
1725                 return;
1726         }
1727         verbose = 1;
1728         COMMAND_1ARG(argc, argv, "SITE IDLE");
1729         verbose = oldverbose;
1730 }
1731
1732 /*
1733  * Ask the other side for help.
1734  */
1735 void
1736 rmthelp(int argc, char *argv[])
1737 {
1738         int oldverbose = verbose;
1739
1740         if (argc == 0) {
1741                 UPRINTF("usage: %s\n", argv[0]);
1742                 code = -1;
1743                 return;
1744         }
1745         verbose = 1;
1746         COMMAND_1ARG(argc, argv, "HELP");
1747         verbose = oldverbose;
1748 }
1749
1750 /*
1751  * Terminate session and exit.
1752  * May be called with 0, NULL.
1753  */
1754 /*VARARGS*/
1755 void
1756 quit(int argc, char *argv[])
1757 {
1758
1759                         /* this may be called with argc == 0, argv == NULL */
1760         if (argc == 0 && argv != NULL) {
1761                 UPRINTF("usage: %s\n", argv[0]);
1762                 code = -1;
1763                 return;
1764         }
1765         if (connected)
1766                 disconnect(0, NULL);
1767         pswitch(1);
1768         if (connected)
1769                 disconnect(0, NULL);
1770         exit(0);
1771 }
1772
1773 /*
1774  * Terminate session, but don't exit.
1775  * May be called with 0, NULL.
1776  */
1777 void
1778 disconnect(int argc, char *argv[])
1779 {
1780
1781                         /* this may be called with argc == 0, argv == NULL */
1782         if (argc == 0 && argv != NULL) {
1783                 UPRINTF("usage: %s\n", argv[0]);
1784                 code = -1;
1785                 return;
1786         }
1787         if (!connected)
1788                 return;
1789         (void)command("QUIT");
1790         cleanuppeer();
1791 }
1792
1793 void
1794 account(int argc, char *argv[])
1795 {
1796         char *ap;
1797         char emptypass[] = "";
1798
1799         if (argc == 0 || argc > 2) {
1800                 UPRINTF("usage: %s [password]\n", argv[0]);
1801                 code = -1;
1802                 return;
1803         }
1804         else if (argc == 2)
1805                 ap = argv[1];
1806         else {
1807                 ap = getpass("Account:");
1808                 if (ap == NULL)
1809                         ap = emptypass;
1810         }
1811         (void)command("ACCT %s", ap);
1812         memset(ap, 0, strlen(ap));
1813 }
1814
1815 sigjmp_buf abortprox;
1816
1817 void
1818 proxabort(int notused)
1819 {
1820
1821         sigint_raised = 1;
1822         alarmtimer(0);
1823         if (!proxy) {
1824                 pswitch(1);
1825         }
1826         if (connected) {
1827                 proxflag = 1;
1828         }
1829         else {
1830                 proxflag = 0;
1831         }
1832         pswitch(0);
1833         siglongjmp(abortprox, 1);
1834 }
1835
1836 void
1837 doproxy(int argc, char *argv[])
1838 {
1839         struct cmd *c;
1840         int cmdpos;
1841         sigfunc oldintr;
1842
1843         if (argc == 0 || (argc == 1 && !another(&argc, &argv, "command"))) {
1844                 UPRINTF("usage: %s command\n", argv[0]);
1845                 code = -1;
1846                 return;
1847         }
1848         c = getcmd(argv[1]);
1849         if (c == (struct cmd *) -1) {
1850                 fputs("?Ambiguous command.\n", ttyout);
1851                 code = -1;
1852                 return;
1853         }
1854         if (c == 0) {
1855                 fputs("?Invalid command.\n", ttyout);
1856                 code = -1;
1857                 return;
1858         }
1859         if (!c->c_proxy) {
1860                 fputs("?Invalid proxy command.\n", ttyout);
1861                 code = -1;
1862                 return;
1863         }
1864         if (sigsetjmp(abortprox, 1)) {
1865                 code = -1;
1866                 return;
1867         }
1868         oldintr = xsignal(SIGINT, proxabort);
1869         pswitch(1);
1870         if (c->c_conn && !connected) {
1871                 fputs("Not connected.\n", ttyout);
1872                 pswitch(0);
1873                 (void)xsignal(SIGINT, oldintr);
1874                 code = -1;
1875                 return;
1876         }
1877         cmdpos = strcspn(line, " \t");
1878         if (cmdpos > 0)         /* remove leading "proxy " from input buffer */
1879                 memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
1880         argv[1] = c->c_name;
1881         (*c->c_handler)(argc-1, argv+1);
1882         if (connected) {
1883                 proxflag = 1;
1884         }
1885         else {
1886                 proxflag = 0;
1887         }
1888         pswitch(0);
1889         (void)xsignal(SIGINT, oldintr);
1890 }
1891
1892 void
1893 setcase(int argc, char *argv[])
1894 {
1895
1896         code = togglevar(argc, argv, &mcase, "Case mapping");
1897 }
1898
1899 /*
1900  * convert the given name to lower case if it's all upper case, into
1901  * a static buffer which is returned to the caller
1902  */
1903 static const char *
1904 docase(char *dst, size_t dlen, const char *src)
1905 {
1906         size_t i;
1907         int dochange = 1;
1908
1909         for (i = 0; src[i] != '\0' && i < dlen - 1; i++) {
1910                 dst[i] = src[i];
1911                 if (islower((unsigned char)dst[i]))
1912                         dochange = 0;
1913         }
1914         dst[i] = '\0';
1915
1916         if (dochange) {
1917                 for (i = 0; dst[i] != '\0'; i++)
1918                         if (isupper((unsigned char)dst[i]))
1919                                 dst[i] = tolower((unsigned char)dst[i]);
1920         }
1921         return dst;
1922 }
1923
1924 void
1925 setcr(int argc, char *argv[])
1926 {
1927
1928         code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
1929 }
1930
1931 void
1932 setntrans(int argc, char *argv[])
1933 {
1934
1935         if (argc == 0 || argc > 3) {
1936                 UPRINTF("usage: %s [inchars [outchars]]\n", argv[0]);
1937                 code = -1;
1938                 return;
1939         }
1940         if (argc == 1) {
1941                 ntflag = 0;
1942                 fputs("Ntrans off.\n", ttyout);
1943                 code = ntflag;
1944                 return;
1945         }
1946         ntflag++;
1947         code = ntflag;
1948         (void)strlcpy(ntin, argv[1], sizeof(ntin));
1949         if (argc == 2) {
1950                 ntout[0] = '\0';
1951                 return;
1952         }
1953         (void)strlcpy(ntout, argv[2], sizeof(ntout));
1954 }
1955
1956 static const char *
1957 dotrans(char *dst, size_t dlen, const char *src)
1958 {
1959         const char *cp1;
1960         char *cp2 = dst;
1961         size_t i, ostop;
1962
1963         for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
1964                 continue;
1965         for (cp1 = src; *cp1; cp1++) {
1966                 int found = 0;
1967                 for (i = 0; *(ntin + i) && i < 16; i++) {
1968                         if (*cp1 == *(ntin + i)) {
1969                                 found++;
1970                                 if (i < ostop) {
1971                                         *cp2++ = *(ntout + i);
1972                                         if (cp2 - dst >= dlen - 1)
1973                                                 goto out;
1974                                 }
1975                                 break;
1976                         }
1977                 }
1978                 if (!found) {
1979                         *cp2++ = *cp1;
1980                 }
1981         }
1982 out:
1983         *cp2 = '\0';
1984         return dst;
1985 }
1986
1987 void
1988 setnmap(int argc, char *argv[])
1989 {
1990         char *cp;
1991
1992         if (argc == 1) {
1993                 mapflag = 0;
1994                 fputs("Nmap off.\n", ttyout);
1995                 code = mapflag;
1996                 return;
1997         }
1998         if (argc == 0 ||
1999             (argc < 3 && !another(&argc, &argv, "mapout")) || argc > 3) {
2000                 UPRINTF("usage: %s [mapin mapout]\n", argv[0]);
2001                 code = -1;
2002                 return;
2003         }
2004         mapflag = 1;
2005         code = 1;
2006         cp = strchr(altarg, ' ');
2007         if (proxy) {
2008                 while(*++cp == ' ')
2009                         continue;
2010                 altarg = cp;
2011                 cp = strchr(altarg, ' ');
2012         }
2013         *cp = '\0';
2014         (void)strlcpy(mapin, altarg, MAXPATHLEN);
2015         while (*++cp == ' ')
2016                 continue;
2017         (void)strlcpy(mapout, cp, MAXPATHLEN);
2018 }
2019
2020 static const char *
2021 domap(char *dst, size_t dlen, const char *src)
2022 {
2023         const char *cp1 = src;
2024         char *cp2 = mapin;
2025         const char *tp[9], *te[9];
2026         int i, toks[9], toknum = 0, match = 1;
2027
2028         for (i=0; i < 9; ++i) {
2029                 toks[i] = 0;
2030         }
2031         while (match && *cp1 && *cp2) {
2032                 switch (*cp2) {
2033                         case '\\':
2034                                 if (*++cp2 != *cp1) {
2035                                         match = 0;
2036                                 }
2037                                 break;
2038                         case '$':
2039                                 if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
2040                                         if (*cp1 != *(++cp2+1)) {
2041                                                 toks[toknum = *cp2 - '1']++;
2042                                                 tp[toknum] = cp1;
2043                                                 while (*++cp1 && *(cp2+1)
2044                                                         != *cp1);
2045                                                 te[toknum] = cp1;
2046                                         }
2047                                         cp2++;
2048                                         break;
2049                                 }
2050                                 /* FALLTHROUGH */
2051                         default:
2052                                 if (*cp2 != *cp1) {
2053                                         match = 0;
2054                                 }
2055                                 break;
2056                 }
2057                 if (match && *cp1) {
2058                         cp1++;
2059                 }
2060                 if (match && *cp2) {
2061                         cp2++;
2062                 }
2063         }
2064         if (!match && *cp1) /* last token mismatch */
2065         {
2066                 toks[toknum] = 0;
2067         }
2068         cp2 = dst;
2069         *cp2 = '\0';
2070         cp1 = mapout;
2071         while (*cp1) {
2072                 match = 0;
2073                 switch (*cp1) {
2074                         case '\\':
2075                                 if (*(cp1 + 1)) {
2076                                         *cp2++ = *++cp1;
2077                                 }
2078                                 break;
2079                         case '[':
2080 LOOP:
2081                                 if (*++cp1 == '$' &&
2082                                     isdigit((unsigned char)*(cp1+1))) {
2083                                         if (*++cp1 == '0') {
2084                                                 const char *cp3 = src;
2085
2086                                                 while (*cp3) {
2087                                                         *cp2++ = *cp3++;
2088                                                 }
2089                                                 match = 1;
2090                                         }
2091                                         else if (toks[toknum = *cp1 - '1']) {
2092                                                 const char *cp3 = tp[toknum];
2093
2094                                                 while (cp3 != te[toknum]) {
2095                                                         *cp2++ = *cp3++;
2096                                                 }
2097                                                 match = 1;
2098                                         }
2099                                 }
2100                                 else {
2101                                         while (*cp1 && *cp1 != ',' &&
2102                                             *cp1 != ']') {
2103                                                 if (*cp1 == '\\') {
2104                                                         cp1++;
2105                                                 }
2106                                                 else if (*cp1 == '$' &&
2107                                                     isdigit((unsigned char)*(cp1+1))) {
2108                                                         if (*++cp1 == '0') {
2109                                                            const char *cp3 = src;
2110
2111                                                            while (*cp3) {
2112                                                                 *cp2++ = *cp3++;
2113                                                            }
2114                                                         }
2115                                                         else if (toks[toknum =
2116                                                             *cp1 - '1']) {
2117                                                            const char *cp3=tp[toknum];
2118
2119                                                            while (cp3 !=
2120                                                                   te[toknum]) {
2121                                                                 *cp2++ = *cp3++;
2122                                                            }
2123                                                         }
2124                                                 }
2125                                                 else if (*cp1) {
2126                                                         *cp2++ = *cp1++;
2127                                                 }
2128                                         }
2129                                         if (!*cp1) {
2130                                                 fputs(
2131                                                 "nmap: unbalanced brackets.\n",
2132                                                     ttyout);
2133                                                 return (src);
2134                                         }
2135                                         match = 1;
2136                                         cp1--;
2137                                 }
2138                                 if (match) {
2139                                         while (*++cp1 && *cp1 != ']') {
2140                                               if (*cp1 == '\\' && *(cp1 + 1)) {
2141                                                         cp1++;
2142                                               }
2143                                         }
2144                                         if (!*cp1) {
2145                                                 fputs(
2146                                                 "nmap: unbalanced brackets.\n",
2147                                                     ttyout);
2148                                                 return (src);
2149                                         }
2150                                         break;
2151                                 }
2152                                 switch (*++cp1) {
2153                                         case ',':
2154                                                 goto LOOP;
2155                                         case ']':
2156                                                 break;
2157                                         default:
2158                                                 cp1--;
2159                                                 goto LOOP;
2160                                 }
2161                                 break;
2162                         case '$':
2163                                 if (isdigit((unsigned char)*(cp1 + 1))) {
2164                                         if (*++cp1 == '0') {
2165                                                 const char *cp3 = src;
2166
2167                                                 while (*cp3) {
2168                                                         *cp2++ = *cp3++;
2169                                                 }
2170                                         }
2171                                         else if (toks[toknum = *cp1 - '1']) {
2172                                                 const char *cp3 = tp[toknum];
2173
2174                                                 while (cp3 != te[toknum]) {
2175                                                         *cp2++ = *cp3++;
2176                                                 }
2177                                         }
2178                                         break;
2179                                 }
2180                                 /* intentional drop through */
2181                         default:
2182                                 *cp2++ = *cp1;
2183                                 break;
2184                 }
2185                 cp1++;
2186         }
2187         *cp2 = '\0';
2188         return *dst ? dst : src;
2189 }
2190
2191 void
2192 setpassive(int argc, char *argv[])
2193 {
2194
2195         if (argc == 1) {
2196                 passivemode = !passivemode;
2197                 activefallback = passivemode;
2198         } else if (argc != 2) {
2199  passiveusage:
2200                 UPRINTF("usage: %s [ on | off | auto ]\n", argv[0]);
2201                 code = -1;
2202                 return;
2203         } else if (strcasecmp(argv[1], "on") == 0) {
2204                 passivemode = 1;
2205                 activefallback = 0;
2206         } else if (strcasecmp(argv[1], "off") == 0) {
2207                 passivemode = 0;
2208                 activefallback = 0;
2209         } else if (strcasecmp(argv[1], "auto") == 0) {
2210                 passivemode = 1;
2211                 activefallback = 1;
2212         } else
2213                 goto passiveusage;
2214         fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
2215             onoff(passivemode), onoff(activefallback));
2216         code = passivemode;
2217 }
2218
2219 void
2220 setepsv4(int argc, char *argv[])
2221 {
2222
2223         code = togglevar(argc, argv, &epsv4,
2224             verbose ? "EPSV/EPRT on IPv4" : NULL);
2225         epsv4bad = 0;
2226 }
2227
2228 void
2229 setsunique(int argc, char *argv[])
2230 {
2231
2232         code = togglevar(argc, argv, &sunique, "Store unique");
2233 }
2234
2235 void
2236 setrunique(int argc, char *argv[])
2237 {
2238
2239         code = togglevar(argc, argv, &runique, "Receive unique");
2240 }
2241
2242 int
2243 parserate(int argc, char *argv[], int cmdlineopt)
2244 {
2245         int dir, max, incr, showonly;
2246         sigfunc oldusr1, oldusr2;
2247
2248         if (argc > 4 || (argc < (cmdlineopt ? 3 : 2))) {
2249  usage:
2250                 if (cmdlineopt)
2251                         UPRINTF(
2252         "usage: %s (all|get|put),maximum-bytes[,increment-bytes]]\n",
2253                             argv[0]);
2254                 else
2255                         UPRINTF(
2256         "usage: %s (all|get|put) [maximum-bytes [increment-bytes]]\n",
2257                             argv[0]);
2258                 return -1;
2259         }
2260         dir = max = incr = showonly = 0;
2261 #define RATE_GET        1
2262 #define RATE_PUT        2
2263 #define RATE_ALL        (RATE_GET | RATE_PUT)
2264
2265         if (strcasecmp(argv[1], "all") == 0)
2266                 dir = RATE_ALL;
2267         else if (strcasecmp(argv[1], "get") == 0)
2268                 dir = RATE_GET;
2269         else if (strcasecmp(argv[1], "put") == 0)
2270                 dir = RATE_PUT;
2271         else
2272                 goto usage;
2273
2274         if (argc >= 3) {
2275                 if ((max = strsuftoi(argv[2])) < 0)
2276                         goto usage;
2277         } else
2278                 showonly = 1;
2279
2280         if (argc == 4) {
2281                 if ((incr = strsuftoi(argv[3])) <= 0)
2282                         goto usage;
2283         } else
2284                 incr = DEFAULTINCR;
2285
2286         oldusr1 = xsignal(SIGUSR1, SIG_IGN);
2287         oldusr2 = xsignal(SIGUSR2, SIG_IGN);
2288         if (dir & RATE_GET) {
2289                 if (!showonly) {
2290                         rate_get = max;
2291                         rate_get_incr = incr;
2292                 }
2293                 if (!cmdlineopt || verbose)
2294                         fprintf(ttyout,
2295                 "Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
2296                             onoff(rate_get), rate_get, rate_get_incr);
2297         }
2298         if (dir & RATE_PUT) {
2299                 if (!showonly) {
2300                         rate_put = max;
2301                         rate_put_incr = incr;
2302                 }
2303                 if (!cmdlineopt || verbose)
2304                         fprintf(ttyout,
2305                 "Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
2306                             onoff(rate_put), rate_put, rate_put_incr);
2307         }
2308         (void)xsignal(SIGUSR1, oldusr1);
2309         (void)xsignal(SIGUSR2, oldusr2);
2310         return 0;
2311 }
2312
2313 void
2314 setrate(int argc, char *argv[])
2315 {
2316
2317         code = parserate(argc, argv, 0);
2318 }
2319
2320 /* change directory to parent directory */
2321 void
2322 cdup(int argc, char *argv[])
2323 {
2324         int r;
2325
2326         if (argc == 0) {
2327                 UPRINTF("usage: %s\n", argv[0]);
2328                 code = -1;
2329                 return;
2330         }
2331         r = command("CDUP");
2332         if (r == ERROR && code == 500) {
2333                 if (verbose)
2334                         fputs("CDUP command not recognized, trying XCUP.\n",
2335                             ttyout);
2336                 r = command("XCUP");
2337         }
2338         if (r == COMPLETE) {
2339                 dirchange = 1;
2340                 updateremotecwd();
2341         }
2342 }
2343
2344 /*
2345  * Restart transfer at specific point
2346  */
2347 void
2348 restart(int argc, char *argv[])
2349 {
2350
2351         if (argc == 0 || argc > 2) {
2352                 UPRINTF("usage: %s [restart-point]\n", argv[0]);
2353                 code = -1;
2354                 return;
2355         }
2356         if (! features[FEAT_REST_STREAM]) {
2357                 fprintf(ttyout,
2358                     "Restart is not supported by the remote server.\n");
2359                 return;
2360         }
2361         if (argc == 2) {
2362                 off_t rp;
2363                 char *ep;
2364
2365                 rp = STRTOLL(argv[1], &ep, 10);
2366                 if (rp < 0 || *ep != '\0')
2367                         fprintf(ttyout, "restart: Invalid offset `%s'\n",
2368                             argv[1]);
2369                 else
2370                         restart_point = rp;
2371         }
2372         if (restart_point == 0)
2373                 fputs("No restart point defined.\n", ttyout);
2374         else
2375                 fprintf(ttyout,
2376                     "Restarting at " LLF " for next get, put or append\n",
2377                     (LLT)restart_point);
2378 }
2379
2380 /*
2381  * Show remote system type
2382  */
2383 void
2384 syst(int argc, char *argv[])
2385 {
2386         int oldverbose = verbose;
2387
2388         if (argc == 0) {
2389                 UPRINTF("usage: %s\n", argv[0]);
2390                 code = -1;
2391                 return;
2392         }
2393         verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
2394         (void)command("SYST");
2395         verbose = oldverbose;
2396 }
2397
2398 void
2399 macdef(int argc, char *argv[])
2400 {
2401         char *tmp;
2402         int c;
2403
2404         if (argc == 0)
2405                 goto usage;
2406         if (macnum == 16) {
2407                 fputs("Limit of 16 macros have already been defined.\n",
2408                     ttyout);
2409                 code = -1;
2410                 return;
2411         }
2412         if ((argc < 2 && !another(&argc, &argv, "macro name")) || argc > 2) {
2413  usage:
2414                 UPRINTF("usage: %s macro_name\n", argv[0]);
2415                 code = -1;
2416                 return;
2417         }
2418         if (interactive)
2419                 fputs(
2420                 "Enter macro line by line, terminating it with a null line.\n",
2421                     ttyout);
2422         (void)strlcpy(macros[macnum].mac_name, argv[1],
2423             sizeof(macros[macnum].mac_name));
2424         if (macnum == 0)
2425                 macros[macnum].mac_start = macbuf;
2426         else
2427                 macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
2428         tmp = macros[macnum].mac_start;
2429         while (tmp != macbuf+4096) {
2430                 if ((c = getchar()) == EOF) {
2431                         fputs("macdef: end of file encountered.\n", ttyout);
2432                         code = -1;
2433                         return;
2434                 }
2435                 if ((*tmp = c) == '\n') {
2436                         if (tmp == macros[macnum].mac_start) {
2437                                 macros[macnum++].mac_end = tmp;
2438                                 code = 0;
2439                                 return;
2440                         }
2441                         if (*(tmp-1) == '\0') {
2442                                 macros[macnum++].mac_end = tmp - 1;
2443                                 code = 0;
2444                                 return;
2445                         }
2446                         *tmp = '\0';
2447                 }
2448                 tmp++;
2449         }
2450         while (1) {
2451                 while ((c = getchar()) != '\n' && c != EOF)
2452                         /* LOOP */;
2453                 if (c == EOF || getchar() == '\n') {
2454                         fputs("Macro not defined - 4K buffer exceeded.\n",
2455                             ttyout);
2456                         code = -1;
2457                         return;
2458                 }
2459         }
2460 }
2461
2462 /*
2463  * Get size of file on remote machine
2464  */
2465 void
2466 sizecmd(int argc, char *argv[])
2467 {
2468         off_t size;
2469
2470         if (argc == 0 || argc > 2 ||
2471             (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2472                 UPRINTF("usage: %s remote-file\n", argv[0]);
2473                 code = -1;
2474                 return;
2475         }
2476         size = remotesize(argv[1], 1);
2477         if (size != -1)
2478                 fprintf(ttyout,
2479                     "%s\t" LLF "\n", argv[1], (LLT)size);
2480         code = (size > 0);
2481 }
2482
2483 /*
2484  * Get last modification time of file on remote machine
2485  */
2486 void
2487 modtime(int argc, char *argv[])
2488 {
2489         time_t mtime;
2490
2491         if (argc == 0 || argc > 2 ||
2492             (argc == 1 && !another(&argc, &argv, "remote-file"))) {
2493                 UPRINTF("usage: %s remote-file\n", argv[0]);
2494                 code = -1;
2495                 return;
2496         }
2497         mtime = remotemodtime(argv[1], 1);
2498         if (mtime != -1)
2499                 fprintf(ttyout, "%s\t%s", argv[1], asctime(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 }