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