]> Zhao Yanbai Git Server - minix.git/commitdiff
Importing usr.bin/ftp 75/475/1
authorThomas Cort <tcort@minix3.org>
Fri, 5 Apr 2013 19:02:06 +0000 (19:02 +0000)
committerThomas Cort <tcort@minix3.org>
Fri, 5 Apr 2013 19:02:06 +0000 (19:02 +0000)
37 files changed:
commands/Makefile
commands/ftp101/Makefile [deleted file]
commands/ftp101/README [deleted file]
commands/ftp101/crc.c [deleted file]
commands/ftp101/file.c [deleted file]
commands/ftp101/file.h [deleted file]
commands/ftp101/ftp.1 [deleted file]
commands/ftp101/ftp.c [deleted file]
commands/ftp101/ftp.h [deleted file]
commands/ftp101/local.c [deleted file]
commands/ftp101/local.h [deleted file]
commands/ftp101/net.c [deleted file]
commands/ftp101/net.h [deleted file]
commands/ftp101/other.c [deleted file]
commands/ftp101/other.h [deleted file]
commands/ftp101/xfer.c [deleted file]
commands/ftp101/xfer.h [deleted file]
releasetools/nbsd_ports
usr.bin/Makefile
usr.bin/ftp/Makefile [new file with mode: 0644]
usr.bin/ftp/cmds.c [new file with mode: 0644]
usr.bin/ftp/cmdtab.c [new file with mode: 0644]
usr.bin/ftp/complete.c [new file with mode: 0644]
usr.bin/ftp/domacro.c [new file with mode: 0644]
usr.bin/ftp/extern.h [new file with mode: 0644]
usr.bin/ftp/fetch.c [new file with mode: 0644]
usr.bin/ftp/ftp.1 [new file with mode: 0644]
usr.bin/ftp/ftp.c [new file with mode: 0644]
usr.bin/ftp/ftp_var.h [new file with mode: 0644]
usr.bin/ftp/main.c [new file with mode: 0644]
usr.bin/ftp/progressbar.c [new file with mode: 0644]
usr.bin/ftp/progressbar.h [new file with mode: 0644]
usr.bin/ftp/ruserpass.c [new file with mode: 0644]
usr.bin/ftp/ssl.c [new file with mode: 0644]
usr.bin/ftp/ssl.h [new file with mode: 0644]
usr.bin/ftp/util.c [new file with mode: 0644]
usr.bin/ftp/version.h [new file with mode: 0644]

index 7a9d7600315cfbdbaadb1ef00de30a9bef6f8eed..3cd1415014db28f50b9a55645aa53475fd22b96e 100644 (file)
@@ -10,7 +10,7 @@ SUBDIR=       add_route arp ash at backup basename btrace \
        dhrystone diff dirname diskctl dumpcore \
        eject env expand factor fbdctl \
        find finger fingerd fix fold format fortune fsck.mfs \
-       ftp101 gcore gcov-pull getty grep hexdump host \
+       gcore gcov-pull getty grep hexdump host \
        hostaddr id ifconfig ifdef \
        intr ipcrm ipcs irdpd isoread last \
        less loadkeys loadramdisk logger look lp \
diff --git a/commands/ftp101/Makefile b/commands/ftp101/Makefile
deleted file mode 100644 (file)
index 553f02a..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Makefile for ftp
-#
-# 01/25/96 Initial Release     Michael Temari
-# 03/08/00                     Michael Temari, <Michael@TemWare.Com>
-# 02/07/05 v. 1.01             Michael Temari, <Michael@TemWare.Com>
-
-PROG=  ftp
-SRCS=  ftp.c local.c file.c xfer.c other.c net.c crc.c
-CPPFLAGS+= -DCRC_ONLY
-
-.include <bsd.prog.mk>
diff --git a/commands/ftp101/README b/commands/ftp101/README
deleted file mode 100644 (file)
index a600177..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-ftp101 --- An FTP client program  for Minix 2.0 
-written by Michael Temari <Michael@TemWare.Com> release 1.01a 2006-06-07
-
-Full download: <a href="/pub/contrib/ftp101.tar.Z">ftp101.tar.Z</a>
-
-FTP is the File Transfer Protocol client that allows you to connect to
-a remote FTP server. 
-
-This version should work equally well with Minix 2 and Minix 3.
-
-Release 1.01a 2006-06-07: minor documentation edits
-Release 1.01  2005-02-07: minor bug fix 
-Release 1.00  2003-12-14: added "ver" command to display current version 
-                          and an ftp(1) man page.
-
-Installation: unpack the tarball, it will create an ftp101 directory. Although
-this is a replacement for the ftp client provided in the Minix 2.0.0 and later
-distributions, it is suggested you unpack and compile in the /usr/local/src
-directory.
-
-zcat < ftp101.tar.Z | tar xvfp -
-
-Invoking make -n will show you what the Makefile will do.
-
-make (or make ftp) compiles a new ftp binary, leaving it in the source
-directory.
-
-make install compiles the binary and installs it in /usr/bin.
-
-make installman installs the man page in /usr/local/man/man1. The
-directory must exist. 
-
-Note: there is a bug in the version of the ftp client distributed with
-Minix 2.0.2 and 2.0.3 that causes a \r (0xd) character to be appended
-to file names in the destination directory when files are downloaded in
-binary mode using the mget command. The bug was corrected in a release 
-prior to 1.00.  
-
-Notes by ASW revised 2006-06-07
diff --git a/commands/ftp101/crc.c b/commands/ftp101/crc.c
deleted file mode 100644 (file)
index b6974cc..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/* Compute checksum                    Author: Johan W. Stevenson */
-
-/* Copyright 1988 by Johan W. Stevenson */
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#if !CRC_ONLY
-
-int errs;
-
-#if __STDC__
-int main(int argc, char **argv);
-void crc(char *fname);
-#else
-void crc();
-#endif
-
-int main(argc, argv)
-int argc;
-char **argv;
-{
-  char line[256];
-
-  if (argc <= 1)
-       crc((char *) 0);
-  else if (argc == 2 && strcmp(argv[1], "-") == 0)
-       while (fgets(line, sizeof line, stdin) != NULL) {
-               if (line[strlen(line) - 1] == '\n')
-                       line[strlen(line) - 1] = '\0';
-               crc(line);
-       }
-  else
-       do {
-               crc(argv[1]);
-               argv++;
-               argc--;
-       } while (argc > 1);
-  return(errs != 0);
-}
-
-#endif
-/* Crctab calculated by Mark G. Mendel, Network Systems Corporation */
-static unsigned short crctab[256] = {
-       0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
-       0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
-       0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
-       0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
-       0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
-       0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
-       0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
-       0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
-       0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
-       0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
-       0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
-       0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
-       0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
-       0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
-       0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
-       0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
-       0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
-       0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
-       0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
-       0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
-       0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
-       0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
-       0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
-       0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
-       0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
-       0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
-       0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
-       0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
-       0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
-       0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
-       0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
-        0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
-};
-
-/* Updcrc macro derived from article Copyright (C) 1986 Stephen Satchell.
- *  NOTE: First argument must be in range 0 to 255.
- *        Second argument is referenced twice.
- *
- * Programmers may incorporate any or all code into their programs,
- * giving proper credit within the source. Publication of the
- * source routines is permitted so long as proper credit is given
- * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg,
- * Omen Technology.
- */
-
-#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp)
-
-#if CRC_ONLY
-unsigned short crc(char *fname)
-#else
-void crc(fname)
-char *fname;
-#endif
-{
-  register int c;
-  register long len = 0;
-  register unsigned short crc = 0;
-  register FILE *fp;
-
-#if CRC_ONLY
-   if((fp = fopen(fname, "r")) == NULL)
-       return 0;
-#else
-  if (fname == NULL)
-       fp = stdin;
-  else if ((fp = fopen(fname, "r")) == NULL) {
-       fprintf(stderr, "crc: cannot open %s\n", fname);
-       errs++;
-       return;
-  }
-#endif
-  while ((c = getc(fp)) != EOF) {
-       len++;
-       crc = updcrc(c, crc);
-  }
-#if CRC_ONLY
-  fclose(fp);
-  return crc;
-#else
-  printf("%05u %6ld", crc, len);
-  if (fname) {
-       printf(" %s", fname);
-       fclose(fp);
-  }
-  printf("\n");
-#endif
-}
diff --git a/commands/ftp101/file.c b/commands/ftp101/file.c
deleted file mode 100644 (file)
index 3c53e93..0000000
+++ /dev/null
@@ -1,935 +0,0 @@
-/* file.c Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <time.h>
-#include <utime.h>
-#include <net/hton.h>
-
-#include "ftp.h"
-#include "file.h"
-#include "net.h"
-
-static char *dir(char *path, int full);
-static int asciisize(int fd, off_t *filesize);
-static off_t asciisetsize(int fd, off_t filesize);
-static int cloneit(char *file, int mode);
-
-#if (__WORD_SIZE == 4)
-static char buffer[8192];
-#else
-static char buffer[2048];
-#endif
-static char line2[512];
-
-static char *dir(path, full)
-char *path;
-int full;
-{
-char cmd[128];
-static char name[32];
-
-   tmpnam(name);
-
-   if(full)
-       sprintf(cmd, "ls -l %s > %s", path, name);
-   else
-       sprintf(cmd, "ls -dA %s > %s", path, name);
-
-   system(cmd);
-
-   return(name);
-}
-
-int DOascii()
-{
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   s = DOcommand("TYPE", "A");
-
-   type = TYPE_A;
-
-   return(s);
-}
-
-int DObinary()
-{
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   s = DOcommand("TYPE", "I");
-
-   type = TYPE_I;
-
-   return(s);
-}
-
-int DOblock()
-{
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   s = DOcommand("MODE", "B");
-
-   mode = MODE_B;
-
-   return(s);
-}
-
-int DOstream()
-{
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   s = DOcommand("MODE", "S");
-
-   mode = MODE_S;
-
-   return(s);
-}
-
-int DOpwd()
-{
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   s = DOcommand("PWD", "");
-
-   if(s == 500 || s == 502)
-       s = DOcommand("XPWD", "");
-
-   return(s);
-}
-
-int DOcd()
-{
-char *path;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Path: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       path = line2;
-   }
-
-   if(!strcmp(path, ".."))
-       s = DOcommand("CDUP", "");
-   else
-       s = DOcommand("CWD", path);
-
-   if(s == 500 || s == 502) {
-       if(!strcmp(path, ".."))
-               s = DOcommand("XCUP", "");
-       else
-               s = DOcommand("XCWD", path);
-   }
-
-   return(s);
-}
-
-int DOmkdir()
-{
-char *path;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Directory: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       path = line2;
-   }
-
-   s = DOcommand("MKD", path);
-
-   if(s == 500 || s == 502)
-       s = DOcommand("XMKD", path);
-
-   return(s);
-}
-
-int DOrmdir()
-{
-char *path;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Directory: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       path = line2;
-   }
-
-   s = DOcommand("RMD", path);
-
-   if(s == 500 || s == 502)
-       s = DOcommand("XRMD", path);
-
-   return(s);
-}
-
-int DOdelete()
-{
-char *file;
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   return(DOcommand("DELE", file));
-}
-
-int DOmdtm()
-{
-char *file;
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   return(DOcommand("MDTM", file));
-}
-
-int DOsize()
-{
-char *file;
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   return(DOcommand("SIZE", file));
-}
-
-int DOstat()
-{
-char *file;
-
-   if(cmdargc < 2) {
-       if(!linkopen) {
-               printf("You must \"OPEN\" a connection first.\n");
-               return(0);
-       } else {
-               return(DOcommand("STAT", ""));
-       }
-   }
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   return(DOcommand("STAT", file));
-}
-
-int DOlist()
-{
-char *path;
-char *local;
-int fd;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2)
-       path = "";
-
-   if(cmdargc < 3)
-       local = "";
-   else
-       local = cmdargv[2];
-
-   if(*local == '\0')
-       fd = 1;
-   else
-       fd = open(local, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", local, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("LIST", path, RETR, fd);
-
-   if(fd > 2)
-       close(fd);
-
-   return(s);
-}
-
-int DOnlst()
-{
-char *path;
-char *local;
-int fd;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2)
-       path = "";
-
-   if(cmdargc < 3)
-       local = "";
-   else
-       local = cmdargv[2];
-
-   if(*local == '\0')
-       fd = 1;
-   else
-       fd = open(local, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", local, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("NLST", path, RETR, fd);
-
-   if(fd > 2)
-       close(fd);
-
-   return(s);
-}
-
-int DOretr()
-{
-char *file, *localfile;
-int fd;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Remote File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   if(cmdargc < 3)
-       localfile = file;
-   else
-       localfile = cmdargv[2];
-
-   fd = open(localfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", localfile, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("RETR", file, RETR, fd);
-
-   close(fd);
-
-   return(s);
-}
-
-int DOrretr()
-{
-char *file, *localfile;
-int fd;
-int s;
-off_t filesize;
-char restart[16];
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Remote File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   if(cmdargc < 3)
-       localfile = file;
-   else
-       localfile = cmdargv[2];
-
-   fd = open(localfile, O_RDWR);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", localfile, strerror(errno));
-       return(0);
-   }
-
-   if(type == TYPE_A) {
-       if(asciisize(fd, &filesize)) {
-               printf("Could not determine ascii file size of %s\n", localfile);
-               close(fd);
-               return(0);
-       }
-   } else
-       filesize = lseek(fd, 0, SEEK_END);
-
-   sprintf(restart, "%u", filesize);
-
-   s = DOcommand("REST", restart);
-
-   if(s != 350) {
-       close(fd);
-       return(s);
-   }
-
-   s = DOdata("RETR", file, RETR, fd);
-
-   close(fd);
-
-   return(s);
-}
-
-char *ttime(time_t t)
-{
-struct tm *tm;
-static char tbuf[16];
-
-   tm = localtime(&t);
-
-   sprintf(tbuf, "%04d%02d%02d%02d%02d.%02d",
-       tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
-       tm->tm_hour, tm->tm_min, tm->tm_sec);
-
-   return(tbuf);
-}
-
-static int cloneit(file, mode)
-char *file;
-int mode;
-{
-int opr;
-int s;
-int ss;
-struct stat st;
-static unsigned short lcrc;
-static unsigned short ccrc;
-static unsigned long csize;
-static char ft;
-static unsigned long maj;
-static unsigned long min;
-static unsigned long uid;
-static unsigned long gid;
-static unsigned long fmode;
-static unsigned long size;
-static unsigned long mtime;
-struct utimbuf ut;
-unsigned short crc(char *fname);
-
-   if(mode == 1) {
-       /* see if file exists locally */
-       ss = stat(file, &st);
-
-       opr = printreply;
-       printreply = 0;
-       s = DOcommand("SITE FDET", file);
-       printreply = opr;
-
-       if((s / 100) != 2)
-               return(-1);
-
-       sscanf(reply, "%*d %c%lu%lu%lu%lu%lu%lu%lu",
-               &ft, &maj, &min, &uid, &gid, &fmode, &size, &mtime);
-
-       if(ft == 'f') {
-               opr = printreply;
-               printreply = 0;
-               s = DOcommand("SITE CCRC", file);
-               printreply = opr;
-               if((s / 100) != 2)
-                       return(-1);
-
-               sscanf(reply, "%*hu %*s%u%lu", &ccrc, &csize);
-               if(ss < 0) return(-1);
-               lcrc = crc(file);
-               if(size != csize || size != st.st_size || ccrc != lcrc)
-                       return(-1);
-       } else
-       if(ss < 0 && ft == 'd') {
-               s = mkdir(file, fmode);
-               printf("mkdir %s\n", file);
-       } else
-       if((ss < 0) && (ft == 'b' || ft == 'c' || ft == 'p')) {
-               s = mknod(file, fmode, maj << 8 | min);
-               printf("mknod %s %lu %lu\n", file, maj, min);
-       } else
-               return(0);
-   }
-   ss = stat(file, &st);
-   if(ss < 0)
-       return(-1);
-   if(st.st_uid != uid || st.st_gid != gid) {
-       s = chown(file, uid, gid);
-       printf("chown %lu:%lu %s\n", uid, gid, file);
-   }
-   if(st.st_mode != fmode) {
-       s = chmod(file, fmode);
-       printf("chmod %04lo %s\n", fmode, file);
-   }
-   if(st.st_mtime != mtime) {
-       ut.actime = mtime;
-       ut.modtime = mtime;
-       s = utime(file, &ut);
-       printf("touch -m -t %s %s\n", ttime(mtime), file);
-   }
-
-   return(0);
-}
-
-int DOMretr()
-{
-char *files;
-int fd, s;
-char *p;
-FILE *fp;
-char name[32];
-
-   if(DOcmdcheck())
-       return(0);
-
-   files = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Files: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       files = line2;
-   }
-
-   tmpnam(name);
-
-   fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", name, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("NLST", files, RETR, fd);
-
-   close(fd);
-
-   if(s == 226 || s == 250) {
-       fp = fopen(name, "r");
-       unlink(name);
-       if(fp == (FILE *)NULL) {
-               printf("Unable to open file listing.\n");
-               return(0);
-       }
-       while(fgets(line2, sizeof(line2), fp) != (char *)NULL) {
-               p = line2 + strlen(line2) - 1;
-               if(p >= line2 && (*p == '\r' || *p == '\n')) *p-- = '\0';
-               if(p >= line2 && (*p == '\r' || *p == '\n')) *p-- = '\0';
-               printf("Retrieving file: %s\n", line2); fflush(stdout);
-               fd = open(line2, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-               if(fd < 0)
-                       printf("Unable to open local file %s\n", line2);
-               else {
-                       s = DOdata("RETR", line2, RETR, fd);
-                       close(fd);
-                       if(s < 0) break;
-               }
-       }
-       fclose(fp);
-   } else
-       unlink(name);
-
-   return(s);
-}
-
-int DOappe()
-{
-char *file, *remotefile;
-int fd;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Local File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   if(cmdargc < 3)
-       remotefile = file;
-   else
-       remotefile = cmdargv[2];
-
-   fd = open(file, O_RDONLY);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", file, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("APPE", remotefile, STOR, fd);
-
-   close(fd);
-
-   return(s);
-}
-
-int DOstor()
-{
-char *file, *remotefile;
-int fd;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Local File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   if(cmdargc < 3)
-       remotefile = file;
-   else
-       remotefile = cmdargv[2];
-
-   fd = open(file, O_RDONLY);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", file, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("STOR", remotefile, STOR, fd);
-
-   close(fd);
-
-   return(s);
-}
-
-int DOrstor()
-{
-char *file, *remotefile;
-int fd;
-int s;
-off_t filesize, rmtsize;
-char restart[16];
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Local File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   if(cmdargc < 3)
-       remotefile = file;
-   else
-       remotefile = cmdargv[2];
-
-   s = DOcommand("SIZE", remotefile);
-
-   if(s != 215)
-       return(s);
-
-   rmtsize = atol(reply+4);
-
-   fd = open(file, O_RDONLY);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", file, strerror(errno));
-       return(0);
-   }
-
-   if(type == TYPE_A)
-       filesize = asciisetsize(fd, rmtsize);
-   else
-       filesize = lseek(fd, rmtsize, SEEK_SET);
-
-   if(filesize != rmtsize) {
-       printf("Could not set file start of %s\n", file);
-       close(fd);
-       return(0);
-   }
-
-   sprintf(restart, "%u", rmtsize);
-
-   s = DOcommand("REST", restart);
-
-   if(s != 350) {
-       close(fd);
-       return(s);
-   }
-
-   s = DOdata("STOR", remotefile, STOR, fd);
-
-   close(fd);
-
-   return(s);
-}
-
-int DOstou()
-{
-char *file, *remotefile;
-int fd;
-int s;
-
-   if(DOcmdcheck())
-       return(0);
-
-   file = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Local File: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       file = line2;
-   }
-
-   if(cmdargc < 3)
-       remotefile = file;
-   else
-       remotefile = cmdargv[2];
-
-   fd = open(file, O_RDONLY);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", file, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("STOU", remotefile, STOR, fd);
-
-   close(fd);
-
-   return(s);
-}
-
-int DOMstor()
-{
-char *files;
-char *name;
-char *p;
-int fd, s;
-FILE *fp;
-
-   if(DOcmdcheck())
-       return(0);
-
-   files = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Files: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       files = line2;
-   }
-
-   name = dir(files, 0);
-
-   fp = fopen(name, "r");
-
-   if(fp == (FILE *)NULL) {
-       printf("Unable to open listing file.\n");
-       return(0);
-   }
-
-   while(fgets(line2, sizeof(line2), fp) != (char *)NULL) {
-       p = line2 + strlen(line2) - 1;
-       if(p >= line2 && (*p == '\r' || *p == '\n')) *p-- = '\0';
-       if(p >= line2 && (*p == '\r' || *p == '\n')) *p-- = '\0';
-       printf("Sending file: %s\n", line2); fflush(stdout);
-       fd = open(line2, O_RDONLY);
-       if(fd < 0)
-               printf("Unable to open local file %s\n", line2);
-       else {
-               s = DOdata("STOR", line2, STOR, fd);
-               close(fd);
-               if(s < 0) break;
-       }
-   }
-   fclose(fp);
-   unlink(name);
-
-   return(s);
-}
-
-static int asciisize(fd, filesize)
-int fd;
-off_t *filesize;
-{
-unsigned long count;
-char *p, *pp;
-int cnt;
-
-   count = 0;
-
-   while((cnt = read(fd, buffer, sizeof(buffer))) > 0) {
-       p = buffer; pp = buffer + cnt;
-       count += cnt;
-       while(p < pp)
-               if(*p++ == '\n')
-                       count++;
-   }
-
-   if(cnt == 0) {
-       *filesize = count;
-       return(0);
-   }
-
-   return(-1);
-}
-
-static off_t asciisetsize(fd, filesize)
-int fd;
-off_t filesize;
-{
-off_t sp;
-int s;
-
-   sp = 0;
-
-   while(sp < filesize) {
-       s = read(fd, buffer, 1);
-       if(s < 0)
-               return(-1);
-       if(s == 0) break;
-       sp++;
-       if(*buffer == '\n')
-               sp++;
-   }
-
-   return(sp);
-}
-
-int DOclone()
-{
-char *files;
-int fd, s;
-char *p;
-FILE *fp;
-char name[32];
-
-   if(DOcmdcheck())
-       return(0);
-
-   files = cmdargv[1];
-
-   tmpnam(name);
-
-   fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-
-   if(fd < 0) {
-       printf("Could not open local file %s. Error %s\n", name, strerror(errno));
-       return(0);
-   }
-
-   s = DOdata("NLST", files, RETR, fd);
-
-   close(fd);
-
-   if(s == 226 || s == 250) {
-       fp = fopen(name, "r");
-       unlink(name);
-       if(fp == (FILE *)NULL) {
-               printf("Unable to open file listing.\n");
-               return(0);
-       }
-       while(fgets(line2, sizeof(line2), fp) != (char *)NULL) {
-               p = line2 + strlen(line2) - 1;
-               if(p >= line2 && (*p == '\r' || *p == '\n')) *p-- = '\0';
-               if(p >= line2 && (*p == '\r' || *p == '\n')) *p-- = '\0';
-               cmdargv[1] = line2;
-               if(cloneit(line2, 1)) {
-                       printf("Retrieving file: %s\n", line2); fflush(stdout);
-                       fd = open(line2, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-                       if(fd < 0)
-                               printf("Unable to open local file %s\n", line2);
-                       else {
-                               s = DOdata("RETR", line2, RETR, fd);
-                               close(fd);
-                               if(s < 0) break;
-                       }
-                       s = cloneit(line2, 2);
-               }
-       }
-       fclose(fp);
-   } else
-       unlink(name);
-
-   return(s);
-}
diff --git a/commands/ftp101/file.h b/commands/ftp101/file.h
deleted file mode 100644 (file)
index bc3ed7f..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/* file.h Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-int recvfile(int fd, int fdin);
-int sendfile(int fd, int fdout);
-int DOascii(void);
-int DObinary(void);
-int DOblock(void);
-int DOstream(void);
-int DOpwd(void);
-int DOcd(void);
-int DOmkdir(void);
-int DOrmdir(void);
-int DOdelete(void);
-int DOmdtm(void);
-int DOsize(void);
-int DOstat(void);
-int DOlist(void);
-int DOnlst(void);
-int DOretr(void);
-int DOrretr(void);
-int DOMretr(void);
-int DOappe(void);
-int DOstor(void);
-int DOrstor(void);
-int DOstou(void);
-int DOMstor(void);
-int DOclone(void);
diff --git a/commands/ftp101/ftp.1 b/commands/ftp101/ftp.1
deleted file mode 100644 (file)
index 078b6a8..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-.TH FTP 1
-.SH NAME
-ftp \- a File Transfer Protocol client for Minix
-.SH SYNOPSIS
-.B ftp
-.RI [ server_name ]
-.SH DESCRIPTION
-.B Ftp
-is a File Transfer Protocol client for Minix written by Michael Temari.  
-.P
-There are no command line options for 
-.B ftp
-except for the optional server name, which may be either a numeric IP address 
-or a domain name resolvable by DNS.
-.P
-If a server name is specified a connection attempt will be made, and you 
-will be prompted for a user name and password by the remote system. 
-Following the login (or immediately, if no server name was specified), the
-.br
-.B ftp>
-.br 
-prompt is displayed. The following commands are accepted at the prompt:
-.P
-Command:      Description
-.br
-!             Escape to a shell
-.br
-append        Append a file to remote host
-.br
-ascii         Set file transfer type to ascii
-.br
-binary        Set file transfer type to binary
-.br
-block         Set file transfer mode to block
-.br
-bye           Close connection and exit
-.br
-cd            Change directory on remote host
-.br
-close         Close connection
-.br
-clone         Clone a file
-.br
-del           Remove file on remote host
-.br
-dir           Display long form remote host directory listing
-.br
-exit          Close connection and exit
-.br
-get           Retrieve a file from remote host
-.br
-help          Display this text
-.br
-lcd           Change directory on local host
-.br
-ldir          Display long form local host directory listing
-.br
-lls           Display local host directory listing
-.br
-lmkdir        Create directory on local host
-.br
-lpwd          Display current directory on local host
-.br
-lrmdir        Remove directory on local host
-.br
-ls            Display remote host directory listing
-.br
-mget          Retrieve multiple files from remote host
-.br
-mkdir         Create directory on remote host
-.br
-mod           Get file modification time
-.br
-mput          Send multiple files to remote host
-.br
-noop          Send the ftp NOOP command
-.br
-open          Open connection to remote host
-.br
-pass          Enter remote user password
-.br
-passive       Toggle passive mode
-.br
-put           Send a file to remote host
-.br
-putu          Send a file to remote host(unique)
-.br
-pwd           Display current directory on remote host
-.br
-quit          Close connection and exit
-.br
-quote         Send raw ftp command to remote host
-.br
-reget         Restart a partial file retrieve from remote host
-.br
-remotehelp    Display ftp commands implemented on remote host
-.br
-reput         Restart a partial file send to remote host
-.br
-rm            Remove file on remote host
-.br
-rmdir         Remove directory on remote host
-.br
-site          Send a site specific command
-.br
-size          Get file size information
-.br
-status        Get connection/file status information
-.br
-stream        Set file transfer mode to stream
-.br
-system        Get remote system type information
-.br
-user          Enter remote user information
-.br
-ver           Display client version information
-
-.SH "SEE ALSO"
-.BR ftpd (8),
-.BR ftpget (1).
-.SH NOTES
-The FTP protocol passes unencrypted usernames and passwords to clients,
-so they are potentially exposed to evildoers with network sniffers. So be 
-wary of using this to exchange files between your own accounts. Obviously 
-if you have a root account on another system and the remote system will 
-accept a login as root this is extremely dangerous. (Many ftp servers will
-not allow a connection by root).
-.P
-Text-mode (ASCII) transfers are the default mode, be sure to enter the 
-"binary" command if you are downloading a program file or a compressed 
-archive, in fact anything other than a text file from a machine with a
-different text-file format than Minix uses.
-.P
-If you are behind a firewall you probably need to use passive mode to 
-successfully transfer files.
-.SH BUGS
-None are known, but there may be some unknown ones. Version 1.00 corrects
-a bug in previous versions that would append a \\r (0xd) character to file
-names on the destination when an mget transfer was used in binary mode.
-
-.SH AUTHOR
-The Minix httpd server was created by and is maintained by Michael Temari
-<Michael@TemWare.Com>. The earliest version was released in 1992, for use
-with Michael's TNet networking extensions for Minix 1.5. 
-.P
-Man page compiled by Al Woodhull <asw@woodhull.com>
-.\" updated 2006-06-18
diff --git a/commands/ftp101/ftp.c b/commands/ftp101/ftp.c
deleted file mode 100644 (file)
index 3e0e800..0000000
+++ /dev/null
@@ -1,407 +0,0 @@
-/* ftp.c Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * ftp          An ftp client program for use with TNET.
- *
- * Usage:       ftp [host]
- *
- * Version:     0.10    06/21/92 (pre-release not yet completed)
- *              0.20    07/01/92
- *              0.30    01/15/96 (Minix 1.7.1 initial release)
- *              0.40    08/27/96
- *              0.50    03/08/00
- *              1.00    12/12/03 (added ver command)
- *              1.01    02/07/05
- *
- * Author:      Michael Temari, <Michael@TemWare.Com>
- */
-
-#include <sys/types.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "ftp.h"
-#include "local.h"
-#include "file.h"
-#include "other.h"
-#include "net.h"
-
-char *FtpVersion = "1.01 02/07/05";
-
-int linkopen;
-int loggedin;
-int type;
-int format;
-int mode;
-int structure;
-int passive;
-int atty;
-
-int cmdargc;
-char *cmdargv[NUMARGS];
-
-int printreply = 1;
-char reply[1024];
-
-/* Already declared in stdio.h */
-#define getline ftp_getline
-
-static void makeargs(char *buff);
-int DOver(void);
-int DOhelp(void);
-static int getline(char *line, int len);
-int main(int argc, char *argv[]);
-
-
-static void makeargs(buff)
-char *buff;
-{
-int i;
-char *p;
-
-   for(i = 0; i < NUMARGS; i++)
-       cmdargv[i] = (char *)0;
-
-   p = buff + strlen(buff) - 1;
-   while(p >= buff)
-       if(*p == '\r' || *p == '\n' || isspace(*p))
-               *p-- = '\0';
-       else
-               break;
-
-   p = buff;
-   cmdargc = 0;
-   while(cmdargc < NUMARGS) {
-       while(*p && isspace(*p))
-               p++;
-       if(*p == '\0')
-               break;
-       cmdargv[cmdargc++] = p;
-       while(*p && !isspace(*p)) {
-               if(cmdargc == 1)
-                       *p = tolower(*p);
-               p++;
-       }
-       if(*p == '\0')
-               break;
-       *p = '\0';
-       p++;
-   }
-}
-
-int readline(prompt, buff, len)
-char *prompt;
-char *buff;
-int len;
-{
-char *p;
-
-   printf(prompt); fflush(stdout);
-
-   if(fgets(buff, len, stdin) == (char *)NULL) {
-       printf("\nEnd of file on input!\n");
-       return(-1);
-   }
-
-   p = buff + strlen(buff) - 1;
-   while(p >= buff)
-       if(*p == '\r' || *p == '\n' || isspace(*p))
-               *p-- = '\0';
-       else
-               break;
-
-   if(!atty) {
-       printf("%s\n", buff);
-       fflush(stdout);
-   }
-
-   return(0);
-}
-
-static int getline(line, len)
-char *line;
-int len;
-{
-int s;
-int gotcr;
-
-   /* leave room for at end for null */
-   len--;
-
-   /* got to be able to put in at least 1 character */
-   if(len < 1)
-       return(-1);
-
-   gotcr = 0;
-   while(len-- > 0) {
-       s = read(ftpcomm_fd, line, 1);
-       if(s != 1)
-               return(-1);
-       if(*line == '\n')
-               break;
-       gotcr = (*line == '\r');
-       line++;
-   }
-   if(gotcr)
-       --line;
-
-   *line = '\0';
-
-   return(0);
-}
-
-int DOgetreply()
-{
-int firsttime;
-int s;
-char code[4];
-
-   do {
-       firsttime = 1;
-       do {
-               if((s = getline(reply, sizeof(reply))) < 0)
-                       return(s);
-               if(printreply) {
-                       printf("%s\n", reply);
-                       fflush(stdout);
-               }
-               if(firsttime) {
-                       firsttime = 0;
-                       strncpy(code, reply, 3);
-                       code[3] = '\0';
-               }
-          } while(strncmp(reply, code, 3) || reply[3] == '-');
-          s = atoi(code);
-   } while(s < 200 && s != 125 && s != 150);
-
-   return(s);
-}
-
-int DOcmdcheck()
-{
-   if(!linkopen) {
-       printf("You must \"OPEN\" a connection first.\n");
-       return(1);
-   }
-
-   if(!loggedin) {
-       printf("You must login first.\n");
-       return(1);
-   }
-
-   return(0);
-}
-
-int DOcommand(ftpcommand, ftparg)
-char *ftpcommand;
-char *ftparg;
-{
-int s;
-#if 1
-static char ss[64];
-   if(*ftparg)
-       sprintf(ss, "%s %s\r\n", ftpcommand, ftparg);
-   else
-       sprintf(ss, "%s\r\n", ftpcommand);
-
-   s = write(ftpcomm_fd, ss, strlen(ss));
-   if(s != strlen(ss))
-       return(-1);
-
-#else
-   s = write(ftpcomm_fd, ftpcommand, strlen(ftpcommand));
-   if(s != strlen(ftpcommand))
-       return(-1);
-
-   if(*ftparg) {
-       s = write(ftpcomm_fd, " ", 1);
-       if(s != 1)
-               return(-1);
-
-       s = write(ftpcomm_fd, ftparg, strlen(ftparg));
-       if(s != strlen(ftparg))
-               return(-1);
-   }
-
-   s = write(ftpcomm_fd, "\r\n", 2);
-   if(s != 2)
-       return(-1);
-#endif
-
-   return(DOgetreply());
-}
-
-int DOver()
-{
-   printf("FTP Version %s\n", FtpVersion);
-   return(0);
-}
-
-int DOhelp()
-{
-char junk[10];
-
-   printf("Command:      Description\n");
-   printf("!             Escape to a shell\n");
-   printf("append        Append a file to remote host\n");
-   printf("ascii         Set file transfer type to ascii\n");
-   printf("binary        Set file transfer type to binary\n");
-   printf("block         Set file transfer mode to block\n");
-   printf("bye           Close connection and exit\n");
-   printf("cd            Change directory on remote host\n");
-   printf("close         Close connection\n");
-   printf("clone         Clone a file\n");
-   printf("del           Remove file on remote host\n");
-   printf("dir           Display long form remote host directory listing\n");
-   printf("exit          Close connection and exit\n");
-   printf("get           Retrieve a file from remote host\n");
-   printf("help          Display this text\n");
-
-   if(readline("Press ENTER to continue... ", junk, sizeof(junk)))
-       return(-1);
-
-   printf("lcd           Change directory on local host\n");
-   printf("ldir          Display long form local host directory listing\n");
-   printf("lls           Display local host directory listing\n");
-   printf("lmkdir        Create directory on local host\n");
-   printf("lpwd          Display current directory on local host\n");
-   printf("lrmdir        Remove directory on local host\n");
-   printf("ls            Display remote host directory listing\n");
-   printf("mget          Retrieve multiple files from remote host\n");
-   printf("mkdir         Create directory on remote host\n");
-   printf("mod           Get file modification time\n");
-   printf("mput          Send multiple files to remote host\n");
-   printf("noop          Send the ftp NOOP command\n");
-
-   if(readline("Press ENTER to continue... ", junk, sizeof(junk)))
-       return(-1);
-
-   printf("open          Open connection to remote host\n");
-   printf("pass          Enter remote user password\n");
-   printf("passive       Toggle passive mode\n");
-   printf("put           Send a file to remote host\n");
-   printf("putu          Send a file to remote host(unique)\n");
-   printf("pwd           Display current directory on remote host\n");
-   printf("quit          Close connection and exit\n");
-   printf("quote         Send raw ftp command to remote host\n");
-   printf("reget         Restart a partial file retrieve from remote host\n");
-   printf("remotehelp    Display ftp commands implemented on remote host\n");
-   printf("reput         Restart a partial file send to remote host\n");
-   printf("rm            Remove file on remote host\n");
-   printf("rmdir         Remove directory on remote host\n");
-
-   if(readline("Press ENTER to continue... ", junk, sizeof(junk)))
-       return(-1);
-
-   printf("site          Send a site specific command\n");
-   printf("size          Get file size information\n");
-   printf("status        Get connection/file status information\n");
-   printf("stream        Set file transfer mode to stream\n");
-   printf("system        Get remote system type information\n");
-   printf("user          Enter remote user information\n");
-   printf("ver           Display client version information\n");
-
-   return(0);
-}
-
-struct commands {
-       char *name;
- int(*func) (void);
-};
-
-static struct commands commands[] = {
-        "!",            DOlshell,
-       "append",       DOappe,
-       "ascii",        DOascii,
-       "binary",       DObinary,
-       "block",        DOblock,
-       "bye",          DOquit,
-       "cd",           DOcd,
-       "close",        DOclose,
-       "clone",        DOclone,
-       "del",          DOdelete,
-       "dir",          DOlist,
-       "exit",         DOquit,
-       "get",          DOretr,
-       "help",         DOhelp,
-       "lcd",          DOlcd,
-        "ldir",         DOllist,
-        "lls",          DOlnlst,
-       "lmkdir",       DOlmkdir,
-       "lpwd",         DOlpwd,
-       "lrmdir",       DOlrmdir,
-       "ls",           DOnlst,
-       "mget",         DOMretr,
-       "mkdir",        DOmkdir,
-       "mod",          DOmdtm,
-       "mput",         DOMstor,
-       "noop",         DOnoop,
-       "open",         DOopen,
-       "pass",         DOpass,
-       "passive",      DOpassive,
-       "put",          DOstor,
-       "putu",         DOstou,
-       "pwd",          DOpwd,
-       "quit",         DOquit,
-       "quote",        DOquote,
-       "reget",        DOrretr,
-       "remotehelp",   DOremotehelp,
-       "reput",        DOrstor,
-       "rm",           DOdelete,
-       "rmdir",        DOrmdir,
-       "site",         DOsite,
-       "size",         DOsize,
-       "status",       DOstat,
-       "stream",       DOstream,
-       "system",       DOsyst,
-       "user",         DOuser,
-       "ver",          DOver,
-       "",     (int (*)())0
-};
-
-int main(argc, argv)
-int argc;
-char *argv[];
-{
-int s;
-struct commands *cmd;
-static char buffer[128];
-
-   if(NETinit())
-       return(-1);
-
-   FTPinit();
-
-   s = 0;
-
-   if(argc > 1) {
-       sprintf(buffer, "open %s ", argv[1]);
-       makeargs(buffer);
-       s = DOopen();
-       if(atty && s > 0) {
-               sprintf(buffer, "user");
-               makeargs(buffer);
-               s = DOuser();
-       }
-   }
-
-   while(s >= 0) {
-       s = readline("ftp>", buffer, sizeof(buffer));
-       if(s < 0) break;
-       makeargs(buffer);
-       if(cmdargc == 0) continue;
-       for(cmd = commands; *cmd->name != '\0'; cmd++)
-               if(!strcmp(cmdargv[0], cmd->name))
-                       break;
-       if(*cmd->name != '\0')
-               s = (*cmd->func)();
-       else {
-               s = 0;
-               printf("Command \"%s\" not recognized.\n", cmdargv[0]);
-       }
-   }
-
-   return(s);
-}
diff --git a/commands/ftp101/ftp.h b/commands/ftp101/ftp.h
deleted file mode 100644 (file)
index 251b129..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/* ftp.h Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-extern int linkopen;
-extern int loggedin;
-extern int type;
-extern int format;
-extern int mode;
-extern int structure;
-extern int passive;
-extern int atty;
-
-#define        NUMARGS 10
-extern int cmdargc;
-extern char *cmdargv[NUMARGS];
-
-extern int printreply;
-extern char reply[1024];
-
-#define        RETR    0
-#define        STOR    1
-
-#define        TYPE_A  0
-#define        TYPE_I  1
-
-#define        MODE_S  0
-#define        MODE_B  1
-
-#define        MODE_B_EOF      64
-
-int readline(char *prompt, char *buff, int len);
-int DOgetreply(void);
-int DOcmdcheck(void);
-int DOcommand(char *ftpcommand, char *ftparg);
diff --git a/commands/ftp101/local.c b/commands/ftp101/local.c
deleted file mode 100644 (file)
index 3a15fa2..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/* local.c Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "ftp.h"
-#include "local.h"
-
-static char line2[512];
-
-static void dodir(char *path, int full);
-
-int DOlpwd()
-{
-   if(getcwd(line2, sizeof(line2)) == (char *)NULL)
-       printf("Could not determine local directory. %s\n", strerror(errno));
-   else
-       printf("Current local directory: %s\n", line2);
-
-   return(0);
-}
-
-int DOlcd()
-{
-char *path;
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Path: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       path = line2;
-   }
-
-   if(chdir(path))
-       printf("Could not change local directory. %s\n", strerror(errno));
-   else
-       return(DOlpwd());
-   
-   return(0);
-}
-
-int DOlmkdir()
-{
-char *path;
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Path: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       path = line2;
-   }
-
-   if(mkdir(path, 0777))
-       printf("Could not make directory %s. %s\n", path, strerror(errno));
-   else
-       printf("Directory created.\n");
-   
-   return(0);
-}
-
-int DOlrmdir()
-{
-char *path;
-
-   path = cmdargv[1];
-
-   if(cmdargc < 2) {
-       if(readline("Path: ", line2, sizeof(line2)) < 0)
-               return(-1);
-       path = line2;
-   }
-
-   if(rmdir(path))
-       printf("Could not remove directory %s. %s\n", path, strerror(errno));
-   else
-       printf("Directory removed.\n");
-   
-   return(0);
-}
-
-int DOllist(void)
-{
-   dodir(".", 1);
-
-   return(0);
-}
-
-int DOlnlst(void)
-{
-   dodir(".", 0);
-
-   return(0);
-}
-
-int DOlshell(void)
-{
-   (void) system("$SHELL");
-
-   return(0);
-}
-
-static void dodir(path, full)
-char *path;
-int full;
-{
-static char cmd[128];
-static char name[32];
-
-   (void) tmpnam(name);
-
-   if(full)
-       sprintf(cmd, "ls -l %s > %s", path, name);
-   else
-       sprintf(cmd, "ls %s > %s", path, name);
-
-   (void) system(cmd);
-   sprintf(cmd, "more %s", name);
-   (void) system(cmd);
-   sprintf(cmd, "rm %s", name);
-   (void) system(cmd);
-}
diff --git a/commands/ftp101/local.h b/commands/ftp101/local.h
deleted file mode 100644 (file)
index fb39663..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* local.h Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-int DOlpwd(void);
-int DOlcd(void);
-int DOlmkdir(void);
-int DOlrmdir(void);
-int DOllist(void);
-int DOlnlst(void);
-int DOlshell(void);
diff --git a/commands/ftp101/net.c b/commands/ftp101/net.c
deleted file mode 100644 (file)
index f6a1556..0000000
+++ /dev/null
@@ -1,569 +0,0 @@
-/* net.c Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <errno.h>
-#include <net/netlib.h>
-#include <net/hton.h>
-#include <net/gen/netdb.h>
-#include <net/gen/in.h>
-#include <net/gen/inet.h>
-#include <net/gen/tcp.h>
-#include <net/gen/tcp_io.h>
-
-#include "ftp.h"
-#include "xfer.h"
-#include "net.h"
-
-void donothing(int sig);
-
-int ftpcomm_fd;
-static ipaddr_t myip;
-static ipaddr_t hostip;
-static char host[256];
-static int lpid;
-
-int NETinit()
-{
-int s;
-char *tcp_device;
-int tcp_fd;
-nwio_tcpconf_t nwio_tcpconf;
-
-   /* All this just to get our ip address */
-
-   if((tcp_device = getenv("TCP_DEVICE")) == (char *)NULL)
-       tcp_device = TCP_DEVICE;
-
-   tcp_fd = open(tcp_device, O_RDWR);
-   if(tcp_fd < 0) {
-       perror("ftp: Could not open tcp_device");
-       return(-1);
-   }
-   s = ioctl(tcp_fd, NWIOGTCPCONF, &nwio_tcpconf);
-   if(s < 0) {
-       perror("ftp: Could not get tcp configuration");
-       return(-1);
-   }
-
-   myip = nwio_tcpconf.nwtc_locaddr;
-
-   close(tcp_fd);
-
-   return(0);
-}
-
-int DOopen()
-{
-nwio_tcpconf_t tcpconf;
-nwio_tcpcl_t tcpcopt;
-char *tcp_device;
-tcpport_t port;
-int s;
-struct hostent *hp;
-struct servent *servent;
-
-   if(linkopen) {
-       printf("Use \"CLOSE\" to close the connection first.\n");
-       return(0);
-   }
-
-   if(cmdargc < 2) {
-       if(readline("Host: ", host, sizeof(host)) < 0)
-               return(-1);
-   } else
-       strncpy(host, cmdargv[1], sizeof(host));
-
-   if((servent = getservbyname("ftp", "tcp")) == (struct servent *)NULL) {
-       fprintf(stderr, "ftp: Could not find ftp tcp service\n");
-       port = htons(21);
-   } else
-       port = (tcpport_t)servent->s_port;
-
-   hp = gethostbyname(host);
-   if(hp == (struct hostent *)NULL) {
-       hostip = (ipaddr_t)0;
-       printf("Unresolved host %s\n", host);
-       return(0);
-   } else
-       memcpy((char *) &hostip, (char *) hp->h_addr, hp->h_length);
-
-   /* This HACK allows the server to establish data connections correctly */
-   /* when using the loopback device to talk to ourselves */
-   if((hostip & ntohl(0xFF000000)) == inet_addr("127.0.0.0"))
-       hostip = myip;
-
-   if((tcp_device = getenv("TCP_DEVICE")) == NULL)
-       tcp_device = "/dev/tcp";
-
-   if((ftpcomm_fd = open(tcp_device, O_RDWR)) < 0) {
-       perror("ftp: open error on tcp device");
-       return(-1);
-   }
-
-   tcpconf.nwtc_flags = NWTC_LP_SEL | NWTC_SET_RA | NWTC_SET_RP;
-   tcpconf.nwtc_remaddr = hostip;
-   tcpconf.nwtc_remport = port;
-
-   s = ioctl(ftpcomm_fd, NWIOSTCPCONF, &tcpconf);
-   if(s < 0) {
-       perror("ftp: ioctl error on NWIOSTCPCONF");
-       close(ftpcomm_fd);
-       return(-1);
-   }
-
-   tcpcopt.nwtcl_flags = 0;
-
-   s = ioctl(ftpcomm_fd, NWIOTCPCONN, &tcpcopt);
-   if(s < 0) {
-       perror("ftp: ioctl error on NWIOTCPCONN");
-       close(ftpcomm_fd);
-       return(-1);
-   }
-
-   s = ioctl(ftpcomm_fd, NWIOGTCPCONF, &tcpconf);
-   if(s < 0) {
-       perror("ftp: ioctl error on NWIOGTCPCONF");
-       close(ftpcomm_fd);
-       return(-1);
-   }
-
-   s = DOgetreply();
-
-   if(s < 0) {
-       close(ftpcomm_fd);
-       return(s);
-   }
-
-   if(s != 220) {
-       close(ftpcomm_fd);
-       return(0);
-   }
-
-   linkopen = 1;
-
-   return(s);
-}
-
-int DOclose()
-{
-   if(!linkopen) {
-       printf("You can't close a connection that isn't open.\n");
-       return(0);
-   }
-
-   close(ftpcomm_fd);
-
-   linkopen = 0;
-   loggedin = 0;
-
-   return(0);
-}
-
-int DOquit()
-{
-int s;
-
-   if(linkopen) {
-       s = DOcommand("QUIT", "");
-       s = DOclose();
-   }
-
-   printf("FTP done.\n");
-
-   exit(0);
-}
-
-void donothing(sig)
-int sig;
-{
-}
-
-int DOdata(datacom, file, direction, fd)
-char *datacom;
-char *file;
-int direction;  /* RETR or STOR */
-int fd;
-{
-nwio_tcpconf_t tcpconf;
-nwio_tcpcl_t tcplopt, tcpcopt;
-char *tcp_device;
-static int ftpdata_fd = -1;
-char *buff;
-ipaddr_t ripaddr;
-tcpport_t rport;
-static tcpport_t lport;
-int s;
-int i;
-int wpid;
-int cs;
-int pfd[2];
-char dummy;
-char port[32];
-int wasopen;
-
-   lport = htons(0xF000);
-
-#ifdef DEBUG
-   printf("DOdata %s %s %d %d\n", datacom, file, direction, fd);
-#endif
-
-   ripaddr = hostip;
-   rport = htons(2);
-
-   /* here we set up a connection to listen on if not passive mode */
-   /* otherwise we use this to connect for passive mode */
-
-   if((tcp_device = getenv("TCP_DEVICE")) == NULL)
-       tcp_device = "/dev/tcp";
-
-   if(ftpdata_fd >= 0 && mode != MODE_B) {
-       close(ftpdata_fd);
-       ftpdata_fd = -1;
-   }
-
-   wasopen = (ftpdata_fd >= 0);
-
-#ifdef DEBUG
-   printf("wasopen = %d\n", wasopen);
-#endif
-
-   if(wasopen)
-       goto WASOPEN;
-
-#ifdef DEBUG
-   printf("b4 open = %d\n", ftpdata_fd);
-#endif
-
-   if(ftpdata_fd == -1)
-       if((ftpdata_fd = open(tcp_device, O_RDWR)) < 0) {
-               perror("ftp: open error on tcp device");
-               return(0);
-       }
-
-#ifdef DEBUG
-   printf("at open = %d\n", ftpdata_fd);
-#endif
-
-   if(passive) {
-#ifdef DEBUG
-       printf("b4 PASV command\n");
-#endif
-       s = DOcommand("PASV", "");
-#ifdef DEBUG
-       printf("PASV command returned %d\n", s);
-#endif
-       if(s != 227) {
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               return(s);
-       }
-       /* decode host and port */
-       buff = reply;
-       while(*buff && (*buff != '(')) buff++;
-       buff++;
-       ripaddr = (ipaddr_t)0;
-       for(i = 0; i < 4; i++) {
-               ripaddr = (ripaddr << 8) + (ipaddr_t)atoi(buff);
-               if((buff = strchr(buff, ',')) == (char *)0) {
-                       printf("Could not parse PASV reply\n");
-                       return(0);
-               }
-               buff++;
-       }
-       rport = (tcpport_t)atoi(buff);
-       if((buff = strchr(buff, ',')) == (char *)0) {
-               printf("Could not parse PASV reply\n");
-               return(0);
-       }
-       buff++;
-       rport = (rport << 8) + (tcpport_t)atoi(buff);
-       ripaddr = ntohl(ripaddr);
-       rport = ntohs(rport);
-#ifdef DEBUG
-       printf("PASV %08x %04x\n", ripaddr, rport);
-#endif
-   }
-
-   while(1) {
-       tcpconf.nwtc_flags = NWTC_SET_RA | NWTC_SET_RP;
-       if(passive || ntohs(lport) >= 0xF000) {
-               tcpconf.nwtc_flags |= NWTC_LP_SEL;
-       } else {
-               /* For no good reason Sun hosts don't like it if they have to
-                * connect to the same port twice in a short time...
-                */
-               lport = htons(ntohs(lport) + 1);
-               tcpconf.nwtc_flags |= NWTC_LP_SET;
-               tcpconf.nwtc_locport = lport;
-       }
-
-       tcpconf.nwtc_remaddr = ripaddr;
-       tcpconf.nwtc_remport = rport;
-
-#ifdef DEBUG
-       printf("b4 STCPCONF locport = %d\n", lport);
-#endif
-
-       s = ioctl(ftpdata_fd, NWIOSTCPCONF, &tcpconf);
-#ifdef DEBUG
-       printf("at STCPCONF %d %d\n", s, errno);
-#endif
-       if(s < 0) {
-               if(errno == EADDRINUSE) continue;
-               perror("ftp: ioctl error on NWIOSTCPCONF");
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               return(0);
-       }
-       break;
-   }
-
-#ifdef DEBUG
-       printf("b4 GTCPCONF\n");
-#endif
-   s = ioctl(ftpdata_fd, NWIOGTCPCONF, &tcpconf);
-#ifdef DEBUG
-   printf("at GTCPCONF %d %d\n", s, errno);
-#endif
-   if(s < 0) {
-       perror("ftp: ioctl error on NWIOGTCPCONF");
-       close(ftpdata_fd);
-       ftpdata_fd = -1;
-       return(0);
-   }
-   lport = tcpconf.nwtc_locport;
-
-#ifdef DEBUG
-   printf("lport = %04x\n", lport);
-#endif
-
-   if(passive) {
-       /* passive mode we connect to them */
-       tcpcopt.nwtcl_flags = 0;
-#ifdef DEBUG
-       printf("Doing TCPCONN\n");
-#endif
-       s = ioctl(ftpdata_fd, NWIOTCPCONN, &tcpcopt);
-#ifdef DEBUG
-       printf("TCPCONN %d %d\n", s, errno);
-#endif
-       if(s < 0) {
-               perror("ftp: error on ioctl NWIOTCPCONN");
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               return(0);
-       }
-#ifdef DEBUG
-       printf("GTCPCONF\n");
-#endif
-       s = ioctl(ftpdata_fd, NWIOGTCPCONF, &tcpconf);
-#ifdef DEBUG
-       printf("GTCPCONF %d %d\n", s, errno);
-#endif
-       if(s < 0) {
-               perror("ftp: error on ioctl NWIOGTCPCONF");
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               return(0);
-       }
-   } else {
-       /* we listen for them */
-       tcplopt.nwtcl_flags = 0;
-#ifdef DEBUG
-       printf("Listen\n");
-#endif
-
-       if (pipe(pfd) < 0) {
-               perror("ftp: could not create a pipe");
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               return(0);
-       }
-       lpid = fork();
-       if(lpid < 0) {
-               perror("ftp: could not fork listener");
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               close(pfd[0]);
-               close(pfd[1]);
-               return(0);
-       } else if(lpid == 0) {
-#ifdef DEBUG
-               printf("Child here\n");
-#endif
-               close(pfd[0]);
-               signal(SIGALRM, donothing);
-               alarm(15);
-               close(pfd[1]);
-#ifdef DEBUG
-               printf("child TCPLISTEN\n");
-#endif
-               s = ioctl(ftpdata_fd, NWIOTCPLISTEN, &tcplopt);
-               alarm(0);
-#ifdef DEBUG
-               printf("listen %d %d\n", s, errno);
-#endif
-               if(s < 0)
-                       exit(errno);            /* error */
-               else
-                       exit(0);                /* connection made */
-       }
-#ifdef DEBUG
-       printf("Fork = %d\n", lpid);
-#endif
-       /* Wait for the pipe to close, then the listener is ready (almost). */
-       close(pfd[1]);
-       (void) read(pfd[0], &dummy, 1);
-       close(pfd[0]);
-       while(1) {
-               wpid = waitpid(lpid, &cs, WNOHANG);
-#ifdef DEBUG
-               printf("waitpid %d %d\n", wpid, cs);
-               printf("GTCPCONF loop\n");
-#endif
-               if(wpid != 0) break;
-               signal(SIGALRM, donothing);
-               alarm(1);
-               s = ioctl(ftpdata_fd, NWIOGTCPCONF, &tcpconf);
-               alarm(0);
-#ifdef DEBUG
-               printf("GTCPCONF loop %d %d\n", s, errno);
-#endif
-               if(s == -1) break;
-               sleep(1);
-       }
-#ifdef DEBUG
-       printf("GTCPCONF = %d\n", s);
-#endif
-   }
-
-#define hiword(x)       ((u16_t)((x) >> 16))
-#define loword(x)       ((u16_t)(x & 0xffff)) 
-#define hibyte(x)       (((x) >> 8) & 0xff)
-#define lobyte(x)       ((x) & 0xff)
-
-   if(!passive) {
-       if(wpid != 0) {
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               cs = (cs >> 8) & 0x00ff;
-               printf("Child listener error %s\n", strerror(cs));
-               return(0);
-       }
-       sprintf(port, "%u,%u,%u,%u,%u,%u",
-               hibyte(hiword(ntohl(myip))), lobyte(hiword(ntohl(myip))),
-               hibyte(loword(ntohl(myip))), lobyte(loword(ntohl(myip))),
-               hibyte(ntohs(lport)), lobyte(ntohs(lport)));
-#ifdef DEBUG
-       printf("sending port command %s\n", port);
-#endif
-       s = DOcommand("PORT", port);
-#ifdef DEBUG
-       printf("port command = %d\n", s);
-#endif
-       if(s != 200) {
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-               kill(lpid, SIGKILL);
-               (void) wait(&cs);
-               return(s);
-       }
-   }
-
-WASOPEN:
-
-#ifdef DEBUG
-   printf("doing data command %s %s\n", datacom, file);
-#endif
-   s = DOcommand(datacom, file);
-#ifdef DEBUG
-   printf("do command reply %d\n", s);
-#endif
-   if(s == 125 || s == 150) {
-       if(!passive && !wasopen) {
-               while(1) {
-#ifdef DEBUG
-                       printf("Waiting for child %d\n", lpid);
-#endif
-                       s = wait(&cs);
-#ifdef DEBUG
-                       printf("Wait returned %d cs=%d errno=%d\n", s, cs, errno);
-#endif
-                       if(s < 0 || s == lpid)
-                               break;
-               }
-               if(s < 0) {
-                       perror("wait error:");
-                       close(ftpdata_fd);
-                       ftpdata_fd = -1;
-                       kill(lpid, SIGKILL);
-                       (void) wait(&cs);
-                       return(s);
-               }
-               if((cs & 0x00ff)) {
-                       printf("Child listener failed %04x\n", cs);
-                       close(ftpdata_fd);
-                       ftpdata_fd = -1;
-                       return(-1);
-               }
-               cs = (cs >> 8) & 0x00ff;
-               if(cs) {
-                       printf("Child listener error %s\n", strerror(cs));
-                       close(ftpdata_fd);
-                       ftpdata_fd = -1;
-                       return(DOgetreply());
-               }
-       }
-#ifdef DEBUG
-       printf("Before recvfile/sendfile call\n");
-#endif
-       switch(direction) {
-               case RETR:
-                       s = recvfile(fd, ftpdata_fd);
-                       break;
-               case STOR:
-                       s = sendfile(fd, ftpdata_fd);
-                       break;
-       }
-#ifdef DEBUG
-       printf("send/recieve %d\n", s);
-#endif
-       if(mode != MODE_B) {
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-       }
-
-       s = DOgetreply();
-#ifdef DEBUG
-       printf("send/recieve reply %d\n", s);
-#endif
-       if(mode == MODE_B && s == 226) {
-               close(ftpdata_fd);
-               ftpdata_fd = -1;
-       }
-   } else {
-       if(!passive) {
-               kill(lpid, SIGKILL);
-               (void) wait(&cs);
-       }
-       close(ftpdata_fd);
-       ftpdata_fd = -1;
-   }
-
-   return(s);
-}
diff --git a/commands/ftp101/net.h b/commands/ftp101/net.h
deleted file mode 100644 (file)
index f4c681f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* net.h Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-int NETinit(void);
-int DOopen(void);
-int DOclose(void);
-int DOquit(void);
-int DOdata(char *datacom, char *file, int direction, int fd);
-
-extern int ftpcomm_fd;
diff --git a/commands/ftp101/other.c b/commands/ftp101/other.c
deleted file mode 100644 (file)
index 41781e9..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/* other.c Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * ftp          An ftp client program for use with TNET.
- *
- * Author:      Michael Temari, <Michael@TemWare.Com>
- */
-
-#include <sys/types.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <termios.h>
-
-#include "ftp.h"
-#include "other.h"
-
-static int docmdargs(char *cmd, int fa);
-
-void FTPinit()
-{
-   linkopen = 0;
-   loggedin = 0;
-   type = TYPE_A;
-   format = 0;
-   mode = MODE_S;
-   structure = 0;
-   passive = 0;
-   atty = isatty(0);
-}
-
-int DOpass()
-{
-int s;
-struct termios oldtty, newtty;
-char *pass;
-char password[64];
-
-   if(!linkopen) {
-       printf("You must \"OPEN\" a connection first.\n");
-       return(0);
-   }
-
-   pass = cmdargv[1];
-
-   s = 0;
-
-   if(cmdargc < 2) {
-       if(atty) {
-               tcgetattr(fileno(stdout), &oldtty);
-               newtty = oldtty;
-               newtty.c_lflag &= ~ECHO;
-               tcsetattr(fileno(stdout), TCSANOW, &newtty);
-       }
-       s = readline("Password: ", password, sizeof(password));
-       if(atty) {
-               tcsetattr(fileno(stdout), TCSANOW, &oldtty);
-               printf("\n");
-       }
-       pass = password;
-   }
-
-   if(s < 0)
-       return(-1);
-
-   s = DOcommand("PASS", pass);
-
-   if(s == 230)
-       loggedin = 1;
-
-   return(s);
-}
-
-int DOuser()
-{
-char *user;
-int s;
-char username[32];
-
-   if(!linkopen) {
-       printf("You must \"OPEN\" a connection first.\n");
-       return(0);
-   }
-
-   loggedin = 0;
-
-   user = cmdargv[1];
-
-   s = 0;
-
-   if(cmdargc < 2) {
-       if(readline("Username: ", username, sizeof(username)) < 0)
-               return(-1);
-       user = username;
-   }
-
-   s = DOcommand("USER", user);
-
-   if(atty && s == 331) {
-       cmdargv[0] = "password";
-       cmdargc = 1;
-       return(DOpass());
-   }
-
-   if(s == 230)
-       loggedin = 1;
-
-   return(s);
-}
-
-int DOnoop()
-{
-   if(DOcmdcheck())
-       return(0);
-
-   return(DOcommand("NOOP", ""));
-}
-
-int DOpassive()
-{
-   passive = 1 - passive;
-
-   printf("Passive mode is now %s\n", (passive ? "ON" : "OFF"));
-
-   return(0);
-}
-
-int DOsyst()
-{
-   if(DOcmdcheck())
-       return(0);
-
-   return(DOcommand("SYST", ""));
-}
-
-int DOremotehelp()
-{
-   if(!linkopen) {
-       printf("You must \"OPEN\" a connection first.\n");
-       return(0);
-   }
-
-   return(DOcommand("HELP", ""));
-}
-
-static int docmdargs(cmd, fa)
-char *cmd;
-int fa;
-{
-int i;
-static char args[512];
-
-   args[0] = '\0';
-
-   for(i = fa; i < cmdargc; i++) {
-       if(i != fa)
-               strcat(args, " ");
-       strcat(args, cmdargv[i]);
-   }
-
-   return(DOcommand(cmd, args));
-}
-
-int DOquote()
-{
-   return(docmdargs(cmdargv[1], 2));
-}
-
-int DOsite()
-{
-   return(docmdargs("SITE", 1));
-}
diff --git a/commands/ftp101/other.h b/commands/ftp101/other.h
deleted file mode 100644 (file)
index d91bb2e..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-/* other.h Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 01/25/96 Initial Release    Michael Temari, <Michael@TemWare.Com>
- */
-
-void FTPinit(void);
-int DOpass(void);
-int DOuser(void);
-int DOnoop(void);
-int DOpassive(void);
-int DOsyst(void);
-int DOremotehelp(void);
-int DOquote(void);
-int DOsite(void);
diff --git a/commands/ftp101/xfer.c b/commands/ftp101/xfer.c
deleted file mode 100644 (file)
index e712814..0000000
+++ /dev/null
@@ -1,305 +0,0 @@
-/* xfer.c Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- *
- * 03/14/00 Initial Release    Michael Temari, <Michael@TemWare.Com>
- *
- */
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <time.h>
-#include <utime.h>
-#include <net/hton.h>
-
-#include "ftp.h"
-#include "xfer.h"
-
-static int asciisend(int fd, int fdout);
-static int binarysend(int fd, int fdout);
-static int asciirecv(int fd, int fdin);
-static int binaryrecv(int fd, int fdin);
-
-#if (__WORD_SIZE == 4)
-static char buffer[8192];
-static char bufout[8192];
-#else
-static char buffer[2048];
-static char bufout[2048];
-#endif
-
-static int asciisend(fd, fdout)
-int fd;
-int fdout;
-{
-int s;
-char c;
-char *p;
-char *op, *ope;
-unsigned long total=0L;
-char block[3];
-
-   if(atty) {
-       printf("Sent ");
-       fflush(stdout);
-   }
-
-   op = bufout;
-   ope = bufout + sizeof(bufout) - 3;
-
-   while((s = read(fd, buffer, sizeof(buffer))) > 0) {
-       total += (long)s;
-       p = buffer;
-       while(s-- > 0) {
-               c = *p++;
-               if(c == '\n') {
-                       *op++ = '\r';
-                       total++;
-               }
-               *op++ = c;
-               if(op >= ope) {
-                       if(mode == MODE_B) {
-                               block[0] = '\0';
-                               *(u16_t *)&block[1] = htons(op - bufout);
-                               write(fdout, block, sizeof(block));
-                       }
-                       write(fdout, bufout, op - bufout);
-                       op = bufout;
-               }
-       }
-       if(atty) {
-               printf("%8lu bytes\b\b\b\b\b\b\b\b\b\b\b\b\b\b", total);
-               fflush(stdout);
-       }
-   }
-   if(op > bufout) {
-       if(mode == MODE_B) {
-               block[0] = MODE_B_EOF;
-               *(u16_t *)&block[1] = htons(op - bufout);
-               write(fdout, block, sizeof(block));
-       }
-       write(fdout, bufout, op - bufout);
-   } else if(mode == MODE_B) {
-       block[0] = MODE_B_EOF;
-       *(u16_t *)&block[1] = htons(0);
-       write(fdout, block, sizeof(block));
-       s = 0;
-   }
-   if(atty) {
-       printf("\n");
-       fflush(stdout);
-   }
-
-   return(s);
-}
-
-static int binarysend(fd, fdout)
-int fd;
-int fdout;
-{
-int s;
-unsigned long total=0L;
-char block[3];
-
-   if(atty) {
-       printf("Sent ");
-       fflush(stdout);
-   }
-
-   while((s = read(fd, buffer, sizeof(buffer))) > 0) {
-       if(mode == MODE_B) {
-               block[0] = MODE_B_EOF;
-               *(u16_t *)&block[1] = htons(s);
-               write(fdout, block, sizeof(block));
-       }
-       write(fdout, buffer, s);
-       total += (long)s;
-       if(atty) {
-               printf("%8lu bytes\b\b\b\b\b\b\b\b\b\b\b\b\b\b", total);
-               fflush(stdout);
-       }
-   }
-   if(mode == MODE_B) {
-       block[0] = MODE_B_EOF;
-       *(u16_t *)&block[1] = htons(0);
-       write(fdout, block, sizeof(block));
-       s = 0;
-   }
-   if(atty) {
-       printf("\n");
-       fflush(stdout);
-   }
-
-   return(s);
-}
-
-int sendfile(fd, fdout)
-int fd;
-int fdout;
-{
-int s;
-
-   switch(type) {
-       case TYPE_A:
-               s = asciisend(fd, fdout);
-               break;
-       default:
-               s = binarysend(fd, fdout);
-   }
-
-   if(s < 0)
-       return(-1);
-   else
-       return(0);
-}
-
-static int asciirecv(fd, fdin)
-int fd;
-int fdin;
-{
-int s;
-int gotcr;
-char c;
-char *p;
-char *op, *ope;
-unsigned long total=0L;
-char block[3];
-unsigned short cnt;
-
-   if(isatty && fd > 2) {
-       printf("Received ");
-       fflush(stdout);
-   }
-   gotcr = 0;
-   op = bufout; ope = bufout + sizeof(bufout) - 3;
-   cnt = 0;
-   while(1) {
-       if(mode != MODE_B)
-               cnt = sizeof(buffer);
-       else
-               if(cnt == 0) {
-                       s = read(fdin, block, sizeof(block));
-                       cnt = ntohs(*(u16_t *)&block[1]);
-                       s = 0;
-                       if(cnt == 0 && block[0] & MODE_B_EOF)
-                               break;
-               }
-       s = read(fdin, buffer, cnt > sizeof(buffer) ? sizeof(buffer) : cnt);
-       if(s <= 0) break;
-       cnt -= s;
-       total += (long)s;
-       p = buffer;
-       while(s-- > 0) {
-               c = *p++;
-               if(gotcr) {
-                       gotcr = 0;
-                       if(c != '\n')
-                               *op++ = '\r';
-               }
-               if(c == '\r')
-                       gotcr = 1;
-               else
-                       *op++ = c;
-               if(op >= ope) {
-                       write(fd, bufout, op - bufout);
-                       op = bufout;
-               }
-       }
-       if(atty && fd > 2) {
-               printf("%8lu bytes\b\b\b\b\b\b\b\b\b\b\b\b\b\b", total);
-               fflush(stdout);
-       }
-       if(cnt == 0 && mode == MODE_B && block[0] & MODE_B_EOF) {
-                       s = 0;
-                       break;
-       }
-   }
-   if(gotcr)
-       *op++ = '\r';
-   if(op > bufout)
-       write(fd, bufout, op - bufout);
-   if(atty && fd > 2) {
-       printf("\n");
-       fflush(stdout);
-   }
-   if((mode == MODE_B && cnt != 0) || s != 0)
-       return(-1);
-   else
-       return(0);
-}
-
-static int binaryrecv(fd, fdin)
-int fd;
-int fdin;
-{
-int s;
-unsigned long total=0L;
-char block[3];
-unsigned short cnt;
-
-   if(atty && fd > 2) {
-       printf("Received ");
-       fflush(stdout);
-   }
-   cnt = 0;
-   while(1) {
-       if(mode != MODE_B)
-               cnt = sizeof(buffer);
-       else
-               if(cnt == 0) {
-                       s = read(fdin, block, sizeof(block));
-                       cnt = ntohs(*(u16_t *)&block[1]);
-                       s = 0;
-                       if(cnt == 0 && block[0] & MODE_B_EOF)
-                               break;
-               }
-       s = read(fdin, buffer, cnt > sizeof(buffer) ? sizeof(buffer) : cnt);
-       if(s <= 0) break;
-       cnt -= s;
-       total += (long)s;
-       write(fd, buffer, s);
-       if(atty && fd > 2) {
-               printf("%8lu bytes\b\b\b\b\b\b\b\b\b\b\b\b\b\b", total);
-               fflush(stdout);
-       }
-       if(cnt == 0 && mode == MODE_B && block[0] & MODE_B_EOF) {
-               s = 0;
-               break;
-       }
-   }
-   if(atty && fd > 2) {
-       printf("\n");
-       fflush(stdout);
-   }
-   if((mode == MODE_B && cnt != 0) || s != 0)
-       return(-1);
-   else
-       return(0);
-}
-
-int recvfile(fd, fdin)
-int fd;
-int fdin;
-{
-int s;
-
-   switch(type) {
-       case TYPE_A:
-               s = asciirecv(fd, fdin);
-               break;
-       default:
-               s = binaryrecv(fd, fdin);
-   }
-
-   if(s < 0)
-       return(-1);
-   else
-       return(0);
-}
diff --git a/commands/ftp101/xfer.h b/commands/ftp101/xfer.h
deleted file mode 100644 (file)
index bab5493..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-/* xfer.h Copyright 1992-2000 by Michael Temari All Rights Reserved
- *
- * This file is part of ftp.
- *
- */
-
-int recvfile(int fd, int fdin);
-int sendfile(int fd, int fdout);
index 5410a42109e7bc2072ddfd611e44fcecd7d6a42a..4afa747e014a3ea78e967bbb5c8990d2adee1f87 100644 (file)
 2012/10/17 12:00:00,usr.bin/ctags
 2011/09/01 13:37:33,usr.bin/du
 2013/03/22 12:00:00,usr.bin/from
+2013/04/05 12:00:00,usr.bin/ftp
 2013/03/18 12:00:00,usr.bin/head
 2012/10/17 12:00:00,usr.bin/genassym
 2013/03/09 12:00:00,usr.bin/getopt
index 665422f1afa3d5fe9f0ead32a48456c69791e764..b1aadeac44f95e7fe0fbf7752cc73835a527d49f 100644 (file)
@@ -10,7 +10,7 @@ SUBDIR= \
        col ctags \
        du \
        \
-       from \
+       from ftp \
        genassym getopt head \
        indent infocmp join \
        ldd \
diff --git a/usr.bin/ftp/Makefile b/usr.bin/ftp/Makefile
new file mode 100644 (file)
index 0000000..e2e819d
--- /dev/null
@@ -0,0 +1,40 @@
+#      $NetBSD: Makefile,v 1.36 2012/12/21 18:07:36 christos Exp $
+#      from: @(#)Makefile      8.2 (Berkeley) 4/3/94
+
+.include <bsd.own.mk>
+
+USE_FORT?= yes # network client
+
+PROG=  ftp
+SRCS=  cmds.c cmdtab.c complete.c domacro.c fetch.c ftp.c main.c \
+       progressbar.c ruserpass.c util.c
+
+# Uncomment the following to provide defaults for gate-ftp operation
+#
+#CPPFLAGS+=-DGATE_SERVER=\"ftp-gw.host\" # -DGATE_PORT=21
+
+.if defined(__MINIX)
+CPPFLAGS+= -DDIRENT_MISSING_D_NAMLEN
+.endif # defined (__MINIX)
+
+.if defined(SMALLPROG)
+CPPFLAGS+=-DNO_EDITCOMPLETE -DNO_ABOUT -DNO_AUTH -DNO_HELP -DNO_STATUS -DNO_DEBUG -DNO_USAGE
+.else
+LDADD+=        -ledit -lterminfo
+DPADD+=        ${LIBEDIT} ${LIBTERMINFO}
+.if (${MKCRYPTO} != "no")
+CPPFLAGS+= -DWITH_SSL
+SRCS+=ssl.c
+LDADD+= -lssl -lcrypto
+DPADD+= ${LIBSSL} ${LIBCRYPTO}
+.endif
+.endif
+
+.if (!defined(SMALLPROG) || defined(SMALLPROG_INET6)) && (${USE_INET6} != "no")
+CPPFLAGS+= -DINET6
+.endif
+
+cmds.o fetch.o: version.h
+main.o:        ftp_var.h
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/ftp/cmds.c b/usr.bin/ftp/cmds.c
new file mode 100644 (file)
index 0000000..66cab2b
--- /dev/null
@@ -0,0 +1,2795 @@
+/*     $NetBSD: cmds.c,v 1.135 2012/12/22 16:57:09 christos Exp $      */
+
+/*-
+ * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
+ * NASA Ames Research Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cmds.c     8.6 (Berkeley) 10/9/94";
+#else
+__RCSID("$NetBSD: cmds.c,v 1.135 2012/12/22 16:57:09 christos Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * FTP User Program -- Command Routines.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <arpa/ftp.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <glob.h>
+#include <limits.h>
+#include <netdb.h>
+#include <paths.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "ftp_var.h"
+#include "version.h"
+
+static struct types {
+       const char      *t_name;
+       const char      *t_mode;
+       int             t_type;
+       const char      *t_arg;
+} types[] = {
+       { "ascii",      "A",    TYPE_A, 0 },
+       { "binary",     "I",    TYPE_I, 0 },
+       { "image",      "I",    TYPE_I, 0 },
+       { "ebcdic",     "E",    TYPE_E, 0 },
+       { "tenex",      "L",    TYPE_L, bytename },
+       { NULL,         NULL,   0, NULL }
+};
+
+static sigjmp_buf       jabort;
+
+static int     confirm(const char *, const char *);
+__dead static void     mintr(int);
+static void    mabort(const char *);
+static void    set_type(const char *);
+
+static const char *doprocess(char *, size_t, const char *, int, int, int);
+static const char *domap(char *, size_t, const char *);
+static const char *docase(char *, size_t, const char *);
+static const char *dotrans(char *, size_t, const char *);
+
+/*
+ * Confirm if "cmd" is to be performed upon "file".
+ * If "file" is NULL, generate a "Continue with" prompt instead.
+ */
+static int
+confirm(const char *cmd, const char *file)
+{
+       const char *errormsg;
+       char cline[BUFSIZ];
+       const char *promptleft, *promptright;
+
+       if (!interactive || confirmrest)
+               return (1);
+       if (file == NULL) {
+               promptleft = "Continue with";
+               promptright = cmd;
+       } else {
+               promptleft = cmd;
+               promptright = file;
+       }
+       while (1) {
+               fprintf(ttyout, "%s %s [anpqy?]? ", promptleft, promptright);
+               (void)fflush(ttyout);
+               if (get_line(stdin, cline, sizeof(cline), &errormsg) < 0) {
+                       mflag = 0;
+                       fprintf(ttyout, "%s; %s aborted\n", errormsg, cmd);
+                       return (0);
+               }
+               switch (tolower((unsigned char)*cline)) {
+                       case 'a':
+                               confirmrest = 1;
+                               fprintf(ttyout,
+                                   "Prompting off for duration of %s.\n", cmd);
+                               break;
+                       case 'p':
+                               interactive = 0;
+                               fputs("Interactive mode: off.\n", ttyout);
+                               break;
+                       case 'q':
+                               mflag = 0;
+                               fprintf(ttyout, "%s aborted.\n", cmd);
+                               /* FALLTHROUGH */
+                       case 'n':
+                               return (0);
+                       case '?':
+                               fprintf(ttyout,
+                                   "  confirmation options:\n"
+                                   "\ta  answer `yes' for the duration of %s\n"
+                                   "\tn  answer `no' for this file\n"
+                                   "\tp  turn off `prompt' mode\n"
+                                   "\tq  stop the current %s\n"
+                                   "\ty  answer `yes' for this file\n"
+                                   "\t?  this help list\n",
+                                   cmd, cmd);
+                               continue;       /* back to while(1) */
+               }
+               return (1);
+       }
+       /* NOTREACHED */
+}
+
+/*
+ * Set transfer type.
+ */
+void
+settype(int argc, char *argv[])
+{
+       struct types *p;
+
+       if (argc == 0 || argc > 2) {
+               const char *sep;
+
+               UPRINTF("usage: %s [", argv[0]);
+               sep = " ";
+               for (p = types; p->t_name; p++) {
+                       fprintf(ttyout, "%s%s", sep, p->t_name);
+                       sep = " | ";
+               }
+               fputs(" ]\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (argc < 2) {
+               fprintf(ttyout, "Using %s mode to transfer files.\n", typename);
+               code = 0;
+               return;
+       }
+       set_type(argv[1]);
+}
+
+void
+set_type(const char *ttype)
+{
+       struct types *p;
+       int comret;
+
+       for (p = types; p->t_name; p++)
+               if (strcmp(ttype, p->t_name) == 0)
+                       break;
+       if (p->t_name == 0) {
+               fprintf(ttyout, "%s: unknown mode.\n", ttype);
+               code = -1;
+               return;
+       }
+       if ((p->t_arg != NULL) && (*(p->t_arg) != '\0'))
+               comret = command("TYPE %s %s", p->t_mode, p->t_arg);
+       else
+               comret = command("TYPE %s", p->t_mode);
+       if (comret == COMPLETE) {
+               (void)strlcpy(typename, p->t_name, sizeof(typename));
+               curtype = type = p->t_type;
+       }
+}
+
+/*
+ * Internal form of settype; changes current type in use with server
+ * without changing our notion of the type for data transfers.
+ * Used to change to and from ascii for listings.
+ */
+void
+changetype(int newtype, int show)
+{
+       struct types *p;
+       int comret, oldverbose = verbose;
+
+       if (newtype == 0)
+               newtype = TYPE_I;
+       if (newtype == curtype)
+               return;
+       if (ftp_debug == 0 && show == 0)
+               verbose = 0;
+       for (p = types; p->t_name; p++)
+               if (newtype == p->t_type)
+                       break;
+       if (p->t_name == 0) {
+               errx(1, "changetype: unknown type %d", newtype);
+       }
+       if (newtype == TYPE_L && bytename[0] != '\0')
+               comret = command("TYPE %s %s", p->t_mode, bytename);
+       else
+               comret = command("TYPE %s", p->t_mode);
+       if (comret == COMPLETE)
+               curtype = newtype;
+       verbose = oldverbose;
+}
+
+/*
+ * Set binary transfer type.
+ */
+/*VARARGS*/
+void
+setbinary(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       set_type("binary");
+}
+
+/*
+ * Set ascii transfer type.
+ */
+/*VARARGS*/
+void
+setascii(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       set_type("ascii");
+}
+
+/*
+ * Set tenex transfer type.
+ */
+/*VARARGS*/
+void
+settenex(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       set_type("tenex");
+}
+
+/*
+ * Set file transfer mode.
+ */
+/*ARGSUSED*/
+void
+setftmode(int argc, char *argv[])
+{
+
+       if (argc != 2) {
+               UPRINTF("usage: %s mode-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       fprintf(ttyout, "We only support %s mode, sorry.\n", modename);
+       code = -1;
+}
+
+/*
+ * Set file transfer format.
+ */
+/*ARGSUSED*/
+void
+setform(int argc, char *argv[])
+{
+
+       if (argc != 2) {
+               UPRINTF("usage: %s format\n", argv[0]);
+               code = -1;
+               return;
+       }
+       fprintf(ttyout, "We only support %s format, sorry.\n", formname);
+       code = -1;
+}
+
+/*
+ * Set file transfer structure.
+ */
+/*ARGSUSED*/
+void
+setstruct(int argc, char *argv[])
+{
+
+       if (argc != 2) {
+               UPRINTF("usage: %s struct-mode\n", argv[0]);
+               code = -1;
+               return;
+       }
+       fprintf(ttyout, "We only support %s structure, sorry.\n", structname);
+       code = -1;
+}
+
+/*
+ * Send a single file.
+ */
+void
+put(int argc, char *argv[])
+{
+       char buf[MAXPATHLEN];
+       const char *cmd;
+       int loc = 0;
+       char *locfile;
+       const char *remfile;
+
+       if (argc == 2) {
+               argc++;
+               argv[2] = argv[1];
+               loc++;
+       }
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "local-file")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s local-file [remote-file]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if ((locfile = globulize(argv[1])) == NULL) {
+               code = -1;
+               return;
+       }
+       remfile = argv[2];
+       if (loc)        /* If argv[2] is a copy of the old argv[1], update it */
+               remfile = locfile;
+       cmd = (argv[0][0] == 'a') ? "APPE" : ((sunique) ? "STOU" : "STOR");
+       remfile = doprocess(buf, sizeof(buf), remfile,
+               0, loc && ntflag, loc && mapflag);
+       sendrequest(cmd, locfile, remfile,
+           locfile != argv[1] || remfile != argv[2]);
+       free(locfile);
+}
+
+static const char *
+doprocess(char *dst, size_t dlen, const char *src,
+    int casef, int transf, int mapf)
+{
+       if (casef)
+               src = docase(dst, dlen, src);
+       if (transf)
+               src = dotrans(dst, dlen, src);
+       if (mapf)
+               src = domap(dst, dlen, src);
+       return src;
+}
+
+/*
+ * Send multiple files.
+ */
+void
+mput(int argc, char *argv[])
+{
+       int i;
+       sigfunc oldintr;
+       int ointer;
+       const char *tp;
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "local-files"))) {
+               UPRINTF("usage: %s local-files\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mflag = 1;
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort(argv[0]);
+       if (proxy) {
+               char *cp;
+
+               while ((cp = remglob(argv, 0, NULL)) != NULL) {
+                       if (*cp == '\0' || !connected) {
+                               mflag = 0;
+                               continue;
+                       }
+                       if (mflag && confirm(argv[0], cp)) {
+                               char buf[MAXPATHLEN];
+                               tp = doprocess(buf, sizeof(buf), cp,
+                                   mcase, ntflag, mapflag);
+                               sendrequest((sunique) ? "STOU" : "STOR",
+                                   cp, tp, cp != tp || !interactive);
+                               if (!mflag && fromatty) {
+                                       ointer = interactive;
+                                       interactive = 1;
+                                       if (confirm(argv[0], NULL)) {
+                                               mflag++;
+                                       }
+                                       interactive = ointer;
+                               }
+                       }
+               }
+               goto cleanupmput;
+       }
+       for (i = 1; i < argc && connected; i++) {
+               char **cpp;
+               glob_t gl;
+               int flags;
+
+               if (!doglob) {
+                       if (mflag && confirm(argv[0], argv[i])) {
+                               char buf[MAXPATHLEN];
+                               tp = doprocess(buf, sizeof(buf), argv[i],
+                                       0, ntflag, mapflag);
+                               sendrequest((sunique) ? "STOU" : "STOR",
+                                   argv[i], tp, tp != argv[i] || !interactive);
+                               if (!mflag && fromatty) {
+                                       ointer = interactive;
+                                       interactive = 1;
+                                       if (confirm(argv[0], NULL)) {
+                                               mflag++;
+                                       }
+                                       interactive = ointer;
+                               }
+                       }
+                       continue;
+               }
+
+               memset(&gl, 0, sizeof(gl));
+               flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
+               if (glob(argv[i], flags, NULL, &gl) || gl.gl_pathc == 0) {
+                       warnx("Glob pattern `%s' not found", argv[i]);
+                       globfree(&gl);
+                       continue;
+               }
+               for (cpp = gl.gl_pathv; cpp && *cpp != NULL && connected;
+                   cpp++) {
+                       if (mflag && confirm(argv[0], *cpp)) {
+                               char buf[MAXPATHLEN];
+                               tp = *cpp;
+                               tp = doprocess(buf, sizeof(buf), *cpp,
+                                   0, ntflag, mapflag);
+                               sendrequest((sunique) ? "STOU" : "STOR",
+                                   *cpp, tp, *cpp != tp || !interactive);
+                               if (!mflag && fromatty) {
+                                       ointer = interactive;
+                                       interactive = 1;
+                                       if (confirm(argv[0], NULL)) {
+                                               mflag++;
+                                       }
+                                       interactive = ointer;
+                               }
+                       }
+               }
+               globfree(&gl);
+       }
+ cleanupmput:
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+}
+
+void
+reget(int argc, char *argv[])
+{
+
+       (void)getit(argc, argv, 1, restart_point ? "r+" : "a");
+}
+
+void
+get(int argc, char *argv[])
+{
+
+       (void)getit(argc, argv, 0, restart_point ? "r+" : "w");
+}
+
+/*
+ * Receive one file.
+ * If restartit is  1, restart the xfer always.
+ * If restartit is -1, restart the xfer only if the remote file is newer.
+ */
+int
+getit(int argc, char *argv[], int restartit, const char *gmode)
+{
+       int     loc, rval;
+       char    *remfile, *olocfile;
+       const char *locfile;
+       char    buf[MAXPATHLEN];
+
+       loc = rval = 0;
+       if (argc == 2) {
+               argc++;
+               argv[2] = argv[1];
+               loc++;
+       }
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "remote-file")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s remote-file [local-file]\n", argv[0]);
+               code = -1;
+               return (0);
+       }
+       remfile = argv[1];
+       if ((olocfile = globulize(argv[2])) == NULL) {
+               code = -1;
+               return (0);
+       }
+       locfile = doprocess(buf, sizeof(buf), olocfile,
+               loc && mcase, loc && ntflag, loc && mapflag);
+       if (restartit) {
+               struct stat stbuf;
+               int ret;
+
+               if (! features[FEAT_REST_STREAM]) {
+                       fprintf(ttyout,
+                           "Restart is not supported by the remote server.\n");
+                       return (0);
+               }
+               ret = stat(locfile, &stbuf);
+               if (restartit == 1) {
+                       if (ret < 0) {
+                               if (errno != ENOENT) {
+                                       warn("Can't stat `%s'", locfile);
+                                       goto freegetit;
+                               }
+                               restart_point = 0;
+                       }
+                       else
+                               restart_point = stbuf.st_size;
+               } else {
+                       if (ret == 0) {
+                               time_t mtime;
+
+                               mtime = remotemodtime(argv[1], 0);
+                               if (mtime == -1)
+                                       goto freegetit;
+                               if (stbuf.st_mtime >= mtime) {
+                                       rval = 1;
+                                       goto freegetit;
+                               }
+                       }
+               }
+       }
+
+       recvrequest("RETR", locfile, remfile, gmode,
+           remfile != argv[1] || locfile != argv[2], loc);
+       restart_point = 0;
+ freegetit:
+       (void)free(olocfile);
+       return (rval);
+}
+
+/* ARGSUSED */
+static void
+mintr(int signo)
+{
+
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       siglongjmp(jabort, 1);
+}
+
+static void
+mabort(const char *cmd)
+{
+       int ointer, oconf;
+
+       if (mflag && fromatty) {
+               ointer = interactive;
+               oconf = confirmrest;
+               interactive = 1;
+               confirmrest = 0;
+               if (confirm(cmd, NULL)) {
+                       interactive = ointer;
+                       confirmrest = oconf;
+                       return;
+               }
+               interactive = ointer;
+               confirmrest = oconf;
+       }
+       mflag = 0;
+}
+
+/*
+ * Get multiple files.
+ */
+void
+mget(int argc, char *argv[])
+{
+       sigfunc oldintr;
+       int ointer;
+       char *cp;
+       const char *tp;
+       int volatile restartit;
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "remote-files"))) {
+               UPRINTF("usage: %s remote-files\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mflag = 1;
+       restart_point = 0;
+       restartit = 0;
+       if (strcmp(argv[0], "mreget") == 0) {
+               if (! features[FEAT_REST_STREAM]) {
+                       fprintf(ttyout,
+                   "Restart is not supported by the remote server.\n");
+                       return;
+               }
+               restartit = 1;
+       }
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort(argv[0]);
+       while ((cp = remglob(argv, proxy, NULL)) != NULL) {
+               char buf[MAXPATHLEN];
+               if (*cp == '\0' || !connected) {
+                       mflag = 0;
+                       continue;
+               }
+               if (! mflag)
+                       continue;
+               if (! fileindir(cp, localcwd)) {
+                       fprintf(ttyout, "Skipping non-relative filename `%s'\n",
+                           cp);
+                       continue;
+               }
+               if (!confirm(argv[0], cp))
+                       continue;
+               tp = doprocess(buf, sizeof(buf), cp, mcase, ntflag, mapflag);
+               if (restartit) {
+                       struct stat stbuf;
+
+                       if (stat(tp, &stbuf) == 0)
+                               restart_point = stbuf.st_size;
+                       else
+                               warn("Can't stat `%s'", tp);
+               }
+               recvrequest("RETR", tp, cp, restart_point ? "r+" : "w",
+                   tp != cp || !interactive, 1);
+               restart_point = 0;
+               if (!mflag && fromatty) {
+                       ointer = interactive;
+                       interactive = 1;
+                       if (confirm(argv[0], NULL))
+                               mflag++;
+                       interactive = ointer;
+               }
+       }
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+}
+
+/*
+ * Read list of filenames from a local file and get those
+ */
+void
+fget(int argc, char *argv[])
+{
+       const char *gmode;
+       FILE    *fp;
+       char    buf[MAXPATHLEN], cmdbuf[MAX_C_NAME];
+
+       if (argc != 2) {
+               UPRINTF("usage: %s localfile\n", argv[0]);
+               code = -1;
+               return;
+       }
+
+       fp = fopen(argv[1], "r");
+       if (fp == NULL) {
+               fprintf(ttyout, "Can't open source file %s\n", argv[1]);
+               code = -1;
+               return;
+       }
+
+       (void)strlcpy(cmdbuf, "get", sizeof(cmdbuf));
+       argv[0] = cmdbuf;
+       gmode = restart_point ? "r+" : "w";
+
+       while (get_line(fp, buf, sizeof(buf), NULL) >= 0) {
+               if (buf[0] == '\0')
+                       continue;
+               argv[1] = buf;
+               (void)getit(argc, argv, 0, gmode);
+       }
+       fclose(fp);
+}
+
+const char *
+onoff(int val)
+{
+
+       return (val ? "on" : "off");
+}
+
+/*
+ * Show status.
+ */
+/*ARGSUSED*/
+void
+status(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+#ifndef NO_STATUS
+       if (connected)
+               fprintf(ttyout, "Connected %sto %s.\n",
+                   connected == -1 ? "and logged in" : "", hostname);
+       else
+               fputs("Not connected.\n", ttyout);
+       if (!proxy) {
+               pswitch(1);
+               if (connected) {
+                       fprintf(ttyout, "Connected for proxy commands to %s.\n",
+                           hostname);
+               }
+               else {
+                       fputs("No proxy connection.\n", ttyout);
+               }
+               pswitch(0);
+       }
+       fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode),
+           *gateserver ? gateserver : "(none)", gateport);
+       fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
+           onoff(passivemode), onoff(activefallback));
+       fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n",
+           modename, typename, formname, structname);
+       fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n",
+           onoff(verbose), onoff(bell), onoff(interactive), onoff(doglob));
+       fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n",
+           onoff(sunique), onoff(runique));
+       fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve));
+       fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase),
+           onoff(crflag));
+       if (ntflag) {
+               fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout);
+       }
+       else {
+               fputs("Ntrans: off.\n", ttyout);
+       }
+       if (mapflag) {
+               fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout);
+       }
+       else {
+               fputs("Nmap: off.\n", ttyout);
+       }
+       fprintf(ttyout,
+           "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
+           onoff(hash), mark, onoff(progress));
+       fprintf(ttyout,
+           "Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
+           onoff(rate_get), rate_get, rate_get_incr);
+       fprintf(ttyout,
+           "Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
+           onoff(rate_put), rate_put, rate_put_incr);
+       fprintf(ttyout,
+           "Socket buffer sizes: send %d, receive %d.\n",
+           sndbuf_size, rcvbuf_size);
+       fprintf(ttyout, "Use of PORT cmds: %s.\n", onoff(sendport));
+       fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4),
+           epsv4bad ? " (disabled for this connection)" : "");
+       fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv6: %s%s.\n", onoff(epsv6),
+           epsv6bad ? " (disabled for this connection)" : "");
+       fprintf(ttyout, "Command line editing: %s.\n",
+#ifdef NO_EDITCOMPLETE
+           "support not compiled in"
+#else  /* !def NO_EDITCOMPLETE */
+           onoff(editing)
+#endif /* !def NO_EDITCOMPLETE */
+           );
+       if (macnum > 0) {
+               int i;
+
+               fputs("Macros:\n", ttyout);
+               for (i=0; i<macnum; i++) {
+                       fprintf(ttyout, "\t%s\n", macros[i].mac_name);
+               }
+       }
+#endif /* !def NO_STATUS */
+       fprintf(ttyout, "Version: %s %s\n", FTP_PRODUCT, FTP_VERSION);
+       code = 0;
+}
+
+/*
+ * Toggle a variable
+ */
+int
+togglevar(int argc, char *argv[], int *var, const char *mesg)
+{
+       if (argc == 1) {
+               *var = !*var;
+       } else if (argc == 2 && strcasecmp(argv[1], "on") == 0) {
+               *var = 1;
+       } else if (argc == 2 && strcasecmp(argv[1], "off") == 0) {
+               *var = 0;
+       } else {
+               UPRINTF("usage: %s [ on | off ]\n", argv[0]);
+               return (-1);
+       }
+       if (mesg)
+               fprintf(ttyout, "%s %s.\n", mesg, onoff(*var));
+       return (*var);
+}
+
+/*
+ * Set beep on cmd completed mode.
+ */
+/*VARARGS*/
+void
+setbell(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &bell, "Bell mode");
+}
+
+/*
+ * Set command line editing
+ */
+/*VARARGS*/
+void
+setedit(int argc, char *argv[])
+{
+
+#ifdef NO_EDITCOMPLETE
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (verbose)
+               fputs("Editing support not compiled in; ignoring command.\n",
+                   ttyout);
+#else  /* !def NO_EDITCOMPLETE */
+       code = togglevar(argc, argv, &editing, "Editing mode");
+       controlediting();
+#endif /* !def NO_EDITCOMPLETE */
+}
+
+/*
+ * Turn on packet tracing.
+ */
+/*VARARGS*/
+void
+settrace(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &trace, "Packet tracing");
+}
+
+/*
+ * Toggle hash mark printing during transfers, or set hash mark bytecount.
+ */
+/*VARARGS*/
+void
+sethash(int argc, char *argv[])
+{
+       if (argc == 1)
+               hash = !hash;
+       else if (argc != 2) {
+               UPRINTF("usage: %s [ on | off | bytecount ]\n",
+                   argv[0]);
+               code = -1;
+               return;
+       } else if (strcasecmp(argv[1], "on") == 0)
+               hash = 1;
+       else if (strcasecmp(argv[1], "off") == 0)
+               hash = 0;
+       else {
+               int nmark;
+
+               nmark = strsuftoi(argv[1]);
+               if (nmark < 1) {
+                       fprintf(ttyout, "mark: bad bytecount value `%s'.\n",
+                           argv[1]);
+                       code = -1;
+                       return;
+               }
+               mark = nmark;
+               hash = 1;
+       }
+       fprintf(ttyout, "Hash mark printing %s", onoff(hash));
+       if (hash)
+               fprintf(ttyout, " (%d bytes/hash mark)", mark);
+       fputs(".\n", ttyout);
+       if (hash)
+               progress = 0;
+       code = hash;
+}
+
+/*
+ * Turn on printing of server echo's.
+ */
+/*VARARGS*/
+void
+setverbose(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &verbose, "Verbose mode");
+}
+
+/*
+ * Toggle PORT/LPRT cmd use before each data connection.
+ */
+/*VARARGS*/
+void
+setport(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
+}
+
+/*
+ * Toggle transfer progress bar.
+ */
+/*VARARGS*/
+void
+setprogress(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &progress, "Progress bar");
+       if (progress)
+               hash = 0;
+}
+
+/*
+ * Turn on interactive prompting during mget, mput, and mdelete.
+ */
+/*VARARGS*/
+void
+setprompt(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &interactive, "Interactive mode");
+}
+
+/*
+ * Toggle gate-ftp mode, or set gate-ftp server
+ */
+/*VARARGS*/
+void
+setgate(int argc, char *argv[])
+{
+       static char gsbuf[MAXHOSTNAMELEN];
+
+       if (argc == 0 || argc > 3) {
+               UPRINTF(
+                   "usage: %s [ on | off | gateserver [port] ]\n", argv[0]);
+               code = -1;
+               return;
+       } else if (argc < 2) {
+               gatemode = !gatemode;
+       } else {
+               if (argc == 2 && strcasecmp(argv[1], "on") == 0)
+                       gatemode = 1;
+               else if (argc == 2 && strcasecmp(argv[1], "off") == 0)
+                       gatemode = 0;
+               else {
+                       if (argc == 3)
+                               gateport = ftp_strdup(argv[2]);
+                       (void)strlcpy(gsbuf, argv[1], sizeof(gsbuf));
+                       gateserver = gsbuf;
+                       gatemode = 1;
+               }
+       }
+       if (gatemode && (gateserver == NULL || *gateserver == '\0')) {
+               fprintf(ttyout,
+                   "Disabling gate-ftp mode - no gate-ftp server defined.\n");
+               gatemode = 0;
+       } else {
+               fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n",
+                   onoff(gatemode), *gateserver ? gateserver : "(none)",
+                   gateport);
+       }
+       code = gatemode;
+}
+
+/*
+ * Toggle metacharacter interpretation on local file names.
+ */
+/*VARARGS*/
+void
+setglob(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &doglob, "Globbing");
+}
+
+/*
+ * Toggle preserving modification times on retrieved files.
+ */
+/*VARARGS*/
+void
+setpreserve(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &preserve, "Preserve modification times");
+}
+
+/*
+ * Set debugging mode on/off and/or set level of debugging.
+ */
+/*VARARGS*/
+void
+setdebug(int argc, char *argv[])
+{
+       if (argc == 0 || argc > 2) {
+               UPRINTF("usage: %s [ on | off | debuglevel ]\n", argv[0]);
+               code = -1;
+               return;
+       } else if (argc == 2) {
+               if (strcasecmp(argv[1], "on") == 0)
+                       ftp_debug = 1;
+               else if (strcasecmp(argv[1], "off") == 0)
+                       ftp_debug = 0;
+               else {
+                       int val;
+
+                       val = strsuftoi(argv[1]);
+                       if (val < 0) {
+                               fprintf(ttyout, "%s: bad debugging value.\n",
+                                   argv[1]);
+                               code = -1;
+                               return;
+                       }
+                       ftp_debug = val;
+               }
+       } else
+               ftp_debug = !ftp_debug;
+       if (ftp_debug)
+               options |= SO_DEBUG;
+       else
+               options &= ~SO_DEBUG;
+       fprintf(ttyout, "Debugging %s (ftp_debug=%d).\n", onoff(ftp_debug), ftp_debug);
+       code = ftp_debug > 0;
+}
+
+/*
+ * Set current working directory on remote machine.
+ */
+void
+cd(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-directory"))) {
+               UPRINTF("usage: %s remote-directory\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("CWD %s", argv[1]);
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("CWD command not recognized, trying XCWD.\n",
+                           ttyout);
+               r = command("XCWD %s", argv[1]);
+       }
+       if (r == COMPLETE) {
+               dirchange = 1;
+               updateremotecwd();
+       }
+}
+
+/*
+ * Set current working directory on local machine.
+ */
+void
+lcd(int argc, char *argv[])
+{
+       char *locdir;
+
+       code = -1;
+       if (argc == 1) {
+               argc++;
+               argv[1] = localhome;
+       }
+       if (argc != 2) {
+               UPRINTF("usage: %s [local-directory]\n", argv[0]);
+               return;
+       }
+       if ((locdir = globulize(argv[1])) == NULL)
+               return;
+       if (chdir(locdir) == -1)
+               warn("Can't chdir `%s'", locdir);
+       else {
+               updatelocalcwd();
+               if (localcwd[0]) {
+                       fprintf(ttyout, "Local directory now: %s\n", localcwd);
+                       code = 0;
+               } else {
+                       fprintf(ttyout, "Unable to determine local directory\n");
+               }
+       }
+       (void)free(locdir);
+}
+
+/*
+ * Delete a single file.
+ */
+void
+delete(int argc, char *argv[])
+{
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (command("DELE %s", argv[1]) == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Delete multiple files.
+ */
+void
+mdelete(int argc, char *argv[])
+{
+       sigfunc oldintr;
+       int ointer;
+       char *cp;
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "remote-files"))) {
+               UPRINTF("usage: %s [remote-files]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mflag = 1;
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort(argv[0]);
+       while ((cp = remglob(argv, 0, NULL)) != NULL) {
+               if (*cp == '\0') {
+                       mflag = 0;
+                       continue;
+               }
+               if (mflag && confirm(argv[0], cp)) {
+                       if (command("DELE %s", cp) == COMPLETE)
+                               dirchange = 1;
+                       if (!mflag && fromatty) {
+                               ointer = interactive;
+                               interactive = 1;
+                               if (confirm(argv[0], NULL)) {
+                                       mflag++;
+                               }
+                               interactive = ointer;
+                       }
+               }
+       }
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+}
+
+/*
+ * Rename a remote file.
+ */
+void
+renamefile(int argc, char *argv[])
+{
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "from-name")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s from-name to-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (command("RNFR %s", argv[1]) == CONTINUE &&
+           command("RNTO %s", argv[2]) == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Get a directory listing of remote files.
+ * Supports being invoked as:
+ *     cmd             runs
+ *     ---             ----
+ *     dir, ls         LIST
+ *     mlsd            MLSD
+ *     nlist           NLST
+ *     pdir, pls       LIST |$PAGER
+ *     pmlsd           MLSD |$PAGER
+ */
+void
+ls(int argc, char *argv[])
+{
+       const char *cmd;
+       char *remdir, *locbuf;
+       const char *locfile;
+       int pagecmd, mlsdcmd;
+
+       remdir = NULL;
+       locbuf = NULL;
+       locfile = "-";
+       pagecmd = mlsdcmd = 0;
+                       /*
+                        * the only commands that start with `p' are
+                        * the `pager' versions.
+                        */
+       if (argv[0][0] == 'p')
+               pagecmd = 1;
+       if (strcmp(argv[0] + pagecmd , "mlsd") == 0) {
+               if (! features[FEAT_MLST]) {
+                       fprintf(ttyout,
+                          "MLSD is not supported by the remote server.\n");
+                       return;
+               }
+               mlsdcmd = 1;
+       }
+       if (argc == 0)
+               goto usage;
+
+       if (mlsdcmd)
+               cmd = "MLSD";
+       else if (strcmp(argv[0] + pagecmd, "nlist") == 0)
+               cmd = "NLST";
+       else
+               cmd = "LIST";
+
+       if (argc > 1)
+               remdir = argv[1];
+       if (argc > 2)
+               locfile = argv[2];
+       if (argc > 3 || ((pagecmd | mlsdcmd) && argc > 2)) {
+ usage:
+               if (pagecmd || mlsdcmd)
+                       UPRINTF("usage: %s [remote-path]\n", argv[0]);
+               else
+                       UPRINTF("usage: %s [remote-path [local-file]]\n",
+                           argv[0]);
+               code = -1;
+               goto freels;
+       }
+
+       if (pagecmd) {
+               const char *p;
+               size_t len;
+
+               p = getoptionvalue("pager");
+               if (EMPTYSTRING(p))
+                       p = DEFAULTPAGER;
+               len = strlen(p) + 2;
+               locbuf = ftp_malloc(len);
+               locbuf[0] = '|';
+               (void)strlcpy(locbuf + 1, p, len - 1);
+               locfile = locbuf;
+       } else if ((strcmp(locfile, "-") != 0) && *locfile != '|') {
+               if ((locbuf = globulize(locfile)) == NULL ||
+                   !confirm("output to local-file:", locbuf)) {
+                       code = -1;
+                       goto freels;
+               }
+               locfile = locbuf;
+       }
+       recvrequest(cmd, locfile, remdir, "w", 0, 0);
+ freels:
+       if (locbuf)
+               (void)free(locbuf);
+}
+
+/*
+ * Get a directory listing of multiple remote files.
+ */
+void
+mls(int argc, char *argv[])
+{
+       sigfunc oldintr;
+       int ointer, i;
+       int volatile dolist;
+       char * volatile dest, *odest;
+       const char *lmode;
+
+       if (argc == 0)
+               goto usage;
+       if (argc < 2 && !another(&argc, &argv, "remote-files"))
+               goto usage;
+       if (argc < 3 && !another(&argc, &argv, "local-file")) {
+ usage:
+               UPRINTF("usage: %s remote-files local-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       odest = dest = argv[argc - 1];
+       argv[argc - 1] = NULL;
+       if (strcmp(dest, "-") && *dest != '|')
+               if (((dest = globulize(dest)) == NULL) ||
+                   !confirm("output to local-file:", dest)) {
+                       code = -1;
+                       return;
+       }
+       dolist = strcmp(argv[0], "mls");
+       mflag = 1;
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort(argv[0]);
+       for (i = 1; mflag && i < argc-1 && connected; i++) {
+               lmode = (i == 1) ? "w" : "a";
+               recvrequest(dolist ? "LIST" : "NLST", dest, argv[i], lmode,
+                   0, 0);
+               if (!mflag && fromatty) {
+                       ointer = interactive;
+                       interactive = 1;
+                       if (confirm(argv[0], NULL)) {
+                               mflag++;
+                       }
+                       interactive = ointer;
+               }
+       }
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+       if (dest != odest)                      /* free up after globulize() */
+               free(dest);
+}
+
+/*
+ * Do a shell escape
+ */
+/*ARGSUSED*/
+void
+shell(int argc, char *argv[])
+{
+       pid_t pid;
+       sigfunc oldintr;
+       char shellnam[MAXPATHLEN];
+       const char *shellp, *namep;
+       int wait_status;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s [command [args]]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       oldintr = xsignal(SIGINT, SIG_IGN);
+       if ((pid = fork()) == 0) {
+               for (pid = 3; pid < 20; pid++)
+                       (void)close(pid);
+               (void)xsignal(SIGINT, SIG_DFL);
+               shellp = getenv("SHELL");
+               if (shellp == NULL)
+                       shellp = _PATH_BSHELL;
+               namep = strrchr(shellp, '/');
+               if (namep == NULL)
+                       namep = shellp;
+               else
+                       namep++;
+               (void)strlcpy(shellnam, namep, sizeof(shellnam));
+               if (ftp_debug) {
+                       fputs(shellp, ttyout);
+                       putc('\n', ttyout);
+               }
+               if (argc > 1) {
+                       execl(shellp, shellnam, "-c", altarg, (char *)0);
+               }
+               else {
+                       execl(shellp, shellnam, (char *)0);
+               }
+               warn("Can't execute `%s'", shellp);
+               code = -1;
+               exit(1);
+       }
+       if (pid > 0)
+               while (wait(&wait_status) != pid)
+                       ;
+       (void)xsignal(SIGINT, oldintr);
+       if (pid == -1) {
+               warn("Can't fork a subshell; try again later");
+               code = -1;
+       } else
+               code = 0;
+}
+
+/*
+ * Send new user information (re-login)
+ */
+void
+user(int argc, char *argv[])
+{
+       char *password;
+       char emptypass[] = "";
+       int n, aflag = 0;
+
+       if (argc == 0)
+               goto usage;
+       if (argc < 2)
+               (void)another(&argc, &argv, "username");
+       if (argc < 2 || argc > 4) {
+ usage:
+               UPRINTF("usage: %s username [password [account]]\n",
+                   argv[0]);
+               code = -1;
+               return;
+       }
+       n = command("USER %s", argv[1]);
+       if (n == CONTINUE) {
+               if (argc < 3) {
+                       password = getpass("Password: ");
+                       if (password == NULL)
+                               password = emptypass;
+               } else {
+                       password = argv[2];
+               }
+               n = command("PASS %s", password);
+               memset(password, 0, strlen(password));
+       }
+       if (n == CONTINUE) {
+               aflag++;
+               if (argc < 4) {
+                       password = getpass("Account: ");
+                       if (password == NULL)
+                               password = emptypass;
+               } else {
+                       password = argv[3];
+               }
+               n = command("ACCT %s", password);
+               memset(password, 0, strlen(password));
+       }
+       if (n != COMPLETE) {
+               fputs("Login failed.\n", ttyout);
+               return;
+       }
+       if (!aflag && argc == 4) {
+               password = argv[3];
+               (void)command("ACCT %s", password);
+               memset(password, 0, strlen(password));
+       }
+       connected = -1;
+       getremoteinfo();
+}
+
+/*
+ * Print working directory on remote machine.
+ */
+/*VARARGS*/
+void
+pwd(int argc, char *argv[])
+{
+
+       code = -1;
+       if (argc != 1) {
+               UPRINTF("usage: %s\n", argv[0]);
+               return;
+       }
+       if (! remotecwd[0])
+               updateremotecwd();
+       if (! remotecwd[0])
+               fprintf(ttyout, "Unable to determine remote directory\n");
+       else {
+               fprintf(ttyout, "Remote directory: %s\n", remotecwd);
+               code = 0;
+       }
+}
+
+/*
+ * Print working directory on local machine.
+ */
+void
+lpwd(int argc, char *argv[])
+{
+
+       code = -1;
+       if (argc != 1) {
+               UPRINTF("usage: %s\n", argv[0]);
+               return;
+       }
+       if (! localcwd[0])
+               updatelocalcwd();
+       if (! localcwd[0])
+               fprintf(ttyout, "Unable to determine local directory\n");
+       else {
+               fprintf(ttyout, "Local directory: %s\n", localcwd);
+               code = 0;
+       }
+}
+
+/*
+ * Make a directory.
+ */
+void
+makedir(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "directory-name"))) {
+               UPRINTF("usage: %s directory-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("MKD %s", argv[1]);
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("MKD command not recognized, trying XMKD.\n",
+                           ttyout);
+               r = command("XMKD %s", argv[1]);
+       }
+       if (r == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Remove a directory.
+ */
+void
+removedir(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "directory-name"))) {
+               UPRINTF("usage: %s directory-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("RMD %s", argv[1]);
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("RMD command not recognized, trying XRMD.\n",
+                           ttyout);
+               r = command("XRMD %s", argv[1]);
+       }
+       if (r == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Send a line, verbatim, to the remote machine.
+ */
+void
+quote(int argc, char *argv[])
+{
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "command line to send"))) {
+               UPRINTF("usage: %s line-to-send\n", argv[0]);
+               code = -1;
+               return;
+       }
+       quote1("", argc, argv);
+}
+
+/*
+ * Send a SITE command to the remote machine.  The line
+ * is sent verbatim to the remote machine, except that the
+ * word "SITE" is added at the front.
+ */
+void
+site(int argc, char *argv[])
+{
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "arguments to SITE command"))){
+               UPRINTF("usage: %s line-to-send\n", argv[0]);
+               code = -1;
+               return;
+       }
+       quote1("SITE ", argc, argv);
+}
+
+/*
+ * Turn argv[1..argc) into a space-separated string, then prepend initial text.
+ * Send the result as a one-line command and get response.
+ */
+void
+quote1(const char *initial, int argc, char *argv[])
+{
+       int i;
+       char buf[BUFSIZ];               /* must be >= sizeof(line) */
+
+       (void)strlcpy(buf, initial, sizeof(buf));
+       for (i = 1; i < argc; i++) {
+               (void)strlcat(buf, argv[i], sizeof(buf));
+               if (i < (argc - 1))
+                       (void)strlcat(buf, " ", sizeof(buf));
+       }
+       if (command("%s", buf) == PRELIM) {
+               while (getreply(0) == PRELIM)
+                       continue;
+       }
+       dirchange = 1;
+}
+
+void
+do_chmod(int argc, char *argv[])
+{
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "mode")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s mode remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       (void)command("SITE CHMOD %s %s", argv[1], argv[2]);
+}
+
+#define COMMAND_1ARG(argc, argv, cmd)                  \
+       if (argc == 1)                                  \
+               command(cmd);                           \
+       else                                            \
+               command(cmd " %s", argv[1])
+
+void
+do_umask(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s [umask]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;
+       COMMAND_1ARG(argc, argv, "SITE UMASK");
+       verbose = oldverbose;
+}
+
+void
+idlecmd(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc < 1 || argc > 2) {
+               UPRINTF("usage: %s [seconds]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;
+       COMMAND_1ARG(argc, argv, "SITE IDLE");
+       verbose = oldverbose;
+}
+
+/*
+ * Ask the other side for help.
+ */
+void
+rmthelp(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;
+       COMMAND_1ARG(argc, argv, "HELP");
+       verbose = oldverbose;
+}
+
+/*
+ * Terminate session and exit.
+ * May be called with 0, NULL.
+ */
+/*VARARGS*/
+void
+quit(int argc, char *argv[])
+{
+
+                       /* this may be called with argc == 0, argv == NULL */
+       if (argc == 0 && argv != NULL) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (connected)
+               disconnect(0, NULL);
+       pswitch(1);
+       if (connected)
+               disconnect(0, NULL);
+       exit(0);
+}
+
+/*
+ * Terminate session, but don't exit.
+ * May be called with 0, NULL.
+ */
+void
+disconnect(int argc, char *argv[])
+{
+
+                       /* this may be called with argc == 0, argv == NULL */
+       if (argc == 0 && argv != NULL) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (!connected)
+               return;
+       (void)command("QUIT");
+       cleanuppeer();
+}
+
+void
+account(int argc, char *argv[])
+{
+       char *ap;
+       char emptypass[] = "";
+
+       if (argc == 0 || argc > 2) {
+               UPRINTF("usage: %s [password]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       else if (argc == 2)
+               ap = argv[1];
+       else {
+               ap = getpass("Account:");
+               if (ap == NULL)
+                       ap = emptypass;
+       }
+       (void)command("ACCT %s", ap);
+       memset(ap, 0, strlen(ap));
+}
+
+sigjmp_buf abortprox;
+
+void
+proxabort(int notused)
+{
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       if (!proxy) {
+               pswitch(1);
+       }
+       if (connected) {
+               proxflag = 1;
+       }
+       else {
+               proxflag = 0;
+       }
+       pswitch(0);
+       siglongjmp(abortprox, 1);
+}
+
+void
+doproxy(int argc, char *argv[])
+{
+       struct cmd *c;
+       int cmdpos;
+       sigfunc oldintr;
+       char cmdbuf[MAX_C_NAME];
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "command"))) {
+               UPRINTF("usage: %s command\n", argv[0]);
+               code = -1;
+               return;
+       }
+       c = getcmd(argv[1]);
+       if (c == (struct cmd *) -1) {
+               fputs("?Ambiguous command.\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (c == 0) {
+               fputs("?Invalid command.\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (!c->c_proxy) {
+               fputs("?Invalid proxy command.\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (sigsetjmp(abortprox, 1)) {
+               code = -1;
+               return;
+       }
+       oldintr = xsignal(SIGINT, proxabort);
+       pswitch(1);
+       if (c->c_conn && !connected) {
+               fputs("Not connected.\n", ttyout);
+               pswitch(0);
+               (void)xsignal(SIGINT, oldintr);
+               code = -1;
+               return;
+       }
+       cmdpos = strcspn(line, " \t");
+       if (cmdpos > 0)         /* remove leading "proxy " from input buffer */
+               memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
+       (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf));
+       argv[1] = cmdbuf;
+       (*c->c_handler)(argc-1, argv+1);
+       if (connected) {
+               proxflag = 1;
+       }
+       else {
+               proxflag = 0;
+       }
+       pswitch(0);
+       (void)xsignal(SIGINT, oldintr);
+}
+
+void
+setcase(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &mcase, "Case mapping");
+}
+
+/*
+ * convert the given name to lower case if it's all upper case, into
+ * a static buffer which is returned to the caller
+ */
+static const char *
+docase(char *dst, size_t dlen, const char *src)
+{
+       size_t i;
+       int dochange = 1;
+
+       for (i = 0; src[i] != '\0' && i < dlen - 1; i++) {
+               dst[i] = src[i];
+               if (islower((unsigned char)dst[i]))
+                       dochange = 0;
+       }
+       dst[i] = '\0';
+
+       if (dochange) {
+               for (i = 0; dst[i] != '\0'; i++)
+                       if (isupper((unsigned char)dst[i]))
+                               dst[i] = tolower((unsigned char)dst[i]);
+       }
+       return dst;
+}
+
+void
+setcr(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
+}
+
+void
+setntrans(int argc, char *argv[])
+{
+
+       if (argc == 0 || argc > 3) {
+               UPRINTF("usage: %s [inchars [outchars]]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (argc == 1) {
+               ntflag = 0;
+               fputs("Ntrans off.\n", ttyout);
+               code = ntflag;
+               return;
+       }
+       ntflag++;
+       code = ntflag;
+       (void)strlcpy(ntin, argv[1], sizeof(ntin));
+       if (argc == 2) {
+               ntout[0] = '\0';
+               return;
+       }
+       (void)strlcpy(ntout, argv[2], sizeof(ntout));
+}
+
+static const char *
+dotrans(char *dst, size_t dlen, const char *src)
+{
+       const char *cp1;
+       char *cp2 = dst;
+       size_t i, ostop;
+
+       for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
+               continue;
+       for (cp1 = src; *cp1; cp1++) {
+               int found = 0;
+               for (i = 0; *(ntin + i) && i < 16; i++) {
+                       if (*cp1 == *(ntin + i)) {
+                               found++;
+                               if (i < ostop) {
+                                       *cp2++ = *(ntout + i);
+                                       if (cp2 - dst >= (ptrdiff_t)(dlen - 1))
+                                               goto out;
+                               }
+                               break;
+                       }
+               }
+               if (!found) {
+                       *cp2++ = *cp1;
+               }
+       }
+out:
+       *cp2 = '\0';
+       return dst;
+}
+
+void
+setnmap(int argc, char *argv[])
+{
+       char *cp;
+
+       if (argc == 1) {
+               mapflag = 0;
+               fputs("Nmap off.\n", ttyout);
+               code = mapflag;
+               return;
+       }
+       if (argc == 0 ||
+           (argc < 3 && !another(&argc, &argv, "mapout")) || argc > 3) {
+               UPRINTF("usage: %s [mapin mapout]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mapflag = 1;
+       code = 1;
+       cp = strchr(altarg, ' ');
+       if (proxy) {
+               while(*++cp == ' ')
+                       continue;
+               altarg = cp;
+               cp = strchr(altarg, ' ');
+       }
+       *cp = '\0';
+       (void)strlcpy(mapin, altarg, MAXPATHLEN);
+       while (*++cp == ' ')
+               continue;
+       (void)strlcpy(mapout, cp, MAXPATHLEN);
+}
+
+static const char *
+domap(char *dst, size_t dlen, const char *src)
+{
+       const char *cp1 = src;
+       char *cp2 = mapin;
+       const char *tp[9], *te[9];
+       int i, toks[9], toknum = 0, match = 1;
+
+       for (i=0; i < 9; ++i) {
+               toks[i] = 0;
+       }
+       while (match && *cp1 && *cp2) {
+               switch (*cp2) {
+                       case '\\':
+                               if (*++cp2 != *cp1) {
+                                       match = 0;
+                               }
+                               break;
+                       case '$':
+                               if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
+                                       if (*cp1 != *(++cp2+1)) {
+                                               toks[toknum = *cp2 - '1']++;
+                                               tp[toknum] = cp1;
+                                               while (*++cp1 && *(cp2+1)
+                                                       != *cp1);
+                                               te[toknum] = cp1;
+                                       }
+                                       cp2++;
+                                       break;
+                               }
+                               /* FALLTHROUGH */
+                       default:
+                               if (*cp2 != *cp1) {
+                                       match = 0;
+                               }
+                               break;
+               }
+               if (match && *cp1) {
+                       cp1++;
+               }
+               if (match && *cp2) {
+                       cp2++;
+               }
+       }
+       if (!match && *cp1) /* last token mismatch */
+       {
+               toks[toknum] = 0;
+       }
+       cp2 = dst;
+       *cp2 = '\0';
+       cp1 = mapout;
+       while (*cp1) {
+               match = 0;
+               switch (*cp1) {
+                       case '\\':
+                               if (*(cp1 + 1)) {
+                                       *cp2++ = *++cp1;
+                               }
+                               break;
+                       case '[':
+LOOP:
+                               if (*++cp1 == '$' &&
+                                   isdigit((unsigned char)*(cp1+1))) {
+                                       if (*++cp1 == '0') {
+                                               const char *cp3 = src;
+
+                                               while (*cp3) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                               match = 1;
+                                       }
+                                       else if (toks[toknum = *cp1 - '1']) {
+                                               const char *cp3 = tp[toknum];
+
+                                               while (cp3 != te[toknum]) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                               match = 1;
+                                       }
+                               }
+                               else {
+                                       while (*cp1 && *cp1 != ',' &&
+                                           *cp1 != ']') {
+                                               if (*cp1 == '\\') {
+                                                       cp1++;
+                                               }
+                                               else if (*cp1 == '$' &&
+                                                   isdigit((unsigned char)*(cp1+1))) {
+                                                       if (*++cp1 == '0') {
+                                                          const char *cp3 = src;
+
+                                                          while (*cp3) {
+                                                               *cp2++ = *cp3++;
+                                                          }
+                                                       }
+                                                       else if (toks[toknum =
+                                                           *cp1 - '1']) {
+                                                          const char *cp3=tp[toknum];
+
+                                                          while (cp3 !=
+                                                                 te[toknum]) {
+                                                               *cp2++ = *cp3++;
+                                                          }
+                                                       }
+                                               }
+                                               else if (*cp1) {
+                                                       *cp2++ = *cp1++;
+                                               }
+                                       }
+                                       if (!*cp1) {
+                                               fputs(
+                                               "nmap: unbalanced brackets.\n",
+                                                   ttyout);
+                                               return (src);
+                                       }
+                                       match = 1;
+                                       cp1--;
+                               }
+                               if (match) {
+                                       while (*++cp1 && *cp1 != ']') {
+                                             if (*cp1 == '\\' && *(cp1 + 1)) {
+                                                       cp1++;
+                                             }
+                                       }
+                                       if (!*cp1) {
+                                               fputs(
+                                               "nmap: unbalanced brackets.\n",
+                                                   ttyout);
+                                               return (src);
+                                       }
+                                       break;
+                               }
+                               switch (*++cp1) {
+                                       case ',':
+                                               goto LOOP;
+                                       case ']':
+                                               break;
+                                       default:
+                                               cp1--;
+                                               goto LOOP;
+                               }
+                               break;
+                       case '$':
+                               if (isdigit((unsigned char)*(cp1 + 1))) {
+                                       if (*++cp1 == '0') {
+                                               const char *cp3 = src;
+
+                                               while (*cp3) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                       }
+                                       else if (toks[toknum = *cp1 - '1']) {
+                                               const char *cp3 = tp[toknum];
+
+                                               while (cp3 != te[toknum]) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                       }
+                                       break;
+                               }
+                               /* intentional drop through */
+                       default:
+                               *cp2++ = *cp1;
+                               break;
+               }
+               cp1++;
+       }
+       *cp2 = '\0';
+       return *dst ? dst : src;
+}
+
+void
+setpassive(int argc, char *argv[])
+{
+
+       if (argc == 1) {
+               passivemode = !passivemode;
+               activefallback = passivemode;
+       } else if (argc != 2) {
+ passiveusage:
+               UPRINTF("usage: %s [ on | off | auto ]\n", argv[0]);
+               code = -1;
+               return;
+       } else if (strcasecmp(argv[1], "on") == 0) {
+               passivemode = 1;
+               activefallback = 0;
+       } else if (strcasecmp(argv[1], "off") == 0) {
+               passivemode = 0;
+               activefallback = 0;
+       } else if (strcasecmp(argv[1], "auto") == 0) {
+               passivemode = 1;
+               activefallback = 1;
+       } else
+               goto passiveusage;
+       fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
+           onoff(passivemode), onoff(activefallback));
+       code = passivemode;
+}
+
+
+void
+setepsv4(int argc, char *argv[])
+{
+       code = togglevar(argc, argv, &epsv4,
+           verbose ? "EPSV/EPRT on IPv4" : NULL);
+       epsv4bad = 0;
+}
+
+void
+setepsv6(int argc, char *argv[])
+{
+       code = togglevar(argc, argv, &epsv6,
+           verbose ? "EPSV/EPRT on IPv6" : NULL);
+       epsv6bad = 0;
+}
+
+void
+setepsv(int argc, char*argv[])
+{
+       setepsv4(argc,argv);
+       setepsv6(argc,argv);
+}
+
+void
+setsunique(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &sunique, "Store unique");
+}
+
+void
+setrunique(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &runique, "Receive unique");
+}
+
+int
+parserate(int argc, char *argv[], int cmdlineopt)
+{
+       int dir, max, incr, showonly;
+       sigfunc oldusr1, oldusr2;
+
+       if (argc > 4 || (argc < (cmdlineopt ? 3 : 2))) {
+ usage:
+               if (cmdlineopt)
+                       UPRINTF(
+       "usage: %s (all|get|put),maximum-bytes[,increment-bytes]]\n",
+                           argv[0]);
+               else
+                       UPRINTF(
+       "usage: %s (all|get|put) [maximum-bytes [increment-bytes]]\n",
+                           argv[0]);
+               return -1;
+       }
+       dir = max = incr = showonly = 0;
+#define        RATE_GET        1
+#define        RATE_PUT        2
+#define        RATE_ALL        (RATE_GET | RATE_PUT)
+
+       if (strcasecmp(argv[1], "all") == 0)
+               dir = RATE_ALL;
+       else if (strcasecmp(argv[1], "get") == 0)
+               dir = RATE_GET;
+       else if (strcasecmp(argv[1], "put") == 0)
+               dir = RATE_PUT;
+       else
+               goto usage;
+
+       if (argc >= 3) {
+               if ((max = strsuftoi(argv[2])) < 0)
+                       goto usage;
+       } else
+               showonly = 1;
+
+       if (argc == 4) {
+               if ((incr = strsuftoi(argv[3])) <= 0)
+                       goto usage;
+       } else
+               incr = DEFAULTINCR;
+
+       oldusr1 = xsignal(SIGUSR1, SIG_IGN);
+       oldusr2 = xsignal(SIGUSR2, SIG_IGN);
+       if (dir & RATE_GET) {
+               if (!showonly) {
+                       rate_get = max;
+                       rate_get_incr = incr;
+               }
+               if (!cmdlineopt || verbose)
+                       fprintf(ttyout,
+               "Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
+                           onoff(rate_get), rate_get, rate_get_incr);
+       }
+       if (dir & RATE_PUT) {
+               if (!showonly) {
+                       rate_put = max;
+                       rate_put_incr = incr;
+               }
+               if (!cmdlineopt || verbose)
+                       fprintf(ttyout,
+               "Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
+                           onoff(rate_put), rate_put, rate_put_incr);
+       }
+       (void)xsignal(SIGUSR1, oldusr1);
+       (void)xsignal(SIGUSR2, oldusr2);
+       return 0;
+}
+
+void
+setrate(int argc, char *argv[])
+{
+
+       code = parserate(argc, argv, 0);
+}
+
+/* change directory to parent directory */
+void
+cdup(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("CDUP");
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("CDUP command not recognized, trying XCUP.\n",
+                           ttyout);
+               r = command("XCUP");
+       }
+       if (r == COMPLETE) {
+               dirchange = 1;
+               updateremotecwd();
+       }
+}
+
+/*
+ * Restart transfer at specific point
+ */
+void
+restart(int argc, char *argv[])
+{
+
+       if (argc == 0 || argc > 2) {
+               UPRINTF("usage: %s [restart-point]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_REST_STREAM]) {
+               fprintf(ttyout,
+                   "Restart is not supported by the remote server.\n");
+               return;
+       }
+       if (argc == 2) {
+               off_t rp;
+               char *ep;
+
+               rp = STRTOLL(argv[1], &ep, 10);
+               if (rp < 0 || *ep != '\0')
+                       fprintf(ttyout, "restart: Invalid offset `%s'\n",
+                           argv[1]);
+               else
+                       restart_point = rp;
+       }
+       if (restart_point == 0)
+               fputs("No restart point defined.\n", ttyout);
+       else
+               fprintf(ttyout,
+                   "Restarting at " LLF " for next get, put or append\n",
+                   (LLT)restart_point);
+}
+
+/*
+ * Show remote system type
+ */
+void
+syst(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       (void)command("SYST");
+       verbose = oldverbose;
+}
+
+void
+macdef(int argc, char *argv[])
+{
+       char *tmp;
+       int c;
+
+       if (argc == 0)
+               goto usage;
+       if (macnum == 16) {
+               fputs("Limit of 16 macros have already been defined.\n",
+                   ttyout);
+               code = -1;
+               return;
+       }
+       if ((argc < 2 && !another(&argc, &argv, "macro name")) || argc > 2) {
+ usage:
+               UPRINTF("usage: %s macro_name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (interactive)
+               fputs(
+               "Enter macro line by line, terminating it with a null line.\n",
+                   ttyout);
+       (void)strlcpy(macros[macnum].mac_name, argv[1],
+           sizeof(macros[macnum].mac_name));
+       if (macnum == 0)
+               macros[macnum].mac_start = macbuf;
+       else
+               macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
+       tmp = macros[macnum].mac_start;
+       while (tmp != macbuf+4096) {
+               if ((c = getchar()) == EOF) {
+                       fputs("macdef: end of file encountered.\n", ttyout);
+                       code = -1;
+                       return;
+               }
+               if ((*tmp = c) == '\n') {
+                       if (tmp == macros[macnum].mac_start) {
+                               macros[macnum++].mac_end = tmp;
+                               code = 0;
+                               return;
+                       }
+                       if (*(tmp-1) == '\0') {
+                               macros[macnum++].mac_end = tmp - 1;
+                               code = 0;
+                               return;
+                       }
+                       *tmp = '\0';
+               }
+               tmp++;
+       }
+       while (1) {
+               while ((c = getchar()) != '\n' && c != EOF)
+                       /* LOOP */;
+               if (c == EOF || getchar() == '\n') {
+                       fputs("Macro not defined - 4K buffer exceeded.\n",
+                           ttyout);
+                       code = -1;
+                       return;
+               }
+       }
+}
+
+/*
+ * Get size of file on remote machine
+ */
+void
+sizecmd(int argc, char *argv[])
+{
+       off_t size;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       size = remotesize(argv[1], 1);
+       if (size != -1)
+               fprintf(ttyout,
+                   "%s\t" LLF "\n", argv[1], (LLT)size);
+       code = (size > 0);
+}
+
+/*
+ * Get last modification time of file on remote machine
+ */
+void
+modtime(int argc, char *argv[])
+{
+       time_t mtime;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mtime = remotemodtime(argv[1], 1);
+       if (mtime != -1)
+               fprintf(ttyout, "%s\t%s", argv[1],
+                   rfc2822time(localtime(&mtime)));
+       code = (mtime > 0);
+}
+
+/*
+ * Show status on remote machine
+ */
+void
+rmtstatus(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s [remote-file]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       COMMAND_1ARG(argc, argv, "STAT");
+}
+
+/*
+ * Get file if modtime is more recent than current file
+ */
+void
+newer(int argc, char *argv[])
+{
+
+       if (getit(argc, argv, -1, "w"))
+               fprintf(ttyout,
+                   "Local file \"%s\" is newer than remote file \"%s\".\n",
+                   argv[2], argv[1]);
+}
+
+/*
+ * Display one local file through $PAGER.
+ */
+void
+lpage(int argc, char *argv[])
+{
+       size_t len;
+       const char *p;
+       char *pager, *locfile;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "local-file"))) {
+               UPRINTF("usage: %s local-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if ((locfile = globulize(argv[1])) == NULL) {
+               code = -1;
+               return;
+       }
+       p = getoptionvalue("pager");
+       if (EMPTYSTRING(p))
+               p = DEFAULTPAGER;
+       len = strlen(p) + strlen(locfile) + 2;
+       pager = ftp_malloc(len);
+       (void)strlcpy(pager, p,         len);
+       (void)strlcat(pager, " ",       len);
+       (void)strlcat(pager, locfile,   len);
+       system(pager);
+       code = 0;
+       (void)free(pager);
+       (void)free(locfile);
+}
+
+/*
+ * Display one remote file through $PAGER.
+ */
+void
+page(int argc, char *argv[])
+{
+       int ohash, orestart_point, overbose;
+       size_t len;
+       const char *p;
+       char *pager;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       p = getoptionvalue("pager");
+       if (EMPTYSTRING(p))
+               p = DEFAULTPAGER;
+       len = strlen(p) + 2;
+       pager = ftp_malloc(len);
+       pager[0] = '|';
+       (void)strlcpy(pager + 1, p, len - 1);
+
+       ohash = hash;
+       orestart_point = restart_point;
+       overbose = verbose;
+       hash = restart_point = verbose = 0;
+       recvrequest("RETR", pager, argv[1], "r+", 1, 0);
+       hash = ohash;
+       restart_point = orestart_point;
+       verbose = overbose;
+       (void)free(pager);
+}
+
+/*
+ * Set the socket send or receive buffer size.
+ */
+void
+setxferbuf(int argc, char *argv[])
+{
+       int size, dir;
+
+       if (argc != 2) {
+ usage:
+               UPRINTF("usage: %s size\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (strcasecmp(argv[0], "sndbuf") == 0)
+               dir = RATE_PUT;
+       else if (strcasecmp(argv[0], "rcvbuf") == 0)
+               dir = RATE_GET;
+       else if (strcasecmp(argv[0], "xferbuf") == 0)
+               dir = RATE_ALL;
+       else
+               goto usage;
+
+       if ((size = strsuftoi(argv[1])) == -1)
+               goto usage;
+
+       if (size == 0) {
+               fprintf(ttyout, "%s: size must be positive.\n", argv[0]);
+               goto usage;
+       }
+
+       if (dir & RATE_PUT)
+               sndbuf_size = size;
+       if (dir & RATE_GET)
+               rcvbuf_size = size;
+       fprintf(ttyout, "Socket buffer sizes: send %d, receive %d.\n",
+           sndbuf_size, rcvbuf_size);
+       code = 0;
+}
+
+/*
+ * Set or display options (defaults are provided by various env vars)
+ */
+void
+setoption(int argc, char *argv[])
+{
+       struct option *o;
+
+       code = -1;
+       if (argc == 0 || (argc != 1 && argc != 3)) {
+               UPRINTF("usage: %s [option value]\n", argv[0]);
+               return;
+       }
+
+#define        OPTIONINDENT ((int) sizeof("https_proxy"))
+       if (argc == 1) {
+               for (o = optiontab; o->name != NULL; o++) {
+                       fprintf(ttyout, "%-*s\t%s\n", OPTIONINDENT,
+                           o->name, o->value ? o->value : "");
+               }
+       } else {
+               set_option(argv[1], argv[2], 1);
+       }
+       code = 0;
+}
+
+void
+set_option(const char * option, const char * value, int doverbose)
+{
+       struct option *o;
+
+       o = getoption(option);
+       if (o == NULL) {
+               fprintf(ttyout, "No such option `%s'.\n", option);
+               return;
+       }
+       FREEPTR(o->value);
+       o->value = ftp_strdup(value);
+       if (verbose && doverbose)
+               fprintf(ttyout, "Setting `%s' to `%s'.\n",
+                   o->name, o->value);
+}
+
+/*
+ * Unset an option
+ */
+void
+unsetoption(int argc, char *argv[])
+{
+       struct option *o;
+
+       code = -1;
+       if (argc == 0 || argc != 2) {
+               UPRINTF("usage: %s option\n", argv[0]);
+               return;
+       }
+
+       o = getoption(argv[1]);
+       if (o == NULL) {
+               fprintf(ttyout, "No such option `%s'.\n", argv[1]);
+               return;
+       }
+       FREEPTR(o->value);
+       fprintf(ttyout, "Unsetting `%s'.\n", o->name);
+       code = 0;
+}
+
+/*
+ * Display features supported by the remote host.
+ */
+void
+feat(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_FEAT]) {
+               fprintf(ttyout,
+                   "FEAT is not supported by the remote server.\n");
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       (void)command("FEAT");
+       verbose = oldverbose;
+}
+
+void
+mlst(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc < 1 || argc > 2) {
+               UPRINTF("usage: %s [remote-path]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_MLST]) {
+               fprintf(ttyout,
+                   "MLST is not supported by the remote server.\n");
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       COMMAND_1ARG(argc, argv, "MLST");
+       verbose = oldverbose;
+}
+
+void
+opts(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc < 2 || argc > 3) {
+               UPRINTF("usage: %s command [options]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_FEAT]) {
+               fprintf(ttyout,
+                   "OPTS is not supported by the remote server.\n");
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       if (argc == 2)
+               command("OPTS %s", argv[1]);
+       else
+               command("OPTS %s %s", argv[1], argv[2]);
+       verbose = oldverbose;
+}
diff --git a/usr.bin/ftp/cmdtab.c b/usr.bin/ftp/cmdtab.c
new file mode 100644 (file)
index 0000000..13d3f4b
--- /dev/null
@@ -0,0 +1,307 @@
+/*     $NetBSD: cmdtab.c,v 1.52 2012/12/22 16:57:09 christos Exp $     */
+
+/*-
+ * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cmdtab.c   8.4 (Berkeley) 10/9/94";
+#else
+__RCSID("$NetBSD: cmdtab.c,v 1.52 2012/12/22 16:57:09 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include "ftp_var.h"
+
+/*
+ * User FTP -- Command Tables.
+ */
+
+#define HSTR   static const char
+
+#ifndef NO_HELP
+HSTR   accounthelp[] = "send account command to remote server";
+HSTR   appendhelp[] =  "append to a file";
+HSTR   asciihelp[] =   "set ascii transfer type";
+HSTR   beephelp[] =    "beep when command completed";
+HSTR   binaryhelp[] =  "set binary transfer type";
+HSTR   casehelp[] =    "toggle mget upper/lower case id mapping";
+HSTR   cdhelp[] =      "change remote working directory";
+HSTR   cduphelp[] =    "change remote working directory to parent directory";
+HSTR   chmodhelp[] =   "change file permissions of remote file";
+HSTR   connecthelp[] = "connect to remote ftp server";
+HSTR   crhelp[] =      "toggle carriage return stripping on ascii gets";
+HSTR   debughelp[] =   "toggle/set debugging mode";
+HSTR   deletehelp[] =  "delete remote file";
+HSTR   disconhelp[] =  "terminate ftp session";
+HSTR   domachelp[] =   "execute macro";
+HSTR   edithelp[] =    "toggle command line editing";
+HSTR   epsvhelp[] =    "toggle use of EPSV/EPRT on both IPv4 and IPV6 ftp";
+HSTR   epsv4help[] =   "toggle use of EPSV/EPRT on IPv4 ftp";
+HSTR   epsv6help[] =   "toggle use of EPSV/EPRT on IPv6 ftp";
+HSTR   feathelp[] =    "show FEATures supported by remote system";
+HSTR   formhelp[] =    "set file transfer format";
+HSTR   gatehelp[] =    "toggle gate-ftp; specify host[:port] to change proxy";
+HSTR   globhelp[] =    "toggle metacharacter expansion of local file names";
+HSTR   hashhelp[] =    "toggle printing `#' marks; specify number to set size";
+HSTR   helphelp[] =    "print local help information";
+HSTR   idlehelp[] =    "get (set) idle timer on remote side";
+HSTR   lcdhelp[] =     "change local working directory";
+HSTR   lpagehelp[] =   "view a local file through your pager";
+HSTR   lpwdhelp[] =    "print local working directory";
+HSTR   lshelp[] =      "list contents of remote path";
+HSTR   macdefhelp[] =  "define a macro";
+HSTR   mdeletehelp[] = "delete multiple files";
+HSTR   mgethelp[] =    "get multiple files";
+HSTR   mregethelp[] =  "get multiple files restarting at end of local file";
+HSTR   fgethelp[] =    "get files using a localfile as a source of names";
+HSTR   mkdirhelp[] =   "make directory on the remote machine";
+HSTR   mlshelp[] =     "list contents of multiple remote directories";
+HSTR   mlsdhelp[] =    "list contents of remote directory in a machine "
+                       "parsable form";
+HSTR   mlsthelp[] =    "list remote path in a machine parsable form";
+HSTR   modehelp[] =    "set file transfer mode";
+HSTR   modtimehelp[] = "show last modification time of remote file";
+HSTR   mputhelp[] =    "send multiple files";
+HSTR   newerhelp[] =   "get file if remote file is newer than local file ";
+HSTR   nmaphelp[] =    "set templates for default file name mapping";
+HSTR   ntranshelp[] =  "set translation table for default file name mapping";
+HSTR   optshelp[] =    "show or set options for remote commands";
+HSTR   pagehelp[] =    "view a remote file through your pager";
+HSTR   passivehelp[] = "toggle use of passive transfer mode";
+HSTR   plshelp[] =     "list contents of remote path through your pager";
+HSTR   pmlsdhelp[] =   "list contents of remote directory in a machine "
+                       "parsable form through your pager";
+HSTR   porthelp[] =    "toggle use of PORT/LPRT cmd for each data connection";
+HSTR   preservehelp[] ="toggle preservation of modification time of "
+                       "retrieved files";
+HSTR   progresshelp[] ="toggle transfer progress meter";
+HSTR   prompthelp[] =  "force interactive prompting on multiple commands";
+HSTR   proxyhelp[] =   "issue command on alternate connection";
+HSTR   pwdhelp[] =     "print working directory on remote machine";
+HSTR   quithelp[] =    "terminate ftp session and exit";
+HSTR   quotehelp[] =   "send arbitrary ftp command";
+HSTR   ratehelp[] =    "set transfer rate limit (in bytes/second)";
+HSTR   receivehelp[] = "receive file";
+HSTR   regethelp[] =   "get file restarting at end of local file";
+HSTR   remotehelp[] =  "get help from remote server";
+HSTR   renamehelp[] =  "rename file";
+HSTR   resethelp[] =   "clear queued command replies";
+HSTR   restarthelp[]=  "restart file transfer at bytecount";
+HSTR   rmdirhelp[] =   "remove directory on the remote machine";
+HSTR   rmtstatushelp[]="show status of remote machine";
+HSTR   runiquehelp[] = "toggle store unique for local files";
+HSTR   sendhelp[] =    "send one file";
+HSTR   sethelp[] =     "set or display options";
+HSTR   shellhelp[] =   "escape to the shell";
+HSTR   sitehelp[] =    "send site specific command to remote server\n"
+                       "\t\tTry \"rhelp site\" or \"site help\" "
+                       "for more information";
+HSTR   sizecmdhelp[] = "show size of remote file";
+HSTR   statushelp[] =  "show current status";
+HSTR   structhelp[] =  "set file transfer structure";
+HSTR   suniquehelp[] = "toggle store unique on remote machine";
+HSTR   systemhelp[] =  "show remote system type";
+HSTR   tenexhelp[] =   "set tenex file transfer type";
+HSTR   tracehelp[] =   "toggle packet tracing";
+HSTR   typehelp[] =    "set file transfer type";
+HSTR   umaskhelp[] =   "get (set) umask on remote side";
+HSTR   unsethelp[] =   "unset an option";
+HSTR   usagehelp[] =   "show command usage";
+HSTR   userhelp[] =    "send new user information";
+HSTR   verbosehelp[] = "toggle verbose mode";
+HSTR   xferbufhelp[] = "set socket send/receive buffer size";
+#endif
+
+HSTR   empty[] = "";
+
+#ifdef NO_HELP
+#define H(x)   empty
+#else
+#define H(x)   x
+#endif
+
+#ifdef NO_EDITCOMPLETE
+#define        CMPL(x)
+#define        CMPL0
+#else  /* !NO_EDITCOMPLETE */
+#define        CMPL(x) #x,
+#define        CMPL0   empty,
+#endif /* !NO_EDITCOMPLETE */
+
+struct cmd cmdtab[] = {
+       { "!",          H(shellhelp),   0, 0, 0, CMPL0          shell },
+       { "$",          H(domachelp),   1, 0, 0, CMPL0          domacro },
+       { "account",    H(accounthelp), 0, 1, 1, CMPL0          account},
+       { "append",     H(appendhelp),  1, 1, 1, CMPL(lr)       put },
+       { "ascii",      H(asciihelp),   0, 1, 1, CMPL0          setascii },
+       { "bell",       H(beephelp),    0, 0, 0, CMPL0          setbell },
+       { "binary",     H(binaryhelp),  0, 1, 1, CMPL0          setbinary },
+       { "bye",        H(quithelp),    0, 0, 0, CMPL0          quit },
+       { "case",       H(casehelp),    0, 0, 1, CMPL0          setcase },
+       { "cd",         H(cdhelp),      0, 1, 1, CMPL(r)        cd },
+       { "cdup",       H(cduphelp),    0, 1, 1, CMPL0          cdup },
+       { "chmod",      H(chmodhelp),   0, 1, 1, CMPL(nr)       do_chmod },
+       { "close",      H(disconhelp),  0, 1, 1, CMPL0          disconnect },
+       { "cr",         H(crhelp),      0, 0, 0, CMPL0          setcr },
+       { "debug",      H(debughelp),   0, 0, 0, CMPL0          setdebug },
+       { "delete",     H(deletehelp),  0, 1, 1, CMPL(r)        delete },
+       { "dir",        H(lshelp),      1, 1, 1, CMPL(rl)       ls },
+       { "disconnect", H(disconhelp),  0, 1, 1, CMPL0          disconnect },
+       { "edit",       H(edithelp),    0, 0, 0, CMPL0          setedit },
+       { "epsv",       H(epsvhelp),    0, 0, 0, CMPL0          setepsv },
+       { "epsv4",      H(epsv4help),   0, 0, 0, CMPL0          setepsv4 },
+       { "epsv6",      H(epsv6help),   0, 0, 0, CMPL0          setepsv6 },
+       { "exit",       H(quithelp),    0, 0, 0, CMPL0          quit },
+       { "features",   H(feathelp),    0, 1, 1, CMPL0          feat },
+       { "fget",       H(fgethelp),    1, 1, 1, CMPL(l)        fget },
+       { "form",       H(formhelp),    0, 1, 1, CMPL0          setform },
+       { "ftp",        H(connecthelp), 0, 0, 1, CMPL0          setpeer },
+       { "gate",       H(gatehelp),    0, 0, 0, CMPL0          setgate },
+       { "get",        H(receivehelp), 1, 1, 1, CMPL(rl)       get },
+       { "glob",       H(globhelp),    0, 0, 0, CMPL0          setglob },
+       { "hash",       H(hashhelp),    0, 0, 0, CMPL0          sethash },
+       { "help",       H(helphelp),    0, 0, 1, CMPL(C)        help },
+       { "idle",       H(idlehelp),    0, 1, 1, CMPL0          idlecmd },
+       { "image",      H(binaryhelp),  0, 1, 1, CMPL0          setbinary },
+       { "lcd",        H(lcdhelp),     0, 0, 0, CMPL(l)        lcd },
+       { "less",       H(pagehelp),    1, 1, 1, CMPL(r)        page },
+       { "lpage",      H(lpagehelp),   0, 0, 0, CMPL(l)        lpage },
+       { "lpwd",       H(lpwdhelp),    0, 0, 0, CMPL0          lpwd },
+       { "ls",         H(lshelp),      1, 1, 1, CMPL(rl)       ls },
+       { "macdef",     H(macdefhelp),  0, 0, 0, CMPL0          macdef },
+       { "mdelete",    H(mdeletehelp), 1, 1, 1, CMPL(R)        mdelete },
+       { "mdir",       H(mlshelp),     1, 1, 1, CMPL(R)        mls },
+       { "mget",       H(mgethelp),    1, 1, 1, CMPL(R)        mget },
+       { "mkdir",      H(mkdirhelp),   0, 1, 1, CMPL(r)        makedir },
+       { "mls",        H(mlshelp),     1, 1, 1, CMPL(R)        mls },
+       { "mlsd",       H(mlsdhelp),    1, 1, 1, CMPL(r)        ls },
+       { "mlst",       H(mlsthelp),    1, 1, 1, CMPL(r)        mlst },
+       { "mode",       H(modehelp),    0, 1, 1, CMPL0          setftmode },
+       { "modtime",    H(modtimehelp), 0, 1, 1, CMPL(r)        modtime },
+       { "more",       H(pagehelp),    1, 1, 1, CMPL(r)        page },
+       { "mput",       H(mputhelp),    1, 1, 1, CMPL(L)        mput },
+       { "mreget",     H(mregethelp),  1, 1, 1, CMPL(R)        mget },
+       { "msend",      H(mputhelp),    1, 1, 1, CMPL(L)        mput },
+       { "newer",      H(newerhelp),   1, 1, 1, CMPL(r)        newer },
+       { "nlist",      H(lshelp),      1, 1, 1, CMPL(rl)       ls },
+       { "nmap",       H(nmaphelp),    0, 0, 1, CMPL0          setnmap },
+       { "ntrans",     H(ntranshelp),  0, 0, 1, CMPL0          setntrans },
+       { "open",       H(connecthelp), 0, 0, 1, CMPL0          setpeer },
+       { "page",       H(pagehelp),    1, 1, 1, CMPL(r)        page },
+       { "passive",    H(passivehelp), 0, 0, 0, CMPL0          setpassive },
+       { "pdir",       H(plshelp),     1, 1, 1, CMPL(r)        ls },
+       { "pls",        H(plshelp),     1, 1, 1, CMPL(r)        ls },
+       { "pmlsd",      H(pmlsdhelp),   1, 1, 1, CMPL(r)        ls },
+       { "preserve",   H(preservehelp),0, 0, 0, CMPL0          setpreserve },
+       { "progress",   H(progresshelp),0, 0, 0, CMPL0          setprogress },
+       { "prompt",     H(prompthelp),  0, 0, 0, CMPL0          setprompt },
+       { "proxy",      H(proxyhelp),   0, 0, 1, CMPL(c)        doproxy },
+       { "put",        H(sendhelp),    1, 1, 1, CMPL(lr)       put },
+       { "pwd",        H(pwdhelp),     0, 1, 1, CMPL0          pwd },
+       { "quit",       H(quithelp),    0, 0, 0, CMPL0          quit },
+       { "quote",      H(quotehelp),   1, 1, 1, CMPL0          quote },
+       { "rate",       H(ratehelp),    0, 0, 0, CMPL0          setrate },
+       { "rcvbuf",     H(xferbufhelp), 0, 0, 0, CMPL0          setxferbuf },
+       { "recv",       H(receivehelp), 1, 1, 1, CMPL(rl)       get },
+       { "reget",      H(regethelp),   1, 1, 1, CMPL(rl)       reget },
+       { "remopts",    H(optshelp),    0, 1, 1, CMPL0          opts },
+       { "rename",     H(renamehelp),  0, 1, 1, CMPL(rr)       renamefile },
+       { "reset",      H(resethelp),   0, 1, 1, CMPL0          reset },
+       { "restart",    H(restarthelp), 1, 1, 1, CMPL0          restart },
+       { "rhelp",      H(remotehelp),  0, 1, 1, CMPL0          rmthelp },
+       { "rmdir",      H(rmdirhelp),   0, 1, 1, CMPL(r)        removedir },
+       { "rstatus",    H(rmtstatushelp),0, 1, 1, CMPL(r)       rmtstatus },
+       { "runique",    H(runiquehelp), 0, 0, 1, CMPL0          setrunique },
+       { "send",       H(sendhelp),    1, 1, 1, CMPL(lr)       put },
+       { "sendport",   H(porthelp),    0, 0, 0, CMPL0          setport },
+       { "set",        H(sethelp),     0, 0, 0, CMPL(o)        setoption },
+       { "site",       H(sitehelp),    0, 1, 1, CMPL0          site },
+       { "size",       H(sizecmdhelp), 1, 1, 1, CMPL(r)        sizecmd },
+       { "sndbuf",     H(xferbufhelp), 0, 0, 0, CMPL0          setxferbuf },
+       { "status",     H(statushelp),  0, 0, 1, CMPL0          status },
+       { "struct",     H(structhelp),  0, 1, 1, CMPL0          setstruct },
+       { "sunique",    H(suniquehelp), 0, 0, 1, CMPL0          setsunique },
+       { "system",     H(systemhelp),  0, 1, 1, CMPL0          syst },
+       { "tenex",      H(tenexhelp),   0, 1, 1, CMPL0          settenex },
+       { "throttle",   H(ratehelp),    0, 0, 0, CMPL0          setrate },
+       { "trace",      H(tracehelp),   0, 0, 0, CMPL0          settrace },
+       { "type",       H(typehelp),    0, 1, 1, CMPL0          settype },
+       { "umask",      H(umaskhelp),   0, 1, 1, CMPL0          do_umask },
+       { "unset",      H(unsethelp),   0, 0, 0, CMPL(o)        unsetoption },
+       { "usage",      H(usagehelp),   0, 0, 1, CMPL(C)        help },
+       { "user",       H(userhelp),    0, 1, 1, CMPL0          user },
+       { "verbose",    H(verbosehelp), 0, 0, 0, CMPL0          setverbose },
+       { "xferbuf",    H(xferbufhelp), 0, 0, 0, CMPL0          setxferbuf },
+       { "?",          H(helphelp),    0, 0, 1, CMPL(C)        help },
+       { NULL,         NULL,           0, 0, 0, CMPL0          NULL },
+};
+
+struct option optiontab[] = {
+       { "anonpass",   NULL },
+       { "ftp_proxy",  NULL },
+       { "http_proxy", NULL },
+       { "https_proxy",NULL },
+       { "no_proxy",   NULL },
+       { "pager",      NULL },
+       { "prompt",     NULL },
+       { "rprompt",    NULL },
+       { NULL,         NULL },
+};
diff --git a/usr.bin/ftp/complete.c b/usr.bin/ftp/complete.c
new file mode 100644 (file)
index 0000000..617af07
--- /dev/null
@@ -0,0 +1,432 @@
+/*     $NetBSD: complete.c,v 1.46 2009/04/12 10:18:52 lukem Exp $      */
+
+/*-
+ * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: complete.c,v 1.46 2009/04/12 10:18:52 lukem Exp $");
+#endif /* not lint */
+
+/*
+ * FTP user program - command and file completion routines
+ */
+
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ftp_var.h"
+
+#ifndef NO_EDITCOMPLETE
+
+static int          comparstr          (const void *, const void *);
+static unsigned char complete_ambiguous        (char *, int, StringList *);
+static unsigned char complete_command  (char *, int);
+static unsigned char complete_local    (char *, int);
+static unsigned char complete_option   (char *, int);
+static unsigned char complete_remote   (char *, int);
+
+static int
+comparstr(const void *a, const void *b)
+{
+       return (strcmp(*(const char * const *)a, *(const char * const *)b));
+}
+
+/*
+ * Determine if complete is ambiguous. If unique, insert.
+ * If no choices, error. If unambiguous prefix, insert that.
+ * Otherwise, list choices. words is assumed to be filtered
+ * to only contain possible choices.
+ * Args:
+ *     word    word which started the match
+ *     list    list by default
+ *     words   stringlist containing possible matches
+ * Returns a result as per el_set(EL_ADDFN, ...)
+ */
+static unsigned char
+complete_ambiguous(char *word, int list, StringList *words)
+{
+       char insertstr[MAXPATHLEN];
+       char *lastmatch, *p;
+       size_t i, j;
+       size_t matchlen, wordlen;
+
+       wordlen = strlen(word);
+       if (words->sl_cur == 0)
+               return (CC_ERROR);      /* no choices available */
+
+       if (words->sl_cur == 1) {       /* only once choice available */
+               p = words->sl_str[0] + wordlen;
+               if (*p == '\0')         /* at end of word? */
+                       return (CC_REFRESH);
+               ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
+               if (el_insertstr(el, insertstr) == -1)
+                       return (CC_ERROR);
+               else
+                       return (CC_REFRESH);
+       }
+
+       if (!list) {
+               matchlen = 0;
+               lastmatch = words->sl_str[0];
+               matchlen = strlen(lastmatch);
+               for (i = 1 ; i < words->sl_cur ; i++) {
+                       for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
+                               if (lastmatch[j] != words->sl_str[i][j])
+                                       break;
+                       if (j < matchlen)
+                               matchlen = j;
+               }
+               if (matchlen > wordlen) {
+                       ftpvis(insertstr, sizeof(insertstr),
+                           lastmatch + wordlen, matchlen - wordlen);
+                       if (el_insertstr(el, insertstr) == -1)
+                               return (CC_ERROR);
+                       else
+                               return (CC_REFRESH_BEEP);
+               }
+       }
+
+       putc('\n', ttyout);
+       qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
+       list_vertical(words);
+       return (CC_REDISPLAY);
+}
+
+/*
+ * Complete a command
+ */
+static unsigned char
+complete_command(char *word, int list)
+{
+       struct cmd *c;
+       StringList *words;
+       size_t wordlen;
+       unsigned char rv;
+
+       words = ftp_sl_init();
+       wordlen = strlen(word);
+
+       for (c = cmdtab; c->c_name != NULL; c++) {
+               if (wordlen > strlen(c->c_name))
+                       continue;
+               if (strncmp(word, c->c_name, wordlen) == 0)
+                       ftp_sl_add(words, ftp_strdup(c->c_name));
+       }
+
+       rv = complete_ambiguous(word, list, words);
+       if (rv == CC_REFRESH) {
+               if (el_insertstr(el, " ") == -1)
+                       rv = CC_ERROR;
+       }
+       sl_free(words, 1);
+       return (rv);
+}
+
+/*
+ * Complete a local file
+ */
+static unsigned char
+complete_local(char *word, int list)
+{
+       StringList *words;
+       char dir[MAXPATHLEN];
+       char *file;
+       DIR *dd;
+       struct dirent *dp;
+       unsigned char rv;
+       size_t len;
+
+       if ((file = strrchr(word, '/')) == NULL) {
+               dir[0] = '.';
+               dir[1] = '\0';
+               file = word;
+       } else {
+               if (file == word) {
+                       dir[0] = '/';
+                       dir[1] = '\0';
+               } else
+                       (void)strlcpy(dir, word, file - word + 1);
+               file++;
+       }
+       if (dir[0] == '~') {
+               char *p;
+
+               if ((p = globulize(dir)) == NULL)
+                       return (CC_ERROR);
+               (void)strlcpy(dir, p, sizeof(dir));
+               free(p);
+       }
+
+       if ((dd = opendir(dir)) == NULL)
+               return (CC_ERROR);
+
+       words = ftp_sl_init();
+       len = strlen(file);
+
+       for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
+               if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+                       continue;
+
+#if defined(DIRENT_MISSING_D_NAMLEN)
+               if (len > strlen(dp->d_name))
+                       continue;
+#else
+               if (len > dp->d_namlen)
+                       continue;
+#endif
+               if (strncmp(file, dp->d_name, len) == 0) {
+                       char *tcp;
+
+                       tcp = ftp_strdup(dp->d_name);
+                       ftp_sl_add(words, tcp);
+               }
+       }
+       closedir(dd);
+
+       rv = complete_ambiguous(file, list, words);
+       if (rv == CC_REFRESH) {
+               struct stat sb;
+               char path[MAXPATHLEN];
+
+               (void)strlcpy(path, dir,                sizeof(path));
+               (void)strlcat(path, "/",                sizeof(path));
+               (void)strlcat(path, words->sl_str[0],   sizeof(path));
+
+               if (stat(path, &sb) >= 0) {
+                       char suffix[2] = " ";
+
+                       if (S_ISDIR(sb.st_mode))
+                               suffix[0] = '/';
+                       if (el_insertstr(el, suffix) == -1)
+                               rv = CC_ERROR;
+               }
+       }
+       sl_free(words, 1);
+       return (rv);
+}
+/*
+ * Complete an option
+ */
+static unsigned char
+complete_option(char *word, int list)
+{
+       struct option *o;
+       StringList *words;
+       size_t wordlen;
+       unsigned char rv;
+
+       words = ftp_sl_init();
+       wordlen = strlen(word);
+
+       for (o = optiontab; o->name != NULL; o++) {
+               if (wordlen > strlen(o->name))
+                       continue;
+               if (strncmp(word, o->name, wordlen) == 0)
+                       ftp_sl_add(words, ftp_strdup(o->name));
+       }
+
+       rv = complete_ambiguous(word, list, words);
+       if (rv == CC_REFRESH) {
+               if (el_insertstr(el, " ") == -1)
+                       rv = CC_ERROR;
+       }
+       sl_free(words, 1);
+       return (rv);
+}
+
+/*
+ * Complete a remote file
+ */
+static unsigned char
+complete_remote(char *word, int list)
+{
+       static StringList *dirlist;
+       static char      lastdir[MAXPATHLEN];
+       StringList      *words;
+       char             dir[MAXPATHLEN];
+       char            *file, *cp;
+       size_t           i;
+       unsigned char    rv;
+       char             cmdbuf[MAX_C_NAME];
+       char            *dummyargv[3] = { NULL, NULL, NULL };
+
+       (void)strlcpy(cmdbuf, "complete", sizeof(cmdbuf));
+       dummyargv[0] = cmdbuf;
+       dummyargv[1] = dir;
+
+       if ((file = strrchr(word, '/')) == NULL) {
+               dir[0] = '\0';
+               file = word;
+       } else {
+               cp = file;
+               while (*cp == '/' && cp > word)
+                       cp--;
+               (void)strlcpy(dir, word, cp - word + 2);
+               file++;
+       }
+
+       if (dirchange || dirlist == NULL ||
+           strcmp(dir, lastdir) != 0) {                /* dir not cached */
+               const char *emesg;
+
+               if (dirlist != NULL)
+                       sl_free(dirlist, 1);
+               dirlist = ftp_sl_init();
+
+               mflag = 1;
+               emesg = NULL;
+               while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) {
+                       char *tcp;
+
+                       if (!mflag)
+                               continue;
+                       if (*cp == '\0') {
+                               mflag = 0;
+                               continue;
+                       }
+                       tcp = strrchr(cp, '/');
+                       if (tcp)
+                               tcp++;
+                       else
+                               tcp = cp;
+                       tcp = ftp_strdup(tcp);
+                       ftp_sl_add(dirlist, tcp);
+               }
+               if (emesg != NULL) {
+                       fprintf(ttyout, "\n%s\n", emesg);
+                       return (CC_REDISPLAY);
+               }
+               (void)strlcpy(lastdir, dir, sizeof(lastdir));
+               dirchange = 0;
+       }
+
+       words = ftp_sl_init();
+       for (i = 0; i < dirlist->sl_cur; i++) {
+               cp = dirlist->sl_str[i];
+               if (strlen(file) > strlen(cp))
+                       continue;
+               if (strncmp(file, cp, strlen(file)) == 0)
+                       ftp_sl_add(words, cp);
+       }
+       rv = complete_ambiguous(file, list, words);
+       sl_free(words, 0);
+       return (rv);
+}
+
+/*
+ * Generic complete routine
+ */
+unsigned char
+complete(EditLine *cel, int ch)
+{
+       static char word[FTPBUFLEN];
+       static size_t lastc_argc, lastc_argo;
+
+       struct cmd *c;
+       const LineInfo *lf;
+       int dolist, cmpltype;
+       size_t celems, len;
+
+       lf = el_line(cel);
+       len = lf->lastchar - lf->buffer;
+       if (len >= sizeof(line))
+               return (CC_ERROR);
+       (void)strlcpy(line, lf->buffer, len + 1);
+       cursor_pos = line + (lf->cursor - lf->buffer);
+       lastc_argc = cursor_argc;       /* remember last cursor pos */
+       lastc_argo = cursor_argo;
+       makeargv();                     /* build argc/argv of current line */
+
+       if (cursor_argo >= sizeof(word))
+               return (CC_ERROR);
+
+       dolist = 0;
+                       /* if cursor and word is same, list alternatives */
+       if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
+           && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "",
+                       cursor_argo) == 0)
+               dolist = 1;
+       else if (cursor_argc < (size_t)margc)
+               (void)strlcpy(word, margv[cursor_argc], cursor_argo + 1);
+       word[cursor_argo] = '\0';
+
+       if (cursor_argc == 0)
+               return (complete_command(word, dolist));
+
+       c = getcmd(margv[0]);
+       if (c == (struct cmd *)-1 || c == 0)
+               return (CC_ERROR);
+       celems = strlen(c->c_complete);
+
+               /* check for 'continuation' completes (which are uppercase) */
+       if ((cursor_argc > celems) && (celems > 0)
+           && isupper((unsigned char) c->c_complete[celems-1]))
+               cursor_argc = celems;
+
+       if (cursor_argc > celems)
+               return (CC_ERROR);
+
+       cmpltype = c->c_complete[cursor_argc - 1];
+       switch (cmpltype) {
+               case 'c':                       /* command complete */
+               case 'C':
+                       return (complete_command(word, dolist));
+               case 'l':                       /* local complete */
+               case 'L':
+                       return (complete_local(word, dolist));
+               case 'n':                       /* no complete */
+               case 'N':                       /* no complete */
+                       return (CC_ERROR);
+               case 'o':                       /* local complete */
+               case 'O':
+                       return (complete_option(word, dolist));
+               case 'r':                       /* remote complete */
+               case 'R':
+                       if (connected != -1) {
+                               fputs("\nMust be logged in to complete.\n",
+                                   ttyout);
+                               return (CC_REDISPLAY);
+                       }
+                       return (complete_remote(word, dolist));
+               default:
+                       errx(1, "complete: unknown complete type `%c'",
+                           cmpltype);
+                       return (CC_ERROR);
+       }
+       /* NOTREACHED */
+}
+
+#endif /* !NO_EDITCOMPLETE */
diff --git a/usr.bin/ftp/domacro.c b/usr.bin/ftp/domacro.c
new file mode 100644 (file)
index 0000000..04799bc
--- /dev/null
@@ -0,0 +1,145 @@
+/*     $NetBSD: domacro.c,v 1.22 2009/04/12 10:18:52 lukem Exp $       */
+
+/*
+ * Copyright (c) 1985, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)domacro.c  8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: domacro.c,v 1.22 2009/04/12 10:18:52 lukem Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ftp_var.h"
+
+void
+domacro(int argc, char *argv[])
+{
+       int i, j, count = 2, loopflg = 0;
+       char *cp1, *cp2, line2[FTPBUFLEN];
+       struct cmd *c;
+       char cmdbuf[MAX_C_NAME];
+
+       if ((argc == 0 && argv != NULL) ||
+           (argc < 2 && !another(&argc, &argv, "macro name"))) {
+               UPRINTF("usage: %s macro_name [args]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       for (i = 0; i < macnum; ++i) {
+               if (!strncmp(argv[1], macros[i].mac_name, 9))
+                       break;
+       }
+       if (i == macnum) {
+               fprintf(ttyout, "'%s' macro not found.\n", argv[1]);
+               code = -1;
+               return;
+       }
+       (void)strlcpy(line2, line, sizeof(line2));
+ TOP:
+       cp1 = macros[i].mac_start;
+       while (cp1 != macros[i].mac_end) {
+               while (isspace((unsigned char)*cp1))
+                       cp1++;
+               cp2 = line;
+               while (*cp1 != '\0') {
+                       switch(*cp1) {
+                       case '\\':
+                               *cp2++ = *++cp1;
+                               break;
+                       case '$':
+                               if (isdigit((unsigned char)*(cp1+1))) {
+                                       j = 0;
+                                       while (isdigit((unsigned char)*++cp1))
+                                               j = 10*j +  *cp1 - '0';
+                                       cp1--;
+                                       if (argc - 2 >= j) {
+                                               (void)strlcpy(cp2, argv[j+1],
+                                                   sizeof(line) - (cp2 - line));
+                                               cp2 += strlen(argv[j+1]);
+                                       }
+                                       break;
+                               }
+                               if (*(cp1+1) == 'i') {
+                                       loopflg = 1;
+                                       cp1++;
+                                       if (count < argc) {
+                                               (void)strlcpy(cp2, argv[count],
+                                                   sizeof(line) - (cp2 - line));
+                                               cp2 += strlen(argv[count]);
+                                       }
+                                       break;
+                               }
+                               /* intentional drop through */
+                       default:
+                               *cp2++ = *cp1;
+                               break;
+                       }
+                       if (*cp1 != '\0')
+                               cp1++;
+               }
+               *cp2 = '\0';
+               makeargv();
+               c = getcmd(margv[0]);
+               if (c == (struct cmd *)-1) {
+                       fputs("?Ambiguous command.\n", ttyout);
+                       code = -1;
+               } else if (c == 0) {
+                       fputs("?Invalid command.\n", ttyout);
+                       code = -1;
+               } else if (c->c_conn && !connected) {
+                       fputs("Not connected.\n", ttyout);
+                       code = -1;
+               } else {
+                       if (verbose) {
+                               fputs(line, ttyout);
+                               putc('\n', ttyout);
+                       }
+                       (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf));
+                       margv[0] = cmdbuf;
+                       (*c->c_handler)(margc, margv);
+                       if (bell && c->c_bell)
+                               (void)putc('\007', ttyout);
+                       (void)strlcpy(line, line2, sizeof(line));
+                       makeargv();
+                       argc = margc;
+                       argv = margv;
+               }
+               if (cp1 != macros[i].mac_end)
+                       cp1++;
+       }
+       if (loopflg && ++count < argc)
+               goto TOP;
+}
diff --git a/usr.bin/ftp/extern.h b/usr.bin/ftp/extern.h
new file mode 100644 (file)
index 0000000..e856ef4
--- /dev/null
@@ -0,0 +1,248 @@
+/*     $NetBSD: extern.h,v 1.80 2012/07/04 06:09:37 is Exp $   */
+
+/*-
+ * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1994 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)extern.h    8.3 (Berkeley) 10/9/94
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+struct sockaddr;
+struct tm;
+struct addrinfo;
+
+void   abort_remote(FILE *);
+void   account(int, char **);
+void   ai_unmapped(struct addrinfo *);
+int    another(int *, char ***, const char *);
+int    auto_fetch(int, char **);
+int    auto_put(int, char **, const char *);
+void   blkfree(char **);
+void   cd(int, char **);
+void   cdup(int, char **);
+void   changetype(int, int);
+void   cleanuppeer(void);
+void   cmdabort(int);
+void   cmdtimeout(int);
+void   cmdscanner(void);
+int    command(const char *, ...)
+     __attribute__((__format__(__printf__, 1, 2)));
+#ifndef NO_EDITCOMPLETE
+unsigned char complete(EditLine *, int);
+void   controlediting(void);
+#endif /* !NO_EDITCOMPLETE */
+void   crankrate(int);
+FILE   *dataconn(const char *);
+void   delete(int, char **);
+void   disconnect(int, char **);
+void   do_chmod(int, char **);
+void   do_umask(int, char **);
+void   domacro(int, char **);
+void   doproxy(int, char **);
+void   feat(int, char **);
+void   fget(int, char **);
+int    fileindir(const char *, const char *);
+int    foregroundproc(void);
+void   formatbuf(char *, size_t, const char *);
+void   ftpvis(char *, size_t, const char *, size_t);
+int    ftp_login(const char *, const char *, const char *);
+void   get(int, char **);
+struct cmd *getcmd(const char *);
+int    getit(int, char **, int, const char *);
+int    get_line(FILE *, char *, size_t, const char **);
+struct option *getoption(const char *);
+char   *getoptionvalue(const char *);
+void   getremoteinfo(void);
+int    getreply(int);
+char   *globulize(const char *);
+char   *gunique(const char *);
+void   help(int, char **);
+char   *hookup(const char *, const char *);
+void   idlecmd(int, char **);
+int    initconn(void);
+__dead void    intr(int);
+int    isipv6addr(const char *);
+void   list_vertical(StringList *);
+void   lcd(int, char **);
+void   lostpeer(int);
+void   lpage(int, char **);
+void   lpwd(int, char **);
+void   ls(int, char **);
+void   macdef(int, char **);
+void   makeargv(void);
+void   makedir(int, char **);
+void   mdelete(int, char **);
+void   mget(int, char **);
+void   mls(int, char **);
+void   mlst(int, char **);
+void   modtime(int, char **);
+void   mput(int, char **);
+const char *onoff(int);
+void   opts(int, char **);
+void   newer(int, char **);
+void   page(int, char **);
+const char *parse_rfc2616time(struct tm *, const char *);
+int    parserate(int, char **, int);
+char   *prompt(void);
+__dead void    proxabort(int);
+void   proxtrans(const char *, const char *, const char *);
+void   psabort(int);
+void   pswitch(int);
+void   put(int, char **);
+void   pwd(int, char **);
+void   quit(int, char **);
+void   quote(int, char **);
+void   quote1(const char *, int, char **);
+void   recvrequest(const char *, const char *, const char *,
+           const char *, int, int);
+void   reget(int, char **);
+char   *remglob(char **, int, const char **);
+time_t remotemodtime(const char *, int);
+off_t  remotesize(const char *, int);
+void   removedir(int, char **);
+void   renamefile(int, char **);
+void   reset(int, char **);
+void   restart(int, char **);
+const char *rfc2822time(const struct tm *);
+void   rmthelp(int, char **);
+void   rmtstatus(int, char **);
+char   *rprompt(void);
+int    ruserpass(const char *, char **, char **, char **);
+void   sendrequest(const char *, const char *, const char *, int);
+void   setascii(int, char **);
+void   setbell(int, char **);
+void   setbinary(int, char **);
+void   setcase(int, char **);
+void   setcr(int, char **);
+void   setdebug(int, char **);
+void   setedit(int, char **);
+void   setepsv4(int, char **);
+void   setepsv6(int, char **);
+void   setepsv(int, char **);
+void   setform(int, char **);
+void   setftmode(int, char **);
+void   setgate(int, char **);
+void   setglob(int, char **);
+void   sethash(int, char **);
+void   setnmap(int, char **);
+void   setntrans(int, char **);
+void   setoption(int, char **);
+void   setpassive(int, char **);
+void   setpeer(int, char **);
+void   setport(int, char **);
+void   setpreserve(int, char **);
+void   setprogress(int, char **);
+void   setprompt(int, char **);
+void   setrate(int, char **);
+void   setrunique(int, char **);
+void   setstruct(int, char **);
+void   setsunique(int, char **);
+void   settenex(int, char **);
+void   settrace(int, char **);
+void   setttywidth(int);
+void   settype(int, char **);
+void   setupsockbufsize(int);
+void   setverbose(int, char **);
+void   setxferbuf(int, char **);
+void   set_option(const char *, const char *, int);
+void   shell(int, char **);
+void   site(int, char **);
+void   sizecmd(int, char **);
+char   *slurpstring(void);
+void   status(int, char **);
+int    strsuftoi(const char *);
+void   syst(int, char **);
+int    togglevar(int, char **, int *, const char *);
+void   unsetoption(int, char **);
+void   updatelocalcwd(void);
+void   updateremotecwd(void);
+void   user(int, char **);
+int    ftp_connect(int, const struct sockaddr *, socklen_t, int);
+int    ftp_listen(int, int);
+int    ftp_poll(struct pollfd *, int, int);
+void   *ftp_malloc(size_t);
+StringList *ftp_sl_init(void);
+void   ftp_sl_add(StringList *, char *);
+char   *ftp_strdup(const char *);
diff --git a/usr.bin/ftp/fetch.c b/usr.bin/ftp/fetch.c
new file mode 100644 (file)
index 0000000..4184765
--- /dev/null
@@ -0,0 +1,1979 @@
+/*     $NetBSD: fetch.c,v 1.202 2013/02/23 13:47:36 christos Exp $     */
+
+/*-
+ * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Scott Aaron Bamford.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: fetch.c,v 1.202 2013/02/23 13:47:36 christos Exp $");
+#endif /* not lint */
+
+/*
+ * FTP User Program -- Command line file retrieval
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+
+#include <arpa/ftp.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#ifdef __minix
+#include <utime.h>
+#endif
+
+#include "ssl.h"
+#include "ftp_var.h"
+#include "version.h"
+
+typedef enum {
+       UNKNOWN_URL_T=-1,
+       HTTP_URL_T,
+#ifdef WITH_SSL
+       HTTPS_URL_T,
+#endif
+       FTP_URL_T,
+       FILE_URL_T,
+       CLASSIC_URL_T
+} url_t;
+
+__dead static void     aborthttp(int);
+#ifndef NO_AUTH
+static int     auth_url(const char *, char **, const char *, const char *);
+static void    base64_encode(const unsigned char *, size_t, unsigned char *);
+#endif
+static int     go_fetch(const char *);
+static int     fetch_ftp(const char *);
+static int     fetch_url(const char *, const char *, char *, char *);
+static const char *match_token(const char **, const char *);
+static int     parse_url(const char *, const char *, url_t *, char **,
+                           char **, char **, char **, in_port_t *, char **);
+static void    url_decode(char *);
+
+static int     redirect_loop;
+
+
+#define        STRNEQUAL(a,b)  (strncasecmp((a), (b), sizeof((b))-1) == 0)
+#define        ISLWS(x)        ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t')
+#define        SKIPLWS(x)      do { while (ISLWS((*x))) x++; } while (0)
+
+
+#define        ABOUT_URL       "about:"        /* propaganda */
+#define        FILE_URL        "file://"       /* file URL prefix */
+#define        FTP_URL         "ftp://"        /* ftp URL prefix */
+#define        HTTP_URL        "http://"       /* http URL prefix */
+#ifdef WITH_SSL
+#define        HTTPS_URL       "https://"      /* https URL prefix */
+
+#define        IS_HTTP_TYPE(urltype) \
+       (((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T))
+#else
+#define        IS_HTTP_TYPE(urltype) \
+       ((urltype) == HTTP_URL_T)
+#endif
+
+/*
+ * Determine if token is the next word in buf (case insensitive).
+ * If so, advance buf past the token and any trailing LWS, and
+ * return a pointer to the token (in buf).  Otherwise, return NULL.
+ * token may be preceded by LWS.
+ * token must be followed by LWS or NUL.  (I.e, don't partial match).
+ */
+static const char *
+match_token(const char **buf, const char *token)
+{
+       const char      *p, *orig;
+       size_t          tlen;
+
+       tlen = strlen(token);
+       p = *buf;
+       SKIPLWS(p);
+       orig = p;
+       if (strncasecmp(p, token, tlen) != 0)
+               return NULL;
+       p += tlen;
+       if (*p != '\0' && !ISLWS(*p))
+               return NULL;
+       SKIPLWS(p);
+       orig = *buf;
+       *buf = p;
+       return orig;
+}
+
+#ifndef NO_AUTH
+/*
+ * Generate authorization response based on given authentication challenge.
+ * Returns -1 if an error occurred, otherwise 0.
+ * Sets response to a malloc(3)ed string; caller should free.
+ */
+static int
+auth_url(const char *challenge, char **response, const char *guser,
+       const char *gpass)
+{
+       const char      *cp, *scheme, *errormsg;
+       char            *ep, *clear, *realm;
+       char             uuser[BUFSIZ], *gotpass;
+       const char      *upass;
+       int              rval;
+       size_t           len, clen, rlen;
+
+       *response = NULL;
+       clear = realm = NULL;
+       rval = -1;
+       cp = challenge;
+       scheme = "Basic";       /* only support Basic authentication */
+       gotpass = NULL;
+
+       DPRINTF("auth_url: challenge `%s'\n", challenge);
+
+       if (! match_token(&cp, scheme)) {
+               warnx("Unsupported authentication challenge `%s'",
+                   challenge);
+               goto cleanup_auth_url;
+       }
+
+#define        REALM "realm=\""
+       if (STRNEQUAL(cp, REALM))
+               cp += sizeof(REALM) - 1;
+       else {
+               warnx("Unsupported authentication challenge `%s'",
+                   challenge);
+               goto cleanup_auth_url;
+       }
+/* XXX: need to improve quoted-string parsing to support \ quoting, etc. */
+       if ((ep = strchr(cp, '\"')) != NULL) {
+               len = ep - cp;
+               realm = (char *)ftp_malloc(len + 1);
+               (void)strlcpy(realm, cp, len + 1);
+       } else {
+               warnx("Unsupported authentication challenge `%s'",
+                   challenge);
+               goto cleanup_auth_url;
+       }
+
+       fprintf(ttyout, "Username for `%s': ", realm);
+       if (guser != NULL) {
+               (void)strlcpy(uuser, guser, sizeof(uuser));
+               fprintf(ttyout, "%s\n", uuser);
+       } else {
+               (void)fflush(ttyout);
+               if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) {
+                       warnx("%s; can't authenticate", errormsg);
+                       goto cleanup_auth_url;
+               }
+       }
+       if (gpass != NULL)
+               upass = gpass;
+       else {
+               gotpass = getpass("Password: ");
+               if (gotpass == NULL) {
+                       warnx("Can't read password");
+                       goto cleanup_auth_url;
+               }
+               upass = gotpass;
+       }
+
+       clen = strlen(uuser) + strlen(upass) + 2;       /* user + ":" + pass + "\0" */
+       clear = (char *)ftp_malloc(clen);
+       (void)strlcpy(clear, uuser, clen);
+       (void)strlcat(clear, ":", clen);
+       (void)strlcat(clear, upass, clen);
+       if (gotpass)
+               memset(gotpass, 0, strlen(gotpass));
+
+                                               /* scheme + " " + enc + "\0" */
+       rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
+       *response = (char *)ftp_malloc(rlen);
+       (void)strlcpy(*response, scheme, rlen);
+       len = strlcat(*response, " ", rlen);
+                       /* use  `clen - 1'  to not encode the trailing NUL */
+       base64_encode((unsigned char *)clear, clen - 1,
+           (unsigned char *)*response + len);
+       memset(clear, 0, clen);
+       rval = 0;
+
+ cleanup_auth_url:
+       FREEPTR(clear);
+       FREEPTR(realm);
+       return (rval);
+}
+
+/*
+ * Encode len bytes starting at clear using base64 encoding into encoded,
+ * which should be at least ((len + 2) * 4 / 3 + 1) in size.
+ */
+static void
+base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded)
+{
+       static const unsigned char enc[] =
+           "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+       unsigned char   *cp;
+       size_t   i;
+
+       cp = encoded;
+       for (i = 0; i < len; i += 3) {
+               *(cp++) = enc[((clear[i + 0] >> 2))];
+               *(cp++) = enc[((clear[i + 0] << 4) & 0x30)
+                           | ((clear[i + 1] >> 4) & 0x0f)];
+               *(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
+                           | ((clear[i + 2] >> 6) & 0x03)];
+               *(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
+       }
+       *cp = '\0';
+       while (i-- > len)
+               *(--cp) = '=';
+}
+#endif
+
+/*
+ * Decode %xx escapes in given string, `in-place'.
+ */
+static void
+url_decode(char *url)
+{
+       unsigned char *p, *q;
+
+       if (EMPTYSTRING(url))
+               return;
+       p = q = (unsigned char *)url;
+
+#define        HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
+       while (*p) {
+               if (p[0] == '%'
+                   && p[1] && isxdigit((unsigned char)p[1])
+                   && p[2] && isxdigit((unsigned char)p[2])) {
+                       *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
+                       p+=3;
+               } else
+                       *q++ = *p++;
+       }
+       *q = '\0';
+}
+
+
+/*
+ * Parse URL of form (per RFC 3986):
+ *     <type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
+ * Returns -1 if a parse error occurred, otherwise 0.
+ * It's the caller's responsibility to url_decode() the returned
+ * user, pass and path.
+ *
+ * Sets type to url_t, each of the given char ** pointers to a
+ * malloc(3)ed strings of the relevant section, and port to
+ * the number given, or ftpport if ftp://, or httpport if http://.
+ *
+ * XXX: this is not totally RFC 3986 compliant; <path> will have the
+ * leading `/' unless it's an ftp:// URL, as this makes things easier
+ * for file:// and http:// URLs.  ftp:// URLs have the `/' between the
+ * host and the URL-path removed, but any additional leading slashes
+ * in the URL-path are retained (because they imply that we should
+ * later do "CWD" with a null argument).
+ *
+ * Examples:
+ *      input URL                       output path
+ *      ---------                       -----------
+ *     "http://host"                   "/"
+ *     "http://host/"                  "/"
+ *     "http://host/path"              "/path"
+ *     "file://host/dir/file"          "dir/file"
+ *     "ftp://host"                    ""
+ *     "ftp://host/"                   ""
+ *     "ftp://host//"                  "/"
+ *     "ftp://host/dir/file"           "dir/file"
+ *     "ftp://host//dir/file"          "/dir/file"
+ */
+static int
+parse_url(const char *url, const char *desc, url_t *utype,
+               char **uuser, char **pass, char **host, char **port,
+               in_port_t *portnum, char **path)
+{
+       const char      *origurl, *tport;
+       char            *cp, *ep, *thost;
+       size_t           len;
+
+       if (url == NULL || desc == NULL || utype == NULL || uuser == NULL
+           || pass == NULL || host == NULL || port == NULL || portnum == NULL
+           || path == NULL)
+               errx(1, "parse_url: invoked with NULL argument!");
+       DPRINTF("parse_url: %s `%s'\n", desc, url);
+
+       origurl = url;
+       *utype = UNKNOWN_URL_T;
+       *uuser = *pass = *host = *port = *path = NULL;
+       *portnum = 0;
+       tport = NULL;
+
+       if (STRNEQUAL(url, HTTP_URL)) {
+               url += sizeof(HTTP_URL) - 1;
+               *utype = HTTP_URL_T;
+               *portnum = HTTP_PORT;
+               tport = httpport;
+       } else if (STRNEQUAL(url, FTP_URL)) {
+               url += sizeof(FTP_URL) - 1;
+               *utype = FTP_URL_T;
+               *portnum = FTP_PORT;
+               tport = ftpport;
+       } else if (STRNEQUAL(url, FILE_URL)) {
+               url += sizeof(FILE_URL) - 1;
+               *utype = FILE_URL_T;
+#ifdef WITH_SSL
+       } else if (STRNEQUAL(url, HTTPS_URL)) {
+               url += sizeof(HTTPS_URL) - 1;
+               *utype = HTTPS_URL_T;
+               *portnum = HTTPS_PORT;
+               tport = httpsport;
+#endif
+       } else {
+               warnx("Invalid %s `%s'", desc, url);
+ cleanup_parse_url:
+               FREEPTR(*uuser);
+               if (*pass != NULL)
+                       memset(*pass, 0, strlen(*pass));
+               FREEPTR(*pass);
+               FREEPTR(*host);
+               FREEPTR(*port);
+               FREEPTR(*path);
+               return (-1);
+       }
+
+       if (*url == '\0')
+               return (0);
+
+                       /* find [user[:pass]@]host[:port] */
+       ep = strchr(url, '/');
+       if (ep == NULL)
+               thost = ftp_strdup(url);
+       else {
+               len = ep - url;
+               thost = (char *)ftp_malloc(len + 1);
+               (void)strlcpy(thost, url, len + 1);
+               if (*utype == FTP_URL_T)        /* skip first / for ftp URLs */
+                       ep++;
+               *path = ftp_strdup(ep);
+       }
+
+       cp = strchr(thost, '@');        /* look for user[:pass]@ in URLs */
+       if (cp != NULL) {
+               if (*utype == FTP_URL_T)
+                       anonftp = 0;    /* disable anonftp */
+               *uuser = thost;
+               *cp = '\0';
+               thost = ftp_strdup(cp + 1);
+               cp = strchr(*uuser, ':');
+               if (cp != NULL) {
+                       *cp = '\0';
+                       *pass = ftp_strdup(cp + 1);
+               }
+               url_decode(*uuser);
+               if (*pass)
+                       url_decode(*pass);
+       }
+
+#ifdef INET6
+                       /*
+                        * Check if thost is an encoded IPv6 address, as per
+                        * RFC 3986:
+                        *      `[' ipv6-address ']'
+                        */
+       if (*thost == '[') {
+               cp = thost + 1;
+               if ((ep = strchr(cp, ']')) == NULL ||
+                   (ep[1] != '\0' && ep[1] != ':')) {
+                       warnx("Invalid address `%s' in %s `%s'",
+                           thost, desc, origurl);
+                       goto cleanup_parse_url;
+               }
+               len = ep - cp;          /* change `[xyz]' -> `xyz' */
+               memmove(thost, thost + 1, len);
+               thost[len] = '\0';
+               if (! isipv6addr(thost)) {
+                       warnx("Invalid IPv6 address `%s' in %s `%s'",
+                           thost, desc, origurl);
+                       goto cleanup_parse_url;
+               }
+               cp = ep + 1;
+               if (*cp == ':')
+                       cp++;
+               else
+                       cp = NULL;
+       } else
+#endif /* INET6 */
+               if ((cp = strchr(thost, ':')) != NULL)
+                       *cp++ = '\0';
+       *host = thost;
+
+                       /* look for [:port] */
+       if (cp != NULL) {
+               unsigned long   nport;
+
+               nport = strtoul(cp, &ep, 10);
+               if (*cp == '\0' || *ep != '\0' ||
+                   nport < 1 || nport > MAX_IN_PORT_T) {
+                       warnx("Unknown port `%s' in %s `%s'",
+                           cp, desc, origurl);
+                       goto cleanup_parse_url;
+               }
+               *portnum = nport;
+               tport = cp;
+       }
+
+       if (tport != NULL)
+               *port = ftp_strdup(tport);
+       if (*path == NULL) {
+               const char *emptypath = "/";
+               if (*utype == FTP_URL_T)        /* skip first / for ftp URLs */
+                       emptypath++;
+               *path = ftp_strdup(emptypath);
+       }
+
+       DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) "
+           "path `%s'\n",
+           STRorNULL(*uuser), STRorNULL(*pass),
+           STRorNULL(*host), STRorNULL(*port),
+           *portnum ? *portnum : -1, STRorNULL(*path));
+
+       return (0);
+}
+
+sigjmp_buf     httpabort;
+
+/*
+ * Retrieve URL, via a proxy if necessary, using HTTP.
+ * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
+ * http_proxy/https_proxy as appropriate.
+ * Supports HTTP redirects.
+ * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
+ * is still open (e.g, ftp xfer with trailing /)
+ */
+static int
+fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
+{
+       struct addrinfo         hints, *res, *res0 = NULL;
+       int                     error;
+       sigfunc volatile        oldintr;
+       sigfunc volatile        oldintp;
+       int volatile            s;
+       struct stat             sb;
+       int volatile            ischunked;
+       int volatile            isproxy;
+       int volatile            rval;
+       int volatile            hcode;
+       int                     len;
+       size_t                  flen;
+       static size_t           bufsize;
+       static char             *xferbuf;
+       const char              *cp, *token;
+       char                    *ep;
+       char                    buf[FTPBUFLEN];
+       const char              *errormsg;
+       char                    *volatile savefile;
+       char                    *volatile auth;
+       char                    *volatile location;
+       char                    *volatile message;
+       char                    *uuser, *pass, *host, *port, *path;
+       char                    *volatile decodedpath;
+       char                    *puser, *ppass, *useragent;
+       off_t                   hashbytes, rangestart, rangeend, entitylen;
+       int                     (*volatile closefunc)(FILE *);
+       FETCH                   *volatile fin;
+       FILE                    *volatile fout;
+       time_t                  mtime;
+       url_t                   urltype;
+       in_port_t               portnum;
+#ifdef WITH_SSL
+       void                    *ssl;
+#endif
+
+       DPRINTF("fetch_url: `%s' proxyenv `%s'\n", url, STRorNULL(proxyenv));
+
+       oldintr = oldintp = NULL;
+       closefunc = NULL;
+       fin = NULL;
+       fout = NULL;
+       s = -1;
+       savefile = NULL;
+       auth = location = message = NULL;
+       ischunked = isproxy = hcode = 0;
+       rval = 1;
+       uuser = pass = host = path = decodedpath = puser = ppass = NULL;
+
+       if (parse_url(url, "URL", &urltype, &uuser, &pass, &host, &port,
+           &portnum, &path) == -1)
+               goto cleanup_fetch_url;
+
+       if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
+           && strcasecmp(host, "localhost") != 0) {
+               warnx("No support for non local file URL `%s'", url);
+               goto cleanup_fetch_url;
+       }
+
+       if (EMPTYSTRING(path)) {
+               if (urltype == FTP_URL_T) {
+                       rval = fetch_ftp(url);
+                       goto cleanup_fetch_url;
+               }
+               if (!IS_HTTP_TYPE(urltype) || outfile == NULL)  {
+                       warnx("Invalid URL (no file after host) `%s'", url);
+                       goto cleanup_fetch_url;
+               }
+       }
+
+       decodedpath = ftp_strdup(path);
+       url_decode(decodedpath);
+
+       if (outfile)
+               savefile = ftp_strdup(outfile);
+       else {
+               cp = strrchr(decodedpath, '/');         /* find savefile */
+               if (cp != NULL)
+                       savefile = ftp_strdup(cp + 1);
+               else
+                       savefile = ftp_strdup(decodedpath);
+       }
+       DPRINTF("fetch_url: savefile `%s'\n", savefile);
+       if (EMPTYSTRING(savefile)) {
+               if (urltype == FTP_URL_T) {
+                       rval = fetch_ftp(url);
+                       goto cleanup_fetch_url;
+               }
+               warnx("No file after directory (you must specify an "
+                   "output file) `%s'", url);
+               goto cleanup_fetch_url;
+       }
+
+       restart_point = 0;
+       filesize = -1;
+       rangestart = rangeend = entitylen = -1;
+       mtime = -1;
+       if (restartautofetch) {
+               if (strcmp(savefile, "-") != 0 && *savefile != '|' &&
+                   stat(savefile, &sb) == 0)
+                       restart_point = sb.st_size;
+       }
+       if (urltype == FILE_URL_T) {            /* file:// URLs */
+               direction = "copied";
+               fin = fetch_open(decodedpath, "r");
+               if (fin == NULL) {
+                       warn("Can't open `%s'", decodedpath);
+                       goto cleanup_fetch_url;
+               }
+               if (fstat(fetch_fileno(fin), &sb) == 0) {
+                       mtime = sb.st_mtime;
+                       filesize = sb.st_size;
+               }
+               if (restart_point) {
+                       if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) < 0) {
+                               warn("Can't seek to restart `%s'",
+                                   decodedpath);
+                               goto cleanup_fetch_url;
+                       }
+               }
+               if (verbose) {
+                       fprintf(ttyout, "Copying %s", decodedpath);
+                       if (restart_point)
+                               fprintf(ttyout, " (restarting at " LLF ")",
+                                   (LLT)restart_point);
+                       fputs("\n", ttyout);
+               }
+               if (0 == rcvbuf_size) {
+                       rcvbuf_size = 8 * 1024; /* XXX */
+               }
+       } else {                                /* ftp:// or http:// URLs */
+               const char *leading;
+               int hasleading;
+
+               if (proxyenv == NULL) {
+#ifdef WITH_SSL
+                       if (urltype == HTTPS_URL_T)
+                               proxyenv = getoptionvalue("https_proxy");
+#endif
+                       if (proxyenv == NULL && IS_HTTP_TYPE(urltype))
+                               proxyenv = getoptionvalue("http_proxy");
+                       else if (urltype == FTP_URL_T)
+                               proxyenv = getoptionvalue("ftp_proxy");
+               }
+               direction = "retrieved";
+               if (! EMPTYSTRING(proxyenv)) {                  /* use proxy */
+                       url_t purltype;
+                       char *phost, *ppath;
+                       char *pport, *no_proxy;
+                       in_port_t pportnum;
+
+                       isproxy = 1;
+
+                               /* check URL against list of no_proxied sites */
+                       no_proxy = getoptionvalue("no_proxy");
+                       if (! EMPTYSTRING(no_proxy)) {
+                               char *np, *np_copy, *np_iter;
+                               unsigned long np_port;
+                               size_t hlen, plen;
+
+                               np_iter = np_copy = ftp_strdup(no_proxy);
+                               hlen = strlen(host);
+                               while ((cp = strsep(&np_iter, " ,")) != NULL) {
+                                       if (*cp == '\0')
+                                               continue;
+                                       if ((np = strrchr(cp, ':')) != NULL) {
+                                               *np++ =  '\0';
+                                               np_port = strtoul(np, &ep, 10);
+                                               if (*np == '\0' || *ep != '\0')
+                                                       continue;
+                                               if (np_port != portnum)
+                                                       continue;
+                                       }
+                                       plen = strlen(cp);
+                                       if (hlen < plen)
+                                               continue;
+                                       if (strncasecmp(host + hlen - plen,
+                                           cp, plen) == 0) {
+                                               isproxy = 0;
+                                               break;
+                                       }
+                               }
+                               FREEPTR(np_copy);
+                               if (isproxy == 0 && urltype == FTP_URL_T) {
+                                       rval = fetch_ftp(url);
+                                       goto cleanup_fetch_url;
+                               }
+                       }
+
+                       if (isproxy) {
+                               if (restart_point) {
+                                       warnx("Can't restart via proxy URL `%s'",
+                                           proxyenv);
+                                       goto cleanup_fetch_url;
+                               }
+                               if (parse_url(proxyenv, "proxy URL", &purltype,
+                                   &puser, &ppass, &phost, &pport, &pportnum,
+                                   &ppath) == -1)
+                                       goto cleanup_fetch_url;
+
+                               if ((!IS_HTTP_TYPE(purltype)
+                                    && purltype != FTP_URL_T) ||
+                                   EMPTYSTRING(phost) ||
+                                   (! EMPTYSTRING(ppath)
+                                    && strcmp(ppath, "/") != 0)) {
+                                       warnx("Malformed proxy URL `%s'",
+                                           proxyenv);
+                                       FREEPTR(phost);
+                                       FREEPTR(pport);
+                                       FREEPTR(ppath);
+                                       goto cleanup_fetch_url;
+                               }
+                               if (isipv6addr(host) &&
+                                   strchr(host, '%') != NULL) {
+                                       warnx(
+"Scoped address notation `%s' disallowed via web proxy",
+                                           host);
+                                       FREEPTR(phost);
+                                       FREEPTR(pport);
+                                       FREEPTR(ppath);
+                                       goto cleanup_fetch_url;
+                               }
+
+                               FREEPTR(host);
+                               host = phost;
+                               FREEPTR(port);
+                               port = pport;
+                               FREEPTR(path);
+                               path = ftp_strdup(url);
+                               FREEPTR(ppath);
+                               urltype = purltype;
+                       }
+               } /* ! EMPTYSTRING(proxyenv) */
+
+               memset(&hints, 0, sizeof(hints));
+               hints.ai_flags = 0;
+               hints.ai_family = family;
+               hints.ai_socktype = SOCK_STREAM;
+               hints.ai_protocol = 0;
+               error = getaddrinfo(host, port, &hints, &res0);
+               if (error) {
+                       warnx("Can't LOOKUP `%s:%s': %s", host, port,
+                           (error == EAI_SYSTEM) ? strerror(errno)
+                                                 : gai_strerror(error));
+                       goto cleanup_fetch_url;
+               }
+               if (res0->ai_canonname)
+                       host = res0->ai_canonname;
+
+               s = -1;
+#ifdef WITH_SSL
+               ssl = NULL;
+#endif
+               for (res = res0; res; res = res->ai_next) {
+                       char    hname[NI_MAXHOST], sname[NI_MAXSERV];
+
+                       ai_unmapped(res);
+                       if (getnameinfo(res->ai_addr, res->ai_addrlen,
+                           hname, sizeof(hname), sname, sizeof(sname),
+                           NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+                               strlcpy(hname, "?", sizeof(hname));
+                               strlcpy(sname, "?", sizeof(sname));
+                       }
+
+                       if (verbose && res0->ai_next) {
+                               fprintf(ttyout, "Trying %s:%s ...\n",
+                                   hname, sname);
+                       }
+
+                       s = socket(res->ai_family, SOCK_STREAM,
+                           res->ai_protocol);
+                       if (s < 0) {
+                               warn(
+                                   "Can't create socket for connection to "
+                                   "`%s:%s'", hname, sname);
+                               continue;
+                       }
+
+                       if (ftp_connect(s, res->ai_addr, res->ai_addrlen,
+                           verbose || !res->ai_next) < 0) {
+                               close(s);
+                               s = -1;
+                               continue;
+                       }
+
+#ifdef WITH_SSL
+                       if (urltype == HTTPS_URL_T) {
+                               if ((ssl = fetch_start_ssl(s)) == NULL) {
+                                       close(s);
+                                       s = -1;
+                                       continue;
+                               }
+                       }
+#endif
+
+                       /* success */
+                       break;
+               }
+
+               if (s < 0) {
+                       warnx("Can't connect to `%s:%s'", host, port);
+                       goto cleanup_fetch_url;
+               }
+
+               fin = fetch_fdopen(s, "r+");
+               fetch_set_ssl(fin, ssl);
+
+               /*
+                * Construct and send the request.
+                */
+               if (verbose)
+                       fprintf(ttyout, "Requesting %s\n", url);
+               leading = "  (";
+               hasleading = 0;
+               if (isproxy) {
+                       if (verbose) {
+                               fprintf(ttyout, "%svia %s:%s", leading,
+                                   host, port);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       fetch_printf(fin, "GET %s HTTP/1.0\r\n", path);
+                       if (flushcache)
+                               fetch_printf(fin, "Pragma: no-cache\r\n");
+               } else {
+                       fetch_printf(fin, "GET %s HTTP/1.1\r\n", path);
+                       if (strchr(host, ':')) {
+                               char *h, *p;
+
+                               /*
+                                * strip off IPv6 scope identifier, since it is
+                                * local to the node
+                                */
+                               h = ftp_strdup(host);
+                               if (isipv6addr(h) &&
+                                   (p = strchr(h, '%')) != NULL) {
+                                       *p = '\0';
+                               }
+                               fetch_printf(fin, "Host: [%s]", h);
+                               free(h);
+                       } else
+                               fetch_printf(fin, "Host: %s", host);
+#ifdef WITH_SSL
+                       if ((urltype == HTTP_URL_T && portnum != HTTP_PORT) ||
+                           (urltype == HTTPS_URL_T && portnum != HTTPS_PORT))
+#else
+                       if (portnum != HTTP_PORT)
+#endif
+                               fetch_printf(fin, ":%u", portnum);
+                       fetch_printf(fin, "\r\n");
+                       fetch_printf(fin, "Accept: */*\r\n");
+                       fetch_printf(fin, "Connection: close\r\n");
+                       if (restart_point) {
+                               fputs(leading, ttyout);
+                               fetch_printf(fin, "Range: bytes=" LLF "-\r\n",
+                                   (LLT)restart_point);
+                               fprintf(ttyout, "restarting at " LLF,
+                                   (LLT)restart_point);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       if (flushcache)
+                               fetch_printf(fin, "Cache-Control: no-cache\r\n");
+               }
+               if ((useragent=getenv("FTPUSERAGENT")) != NULL) {
+                       fetch_printf(fin, "User-Agent: %s\r\n", useragent);
+               } else {
+                       fetch_printf(fin, "User-Agent: %s/%s\r\n",
+                           FTP_PRODUCT, FTP_VERSION);
+               }
+               if (wwwauth) {
+                       if (verbose) {
+                               fprintf(ttyout, "%swith authorization",
+                                   leading);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       fetch_printf(fin, "Authorization: %s\r\n", wwwauth);
+               }
+               if (proxyauth) {
+                       if (verbose) {
+                               fprintf(ttyout,
+                                   "%swith proxy authorization", leading);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
+               }
+               if (verbose && hasleading)
+                       fputs(")\n", ttyout);
+               fetch_printf(fin, "\r\n");
+               if (fetch_flush(fin) == EOF) {
+                       warn("Writing HTTP request");
+                       goto cleanup_fetch_url;
+               }
+
+                               /* Read the response */
+               len = fetch_getline(fin, buf, sizeof(buf), &errormsg);
+               if (len < 0) {
+                       if (*errormsg == '\n')
+                               errormsg++;
+                       warnx("Receiving HTTP reply: %s", errormsg);
+                       goto cleanup_fetch_url;
+               }
+               while (len > 0 && (ISLWS(buf[len-1])))
+                       buf[--len] = '\0';
+               DPRINTF("fetch_url: received `%s'\n", buf);
+
+                               /* Determine HTTP response code */
+               cp = strchr(buf, ' ');
+               if (cp == NULL)
+                       goto improper;
+               else
+                       cp++;
+               hcode = strtol(cp, &ep, 10);
+               if (*ep != '\0' && !isspace((unsigned char)*ep))
+                       goto improper;
+               message = ftp_strdup(cp);
+
+                               /* Read the rest of the header. */
+               while (1) {
+                       len = fetch_getline(fin, buf, sizeof(buf), &errormsg);
+                       if (len < 0) {
+                               if (*errormsg == '\n')
+                                       errormsg++;
+                               warnx("Receiving HTTP reply: %s", errormsg);
+                               goto cleanup_fetch_url;
+                       }
+                       while (len > 0 && (ISLWS(buf[len-1])))
+                               buf[--len] = '\0';
+                       if (len == 0)
+                               break;
+                       DPRINTF("fetch_url: received `%s'\n", buf);
+
+               /*
+                * Look for some headers
+                */
+
+                       cp = buf;
+
+                       if (match_token(&cp, "Content-Length:")) {
+                               filesize = STRTOLL(cp, &ep, 10);
+                               if (filesize < 0 || *ep != '\0')
+                                       goto improper;
+                               DPRINTF("fetch_url: parsed len as: " LLF "\n",
+                                   (LLT)filesize);
+
+                       } else if (match_token(&cp, "Content-Range:")) {
+                               if (! match_token(&cp, "bytes"))
+                                       goto improper;
+
+                               if (*cp == '*')
+                                       cp++;
+                               else {
+                                       rangestart = STRTOLL(cp, &ep, 10);
+                                       if (rangestart < 0 || *ep != '-')
+                                               goto improper;
+                                       cp = ep + 1;
+                                       rangeend = STRTOLL(cp, &ep, 10);
+                                       if (rangeend < 0 || rangeend < rangestart)
+                                               goto improper;
+                                       cp = ep;
+                               }
+                               if (*cp != '/')
+                                       goto improper;
+                               cp++;
+                               if (*cp == '*')
+                                       cp++;
+                               else {
+                                       entitylen = STRTOLL(cp, &ep, 10);
+                                       if (entitylen < 0)
+                                               goto improper;
+                                       cp = ep;
+                               }
+                               if (*cp != '\0')
+                                       goto improper;
+
+#ifndef NO_DEBUG
+                               if (ftp_debug) {
+                                       fprintf(ttyout, "parsed range as: ");
+                                       if (rangestart == -1)
+                                               fprintf(ttyout, "*");
+                                       else
+                                               fprintf(ttyout, LLF "-" LLF,
+                                                   (LLT)rangestart,
+                                                   (LLT)rangeend);
+                                       fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
+                               }
+#endif
+                               if (! restart_point) {
+                                       warnx(
+                                   "Received unexpected Content-Range header");
+                                       goto cleanup_fetch_url;
+                               }
+
+                       } else if (match_token(&cp, "Last-Modified:")) {
+                               struct tm parsed;
+                               const char *t;
+
+                               memset(&parsed, 0, sizeof(parsed));
+                               t = parse_rfc2616time(&parsed, cp);
+                               if (t != NULL) {
+                                       parsed.tm_isdst = -1;
+                                       if (*t == '\0')
+                                               mtime = timegm(&parsed);
+#ifndef NO_DEBUG
+                                       if (ftp_debug && mtime != -1) {
+                                               fprintf(ttyout,
+                                                   "parsed time as: %s",
+                                               rfc2822time(localtime(&mtime)));
+                                       }
+#endif
+                               }
+
+                       } else if (match_token(&cp, "Location:")) {
+                               location = ftp_strdup(cp);
+                               DPRINTF("fetch_url: parsed location as `%s'\n",
+                                   cp);
+
+                       } else if (match_token(&cp, "Transfer-Encoding:")) {
+                               if (match_token(&cp, "binary")) {
+                                       warnx(
+                       "Bogus transfer encoding `binary' (fetching anyway)");
+                                       continue;
+                               }
+                               if (! (token = match_token(&cp, "chunked"))) {
+                                       warnx(
+                                   "Unsupported transfer encoding `%s'",
+                                           token);
+                                       goto cleanup_fetch_url;
+                               }
+                               ischunked++;
+                               DPRINTF("fetch_url: using chunked encoding\n");
+
+                       } else if (match_token(&cp, "Proxy-Authenticate:")
+                               || match_token(&cp, "WWW-Authenticate:")) {
+                               if (! (token = match_token(&cp, "Basic"))) {
+                                       DPRINTF(
+                       "fetch_url: skipping unknown auth scheme `%s'\n",
+                                                   token);
+                                       continue;
+                               }
+                               FREEPTR(auth);
+                               auth = ftp_strdup(token);
+                               DPRINTF("fetch_url: parsed auth as `%s'\n", cp);
+                       }
+
+               }
+                               /* finished parsing header */
+
+               switch (hcode) {
+               case 200:
+                       break;
+               case 206:
+                       if (! restart_point) {
+                               warnx("Not expecting partial content header");
+                               goto cleanup_fetch_url;
+                       }
+                       break;
+               case 300:
+               case 301:
+               case 302:
+               case 303:
+               case 305:
+               case 307:
+                       if (EMPTYSTRING(location)) {
+                               warnx(
+                               "No redirection Location provided by server");
+                               goto cleanup_fetch_url;
+                       }
+                       if (redirect_loop++ > 5) {
+                               warnx("Too many redirections requested");
+                               goto cleanup_fetch_url;
+                       }
+                       if (hcode == 305) {
+                               if (verbose)
+                                       fprintf(ttyout, "Redirected via %s\n",
+                                           location);
+                               rval = fetch_url(url, location,
+                                   proxyauth, wwwauth);
+                       } else {
+                               if (verbose)
+                                       fprintf(ttyout, "Redirected to %s\n",
+                                           location);
+                               rval = go_fetch(location);
+                       }
+                       goto cleanup_fetch_url;
+#ifndef NO_AUTH
+               case 401:
+               case 407:
+                   {
+                       char **authp;
+                       char *auser, *apass;
+
+                       if (hcode == 401) {
+                               authp = &wwwauth;
+                               auser = uuser;
+                               apass = pass;
+                       } else {
+                               authp = &proxyauth;
+                               auser = puser;
+                               apass = ppass;
+                       }
+                       if (verbose || *authp == NULL ||
+                           auser == NULL || apass == NULL)
+                               fprintf(ttyout, "%s\n", message);
+                       if (EMPTYSTRING(auth)) {
+                               warnx(
+                           "No authentication challenge provided by server");
+                               goto cleanup_fetch_url;
+                       }
+                       if (*authp != NULL) {
+                               char reply[10];
+
+                               fprintf(ttyout,
+                                   "Authorization failed. Retry (y/n)? ");
+                               if (get_line(stdin, reply, sizeof(reply), NULL)
+                                   < 0) {
+                                       goto cleanup_fetch_url;
+                               }
+                               if (tolower((unsigned char)reply[0]) != 'y')
+                                       goto cleanup_fetch_url;
+                               auser = NULL;
+                               apass = NULL;
+                       }
+                       if (auth_url(auth, authp, auser, apass) == 0) {
+                               rval = fetch_url(url, proxyenv,
+                                   proxyauth, wwwauth);
+                               memset(*authp, 0, strlen(*authp));
+                               FREEPTR(*authp);
+                       }
+                       goto cleanup_fetch_url;
+                   }
+#endif
+               default:
+                       if (message)
+                               warnx("Error retrieving file `%s'", message);
+                       else
+                               warnx("Unknown error retrieving file");
+                       goto cleanup_fetch_url;
+               }
+       }               /* end of ftp:// or http:// specific setup */
+
+                       /* Open the output file. */
+       if (strcmp(savefile, "-") == 0) {
+               fout = stdout;
+       } else if (*savefile == '|') {
+               oldintp = xsignal(SIGPIPE, SIG_IGN);
+               fout = popen(savefile + 1, "w");
+               if (fout == NULL) {
+                       warn("Can't execute `%s'", savefile + 1);
+                       goto cleanup_fetch_url;
+               }
+               closefunc = pclose;
+       } else {
+               if ((rangeend != -1 && rangeend <= restart_point) ||
+                   (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
+                       /* already done */
+                       if (verbose)
+                               fprintf(ttyout, "already done\n");
+                       rval = 0;
+                       goto cleanup_fetch_url;
+               }
+               if (restart_point && rangestart != -1) {
+                       if (entitylen != -1)
+                               filesize = entitylen;
+                       if (rangestart != restart_point) {
+                               warnx(
+                                   "Size of `%s' differs from save file `%s'",
+                                   url, savefile);
+                               goto cleanup_fetch_url;
+                       }
+                       fout = fopen(savefile, "a");
+               } else
+                       fout = fopen(savefile, "w");
+               if (fout == NULL) {
+                       warn("Can't open `%s'", savefile);
+                       goto cleanup_fetch_url;
+               }
+               closefunc = fclose;
+       }
+
+                       /* Trap signals */
+       if (sigsetjmp(httpabort, 1))
+               goto cleanup_fetch_url;
+       (void)xsignal(SIGQUIT, psummary);
+       oldintr = xsignal(SIGINT, aborthttp);
+
+       assert(rcvbuf_size > 0);
+       if ((size_t)rcvbuf_size > bufsize) {
+               if (xferbuf)
+                       (void)free(xferbuf);
+               bufsize = rcvbuf_size;
+               xferbuf = ftp_malloc(bufsize);
+       }
+
+       bytes = 0;
+       hashbytes = mark;
+       progressmeter(-1);
+
+                       /* Finally, suck down the file. */
+       do {
+               long chunksize;
+               short lastchunk;
+
+               chunksize = 0;
+               lastchunk = 0;
+                                       /* read chunk-size */
+               if (ischunked) {
+                       if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
+                               warnx("Unexpected EOF reading chunk-size");
+                               goto cleanup_fetch_url;
+                       }
+                       errno = 0;
+                       chunksize = strtol(xferbuf, &ep, 16);
+                       if (ep == xferbuf) {
+                               warnx("Invalid chunk-size");
+                               goto cleanup_fetch_url;
+                       }
+                       if (errno == ERANGE || chunksize < 0) {
+                               errno = ERANGE;
+                               warn("Chunk-size `%.*s'",
+                                   (int)(ep-xferbuf), xferbuf);
+                               goto cleanup_fetch_url;
+                       }
+
+                               /*
+                                * XXX: Work around bug in Apache 1.3.9 and
+                                *      1.3.11, which incorrectly put trailing
+                                *      space after the chunk-size.
+                                */
+                       while (*ep == ' ')
+                               ep++;
+
+                                       /* skip [ chunk-ext ] */
+                       if (*ep == ';') {
+                               while (*ep && *ep != '\r')
+                                       ep++;
+                       }
+
+                       if (strcmp(ep, "\r\n") != 0) {
+                               warnx("Unexpected data following chunk-size");
+                               goto cleanup_fetch_url;
+                       }
+                       DPRINTF("fetch_url: got chunk-size of " LLF "\n",
+                           (LLT)chunksize);
+                       if (chunksize == 0) {
+                               lastchunk = 1;
+                               goto chunkdone;
+                       }
+               }
+                                       /* transfer file or chunk */
+               while (1) {
+                       struct timeval then, now, td;
+                       off_t bufrem;
+
+                       if (rate_get)
+                               (void)gettimeofday(&then, NULL);
+                       bufrem = rate_get ? rate_get : (off_t)bufsize;
+                       if (ischunked)
+                               bufrem = MIN(chunksize, bufrem);
+                       while (bufrem > 0) {
+                               flen = fetch_read(xferbuf, sizeof(char),
+                                   MIN((off_t)bufsize, bufrem), fin);
+                               if (flen <= 0)
+                                       goto chunkdone;
+                               bytes += flen;
+                               bufrem -= flen;
+                               if (fwrite(xferbuf, sizeof(char), flen, fout)
+                                   != flen) {
+                                       warn("Writing `%s'", savefile);
+                                       goto cleanup_fetch_url;
+                               }
+                               if (hash && !progress) {
+                                       while (bytes >= hashbytes) {
+                                               (void)putc('#', ttyout);
+                                               hashbytes += mark;
+                                       }
+                                       (void)fflush(ttyout);
+                               }
+                               if (ischunked) {
+                                       chunksize -= flen;
+                                       if (chunksize <= 0)
+                                               break;
+                               }
+                       }
+                       if (rate_get) {
+                               while (1) {
+                                       (void)gettimeofday(&now, NULL);
+                                       timersub(&now, &then, &td);
+                                       if (td.tv_sec > 0)
+                                               break;
+                                       usleep(1000000 - td.tv_usec);
+                               }
+                       }
+                       if (ischunked && chunksize <= 0)
+                               break;
+               }
+                                       /* read CRLF after chunk*/
+ chunkdone:
+               if (ischunked) {
+                       if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
+                               warnx("Unexpected EOF reading chunk CRLF");
+                               goto cleanup_fetch_url;
+                       }
+                       if (strcmp(xferbuf, "\r\n") != 0) {
+                               warnx("Unexpected data following chunk");
+                               goto cleanup_fetch_url;
+                       }
+                       if (lastchunk)
+                               break;
+               }
+       } while (ischunked);
+
+/* XXX: deal with optional trailer & CRLF here? */
+
+       if (hash && !progress && bytes > 0) {
+               if (bytes < mark)
+                       (void)putc('#', ttyout);
+               (void)putc('\n', ttyout);
+       }
+       if (fetch_error(fin)) {
+               warn("Reading file");
+               goto cleanup_fetch_url;
+       }
+       progressmeter(1);
+       (void)fflush(fout);
+       if (closefunc == fclose && mtime != -1) {
+#ifdef __minix
+               struct utimbuf utb;
+#endif /* __minix */
+
+               struct timeval tval[2];
+
+               (void)gettimeofday(&tval[0], NULL);
+
+#ifdef __minix
+               utb.actime = tval[0].tv_sec;
+               utb.modtime = mtime;
+#else /* !__minix */
+               tval[1].tv_sec = mtime;
+               tval[1].tv_usec = 0;
+#endif /* !__minix */
+
+               (*closefunc)(fout);
+               fout = NULL;
+
+#ifdef __minix
+               if (utime(savefile, &utb) == -1) {
+                       fprintf(ttyout,
+                           "Can't change modification time to %s",
+                           rfc2822time(localtime(&mtime)));
+               }
+#else /* !__minix */
+               if (utimes(savefile, tval) == -1) {
+                       fprintf(ttyout,
+                           "Can't change modification time to %s",
+                           rfc2822time(localtime(&mtime)));
+               }
+#endif /* !__minix */
+
+       }
+       if (bytes > 0)
+               ptransfer(0);
+       bytes = 0;
+
+       rval = 0;
+       goto cleanup_fetch_url;
+
+ improper:
+       warnx("Improper response from `%s:%s'", host, port);
+
+ cleanup_fetch_url:
+       if (oldintr)
+               (void)xsignal(SIGINT, oldintr);
+       if (oldintp)
+               (void)xsignal(SIGPIPE, oldintp);
+       if (fin != NULL)
+               fetch_close(fin);
+       else if (s != -1)
+               close(s);
+       if (closefunc != NULL && fout != NULL)
+               (*closefunc)(fout);
+       if (res0)
+               freeaddrinfo(res0);
+       FREEPTR(savefile);
+       FREEPTR(uuser);
+       if (pass != NULL)
+               memset(pass, 0, strlen(pass));
+       FREEPTR(pass);
+       FREEPTR(host);
+       FREEPTR(port);
+       FREEPTR(path);
+       FREEPTR(decodedpath);
+       FREEPTR(puser);
+       if (ppass != NULL)
+               memset(ppass, 0, strlen(ppass));
+       FREEPTR(ppass);
+       FREEPTR(auth);
+       FREEPTR(location);
+       FREEPTR(message);
+       return (rval);
+}
+
+/*
+ * Abort a HTTP retrieval
+ */
+static void
+aborthttp(int notused)
+{
+       char msgbuf[100];
+       size_t len;
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
+       write(fileno(ttyout), msgbuf, len);
+       siglongjmp(httpabort, 1);
+}
+
+/*
+ * Retrieve ftp URL or classic ftp argument using FTP.
+ * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
+ * is still open (e.g, ftp xfer with trailing /)
+ */
+static int
+fetch_ftp(const char *url)
+{
+       char            *cp, *xargv[5], rempath[MAXPATHLEN];
+       char            *host, *path, *dir, *file, *uuser, *pass;
+       char            *port;
+       char             cmdbuf[MAXPATHLEN];
+       char             dirbuf[4];
+       int              dirhasglob, filehasglob, rval, transtype, xargc;
+       int              oanonftp, oautologin;
+       in_port_t        portnum;
+       url_t            urltype;
+
+       DPRINTF("fetch_ftp: `%s'\n", url);
+       host = path = dir = file = uuser = pass = NULL;
+       port = NULL;
+       rval = 1;
+       transtype = TYPE_I;
+
+       if (STRNEQUAL(url, FTP_URL)) {
+               if ((parse_url(url, "URL", &urltype, &uuser, &pass,
+                   &host, &port, &portnum, &path) == -1) ||
+                   (uuser != NULL && *uuser == '\0') ||
+                   EMPTYSTRING(host)) {
+                       warnx("Invalid URL `%s'", url);
+                       goto cleanup_fetch_ftp;
+               }
+               /*
+                * Note: Don't url_decode(path) here.  We need to keep the
+                * distinction between "/" and "%2F" until later.
+                */
+
+                                       /* check for trailing ';type=[aid]' */
+               if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
+                       if (strcasecmp(cp, ";type=a") == 0)
+                               transtype = TYPE_A;
+                       else if (strcasecmp(cp, ";type=i") == 0)
+                               transtype = TYPE_I;
+                       else if (strcasecmp(cp, ";type=d") == 0) {
+                               warnx(
+                           "Directory listing via a URL is not supported");
+                               goto cleanup_fetch_ftp;
+                       } else {
+                               warnx("Invalid suffix `%s' in URL `%s'", cp,
+                                   url);
+                               goto cleanup_fetch_ftp;
+                       }
+                       *cp = 0;
+               }
+       } else {                        /* classic style `[user@]host:[file]' */
+               urltype = CLASSIC_URL_T;
+               host = ftp_strdup(url);
+               cp = strchr(host, '@');
+               if (cp != NULL) {
+                       *cp = '\0';
+                       uuser = host;
+                       anonftp = 0;    /* disable anonftp */
+                       host = ftp_strdup(cp + 1);
+               }
+               cp = strchr(host, ':');
+               if (cp != NULL) {
+                       *cp = '\0';
+                       path = ftp_strdup(cp + 1);
+               }
+       }
+       if (EMPTYSTRING(host))
+               goto cleanup_fetch_ftp;
+
+                       /* Extract the file and (if present) directory name. */
+       dir = path;
+       if (! EMPTYSTRING(dir)) {
+               /*
+                * If we are dealing with classic `[user@]host:[path]' syntax,
+                * then a path of the form `/file' (resulting from input of the
+                * form `host:/file') means that we should do "CWD /" before
+                * retrieving the file.  So we set dir="/" and file="file".
+                *
+                * But if we are dealing with URLs like `ftp://host/path' then
+                * a path of the form `/file' (resulting from a URL of the form
+                * `ftp://host//file') means that we should do `CWD ' (with an
+                * empty argument) before retrieving the file.  So we set
+                * dir="" and file="file".
+                *
+                * If the path does not contain / at all, we set dir=NULL.
+                * (We get a path without any slashes if we are dealing with
+                * classic `[user@]host:[file]' or URL `ftp://host/file'.)
+                *
+                * In all other cases, we set dir to a string that does not
+                * include the final '/' that separates the dir part from the
+                * file part of the path.  (This will be the empty string if
+                * and only if we are dealing with a path of the form `/file'
+                * resulting from an URL of the form `ftp://host//file'.)
+                */
+               cp = strrchr(dir, '/');
+               if (cp == dir && urltype == CLASSIC_URL_T) {
+                       file = cp + 1;
+                       (void)strlcpy(dirbuf, "/", sizeof(dirbuf));
+                       dir = dirbuf;
+               } else if (cp != NULL) {
+                       *cp++ = '\0';
+                       file = cp;
+               } else {
+                       file = dir;
+                       dir = NULL;
+               }
+       } else
+               dir = NULL;
+       if (urltype == FTP_URL_T && file != NULL) {
+               url_decode(file);
+               /* but still don't url_decode(dir) */
+       }
+       DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s "
+           "path `%s' dir `%s' file `%s'\n",
+           STRorNULL(uuser), STRorNULL(pass),
+           STRorNULL(host), STRorNULL(port),
+           STRorNULL(path), STRorNULL(dir), STRorNULL(file));
+
+       dirhasglob = filehasglob = 0;
+       if (doglob && urltype == CLASSIC_URL_T) {
+               if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
+                       dirhasglob = 1;
+               if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
+                       filehasglob = 1;
+       }
+
+                       /* Set up the connection */
+       oanonftp = anonftp;
+       if (connected)
+               disconnect(0, NULL);
+       anonftp = oanonftp;
+       (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf));
+       xargv[0] = cmdbuf;
+       xargv[1] = host;
+       xargv[2] = NULL;
+       xargc = 2;
+       if (port) {
+               xargv[2] = port;
+               xargv[3] = NULL;
+               xargc = 3;
+       }
+       oautologin = autologin;
+               /* don't autologin in setpeer(), use ftp_login() below */
+       autologin = 0;
+       setpeer(xargc, xargv);
+       autologin = oautologin;
+       if ((connected == 0) ||
+           (connected == 1 && !ftp_login(host, uuser, pass))) {
+               warnx("Can't connect or login to host `%s:%s'",
+                       host, port ? port : "?");
+               goto cleanup_fetch_ftp;
+       }
+
+       switch (transtype) {
+       case TYPE_A:
+               setascii(1, xargv);
+               break;
+       case TYPE_I:
+               setbinary(1, xargv);
+               break;
+       default:
+               errx(1, "fetch_ftp: unknown transfer type %d", transtype);
+       }
+
+               /*
+                * Change directories, if necessary.
+                *
+                * Note: don't use EMPTYSTRING(dir) below, because
+                * dir=="" means something different from dir==NULL.
+                */
+       if (dir != NULL && !dirhasglob) {
+               char *nextpart;
+
+               /*
+                * If we are dealing with a classic `[user@]host:[path]'
+                * (urltype is CLASSIC_URL_T) then we have a raw directory
+                * name (not encoded in any way) and we can change
+                * directories in one step.
+                *
+                * If we are dealing with an `ftp://host/path' URL
+                * (urltype is FTP_URL_T), then RFC 3986 says we need to
+                * send a separate CWD command for each unescaped "/"
+                * in the path, and we have to interpret %hex escaping
+                * *after* we find the slashes.  It's possible to get
+                * empty components here, (from multiple adjacent
+                * slashes in the path) and RFC 3986 says that we should
+                * still do `CWD ' (with a null argument) in such cases.
+                *
+                * Many ftp servers don't support `CWD ', so if there's an
+                * error performing that command, bail out with a descriptive
+                * message.
+                *
+                * Examples:
+                *
+                * host:                        dir="", urltype=CLASSIC_URL_T
+                *              logged in (to default directory)
+                * host:file                    dir=NULL, urltype=CLASSIC_URL_T
+                *              "RETR file"
+                * host:dir/                    dir="dir", urltype=CLASSIC_URL_T
+                *              "CWD dir", logged in
+                * ftp://host/                  dir="", urltype=FTP_URL_T
+                *              logged in (to default directory)
+                * ftp://host/dir/              dir="dir", urltype=FTP_URL_T
+                *              "CWD dir", logged in
+                * ftp://host/file              dir=NULL, urltype=FTP_URL_T
+                *              "RETR file"
+                * ftp://host//file             dir="", urltype=FTP_URL_T
+                *              "CWD ", "RETR file"
+                * host:/file                   dir="/", urltype=CLASSIC_URL_T
+                *              "CWD /", "RETR file"
+                * ftp://host///file            dir="/", urltype=FTP_URL_T
+                *              "CWD ", "CWD ", "RETR file"
+                * ftp://host/%2F/file          dir="%2F", urltype=FTP_URL_T
+                *              "CWD /", "RETR file"
+                * ftp://host/foo/file          dir="foo", urltype=FTP_URL_T
+                *              "CWD foo", "RETR file"
+                * ftp://host/foo/bar/file      dir="foo/bar"
+                *              "CWD foo", "CWD bar", "RETR file"
+                * ftp://host//foo/bar/file     dir="/foo/bar"
+                *              "CWD ", "CWD foo", "CWD bar", "RETR file"
+                * ftp://host/foo//bar/file     dir="foo//bar"
+                *              "CWD foo", "CWD ", "CWD bar", "RETR file"
+                * ftp://host/%2F/foo/bar/file  dir="%2F/foo/bar"
+                *              "CWD /", "CWD foo", "CWD bar", "RETR file"
+                * ftp://host/%2Ffoo/bar/file   dir="%2Ffoo/bar"
+                *              "CWD /foo", "CWD bar", "RETR file"
+                * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar"
+                *              "CWD /foo/bar", "RETR file"
+                * ftp://host/%2Ffoo%2Fbar%2Ffile       dir=NULL
+                *              "RETR /foo/bar/file"
+                *
+                * Note that we don't need `dir' after this point.
+                */
+               do {
+                       if (urltype == FTP_URL_T) {
+                               nextpart = strchr(dir, '/');
+                               if (nextpart) {
+                                       *nextpart = '\0';
+                                       nextpart++;
+                               }
+                               url_decode(dir);
+                       } else
+                               nextpart = NULL;
+                       DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n",
+                           STRorNULL(dir), STRorNULL(nextpart));
+                       if (urltype == FTP_URL_T || *dir != '\0') {
+                               (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf));
+                               xargv[0] = cmdbuf;
+                               xargv[1] = dir;
+                               xargv[2] = NULL;
+                               dirchange = 0;
+                               cd(2, xargv);
+                               if (! dirchange) {
+                                       if (*dir == '\0' && code == 500)
+                                               fprintf(stderr,
+"\n"
+"ftp: The `CWD ' command (without a directory), which is required by\n"
+"     RFC 3986 to support the empty directory in the URL pathname (`//'),\n"
+"     conflicts with the server's conformance to RFC 959.\n"
+"     Try the same URL without the `//' in the URL pathname.\n"
+"\n");
+                                       goto cleanup_fetch_ftp;
+                               }
+                       }
+                       dir = nextpart;
+               } while (dir != NULL);
+       }
+
+       if (EMPTYSTRING(file)) {
+               rval = -1;
+               goto cleanup_fetch_ftp;
+       }
+
+       if (dirhasglob) {
+               (void)strlcpy(rempath, dir,     sizeof(rempath));
+               (void)strlcat(rempath, "/",     sizeof(rempath));
+               (void)strlcat(rempath, file,    sizeof(rempath));
+               file = rempath;
+       }
+
+                       /* Fetch the file(s). */
+       xargc = 2;
+       (void)strlcpy(cmdbuf, "get", sizeof(cmdbuf));
+       xargv[0] = cmdbuf;
+       xargv[1] = file;
+       xargv[2] = NULL;
+       if (dirhasglob || filehasglob) {
+               int ointeractive;
+
+               ointeractive = interactive;
+               interactive = 0;
+               if (restartautofetch)
+                       (void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf));
+               else
+                       (void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf));
+               xargv[0] = cmdbuf;
+               mget(xargc, xargv);
+               interactive = ointeractive;
+       } else {
+               if (outfile == NULL) {
+                       cp = strrchr(file, '/');        /* find savefile */
+                       if (cp != NULL)
+                               outfile = cp + 1;
+                       else
+                               outfile = file;
+               }
+               xargv[2] = (char *)outfile;
+               xargv[3] = NULL;
+               xargc++;
+               if (restartautofetch)
+                       reget(xargc, xargv);
+               else
+                       get(xargc, xargv);
+       }
+
+       if ((code / 100) == COMPLETE)
+               rval = 0;
+
+ cleanup_fetch_ftp:
+       FREEPTR(port);
+       FREEPTR(host);
+       FREEPTR(path);
+       FREEPTR(uuser);
+       if (pass)
+               memset(pass, 0, strlen(pass));
+       FREEPTR(pass);
+       return (rval);
+}
+
+/*
+ * Retrieve the given file to outfile.
+ * Supports arguments of the form:
+ *     "host:path", "ftp://host/path"  if $ftpproxy, call fetch_url() else
+ *                                     call fetch_ftp()
+ *     "http://host/path"              call fetch_url() to use HTTP
+ *     "file:///path"                  call fetch_url() to copy
+ *     "about:..."                     print a message
+ *
+ * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
+ * is still open (e.g, ftp xfer with trailing /)
+ */
+static int
+go_fetch(const char *url)
+{
+       char *proxyenv;
+       char *p;
+
+#ifndef NO_ABOUT
+       /*
+        * Check for about:*
+        */
+       if (STRNEQUAL(url, ABOUT_URL)) {
+               url += sizeof(ABOUT_URL) -1;
+               if (strcasecmp(url, "ftp") == 0 ||
+                   strcasecmp(url, "tnftp") == 0) {
+                       fputs(
+"This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n"
+"for the NetBSD project.  Execute `man ftp' for more details.\n", ttyout);
+               } else if (strcasecmp(url, "lukem") == 0) {
+                       fputs(
+"Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
+"Please email feedback to <lukem@NetBSD.org>.\n", ttyout);
+               } else if (strcasecmp(url, "netbsd") == 0) {
+                       fputs(
+"NetBSD is a freely available and redistributable UNIX-like operating system.\n"
+"For more information, see http://www.NetBSD.org/\n", ttyout);
+               } else if (strcasecmp(url, "version") == 0) {
+                       fprintf(ttyout, "Version: %s %s%s\n",
+                           FTP_PRODUCT, FTP_VERSION,
+#ifdef INET6
+                           ""
+#else
+                           " (-IPv6)"
+#endif
+                       );
+               } else {
+                       fprintf(ttyout, "`%s' is an interesting topic.\n", url);
+               }
+               fputs("\n", ttyout);
+               return (0);
+       }
+#endif
+
+       /*
+        * Check for file:// and http:// URLs.
+        */
+       if (STRNEQUAL(url, HTTP_URL)
+#ifdef WITH_SSL
+           || STRNEQUAL(url, HTTPS_URL)
+#endif
+           || STRNEQUAL(url, FILE_URL))
+               return (fetch_url(url, NULL, NULL, NULL));
+
+       /*
+        * If it contains "://" but does not begin with ftp://
+        * or something that was already handled, then it's
+        * unsupported.
+        *
+        * If it contains ":" but not "://" then we assume the
+        * part before the colon is a host name, not an URL scheme,
+        * so we don't try to match that here.
+        */
+       if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL))
+               errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url);
+
+       /*
+        * Try FTP URL-style and host:file arguments next.
+        * If ftpproxy is set with an FTP URL, use fetch_url()
+        * Othewise, use fetch_ftp().
+        */
+       proxyenv = getoptionvalue("ftp_proxy");
+       if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL))
+               return (fetch_url(url, NULL, NULL, NULL));
+
+       return (fetch_ftp(url));
+}
+
+/*
+ * Retrieve multiple files from the command line,
+ * calling go_fetch() for each file.
+ *
+ * If an ftp path has a trailing "/", the path will be cd-ed into and
+ * the connection remains open, and the function will return -1
+ * (to indicate the connection is alive).
+ * If an error occurs the return value will be the offset+1 in
+ * argv[] of the file that caused a problem (i.e, argv[x]
+ * returns x+1)
+ * Otherwise, 0 is returned if all files retrieved successfully.
+ */
+int
+auto_fetch(int argc, char *argv[])
+{
+       volatile int    argpos, rval;
+
+       argpos = rval = 0;
+
+       if (sigsetjmp(toplevel, 1)) {
+               if (connected)
+                       disconnect(0, NULL);
+               if (rval > 0)
+                       rval = argpos + 1;
+               return (rval);
+       }
+       (void)xsignal(SIGINT, intr);
+       (void)xsignal(SIGPIPE, lostpeer);
+
+       /*
+        * Loop through as long as there's files to fetch.
+        */
+       for (; (rval == 0) && (argpos < argc); argpos++) {
+               if (strchr(argv[argpos], ':') == NULL)
+                       break;
+               redirect_loop = 0;
+               if (!anonftp)
+                       anonftp = 2;    /* Handle "automatic" transfers. */
+               rval = go_fetch(argv[argpos]);
+               if (outfile != NULL && strcmp(outfile, "-") != 0
+                   && outfile[0] != '|')
+                       outfile = NULL;
+               if (rval > 0)
+                       rval = argpos + 1;
+       }
+
+       if (connected && rval != -1)
+               disconnect(0, NULL);
+       return (rval);
+}
+
+
+/*
+ * Upload multiple files from the command line.
+ *
+ * If an error occurs the return value will be the offset+1 in
+ * argv[] of the file that caused a problem (i.e, argv[x]
+ * returns x+1)
+ * Otherwise, 0 is returned if all files uploaded successfully.
+ */
+int
+auto_put(int argc, char **argv, const char *uploadserver)
+{
+       char    *uargv[4], *path, *pathsep;
+       int      uargc, rval, argpos;
+       size_t   len;
+       char     cmdbuf[MAX_C_NAME];
+
+       (void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf));
+       uargv[0] = cmdbuf;
+       uargv[1] = argv[0];
+       uargc = 2;
+       uargv[2] = uargv[3] = NULL;
+       pathsep = NULL;
+       rval = 1;
+
+       DPRINTF("auto_put: target `%s'\n", uploadserver);
+
+       path = ftp_strdup(uploadserver);
+       len = strlen(path);
+       if (path[len - 1] != '/' && path[len - 1] != ':') {
+                       /*
+                        * make sure we always pass a directory to auto_fetch
+                        */
+               if (argc > 1) {         /* more than one file to upload */
+                       len = strlen(uploadserver) + 2; /* path + "/" + "\0" */
+                       free(path);
+                       path = (char *)ftp_malloc(len);
+                       (void)strlcpy(path, uploadserver, len);
+                       (void)strlcat(path, "/", len);
+               } else {                /* single file to upload */
+                       (void)strlcpy(cmdbuf, "put", sizeof(cmdbuf));
+                       uargv[0] = cmdbuf;
+                       pathsep = strrchr(path, '/');
+                       if (pathsep == NULL) {
+                               pathsep = strrchr(path, ':');
+                               if (pathsep == NULL) {
+                                       warnx("Invalid URL `%s'", path);
+                                       goto cleanup_auto_put;
+                               }
+                               pathsep++;
+                               uargv[2] = ftp_strdup(pathsep);
+                               pathsep[0] = '/';
+                       } else
+                               uargv[2] = ftp_strdup(pathsep + 1);
+                       pathsep[1] = '\0';
+                       uargc++;
+               }
+       }
+       DPRINTF("auto_put: URL `%s' argv[2] `%s'\n",
+           path, STRorNULL(uargv[2]));
+
+                       /* connect and cwd */
+       rval = auto_fetch(1, &path);
+       if(rval >= 0)
+               goto cleanup_auto_put;
+
+       rval = 0;
+
+                       /* target filename provided; upload 1 file */
+                       /* XXX : is this the best way? */
+       if (uargc == 3) {
+               uargv[1] = argv[0];
+               put(uargc, uargv);
+               if ((code / 100) != COMPLETE)
+                       rval = 1;
+       } else {        /* otherwise a target dir: upload all files to it */
+               for(argpos = 0; argv[argpos] != NULL; argpos++) {
+                       uargv[1] = argv[argpos];
+                       mput(uargc, uargv);
+                       if ((code / 100) != COMPLETE) {
+                               rval = argpos + 1;
+                               break;
+                       }
+               }
+       }
+
+ cleanup_auto_put:
+       free(path);
+       FREEPTR(uargv[2]);
+       return (rval);
+}
diff --git a/usr.bin/ftp/ftp.1 b/usr.bin/ftp/ftp.1
new file mode 100644 (file)
index 0000000..931f3b8
--- /dev/null
@@ -0,0 +1,2421 @@
+.\"    $NetBSD: ftp.1,v 1.134 2012/12/22 16:57:10 christos Exp $
+.\"
+.\" Copyright (c) 1996-2010 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Luke Mewburn.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\"
+.\" Copyright (c) 1985, 1989, 1990, 1993
+.\"    The Regents of the University of California.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"    @(#)ftp.1       8.3 (Berkeley) 10/9/94
+.\"
+.Dd December 22, 2012
+.Dt FTP 1
+.Os
+.Sh NAME
+.Nm ftp
+.Nd Internet file transfer program
+.Sh SYNOPSIS
+.Nm
+.Op Fl 46AadefginpRtVv
+.Op Fl N Ar netrc
+.Op Fl o Ar output
+.Op Fl P Ar port
+.Op Fl q Ar quittime
+.Op Fl r Ar retry
+.Op Fl s Ar srcaddr
+.Bk -words
+.\" [-T dir,max[,inc]]
+.Oo
+.Fl T Xo
+.Sm off
+.Ar dir ,
+.Ar max
+.Op , Ar inc
+.Sm on
+.Xc
+.Oc
+.Ek
+.Bk -words
+.\" [[user@]host [port]]
+.Oo
+.Oo Ar user Ns Li \&@ Oc Ns Ar host
+.Op Ar port
+.Oc
+.Ek
+.Bk -words
+.\" [[user@]host:[path][/]]
+.Sm off
+.Oo
+.Op Ar user Li \&@
+.Ar host Li \&:
+.Op Ar path
+.Op Li /
+.Oc
+.Sm on
+.Ek
+.Bk -words
+.\" [file:///path]
+.Sm off
+.Oo
+.Li file:/// Ar path
+.Oc
+.Sm on
+.Ek
+.Bk -words
+.\" [ftp://[user[:password]@]host[:port]/path[/]]
+.Sm off
+.Oo
+.Li ftp://
+.Oo Ar user
+.Op Li \&: Ar password
+.Li \&@ Oc
+.Ar host Oo Li \&: Ar port Oc
+.Li / Ar path
+.Op Li /
+.Op Li ;type= Ar X
+.Oc
+.Sm on
+.Ek
+.Bk -words
+.\" [http://[user[:password]@]host[:port]/path]
+.Sm off
+.Oo
+.Li http://
+.Oo Ar user
+.Op Li \&: Ar password
+.Li \&@ Oc
+.Ar host Oo Li \&: Ar port Oc
+.Li / Ar path
+.Oc
+.Sm on
+.Ek
+.Op Ar \&.\&.\&.
+.Nm
+.Bk -words
+.Fl u Ar URL Ar file
+.Ek
+.Op Ar \&.\&.\&.
+.Sh DESCRIPTION
+.Nm
+is the user interface to the Internet standard File Transfer Protocol.
+The program allows a user to transfer files to and from a
+remote network site.
+.Pp
+The last five arguments will fetch a file using the
+.Tn FTP
+or
+.Tn HTTP
+protocols, or by direct copying, into the current directory.
+This is ideal for scripts.
+Refer to
+.Sx AUTO-FETCHING FILES
+below for more information.
+.Pp
+Options may be specified at the command line, or to the
+command interpreter.
+.Bl -tag -width Fl
+.It Fl 4
+Forces
+.Nm
+to only use IPv4 addresses.
+.It Fl 6
+Forces
+.Nm
+to only use IPv6 addresses.
+.It Fl A
+Force active mode ftp.
+By default,
+.Nm
+will try to use passive mode ftp and fall back to active mode
+if passive is not supported by the server.
+This option causes
+.Nm
+to always use an active connection.
+It is only useful for connecting to very old servers that do not
+implement passive mode properly.
+.It Fl a
+Causes
+.Nm
+to bypass normal login procedure, and use an anonymous login instead.
+.It Fl d
+Enables debugging.
+.It Fl e
+Disables command line editing.
+This is useful for Emacs ange-ftp mode.
+.It Fl f
+Forces a cache reload for transfers that go through the
+.Tn FTP
+or
+.Tn HTTP
+proxies.
+.It Fl g
+Disables file name globbing.
+.It Fl i
+Turns off interactive prompting during
+multiple file transfers.
+.It Fl N Ar netrc
+Use
+.Ar netrc
+instead of
+.Pa ~/.netrc .
+Refer to
+.Sx THE .netrc FILE
+for more information.
+.It Fl n
+Restrains
+.Nm
+from attempting
+.Dq auto-login
+upon initial connection for non auto-fetch transfers.
+If auto-login is enabled,
+.Nm
+will check the
+.Pa .netrc
+(see below) file in the user's home directory for an entry describing
+an account on the remote machine.
+If no entry exists,
+.Nm
+will prompt for the remote machine login name (default is the user
+identity on the local machine), and, if necessary, prompt for a password
+and an account with which to login.
+To override the auto-login for auto-fetch transfers, specify the
+username (and optionally, password) as appropriate.
+.It Fl o Ar output
+When auto-fetching files, save the contents in
+.Ar output .
+.Ar output
+is parsed according to the
+.Sx FILE NAMING CONVENTIONS
+below.
+If
+.Ar output
+is not
+.Sq -
+or doesn't start with
+.Sq \&| ,
+then only the first file specified will be retrieved into
+.Ar output ;
+all other files will be retrieved into the basename of their
+remote name.
+.It Fl P Ar port
+Sets the port number to
+.Ar port .
+.It Fl p
+Enable passive mode operation for use behind connection filtering firewalls.
+This option has been deprecated as
+.Nm
+now tries to use passive mode by default, falling back to active mode
+if the server does not support passive connections.
+.It Fl q Ar quittime
+Quit if the connection has stalled for
+.Ar quittime
+seconds.
+.It Fl R
+Restart all non-proxied auto-fetches.
+.It Fl r Ar wait
+Retry the connection attempt if it failed, pausing for
+.Ar wait
+seconds.
+.It Fl s Ar srcaddr
+Uses
+.Ar srcaddr
+as the local IP address for all connections.
+.It Fl t
+Enables packet tracing.
+.It Fl T Ar direction Ns , Ns Ar maximum Ns Oo , Ns Ar increment Oc
+Set the maximum transfer rate for
+.Ar direction
+to
+.Ar maximum
+bytes/second,
+and if specified, the increment to
+.Ar increment
+bytes/second.
+Refer to
+.Ic rate
+for more information.
+.It Fl u Ar URL file Op \&.\&.\&.
+Upload files on the command line to
+.Ar URL
+where
+.Ar URL
+is one of the ftp URL types as supported by auto-fetch
+(with an optional target filename for single file uploads), and
+.Ar file
+is one or more local files to be uploaded.
+.It Fl V
+Disable
+.Ic verbose
+and
+.Ic progress ,
+overriding the default of enabled when output is to a terminal.
+.It Fl v
+Enable
+.Ic verbose
+and
+.Ic progress .
+This is the default if output is to a terminal (and in the case of
+.Ic progress ,
+.Nm
+is the foreground process).
+Forces
+.Nm
+to show all responses from the remote server, as well
+as report on data transfer statistics.
+.El
+.Pp
+The client host with which
+.Nm
+is to communicate may be specified on the command line.
+If this is done,
+.Nm
+will immediately attempt to establish a connection to an
+.Tn FTP
+server on that host; otherwise,
+.Nm
+will enter its command interpreter and await instructions
+from the user.
+When
+.Nm
+is awaiting commands from the user the prompt
+.Ql ftp\*[Gt]
+is provided to the user.
+The following commands are recognized
+by
+.Nm ftp :
+.Bl -tag -width Ic
+.It Ic \&! Op Ar command Op Ar args
+Invoke an interactive shell on the local machine.
+If there are arguments, the first is taken to be a command to execute
+directly, with the rest of the arguments as its arguments.
+.It Ic \&$ Ar macro-name Op Ar args
+Execute the macro
+.Ar macro-name
+that was defined with the
+.Ic macdef
+command.
+Arguments are passed to the macro unglobbed.
+.It Ic account Op Ar passwd
+Supply a supplemental password required by a remote system for access
+to resources once a login has been successfully completed.
+If no argument is included, the user will be prompted for an account
+password in a non-echoing input mode.
+.It Ic append Ar local-file Op Ar remote-file
+Append a local file to a file on the remote machine.
+If
+.Ar remote-file
+is left unspecified, the local file name is used in naming the
+remote file after being altered by any
+.Ic ntrans
+or
+.Ic nmap
+setting.
+File transfer uses the current settings for
+.Ic type  ,
+.Ic format ,
+.Ic mode  ,
+and
+.Ic structure .
+.It Ic ascii
+Set the file transfer
+.Ic type
+to network
+.Tn ASCII .
+This is the default type.
+.It Ic bell
+Arrange that a bell be sounded after each file transfer
+command is completed.
+.It Ic binary
+Set the file transfer
+.Ic type
+to support binary image transfer.
+.It Ic bye
+Terminate the
+.Tn FTP
+session with the remote server
+and exit
+.Nm ftp .
+An end of file will also terminate the session and exit.
+.It Ic case
+Toggle remote computer file name case mapping during
+.Ic get ,
+.Ic mget
+and
+.Ic mput
+commands.
+When
+.Ic case
+is on (default is off), remote computer file names with all letters in
+upper case are written in the local directory with the letters mapped
+to lower case.
+.It Ic \&cd Ar remote-directory
+Change the working directory on the remote machine
+to
+.Ar remote-directory .
+.It Ic cdup
+Change the remote machine working directory to the parent of the
+current remote machine working directory.
+.It Ic chmod Ar mode remote-file
+Change the permission modes of the file
+.Ar remote-file
+on the remote
+system to
+.Ar mode .
+.It Ic close
+Terminate the
+.Tn FTP
+session with the remote server, and
+return to the command interpreter.
+Any defined macros are erased.
+.It Ic \&cr
+Toggle carriage return stripping during
+ascii type file retrieval.
+Records are denoted by a carriage return/linefeed sequence
+during ascii type file transfer.
+When
+.Ic \&cr
+is on (the default), carriage returns are stripped from this
+sequence to conform with the
+.Ux
+single linefeed record
+delimiter.
+Records on
+.Pf non\- Ns Ux
+remote systems may contain single linefeeds;
+when an ascii type transfer is made, these linefeeds may be
+distinguished from a record delimiter only when
+.Ic \&cr
+is off.
+.It Ic delete Ar remote-file
+Delete the file
+.Ar remote-file
+on the remote machine.
+.It Ic dir Op Ar remote-path Op Ar local-file
+Print a listing of the contents of a
+directory on the remote machine.
+The listing includes any system-dependent information that the server
+chooses to include; for example, most
+.Ux
+systems will produce
+output from the command
+.Ql ls \-l .
+If
+.Ar remote-path
+is left unspecified, the current working directory is used.
+If interactive prompting is on,
+.Nm
+will prompt the user to verify that the last argument is indeed the
+target local file for receiving
+.Ic dir
+output.
+If no local file is specified, or if
+.Ar local-file
+is
+.Sq Fl ,
+the output is sent to the terminal.
+.It Ic disconnect
+A synonym for
+.Ic close .
+.It Ic edit
+Toggle command line editing, and context sensitive command and file
+completion.
+This is automatically enabled if input is from a terminal, and
+disabled otherwise.
+.It Ic epsv epsv4 epsv6
+Toggle the use of the extended
+.Dv EPSV
+and
+.Dv EPRT
+commands on all IP, IPv4, and IPv6 connections respectively.
+First try
+.Dv EPSV /
+.Dv EPRT ,
+and then
+.Dv PASV /
+.Dv PORT .
+This is enabled by default.
+If an extended command fails then this option will be temporarily
+disabled for the duration of the current connection, or until
+.Ic epsv ,
+.Ic epsv4 ,
+or
+.Ic epsv6
+is executed again.
+.It Ic exit
+A synonym for
+.Ic bye .
+.It Ic features
+Display what features the remote server supports (using the
+.Dv FEAT
+command).
+.It Ic fget Ar localfile
+Retrieve the files listed in
+.Ar localfile ,
+which has one line per filename.
+.It Ic form Ar format
+Set the file transfer
+.Ic form
+to
+.Ar format .
+The default (and only supported)
+format is
+.Dq non-print .
+.It Ic ftp Ar host Op Ar port
+A synonym for
+.Ic open .
+.It Ic ftp_debug Op Ar ftp_debug-value
+Toggle debugging mode.
+If an optional
+.Ar ftp_debug-value
+is specified it is used to set the debugging level.
+When debugging is on,
+.Nm
+prints each command sent to the remote machine, preceded
+by the string
+.Ql \-\-\*[Gt] .
+.It Ic gate Op Ar host Op Ar port
+Toggle gate-ftp mode, which used to connect through the
+TIS FWTK and Gauntlet ftp proxies.
+This will not be permitted if the gate-ftp server hasn't been set
+(either explicitly by the user, or from the
+.Ev FTPSERVER
+environment variable).
+If
+.Ar host
+is given,
+then gate-ftp mode will be enabled, and the gate-ftp server will be set to
+.Ar host .
+If
+.Ar port
+is also given, that will be used as the port to connect to on the
+gate-ftp server.
+.It Ic get Ar remote-file Op Ar local-file
+Retrieve the
+.Ar remote-file
+and store it on the local machine.
+If the local
+file name is not specified, it is given the same
+name it has on the remote machine, subject to
+alteration by the current
+.Ic case  ,
+.Ic ntrans ,
+and
+.Ic nmap
+settings.
+The current settings for
+.Ic type  ,
+.Ic form ,
+.Ic mode  ,
+and
+.Ic structure
+are used while transferring the file.
+.It Ic glob
+Toggle filename expansion for
+.Ic mdelete  ,
+.Ic mget ,
+.Ic mput ,
+and
+.Ic mreget .
+If globbing is turned off with
+.Ic glob  ,
+the file name arguments
+are taken literally and not expanded.
+Globbing for
+.Ic mput
+is done as in
+.Xr csh 1 .
+For
+.Ic mdelete ,
+.Ic mget ,
+and
+.Ic mreget ,
+each remote file name is expanded
+separately on the remote machine and the lists are not merged.
+Expansion of a directory name is likely to be
+different from expansion of the name of an ordinary file:
+the exact result depends on the foreign operating system and ftp server,
+and can be previewed by doing
+.Ql mls remote-files \-
+Note:
+.Ic mget ,
+.Ic mput
+and
+.Ic mreget
+are not meant to transfer
+entire directory subtrees of files.
+That can be done by
+transferring a
+.Xr tar 1
+archive of the subtree (in binary mode).
+.It Ic hash Op Ar size
+Toggle hash-sign
+.Pq Sq #
+printing for each data block transferred.
+The size of a data block defaults to 1024 bytes.
+This can be changed by specifying
+.Ar size
+in bytes.
+Enabling
+.Ic hash
+disables
+.Ic progress .
+.It Ic help Op Ar command
+Print an informative message about the meaning of
+.Ar command .
+If no argument is given,
+.Nm
+prints a list of the known commands.
+.It Ic idle Op Ar seconds
+Set the inactivity timer on the remote server to
+.Ar seconds
+seconds.
+If
+.Ar seconds
+is omitted, the current inactivity timer is printed.
+.It Ic image
+A synonym for
+.Ic binary .
+.It Ic lcd Op Ar directory
+Change the working directory on the local machine.
+If
+no
+.Ar directory
+is specified, the user's home directory is used.
+.It Ic less Ar file
+A synonym for
+.Ic page .
+.It Ic lpage Ar local-file
+Display
+.Ar local-file
+with the program specified by the
+.Ic "set pager"
+option.
+.It Ic lpwd
+Print the working directory on the local machine.
+.It Ic \&ls Op Ar remote-path Op Ar local-file
+A synonym for
+.Ic dir .
+.It Ic macdef Ar macro-name
+Define a macro.
+Subsequent lines are stored as the macro
+.Ar macro-name  ;
+a null line (consecutive newline characters in a file or carriage
+returns from the terminal) terminates macro input mode.
+There is a limit of 16 macros and 4096 total characters in all
+defined macros.
+Macro names can be a maximum of 8 characters.
+Macros are only applicable to the current session they are
+defined within (or if defined outside a session, to the session
+invoked with the next
+.Ic open
+command), and remain defined until a
+.Ic close
+command is executed.
+To invoke a macro, use the
+.Ic $
+command (see above).
+.Pp
+The macro processor interprets
+.Sq $
+and
+.Sq \e
+as special characters.
+A
+.Sq $
+followed by a number (or numbers) is replaced by the
+corresponding argument on the macro invocation command line.
+A
+.Sq $
+followed by an
+.Sq i
+signals the macro processor that the executing macro is to be
+looped.
+On the first pass
+.Dq $i
+is replaced by the first argument on the macro invocation command
+line, on the second pass it is replaced by the second argument,
+and so on.
+A
+.Sq \e
+followed by any character is replaced by that character.
+Use the
+.Sq \e
+to prevent special treatment of the
+.Sq $ .
+.It Ic mdelete Op Ar remote-files
+Delete the
+.Ar remote-files
+on the remote machine.
+.It Ic mdir Ar remote-files local-file
+Like
+.Ic dir  ,
+except multiple remote files may be specified.
+If interactive prompting is on,
+.Nm
+will prompt the user to verify that the last argument is indeed the
+target local file for receiving
+.Ic mdir
+output.
+.It Ic mget Ar remote-files
+Expand the
+.Ar remote-files
+on the remote machine
+and do a
+.Ic get
+for each file name thus produced.
+See
+.Ic glob
+for details on the filename expansion.
+Resulting file names will then be processed according to
+.Ic case  ,
+.Ic ntrans ,
+and
+.Ic nmap
+settings.
+Files are transferred into the local working directory,
+which can be changed with
+.Ql lcd directory ;
+new local directories can be created with
+.Ql "\&! mkdir directory" .
+.It Ic mkdir Ar directory-name
+Make a directory on the remote machine.
+.It Ic mls Ar remote-files local-file
+Like
+.Ic ls  ,
+except multiple remote files may be specified,
+and the
+.Ar local-file
+must be specified.
+If interactive prompting is on,
+.Nm
+will prompt the user to verify that the last argument is indeed the
+target local file for receiving
+.Ic mls
+output.
+.It Ic mlsd Op Ar remote-path
+Display the contents of
+.Ar remote-path
+(which should default to the current directory if not given)
+in a machine-parsable form, using
+.Dv MLSD .
+The format of display can be changed with
+.Sq "remopts mlst ..." .
+.It Ic mlst Op Ar remote-path
+Display the details about
+.Ar remote-path
+(which should default to the current directory if not given)
+in a machine-parsable form, using
+.Dv MLST .
+The format of display can be changed with
+.Sq "remopts mlst ..." .
+.It Ic mode Ar mode-name
+Set the file transfer
+.Ic mode
+to
+.Ar mode-name .
+The default (and only supported)
+mode is
+.Dq stream .
+.It Ic modtime Ar remote-file
+Show the last modification time of the file on the remote machine, in
+.Li RFC 2822
+format.
+.It Ic more Ar file
+A synonym for
+.Ic page .
+.It Ic mput Ar local-files
+Expand wild cards in the list of local files given as arguments
+and do a
+.Ic put
+for each file in the resulting list.
+See
+.Ic glob
+for details of filename expansion.
+Resulting file names will then be processed according to
+.Ic ntrans
+and
+.Ic nmap
+settings.
+.It Ic mreget Ar remote-files
+As per
+.Ic mget ,
+but performs a
+.Ic reget
+instead of
+.Ic get .
+.It Ic msend Ar local-files
+A synonym for
+.Ic mput .
+.It Ic newer Ar remote-file Op Ar local-file
+Get the file only if the modification time of the remote file is more
+recent that the file on the current system.
+If the file does not
+exist on the current system, the remote file is considered
+.Ic newer .
+Otherwise, this command is identical to
+.Ar get .
+.It Ic nlist Op Ar remote-path Op Ar local-file
+A synonym for
+.Ic ls .
+.It Ic nmap Op Ar inpattern outpattern
+Set or unset the filename mapping mechanism.
+If no arguments are specified, the filename mapping mechanism is unset.
+If arguments are specified, remote filenames are mapped during
+.Ic mput
+commands and
+.Ic put
+commands issued without a specified remote target filename.
+If arguments are specified, local filenames are mapped during
+.Ic mget
+commands and
+.Ic get
+commands issued without a specified local target filename.
+This command is useful when connecting to a
+.No non\- Ns Ux
+remote computer
+with different file naming conventions or practices.
+The mapping follows the pattern set by
+.Ar inpattern
+and
+.Ar outpattern .
+.Op Ar Inpattern
+is a template for incoming filenames (which may have already been
+processed according to the
+.Ic ntrans
+and
+.Ic case
+settings).
+Variable templating is accomplished by including the
+sequences
+.Dq $1 ,
+.Dq $2 ,
+\&...
+.Dq $9
+in
+.Ar inpattern .
+Use
+.Sq \e
+to prevent this special treatment of the
+.Sq $
+character.
+All other characters are treated literally, and are used to determine the
+.Ic nmap
+.Op Ar inpattern
+variable values.
+For example, given
+.Ar inpattern
+$1.$2 and the remote file name "mydata.data", $1 would have the value
+"mydata", and $2 would have the value "data".
+The
+.Ar outpattern
+determines the resulting mapped filename.
+The sequences
+.Dq $1 ,
+.Dq $2 ,
+\&...
+.Dq $9
+are replaced by any value resulting from the
+.Ar inpattern
+template.
+The sequence
+.Dq $0
+is replaced by the original filename.
+Additionally, the sequence
+.Dq Op Ar seq1 , Ar seq2
+is replaced by
+.Op Ar seq1
+if
+.Ar seq1
+is not a null string; otherwise it is replaced by
+.Ar seq2 .
+For example, the command
+.Pp
+.Bd -literal -offset indent -compact
+nmap $1.$2.$3 [$1,$2].[$2,file]
+.Ed
+.Pp
+would yield
+the output filename "myfile.data" for input filenames "myfile.data" and
+"myfile.data.old", "myfile.file" for the input filename "myfile", and
+"myfile.myfile" for the input filename ".myfile".
+Spaces may be included in
+.Ar outpattern  ,
+as in the example:
+.Dl nmap $1 sed "s/  *$//" \*[Gt] $1
+Use the
+.Sq \e
+character to prevent special treatment
+of the
+.Sq $ ,
+.Sq \&[ ,
+.Sq \&] ,
+and
+.Sq \&,
+characters.
+.It Ic ntrans Op Ar inchars Op Ar outchars
+Set or unset the filename character translation mechanism.
+If no arguments are specified, the filename character
+translation mechanism is unset.
+If arguments are specified, characters in
+remote filenames are translated during
+.Ic mput
+commands and
+.Ic put
+commands issued without a specified remote target filename.
+If arguments are specified, characters in
+local filenames are translated during
+.Ic mget
+commands and
+.Ic get
+commands issued without a specified local target filename.
+This command is useful when connecting to a
+.No non\- Ns Ux
+remote computer
+with different file naming conventions or practices.
+Characters in a filename matching a character in
+.Ar inchars
+are replaced with the corresponding character in
+.Ar outchars .
+If the character's position in
+.Ar inchars
+is longer than the length of
+.Ar outchars  ,
+the character is deleted from the file name.
+.It Ic open Ar host Op Ar port
+Establish a connection to the specified
+.Ar host
+.Tn FTP
+server.
+An optional port number may be supplied,
+in which case,
+.Nm
+will attempt to contact an
+.Tn FTP
+server at that port.
+If the
+.Ic "set auto-login"
+option is on (default),
+.Nm
+will also attempt to automatically log the user in to
+the
+.Tn FTP
+server (see below).
+.It Ic page Ar file
+Retrieve
+.Ic file
+and display with the program specified by the
+.Ic "set pager"
+option.
+.It Ic passive Op Cm auto
+Toggle passive mode (if no arguments are given).
+If
+.Cm auto
+is given, act as if
+.Ev FTPMODE
+is set to
+.Sq auto .
+If passive mode is turned on (default),
+.Nm
+will send a
+.Dv PASV
+command for all data connections instead of a
+.Dv PORT
+command.
+The
+.Dv PASV
+command requests that the remote server open a port for the data connection
+and return the address of that port.
+The remote server listens on that port and the client connects to it.
+When using the more traditional
+.Dv PORT
+command, the client listens on a port and sends that address to the remote
+server, who connects back to it.
+Passive mode is useful when using
+.Nm
+through a gateway router or host that controls the directionality of
+traffic.
+(Note that though
+.Tn FTP
+servers are required to support the
+.Dv PASV
+command by
+.Li RFC 1123 ,
+some do not.)
+.It Ic pdir Op Ar remote-path
+Perform
+.Ic dir
+.Op Ar remote-path ,
+and display the result with the program specified by the
+.Ic "set pager"
+option.
+.It Ic pls Op Ar remote-path
+Perform
+.Ic ls
+.Op Ar remote-path ,
+and display the result with the program specified by the
+.Ic "set pager"
+option.
+.It Ic pmlsd Op Ar remote-path
+Perform
+.Ic mlsd
+.Op Ar remote-path ,
+and display the result with the program specified by the
+.Ic "set pager"
+option.
+.It Ic preserve
+Toggle preservation of modification times on retrieved files.
+.It Ic progress
+Toggle display of transfer progress bar.
+The progress bar will be disabled for a transfer that has
+.Ar local-file
+as
+.Sq Fl
+or a command that starts with
+.Sq \&| .
+Refer to
+.Sx FILE NAMING CONVENTIONS
+for more information.
+Enabling
+.Ic progress
+disables
+.Ic hash .
+.It Ic prompt
+Toggle interactive prompting.
+Interactive prompting
+occurs during multiple file transfers to allow the
+user to selectively retrieve or store files.
+If prompting is turned off (default is on), any
+.Ic mget
+or
+.Ic mput
+will transfer all files, and any
+.Ic mdelete
+will delete all files.
+.Pp
+When prompting is on, the following commands are available at a prompt:
+.Bl -tag -width 2n -offset indent
+.It Cm a
+Answer
+.Sq yes
+to the current file, and automatically answer
+.Sq yes
+to any remaining files for the current command.
+.It Cm n
+Answer
+.Sq no ,
+and do not transfer the file.
+.It Cm p
+Answer
+.Sq yes
+to the current file, and turn off prompt mode
+(as is
+.Dq prompt off
+had been given).
+.It Cm q
+Terminate the current operation.
+.It Cm y
+Answer
+.Sq yes ,
+and transfer the file.
+.It Cm \&?
+Display a help message.
+.El
+.Pp
+Any other response will answer
+.Sq yes
+to the current file.
+.It Ic proxy Ar ftp-command
+Execute an ftp command on a secondary control connection.
+This command allows simultaneous connection to two remote
+.Tn FTP
+servers for transferring files between the two servers.
+The first
+.Ic proxy
+command should be an
+.Ic open  ,
+to establish the secondary control connection.
+Enter the command "proxy ?" to see other
+.Tn FTP
+commands executable on the secondary connection.
+The following commands behave differently when prefaced by
+.Ic proxy  :
+.Ic open
+will not define new macros during the auto-login process,
+.Ic close
+will not erase existing macro definitions,
+.Ic get
+and
+.Ic mget
+transfer files from the host on the primary control connection
+to the host on the secondary control connection, and
+.Ic put  ,
+.Ic mput ,
+and
+.Ic append
+transfer files from the host on the secondary control connection
+to the host on the primary control connection.
+Third party file transfers depend upon support of the
+.Tn FTP
+protocol
+.Dv PASV
+command by the server on the secondary control connection.
+.It Ic put Ar local-file Op Ar remote-file
+Store a local file on the remote machine.
+If
+.Ar remote-file
+is left unspecified, the local file name is used
+after processing according to any
+.Ic ntrans
+or
+.Ic nmap
+settings
+in naming the remote file.
+File transfer uses the
+current settings for
+.Ic type  ,
+.Ic format ,
+.Ic mode  ,
+and
+.Ic structure .
+.It Ic pwd
+Print the name of the current working directory on the remote
+machine.
+.It Ic quit
+A synonym for
+.Ic bye .
+.It Ic quote Ar arg1 arg2 ...
+The arguments specified are sent, verbatim, to the remote
+.Tn FTP
+server.
+.It Ic rate Ar direction Oo Ar maximum Oo Ar increment Oc Oc
+Throttle the maximum transfer rate to
+.Ar maximum
+bytes/second.
+If
+.Ar maximum
+is 0, disable the throttle.
+.Pp
+.Ar direction
+may be one of:
+.Bl -tag -width "all" -offset indent -compact
+.It Cm all
+Both directions.
+.It Cm get
+Incoming transfers.
+.It Cm put
+Outgoing transfers.
+.El
+.Pp
+.Ar maximum
+can be modified on the fly by
+.Ar increment
+bytes (default: 1024) each time a given signal is received:
+.Bl -tag -width "SIGUSR1" -offset indent
+.It Dv SIGUSR1
+Increment
+.Ar maximum
+by
+.Ar increment
+bytes.
+.It Dv SIGUSR2
+Decrement
+.Ar maximum
+by
+.Ar increment
+bytes.
+The result must be a positive number.
+.El
+.Pp
+If
+.Ar maximum
+is not supplied, the current throttle rates are displayed.
+.Pp
+Note:
+.Ic rate
+is not yet implemented for ascii mode transfers.
+.It Ic rcvbuf Ar size
+Set the size of the socket receive buffer to
+.Ar size .
+.It Ic recv Ar remote-file Op Ar local-file
+A synonym for
+.Ic get .
+.It Ic reget Ar remote-file Op Ar local-file
+.Ic reget
+acts like
+.Ic get ,
+except that if
+.Ar local-file
+exists and is
+smaller than
+.Ar remote-file  ,
+.Ar local-file
+is presumed to be
+a partially transferred copy of
+.Ar remote-file
+and the transfer
+is continued from the apparent point of failure.
+This command
+is useful when transferring very large files over networks that
+are prone to dropping connections.
+.It Ic remopts Ar command Op Ar command-options
+Set options on the remote
+.Tn FTP
+server for
+.Ar command
+to
+.Ar command-options
+(whose absence is handled on a command-specific basis).
+Remote
+.Tn FTP
+commands known to support options include:
+.Sq MLST
+(used for
+.Dv MLSD
+and
+.Dv MLST ) .
+.It Ic rename Op Ar from Op Ar to
+Rename the file
+.Ar from
+on the remote machine, to the file
+.Ar to .
+.It Ic reset
+Clear reply queue.
+This command re-synchronizes command/reply sequencing with the remote
+.Tn FTP
+server.
+Resynchronization may be necessary following a violation of the
+.Tn FTP
+protocol by the remote server.
+.It Ic restart Ar marker
+Restart the immediately following
+.Ic get
+or
+.Ic put
+at the
+indicated
+.Ar marker .
+On
+.Ux
+systems, marker is usually a byte
+offset into the file.
+.It Ic rhelp Op Ar command-name
+Request help from the remote
+.Tn FTP
+server.
+If a
+.Ar command-name
+is specified it is supplied to the server as well.
+.It Ic rmdir Ar directory-name
+Delete a directory on the remote machine.
+.It Ic rstatus Op Ar remote-file
+With no arguments, show status of remote machine.
+If
+.Ar remote-file
+is specified, show status of
+.Ar remote-file
+on remote machine.
+.It Ic runique
+Toggle storing of files on the local system with unique filenames.
+If a file already exists with a name equal to the target
+local filename for a
+.Ic get
+or
+.Ic mget
+command, a ".1" is appended to the name.
+If the resulting name matches another existing file,
+a ".2" is appended to the original name.
+If this process continues up to ".99", an error
+message is printed, and the transfer does not take place.
+The generated unique filename will be reported.
+Note that
+.Ic runique
+will not affect local files generated from a shell command
+(see below).
+The default value is off.
+.It Ic send Ar local-file Op Ar remote-file
+A synonym for
+.Ic put .
+.It Ic sendport
+Toggle the use of
+.Dv PORT
+commands.
+By default,
+.Nm
+will attempt to use a
+.Dv PORT
+command when establishing
+a connection for each data transfer.
+The use of
+.Dv PORT
+commands can prevent delays
+when performing multiple file transfers.
+If the
+.Dv PORT
+command fails,
+.Nm
+will use the default data port.
+When the use of
+.Dv PORT
+commands is disabled, no attempt will be made to use
+.Dv PORT
+commands for each data transfer.
+This is useful
+for certain
+.Tn FTP
+implementations which do ignore
+.Dv PORT
+commands but, incorrectly, indicate they've been accepted.
+.It Ic set Op Ar option Ar value
+Set
+.Ar option
+to
+.Ar value .
+If
+.Ar option
+and
+.Ar value
+are not given, display all of the options and their values.
+The currently supported options are:
+.Bl -tag -width "https_proxy" -offset indent
+.It Cm anonpass
+Defaults to
+.Ev $FTPANONPASS
+.It Cm ftp_proxy
+Defaults to
+.Ev $ftp_proxy .
+.It Cm http_proxy
+Defaults to
+.Ev $http_proxy .
+.It Cm https_proxy
+Defaults to
+.Ev $https_proxy .
+.It Cm no_proxy
+Defaults to
+.Ev $no_proxy .
+.It Cm pager
+Defaults to
+.Ev $PAGER .
+.It Cm prompt
+Defaults to
+.Ev $FTPPROMPT .
+.It Cm rprompt
+Defaults to
+.Ev $FTPRPROMPT .
+.El
+.It Ic site Ar arg1 arg2 ...
+The arguments specified are sent, verbatim, to the remote
+.Tn FTP
+server as a
+.Dv SITE
+command.
+.It Ic size Ar remote-file
+Return size of
+.Ar remote-file
+on remote machine.
+.It Ic sndbuf Ar size
+Set the size of the socket send buffer to
+.Ar size .
+.It Ic status
+Show the current status of
+.Nm ftp .
+.It Ic struct Ar struct-name
+Set the file transfer
+.Ar structure
+to
+.Ar struct-name .
+The default (and only supported)
+structure is
+.Dq file .
+.It Ic sunique
+Toggle storing of files on remote machine under unique file names.
+The remote
+.Tn FTP
+server must support
+.Tn FTP
+protocol
+.Dv STOU
+command for
+successful completion.
+The remote server will report unique name.
+Default value is off.
+.It Ic system
+Show the type of operating system running on the remote machine.
+.It Ic tenex
+Set the file transfer type to that needed to
+talk to
+.Tn TENEX
+machines.
+.It Ic throttle
+A synonym for
+.Ic rate .
+.It Ic trace
+Toggle packet tracing.
+.It Ic type Op Ar type-name
+Set the file transfer
+.Ic type
+to
+.Ar type-name .
+If no type is specified, the current type
+is printed.
+The default type is network
+.Tn ASCII .
+.It Ic umask Op Ar newmask
+Set the default umask on the remote server to
+.Ar newmask .
+If
+.Ar newmask
+is omitted, the current umask is printed.
+.It Ic unset Ar option
+Unset
+.Ar option .
+Refer to
+.Ic set
+for more information.
+.It Ic usage Ar command
+Print the usage message for
+.Ar command .
+.It Ic user Ar user-name Oo Ar password Oo Ar account Oc Oc
+Identify yourself to the remote
+.Tn FTP
+server.
+If the
+.Ar password
+is not specified and the server requires it,
+.Nm
+will prompt the user for it (after disabling local echo).
+If an
+.Ar account
+field is not specified, and the
+.Tn FTP
+server
+requires it, the user will be prompted for it.
+If an
+.Ar account
+field is specified, an account command will
+be relayed to the remote server after the login sequence
+is completed if the remote server did not require it
+for logging in.
+Unless
+.Nm
+is invoked with
+.Dq auto-login
+disabled, this process is done automatically on initial connection to the
+.Tn FTP
+server.
+.It Ic verbose
+Toggle verbose mode.
+In verbose mode, all responses from
+the
+.Tn FTP
+server are displayed to the user.
+In addition,
+if verbose is on, when a file transfer completes, statistics
+regarding the efficiency of the transfer are reported.
+By default,
+verbose is on.
+.It Ic xferbuf Ar size
+Set the size of the socket send and receive buffers to
+.Ar size .
+.It Ic \&? Op Ar command
+A synonym for
+.Ic help .
+.El
+.Pp
+Command arguments which have embedded spaces may be quoted with
+quote
+.Sq \&"
+marks.
+.Pp
+Commands which toggle settings can take an explicit
+.Ic on
+or
+.Ic off
+argument to force the setting appropriately.
+.Pp
+Commands which take a byte count as an argument
+(e.g.,
+.Ic hash ,
+.Ic rate ,
+and
+.Ic xferbuf )
+support an optional suffix on the argument which changes the
+interpretation of the argument.
+Supported suffixes are:
+.Bl -tag -width 3n -offset indent -compact
+.It Li b
+Causes no modification.
+(Optional)
+.It Li k
+Kilo; multiply the argument by 1024
+.It Li m
+Mega; multiply the argument by 1048576
+.It Li g
+Giga; multiply the argument by 1073741824
+.El
+.Pp
+If
+.Nm
+receives a
+.Dv SIGINFO
+(see the
+.Dq status
+argument of
+.Xr stty 1 )
+or
+.Dv SIGQUIT
+signal whilst a transfer is in progress, the current transfer rate
+statistics will be written to the standard error output, in the
+same format as the standard completion message.
+.Sh AUTO-FETCHING FILES
+In addition to standard commands, this version of
+.Nm
+supports an auto-fetch feature.
+To enable auto-fetch, simply pass the list of hostnames/files
+on the command line.
+.Pp
+The following formats are valid syntax for an auto-fetch element:
+.Bl -tag -width "FOO "
+.\" [user@]host:[path][/]
+.It Oo Ar user Ns Li \&@ Oc Ns Ar host Ns Li \&: Ns Oo Ar path Oc \
+Ns Oo Li / Oc
+.Dq Classic
+.Tn FTP
+format.
+.Pp
+If
+.Ar path
+contains a glob character and globbing is enabled,
+(see
+.Ic glob ) ,
+then the equivalent of
+.Ql mget path
+is performed.
+.Pp
+If the directory component of
+.Ar path
+contains no globbing characters,
+it is stored locally with the name basename (see
+.Xr basename 1 )
+of
+.Ic path ,
+in the current directory.
+Otherwise, the full remote name is used as the local name,
+relative to the local root directory.
+.\" ftp://[user[:password]@]host[:port]/path[/][;type=X]
+.It Li ftp:// Ns Oo Ar user Ns Oo Ns Li \&: Ns Ar password Oc Ns Li \&@ Oc \
+Ns Ar host Ns Oo Li \&: Ns Ar port Oc Ns Li / Ns Ar path Ns Oo Li / Oc \
+Ns Oo Li ;type= Ns Ar X Oc
+An
+.Tn FTP
+URL, retrieved using the
+.Tn FTP
+protocol if
+.Ic "set ftp_proxy"
+isn't defined.
+Otherwise, transfer the URL using
+.Tn HTTP
+via the proxy defined in
+.Ic "set ftp_proxy" .
+If
+.Ic "set ftp_proxy"
+isn't defined and
+.Ar user
+is given, login as
+.Ar user .
+In this case, use
+.Ar password
+if supplied, otherwise prompt the user for one.
+.Pp
+If a suffix of
+.Sq ;type=A
+or
+.Sq ;type=I
+is supplied, then the transfer type will take place as
+ascii or binary (respectively).
+The default transfer type is binary.
+.Pp
+In order to be compliant with
+.Li RFC 3986 ,
+.Nm
+interprets the
+.Ar path
+part of an
+.Dq ftp://
+auto-fetch URL as follows:
+.Bl -bullet
+.It
+The
+.Sq Li /
+immediately after the
+.Ar host Ns Oo Li \&: Ns Ar port Oc
+is interpreted as a separator before the
+.Ar path ,
+and not as part of the
+.Ar path
+itself.
+.It
+The
+.Ar path
+is interpreted as a
+.So Li / Sc Ns -separated
+list of name components.
+For all but the last such component,
+.Nm
+performs the equivalent of a
+.Ic cd
+command.
+For the last path component,
+.Nm
+performs the equivalent of a
+.Ic get
+command.
+.It
+Empty name components,
+which result from
+.Sq Li //
+within the
+.Ar path ,
+or from an extra
+.Sq Li /
+at the beginning of the
+.Ar path ,
+will cause the equivalent of a
+.Ic cd
+command without a directory name.
+This is unlikely to be useful.
+.It
+Any
+.Sq Li \&% Ns Ar XX
+codes
+(per
+.Li RFC 3986 )
+within the path components are decoded, with
+.Ar XX
+representing a character code in hexadecimal.
+This decoding takes place after the
+.Ar path
+has been split into components,
+but before each component is used in the equivalent of a
+.Ic cd
+or
+.Ic get
+command.
+Some often-used codes are
+.Sq Li \&%2F
+(which represents
+.Sq Li / )
+and
+.Sq Li \&%7E
+(which represents
+.Sq Li ~ ) .
+.El
+.Pp
+The above interpretation has the following consequences:
+.Bl -bullet
+.It
+The path is interpreted relative to the
+default login directory of the specified user or of the
+.Sq anonymous
+user.
+If the
+.Pa /
+directory is required, use a leading path of
+.Dq %2F .
+If a user's home directory is required (and the remote server supports
+the syntax), use a leading path of
+.Dq %7Euser/ .
+For example, to retrieve
+.Pa /etc/motd
+from
+.Sq localhost
+as the user
+.Sq myname
+with the password
+.Sq mypass ,
+use
+.Dq ftp://myname:mypass@localhost/%2fetc/motd
+.It
+The exact
+.Ic cd
+and
+.Ic get
+commands can be controlled by careful choice of
+where to use
+.Sq /
+and where to use
+.Sq %2F
+(or
+.Sq %2f ) .
+For example, the following URLs correspond to the
+equivalents of the indicated commands:
+.Bl -tag -width "ftp://host/%2Fdir1%2Fdir2%2Ffile"
+.It ftp://host/dir1/dir2/file
+.Dq "cd dir1" ,
+.Dq "cd dir2" ,
+.Dq "get file" .
+.It ftp://host/%2Fdir1/dir2/file
+.Dq "cd /dir1" ,
+.Dq "cd dir2" ,
+.Dq "get file" .
+.It ftp://host/dir1%2Fdir2/file
+.Dq "cd dir1/dir2" ,
+.Dq "get file" .
+.It ftp://host/%2Fdir1%2Fdir2/file
+.Dq "cd /dir1/dir2" ,
+.Dq "get file" .
+.It ftp://host/dir1%2Fdir2%2Ffile
+.Dq "get dir1/dir2/file" .
+.It ftp://host/%2Fdir1%2Fdir2%2Ffile
+.Dq "get /dir1/dir2/file" .
+.El
+.It
+You must have appropriate access permission for each of the
+intermediate directories that is used in the equivalent of a
+.Ic cd
+command.
+.El
+.\" http://[user[:password]@]host[:port]/path
+.It Li http:// Ns Oo Ar user Ns Oo Li \&: Ns Ar password Oc Ns Li \&@ Oc \
+Ns Ar host Ns Oo Li \&: Ns Ar port Oc Ns Li / Ns Ar path
+An
+.Tn HTTP
+URL, retrieved using the
+.Tn HTTP
+protocol.
+If
+.Ic "set http_proxy"
+is defined, it is used as a URL to an
+.Tn HTTP
+proxy server.
+If
+.Tn HTTP
+authorization is required to retrieve
+.Ar path ,
+and
+.Sq user
+(and optionally
+.Sq password )
+is in the URL, use them for the first attempt to authenticate.
+.\" https://[user[:password]@]host[:port]/path
+.It Li https:// Ns Oo Ar user Ns Oo Li \&: Ns Ar password Oc Ns Li \&@ Oc \
+Ns Ar host Ns Oo Li \&: Ns Ar port Oc Ns Li / Ns Ar path
+An
+.Tn HTTPS
+URL, retrieved using the
+.Tn HTTPS
+protocol.
+If
+.Ic "set https_proxy"
+is defined, it is used as a URL to an
+.Tn HTTPS
+proxy server.
+If
+.Tn HTTPS
+authorization is required to retrieve
+.Ar path ,
+and
+.Sq user
+(and optionally
+.Sq password )
+is in the URL, use them for the first attempt to authenticate.
+There is currently no certificate validation and verification.
+.\" file:///path
+.It Li file:/// Ns Ar path
+A local URL, copied from
+.Pa / Ns Ar path
+on the local host.
+.\" about:
+.It Li about: Ns Ar topic
+Display information regarding
+.Ar topic ;
+no file is retrieved for this auto-fetched element.
+Supported values include:
+.Bl -tag -width "about:version"
+.It Li about:ftp
+Information about
+.Nm ftp .
+.It Li about:version
+The version of
+.Nm ftp .
+Useful to provide when reporting problems.
+.El
+.El
+.Pp
+Unless noted otherwise above, and
+.Fl o Ar output
+is not given, the file is stored in the current directory as the
+.Xr basename 1
+of
+.Ar path .
+Note that if a
+.Tn HTTP
+redirect is received, the fetch is retried using the new target URL
+supplied by the server, with a corresponding new
+.Ar path .
+Using an explicit
+.Fl o Ar output
+is recommended, to avoid writing to unexpected file names.
+.Pp
+If a classic format or an
+.Tn FTP
+URL format has a trailing
+.Sq /
+or an empty
+.Ar path
+component, then
+.Nm
+will connect to the site and
+.Ic cd
+to the directory given as the path, and leave the user in interactive
+mode ready for further input.
+This will not work if
+.Ic "set ftp_proxy"
+is being used.
+.Pp
+Direct
+.Tn HTTP
+transfers use HTTP 1.1.
+Proxied
+.Tn FTP
+and
+.Tn HTTP
+transfers use HTTP 1.0.
+.Pp
+If
+.Fl R
+is given, all auto-fetches that don't go via the
+.Tn FTP
+or
+.Tn HTTP
+proxies will be restarted.
+For
+.Tn FTP ,
+this is implemented by using
+.Nm reget
+instead of
+.Nm get .
+For
+.Tn HTTP ,
+this is implemented by using the
+.Sq "Range: bytes="
+.Tn "HTTP/1.1"
+directive.
+.Pp
+If WWW or proxy WWW authentication is required, you will be prompted
+to enter a username and password to authenticate with.
+.Pp
+When specifying IPv6 numeric addresses in a URL, you need to
+surround the address in square brackets.
+E.g.:
+.Dq ftp://[::1]:21/ .
+This is because colons are used in IPv6 numeric address as well as
+being the separator for the port number.
+.Sh ABORTING A FILE TRANSFER
+To abort a file transfer, use the terminal interrupt key
+(usually Ctrl-C).
+Sending transfers will be immediately halted.
+Receiving transfers will be halted by sending an
+.Tn FTP
+protocol
+.Dv ABOR
+command to the remote server, and discarding any further data received.
+The speed at which this is accomplished depends upon the remote
+server's support for
+.Dv ABOR
+processing.
+If the remote server does not support the
+.Dv ABOR
+command, the prompt will not appear until the remote server has completed
+sending the requested file.
+.Pp
+If the terminal interrupt key sequence is used whilst
+.Nm
+is awaiting a reply from the remote server for the ABOR processing,
+then the connection will be closed.
+This is different from the traditional behaviour (which ignores the
+terminal interrupt during this phase), but is considered more useful.
+.Sh FILE NAMING CONVENTIONS
+Files specified as arguments to
+.Nm
+commands are processed according to the following rules.
+.Bl -enum
+.It
+If the file name
+.Sq Fl
+is specified, the
+.Ar stdin
+(for reading) or
+.Ar stdout
+(for writing) is used.
+.It
+If the first character of the file name is
+.Sq \&| ,
+the
+remainder of the argument is interpreted as a shell command.
+.Nm
+then forks a shell, using
+.Xr popen 3
+with the argument supplied, and reads (writes) from the stdout
+(stdin).
+If the shell command includes spaces, the argument
+must be quoted; e.g.
+.Dq Qq Li \&| ls\ \-lt .
+A particularly
+useful example of this mechanism is:
+.Dq Li dir \&"\&" \&|more .
+.It
+Failing the above checks, if
+.Dq globbing
+is enabled, local file names are expanded according to the rules
+used in the
+.Xr csh 1 ;
+see the
+.Ic glob
+command.
+If the
+.Nm
+command expects a single local file (e.g.
+.Ic put  ) ,
+only the first filename generated by the "globbing" operation is used.
+.It
+For
+.Ic mget
+commands and
+.Ic get
+commands with unspecified local file names, the local filename is
+the remote filename, which may be altered by a
+.Ic case  ,
+.Ic ntrans ,
+or
+.Ic nmap
+setting.
+The resulting filename may then be altered if
+.Ic runique
+is on.
+.It
+For
+.Ic mput
+commands and
+.Ic put
+commands with unspecified remote file names, the remote filename is
+the local filename, which may be altered by a
+.Ic ntrans
+or
+.Ic nmap
+setting.
+The resulting filename may then be altered by the remote server if
+.Ic sunique
+is on.
+.El
+.Sh FILE TRANSFER PARAMETERS
+The
+.Tn FTP
+specification specifies many parameters which may affect a file transfer.
+The
+.Ic type
+may be one of
+.Dq ascii ,
+.Dq image
+(binary),
+.Dq ebcdic ,
+and
+.Dq local byte size
+(for
+.Tn PDP Ns -10's
+and
+.Tn PDP Ns -20's
+mostly).
+.Nm
+supports the ascii and image types of file transfer,
+plus local byte size 8 for
+.Ic tenex
+mode transfers.
+.Pp
+.Nm
+supports only the default values for the remaining
+file transfer parameters:
+.Ic mode ,
+.Ic form ,
+and
+.Ic struct .
+.Sh THE .netrc FILE
+The
+.Pa .netrc
+file contains login and initialization information
+used by the auto-login process.
+It resides in the user's home directory,
+unless overridden with the
+.Fl N Ar netrc
+option, or specified in the
+.Ev NETRC
+environment variable.
+The following tokens are recognized; they may be separated by spaces,
+tabs, or new-lines:
+.Bl -tag -width password
+.It Ic machine Ar name
+Identify a remote machine
+.Ar name .
+The auto-login process searches the
+.Pa .netrc
+file for a
+.Ic machine
+token that matches the remote machine specified on the
+.Nm
+command line or as an
+.Ic open
+command argument.
+Once a match is made, the subsequent
+.Pa .netrc
+tokens are processed,
+stopping when the end of file is reached or another
+.Ic machine
+or a
+.Ic default
+token is encountered.
+.It Ic default
+This is the same as
+.Ic machine
+.Ar name
+except that
+.Ic default
+matches any name.
+There can be only one
+.Ic default
+token, and it must be after all
+.Ic machine
+tokens.
+This is normally used as:
+.Pp
+.Dl default login anonymous password user@site
+.Pp
+thereby giving the user an automatic anonymous
+.Tn FTP
+login to
+machines not specified in
+.Pa .netrc .
+This can be overridden
+by using the
+.Fl n
+flag to disable auto-login.
+.It Ic login Ar name
+Identify a user on the remote machine.
+If this token is present, the auto-login process will initiate
+a login using the specified
+.Ar name .
+.It Ic password Ar string
+Supply a password.
+If this token is present, the auto-login process will supply the
+specified string if the remote server requires a password as part
+of the login process.
+Note that if this token is present in the
+.Pa .netrc
+file for any user other
+than
+.Ar anonymous  ,
+.Nm
+will abort the auto-login process if the
+.Pa .netrc
+is readable by
+anyone besides the user.
+.It Ic account Ar string
+Supply an additional account password.
+If this token is present, the auto-login process will supply the
+specified string if the remote server requires an additional
+account password, or the auto-login process will initiate an
+.Dv ACCT
+command if it does not.
+.It Ic macdef Ar name
+Define a macro.
+This token functions like the
+.Nm
+.Ic macdef
+command functions.
+A macro is defined with the specified name; its contents begin with the
+next
+.Pa .netrc
+line and continue until a blank line (consecutive new-line
+characters) is encountered.
+Like the other tokens in the
+.Pa .netrc
+file, a
+.Ic macdef
+is applicable only to the
+.Ic machine
+definition preceding it.
+A
+.Ic macdef
+entry cannot be used by multiple
+.Ic machine
+definitions; rather, it must be defined following each
+.Ic machine
+it is intended to be used with.
+If a macro named
+.Ic init
+is defined, it is automatically executed as the last step in the
+auto-login process.
+For example,
+.Bd -literal -offset indent
+default
+macdef init
+epsv4 off
+.Ed
+.Pp
+followed by a blank line.
+.El
+.Sh COMMAND LINE EDITING
+.Nm
+supports interactive command line editing, via the
+.Xr editline 3
+library.
+It is enabled with the
+.Ic edit
+command, and is enabled by default if input is from a tty.
+Previous lines can be recalled and edited with the arrow keys,
+and other GNU Emacs-style editing keys may be used as well.
+.Pp
+The
+.Xr editline 3
+library is configured with a
+.Pa .editrc
+file - refer to
+.Xr editrc 5
+for more information.
+.Pp
+An extra key binding is available to
+.Nm
+to provide context sensitive command and filename completion
+(including remote file completion).
+To use this, bind a key to the
+.Xr editline 3
+command
+.Ic ftp-complete .
+By default, this is bound to the TAB key.
+.Sh COMMAND LINE PROMPT
+By default,
+.Nm
+displays a command line prompt of
+.Dq "ftp\*[Gt] "
+to the user.
+This can be changed with the
+.Ic "set prompt"
+command.
+.Pp
+A prompt can be displayed on the right side of the screen (after the
+command input) with the
+.Ic "set rprompt"
+command.
+.Pp
+The following formatting sequences are replaced by the given
+information:
+.Bl -tag -width "%% " -offset indent
+.It Li \&%/
+The current remote working directory.
+.\" %c[[0]n], %.[[0]n]
+.It \&%c Ns Oo Oo Li 0 Oc Ns Ar n Oc , Ns Li \&%. Ns Oo Oo Li 0 Oc Ns Ar n Oc
+The trailing component of the current remote working directory, or
+.Em n
+trailing components if a digit
+.Em n
+is given.
+If
+.Em n
+begins with
+.Sq 0 ,
+the number of skipped components precede the trailing component(s) in
+the format
+.\" ``/<number>trailing''
+.Do
+.Sm off
+.Li / Li \*[Lt] Va number Li \*[Gt]
+.Va trailing
+.Sm on
+.Dc
+(for
+.Sq \&%c )
+or
+.\" ``...trailing''
+.Dq Li \&... Ns Va trailing
+(for
+.Sq \&%. ) .
+.It Li \&%M
+The remote host name.
+.It Li \&%m
+The remote host name, up to the first
+.Sq \&. .
+.It Li \&%n
+The remote user name.
+.It Li \&%%
+A single
+.Sq % .
+.El
+.Sh ENVIRONMENT
+.Nm
+uses the following environment variables.
+.Bl -tag -width "FTPSERVERPORT"
+.It Ev FTPANONPASS
+Password to send in an anonymous
+.Tn FTP
+transfer.
+Defaults to
+.Dq Li `whoami`@ .
+.It Ev FTPMODE
+Overrides the default operation mode.
+Support values are:
+.Bl -tag -width "passive"
+.It Cm active
+active mode
+.Tn FTP
+only
+.It Cm auto
+automatic determination of passive or active (this is the default)
+.It Cm gate
+gate-ftp mode
+.It Cm passive
+passive mode
+.Tn FTP
+only
+.El
+.It Ev FTPPROMPT
+Command-line prompt to use.
+Defaults to
+.Dq "ftp\*[Gt] " .
+Refer to
+.Sx COMMAND LINE PROMPT
+for more information.
+.It Ev FTPRPROMPT
+Command-line right side prompt to use.
+Defaults to
+.Dq "" .
+Refer to
+.Sx COMMAND LINE PROMPT
+for more information.
+.It Ev FTPSERVER
+Host to use as gate-ftp server when
+.Ic gate
+is enabled.
+.It Ev FTPSERVERPORT
+Port to use when connecting to gate-ftp server when
+.Ic gate
+is enabled.
+Default is port returned by a
+.Fn getservbyname
+lookup of
+.Dq ftpgate/tcp .
+.It Ev FTPUSERAGENT
+The value to send for the
+.Tn HTTP
+User-Agent
+header.
+.It Ev HOME
+For default location of a
+.Pa .netrc
+file, if one exists.
+.It Ev NETRC
+An alternate location of the
+.Pa .netrc
+file.
+.It Ev PAGER
+Used by various commands to display files.
+Defaults to
+.Xr more 1
+if empty or not set.
+.It Ev SHELL
+For default shell.
+.It Ev ftp_proxy
+URL of
+.Tn FTP
+proxy to use when making
+.Tn FTP
+URL requests
+(if not defined, use the standard
+.Tn FTP
+protocol).
+.Pp
+See
+.Ev http_proxy
+for further notes about proxy use.
+.It Ev http_proxy
+URL of
+.Tn HTTP
+proxy to use when making
+.Tn HTTP
+URL requests.
+If proxy authentication is required and there is a username and
+password in this URL, they will automatically be used in the first
+attempt to authenticate to the proxy.
+.Pp
+If
+.Dq unsafe
+URL characters are required in the username or password
+(for example
+.Sq @
+or
+.Sq / ) ,
+encode them with
+.Li RFC 3986
+.Sq Li \&% Ns Ar XX
+encoding.
+.Pp
+Note that the use of a username and password in
+.Ev ftp_proxy
+and
+.Ev http_proxy
+may be incompatible with other programs that use it
+(such as
+.Xr lynx 1 ) .
+.Pp
+.Em NOTE :
+this is not used for interactive sessions, only for command-line
+fetches.
+.It Ev no_proxy
+A space or comma separated list of hosts (or domains) for which
+proxying is not to be used.
+Each entry may have an optional trailing ":port", which restricts
+the matching to connections to that port.
+.El
+.Sh EXTENDED PASSIVE MODE AND FIREWALLS
+Some firewall configurations do not allow
+.Nm
+to use extended passive mode.
+If you find that even a simple
+.Ic ls
+appears to hang after printing a message such as this:
+.Pp
+.Dl 229 Entering Extended Passive Mode (|||58551|)
+.Pp
+then you will need to disable extended passive mode with
+.Ic epsv4 off .
+See the above section
+.Sx The .netrc File
+for an example of how to make this automatic.
+.Sh SEE ALSO
+.Xr getservbyname 3 ,
+.Xr editrc 5 ,
+.Xr services 5 ,
+.Xr ftpd 8
+.Sh STANDARDS
+.Nm
+attempts to be compliant with:
+.Bl -tag -offset indent -width 8n
+.It Li RFC 959
+.Em File Transfer Protocol
+.It Li RFC 1123
+.Em Requirements for Internet Hosts - Application and Support
+.It Li RFC 1635
+.Em How to Use Anonymous FTP
+.It Li RFC 2389
+.Em Feature negotiation mechanism for the File Transfer Protocol
+.It Li RFC 2428
+.Em FTP Extensions for IPv6 and NATs
+.It Li RFC 2616
+.Em Hypertext Transfer Protocol -- HTTP/1.1
+.It Li RFC 2822
+.Em Internet Message Format
+.It Li RFC 3659
+.Em Extensions to FTP
+.It Li RFC 3986
+.Em Uniform Resource Identifier (URI)
+.El
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
+.Pp
+Various features such as command line editing, context sensitive
+command and file completion, dynamic progress bar, automatic
+fetching of files and URLs, modification time preservation,
+transfer rate throttling, configurable command line prompt,
+and other enhancements over the standard
+.Bx
+.Nm
+were implemented in
+.Nx 1.3
+and later releases
+by
+.An Luke Mewburn
+.Aq lukem@NetBSD.org .
+.Pp
+IPv6 support was added by the WIDE/KAME project
+(but may not be present in all non-NetBSD versions of this program, depending
+if the operating system supports IPv6 in a similar manner to KAME).
+.Sh BUGS
+Correct execution of many commands depends upon proper behavior
+by the remote server.
+.Pp
+An error in the treatment of carriage returns
+in the
+.Bx 4.2
+ascii-mode transfer code
+has been corrected.
+This correction may result in incorrect transfers of binary files
+to and from
+.Bx 4.2
+servers using the ascii type.
+Avoid this problem by using the binary image type.
+.Pp
+.Nm
+assumes that all IPv4 mapped addresses
+.Po
+IPv6 addresses with a form like
+.Li ::ffff:10.1.1.1
+.Pc
+indicate IPv4 destinations which can be handled by
+.Dv AF_INET
+sockets.
+However, in certain IPv6 network configurations, this assumption is not true.
+In such an environment, IPv4 mapped addresses must be passed to
+.Dv AF_INET6
+sockets directly.
+For example, if your site uses a SIIT translator for IPv6-to-IPv4 translation,
+.Nm
+is unable to support your configuration.
diff --git a/usr.bin/ftp/ftp.c b/usr.bin/ftp/ftp.c
new file mode 100644 (file)
index 0000000..88bc905
--- /dev/null
@@ -0,0 +1,2178 @@
+/*     $NetBSD: ftp.c,v 1.164 2012/07/04 06:09:37 is Exp $     */
+
+/*-
+ * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ftp.c      8.6 (Berkeley) 10/27/94";
+#else
+__RCSID("$NetBSD: ftp.c,v 1.164 2012/07/04 06:09:37 is Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#ifndef __minix
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#endif
+#include <arpa/inet.h>
+#include <arpa/ftp.h>
+#include <arpa/telnet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#ifdef __minix
+#include <utime.h>
+#endif
+
+#include "ftp_var.h"
+
+volatile sig_atomic_t  abrtflag;
+volatile sig_atomic_t  timeoutflag;
+
+sigjmp_buf     ptabort;
+int    ptabflg;
+int    ptflag = 0;
+char   pasv[BUFSIZ];   /* passive port for proxy data connection */
+
+static int empty(FILE *, FILE *, int);
+__dead static void abort_squared(int);
+
+struct sockinet {
+       union sockunion {
+               struct sockaddr_in  su_sin;
+#ifdef INET6
+               struct sockaddr_in6 su_sin6;
+#endif
+       } si_su;
+#if !defined(HAVE_STRUCT_SOCKADDR_IN_SIN_LEN)
+       int     si_len;
+#endif
+};
+
+#if !defined(HAVE_STRUCT_SOCKADDR_IN_SIN_LEN)
+# define su_len                si_len
+#else
+# define su_len                si_su.su_sin.sin_len
+#endif
+#define su_family      si_su.su_sin.sin_family
+#define su_port                si_su.su_sin.sin_port
+
+struct sockinet myctladdr, hisctladdr, data_addr;
+
+char *
+hookup(const char *host, const char *port)
+{
+       int s = -1, error;
+       struct addrinfo hints, *res, *res0;
+       static char hostnamebuf[MAXHOSTNAMELEN];
+       socklen_t len;
+#ifndef __minix
+       int on = 1;
+#endif
+
+       memset((char *)&hisctladdr, 0, sizeof (hisctladdr));
+       memset((char *)&myctladdr, 0, sizeof (myctladdr));
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_flags = AI_CANONNAME;
+       hints.ai_family = family;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = 0;
+       error = getaddrinfo(host, port, &hints, &res0);
+       if (error) {
+               warnx("Can't lookup `%s:%s': %s", host, port,
+                   (error == EAI_SYSTEM) ? strerror(errno)
+                                         : gai_strerror(error));
+               code = -1;
+               return (0);
+       }
+
+       if (res0->ai_canonname)
+               (void)strlcpy(hostnamebuf, res0->ai_canonname,
+                   sizeof(hostnamebuf));
+       else
+               (void)strlcpy(hostnamebuf, host, sizeof(hostnamebuf));
+       hostname = hostnamebuf;
+
+       for (res = res0; res; res = res->ai_next) {
+               char hname[NI_MAXHOST], sname[NI_MAXSERV];
+
+               ai_unmapped(res);
+               if (getnameinfo(res->ai_addr, res->ai_addrlen,
+                   hname, sizeof(hname), sname, sizeof(sname),
+                   NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+                       strlcpy(hname, "?", sizeof(hname));
+                       strlcpy(sname, "?", sizeof(sname));
+               }
+               if (verbose && res0->ai_next) {
+                               /* if we have multiple possibilities */
+                       fprintf(ttyout, "Trying %s:%s ...\n", hname, sname);
+               }
+               s = socket(res->ai_family, SOCK_STREAM, res->ai_protocol);
+               if (s < 0) {
+                       warn("Can't create socket for connection to `%s:%s'",
+                           hname, sname);
+                       continue;
+               }
+               if (ftp_connect(s, res->ai_addr, res->ai_addrlen,
+                   verbose || !res->ai_next) < 0) {
+                       close(s);
+                       s = -1;
+                       continue;
+               }
+
+               /* finally we got one */
+               break;
+       }
+       if (s < 0) {
+               warnx("Can't connect to `%s:%s'", host, port);
+               code = -1;
+               freeaddrinfo(res0);
+               return 0;
+       }
+       memcpy(&hisctladdr.si_su, res->ai_addr, res->ai_addrlen);
+       hisctladdr.su_len = res->ai_addrlen;
+       freeaddrinfo(res0);
+       res0 = res = NULL;
+
+       len = hisctladdr.su_len;
+       if (getsockname(s, (struct sockaddr *)&myctladdr.si_su, &len) == -1) {
+               warn("Can't determine my address of connection to `%s:%s'",
+                   host, port);
+               code = -1;
+               goto bad;
+       }
+       myctladdr.su_len = len;
+
+#ifdef IPTOS_LOWDELAY
+       if (hisctladdr.su_family == AF_INET) {
+               int tos = IPTOS_LOWDELAY;
+               if (setsockopt(s, IPPROTO_IP, IP_TOS,
+                               (void *)&tos, sizeof(tos)) == -1) {
+                               DWARN("setsockopt %s (ignored)",
+                                   "IPTOS_LOWDELAY");
+               }
+       }
+#endif
+       cin = fdopen(s, "r");
+       cout = fdopen(s, "w");
+       if (cin == NULL || cout == NULL) {
+               warnx("Can't fdopen socket");
+               if (cin)
+                       (void)fclose(cin);
+               if (cout)
+                       (void)fclose(cout);
+               code = -1;
+               goto bad;
+       }
+       if (verbose)
+               fprintf(ttyout, "Connected to %s.\n", hostname);
+       if (getreply(0) > 2) {  /* read startup message from server */
+               if (cin)
+                       (void)fclose(cin);
+               if (cout)
+                       (void)fclose(cout);
+               code = -1;
+               goto bad;
+       }
+
+#ifndef __minix
+       if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE,
+                       (void *)&on, sizeof(on)) == -1) {
+               DWARN("setsockopt %s (ignored)", "SO_OOBINLINE");
+       }
+#endif
+
+       return (hostname);
+ bad:
+       (void)close(s);
+       return (NULL);
+}
+
+void
+cmdabort(int notused)
+{
+       int oerrno = errno;
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       abrtflag++;
+       if (ptflag)
+               siglongjmp(ptabort, 1);
+       errno = oerrno;
+}
+
+void
+cmdtimeout(int notused)
+{
+       int oerrno = errno;
+
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       timeoutflag++;
+       if (ptflag)
+               siglongjmp(ptabort, 1);
+       errno = oerrno;
+}
+
+/*VARARGS*/
+int
+command(const char *fmt, ...)
+{
+       va_list ap;
+       int r;
+       sigfunc oldsigint;
+
+#ifndef NO_DEBUG
+       if (ftp_debug) {
+               fputs("---> ", ttyout);
+               va_start(ap, fmt);
+               if (strncmp("PASS ", fmt, 5) == 0)
+                       fputs("PASS XXXX", ttyout);
+               else if (strncmp("ACCT ", fmt, 5) == 0)
+                       fputs("ACCT XXXX", ttyout);
+               else
+                       vfprintf(ttyout, fmt, ap);
+               va_end(ap);
+               putc('\n', ttyout);
+       }
+#endif
+       if (cout == NULL) {
+               warnx("No control connection for command");
+               code = -1;
+               return (0);
+       }
+
+       abrtflag = 0;
+
+       oldsigint = xsignal(SIGINT, cmdabort);
+
+       va_start(ap, fmt);
+       vfprintf(cout, fmt, ap);
+       va_end(ap);
+       fputs("\r\n", cout);
+       (void)fflush(cout);
+       cpend = 1;
+       r = getreply(!strcmp(fmt, "QUIT"));
+       if (abrtflag && oldsigint != SIG_IGN)
+               (*oldsigint)(SIGINT);
+       (void)xsignal(SIGINT, oldsigint);
+       return (r);
+}
+
+static const char *m421[] = {
+       "remote server timed out. Connection closed",
+       "user interrupt. Connection closed",
+       "remote server has closed connection",
+};
+
+int
+getreply(int expecteof)
+{
+       char current_line[BUFSIZ];      /* last line of previous reply */
+       int c, n, lineno;
+       int dig;
+       int originalcode = 0, continuation = 0;
+       sigfunc oldsigint, oldsigalrm;
+       int pflag = 0;
+       char *cp, *pt = pasv;
+
+       abrtflag = 0;
+       timeoutflag = 0;
+
+       oldsigint = xsignal(SIGINT, cmdabort);
+       oldsigalrm = xsignal(SIGALRM, cmdtimeout);
+
+       for (lineno = 0 ;; lineno++) {
+               dig = n = code = 0;
+               cp = current_line;
+               while (alarmtimer(quit_time ? quit_time : 60),
+                      ((c = getc(cin)) != '\n')) {
+                       if (c == IAC) {     /* handle telnet commands */
+                               switch (c = getc(cin)) {
+                               case WILL:
+                               case WONT:
+                                       c = getc(cin);
+                                       fprintf(cout, "%c%c%c", IAC, DONT, c);
+                                       (void)fflush(cout);
+                                       break;
+                               case DO:
+                               case DONT:
+                                       c = getc(cin);
+                                       fprintf(cout, "%c%c%c", IAC, WONT, c);
+                                       (void)fflush(cout);
+                                       break;
+                               default:
+                                       break;
+                               }
+                               continue;
+                       }
+                       dig++;
+                       if (c == EOF) {
+                               /*
+                                * these will get trashed by pswitch()
+                                * in lostpeer()
+                                */
+                               int reply_timeoutflag = timeoutflag;
+                               int reply_abrtflag = abrtflag;
+
+                               alarmtimer(0);
+                               if (expecteof && feof(cin)) {
+                                       (void)xsignal(SIGINT, oldsigint);
+                                       (void)xsignal(SIGALRM, oldsigalrm);
+                                       code = 221;
+                                       return (0);
+                               }
+                               cpend = 0;
+                               lostpeer(0);
+                               if (verbose) {
+                                       size_t midx;
+                                       if (reply_timeoutflag)
+                                               midx = 0;
+                                       else if (reply_abrtflag)
+                                               midx = 1;
+                                       else
+                                               midx = 2;
+                                       (void)fprintf(ttyout,
+                           "421 Service not available, %s.\n", m421[midx]);
+                                       (void)fflush(ttyout);
+                               }
+                               code = 421;
+                               (void)xsignal(SIGINT, oldsigint);
+                               (void)xsignal(SIGALRM, oldsigalrm);
+                               return (4);
+                       }
+                       if (c != '\r' && (verbose > 0 ||
+                           ((verbose > -1 && n == '5' && dig > 4) &&
+                           (((!n && c < '5') || (n && n < '5'))
+                            || !retry_connect)))) {
+                               if (proxflag &&
+                                  (dig == 1 || (dig == 5 && verbose == 0)))
+                                       fprintf(ttyout, "%s:", hostname);
+                               (void)putc(c, ttyout);
+                       }
+                       if (dig < 4 && isdigit(c))
+                               code = code * 10 + (c - '0');
+                       if (!pflag && (code == 227 || code == 228))
+                               pflag = 1;
+                       else if (!pflag && code == 229)
+                               pflag = 100;
+                       if (dig > 4 && pflag == 1 && isdigit(c))
+                               pflag = 2;
+                       if (pflag == 2) {
+                               if (c != '\r' && c != ')') {
+                                       if (pt < &pasv[sizeof(pasv) - 1])
+                                               *pt++ = c;
+                               } else {
+                                       *pt = '\0';
+                                       pflag = 3;
+                               }
+                       }
+                       if (pflag == 100 && c == '(')
+                               pflag = 2;
+                       if (dig == 4 && c == '-') {
+                               if (continuation)
+                                       code = 0;
+                               continuation++;
+                       }
+                       if (n == 0)
+                               n = c;
+                       if (cp < &current_line[sizeof(current_line) - 1])
+                               *cp++ = c;
+               }
+               if (verbose > 0 || ((verbose > -1 && n == '5') &&
+                   (n < '5' || !retry_connect))) {
+                       (void)putc(c, ttyout);
+                       (void)fflush(ttyout);
+               }
+               if (cp[-1] == '\r')
+                       cp[-1] = '\0';
+               *cp = '\0';
+               if (lineno == 0)
+                       (void)strlcpy(reply_string, current_line,
+                           sizeof(reply_string));
+               if (lineno > 0 && code == 0 && reply_callback != NULL)
+                       (*reply_callback)(current_line);
+               if (continuation && code != originalcode) {
+                       if (originalcode == 0)
+                               originalcode = code;
+                       continue;
+               }
+               if (n != '1')
+                       cpend = 0;
+               alarmtimer(0);
+               (void)xsignal(SIGINT, oldsigint);
+               (void)xsignal(SIGALRM, oldsigalrm);
+               if (code == 421 || originalcode == 421)
+                       lostpeer(0);
+               if (abrtflag && oldsigint != cmdabort && oldsigint != SIG_IGN)
+                       (*oldsigint)(SIGINT);
+               if (timeoutflag && oldsigalrm != cmdtimeout &&
+                   oldsigalrm != SIG_IGN)
+                       (*oldsigalrm)(SIGINT);
+               return (n - '0');
+       }
+}
+
+static int
+empty(FILE *ecin, FILE *din, int sec)
+{
+       int             nr, nfd;
+       struct pollfd   pfd[2];
+
+       nfd = 0;
+       if (ecin) {
+               pfd[nfd].fd = fileno(ecin);
+               pfd[nfd++].events = POLLIN;
+       }
+
+       if (din) {
+               pfd[nfd].fd = fileno(din);
+               pfd[nfd++].events = POLLIN;
+       }
+
+       if ((nr = ftp_poll(pfd, nfd, sec * 1000)) <= 0)
+               return nr;
+
+       nr = 0;
+       nfd = 0;
+       if (ecin)
+               nr |= (pfd[nfd++].revents & POLLIN) ? 1 : 0;
+       if (din)
+               nr |= (pfd[nfd++].revents & POLLIN) ? 2 : 0;
+       return nr;
+}
+
+sigjmp_buf     xferabort;
+
+__dead static void
+abortxfer(int notused)
+{
+       char msgbuf[100];
+       size_t len;
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       mflag = 0;
+       abrtflag = 0;
+       switch (direction[0]) {
+       case 'r':
+               strlcpy(msgbuf, "\nreceive", sizeof(msgbuf));
+               break;
+       case 's':
+               strlcpy(msgbuf, "\nsend", sizeof(msgbuf));
+               break;
+       default:
+               errx(1, "abortxfer: unknown direction `%s'", direction);
+       }
+       len = strlcat(msgbuf, " aborted. Waiting for remote to finish abort.\n",
+           sizeof(msgbuf));
+       write(fileno(ttyout), msgbuf, len);
+       siglongjmp(xferabort, 1);
+}
+
+/*
+ * Read data from infd & write to outfd, using buf/bufsize as the temporary
+ * buffer, dealing with short writes.
+ * If rate_limit != 0, rate-limit the transfer.
+ * If hash_interval != 0, fputc('c', ttyout) every hash_interval bytes.
+ * Updates global variables: bytes.
+ * Returns 0 if ok, 1 if there was a read error, 2 if there was a write error.
+ * In the case of error, errno contains the appropriate error code.
+ */
+static int
+copy_bytes(int infd, int outfd, char *buf, size_t bufsize,
+       int rate_limit, int hash_interval)
+{
+       volatile off_t  hashc;
+       ssize_t         inc, outc;
+       char            *bufp;
+       struct timeval  tvthen, tvnow, tvdiff;
+       off_t           bufrem, bufchunk;
+       int             serr;
+
+       hashc = hash_interval;
+       if (rate_limit)
+               bufchunk = rate_limit;
+       else
+               bufchunk = bufsize;
+
+       while (1) {
+               if (rate_limit) {
+                       (void)gettimeofday(&tvthen, NULL);
+               }
+               errno = 0;
+               inc = outc = 0;
+                                       /* copy bufchunk at a time */
+               bufrem = bufchunk;
+               while (bufrem > 0) {
+                       inc = read(infd, buf, MIN((off_t)bufsize, bufrem));
+                       if (inc <= 0)
+                               goto copy_done;
+                       bytes += inc;
+                       bufrem -= inc;
+                       bufp = buf;
+                       while (inc > 0) {
+                               outc = write(outfd, bufp, inc);
+                               if (outc < 0)
+                                       goto copy_done;
+                               inc -= outc;
+                               bufp += outc;
+                       }
+                       if (hash_interval) {
+                               while (bytes >= hashc) {
+                                       (void)putc('#', ttyout);
+                                       hashc += hash_interval;
+                               }
+                               (void)fflush(ttyout);
+                       }
+               }
+               if (rate_limit) {       /* rate limited; wait if necessary */
+                       while (1) {
+                               (void)gettimeofday(&tvnow, NULL);
+                               timersub(&tvnow, &tvthen, &tvdiff);
+                               if (tvdiff.tv_sec > 0)
+                                       break;
+                               usleep(1000000 - tvdiff.tv_usec);
+                       }
+               }
+       }
+
+ copy_done:
+       serr = errno;
+       if (hash_interval && bytes > 0) {
+               if (bytes < hash_interval)
+                       (void)putc('#', ttyout);
+               (void)putc('\n', ttyout);
+               (void)fflush(ttyout);
+       }
+       errno = serr;
+       if (inc == -1)
+               return 1;
+       if (outc == -1)
+               return 2;
+
+       return 0;
+}
+
+void
+sendrequest(const char *cmd, const char *local, const char *remote,
+           int printnames)
+{
+       struct stat st;
+       int c;
+       FILE *volatile fin;
+       FILE *volatile dout;
+       int (*volatile closefunc)(FILE *);
+       sigfunc volatile oldintr;
+       sigfunc volatile oldintp;
+       off_t volatile hashbytes;
+       int hash_interval;
+       const char *lmode;
+       static size_t bufsize;
+       static char *buf;
+       int oprogress;
+
+       hashbytes = mark;
+       direction = "sent";
+       dout = NULL;
+       bytes = 0;
+       filesize = -1;
+       oprogress = progress;
+       if (verbose && printnames) {
+               if (*local != '-')
+                       fprintf(ttyout, "local: %s ", local);
+               if (remote)
+                       fprintf(ttyout, "remote: %s\n", remote);
+       }
+       if (proxy) {
+               proxtrans(cmd, local, remote);
+               return;
+       }
+       if (curtype != type)
+               changetype(type, 0);
+       closefunc = NULL;
+       oldintr = NULL;
+       oldintp = NULL;
+       lmode = "w";
+       if (sigsetjmp(xferabort, 1)) {
+               while (cpend)
+                       (void)getreply(0);
+               code = -1;
+               goto cleanupsend;
+       }
+       (void)xsignal(SIGQUIT, psummary);
+       oldintr = xsignal(SIGINT, abortxfer);
+       if (strcmp(local, "-") == 0) {
+               fin = stdin;
+               progress = 0;
+       } else if (*local == '|') {
+               oldintp = xsignal(SIGPIPE, SIG_IGN);
+               fin = popen(local + 1, "r");
+               if (fin == NULL) {
+                       warn("Can't execute `%s'", local + 1);
+                       code = -1;
+                       goto cleanupsend;
+               }
+               progress = 0;
+               closefunc = pclose;
+       } else {
+               fin = fopen(local, "r");
+               if (fin == NULL) {
+                       warn("Can't open `%s'", local);
+                       code = -1;
+                       goto cleanupsend;
+               }
+               closefunc = fclose;
+               if (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode)) {
+                       fprintf(ttyout, "%s: not a plain file.\n", local);
+                       code = -1;
+                       goto cleanupsend;
+               }
+               filesize = st.st_size;
+       }
+       if (initconn()) {
+               code = -1;
+               goto cleanupsend;
+       }
+       if (sigsetjmp(xferabort, 1))
+               goto abort;
+
+       if (restart_point &&
+           (strcmp(cmd, "STOR") == 0 || strcmp(cmd, "APPE") == 0)) {
+               int rc;
+
+               rc = -1;
+               switch (curtype) {
+               case TYPE_A:
+                       rc = fseeko(fin, restart_point, SEEK_SET);
+                       break;
+               case TYPE_I:
+               case TYPE_L:
+                       rc = lseek(fileno(fin), restart_point, SEEK_SET);
+                       break;
+               }
+               if (rc < 0) {
+                       warn("Can't seek to restart `%s'", local);
+                       goto cleanupsend;
+               }
+               if (command("REST " LLF, (LLT)restart_point) != CONTINUE)
+                       goto cleanupsend;
+               lmode = "r+";
+       }
+       if (remote) {
+               if (command("%s %s", cmd, remote) != PRELIM)
+                       goto cleanupsend;
+       } else {
+               if (command("%s", cmd) != PRELIM)
+                       goto cleanupsend;
+       }
+       dirchange = 1;
+       dout = dataconn(lmode);
+       if (dout == NULL)
+               goto abort;
+
+       assert(sndbuf_size > 0);
+       if ((size_t)sndbuf_size > bufsize) {
+               if (buf)
+                       (void)free(buf);
+               bufsize = sndbuf_size;
+               buf = ftp_malloc(bufsize);
+       }
+
+       progressmeter(-1);
+       oldintp = xsignal(SIGPIPE, SIG_IGN);
+       hash_interval = (hash && (!progress || filesize < 0)) ? mark : 0;
+
+       switch (curtype) {
+
+       case TYPE_I:
+       case TYPE_L:
+               c = copy_bytes(fileno(fin), fileno(dout), buf, bufsize,
+                              rate_put, hash_interval);
+               if (c == 1) {
+                       warn("Reading `%s'", local);
+               } else if (c == 2) {
+                       if (errno != EPIPE)
+                               warn("Writing to network");
+                       bytes = -1;
+               }
+               break;
+
+       case TYPE_A:
+               while ((c = getc(fin)) != EOF) {
+                       if (c == '\n') {
+                               while (hash_interval && bytes >= hashbytes) {
+                                       (void)putc('#', ttyout);
+                                       (void)fflush(ttyout);
+                                       hashbytes += mark;
+                               }
+                               if (ferror(dout))
+                                       break;
+                               (void)putc('\r', dout);
+                               bytes++;
+                       }
+                       (void)putc(c, dout);
+                       bytes++;
+#if 0  /* this violates RFC 959 */
+                       if (c == '\r') {
+                               (void)putc('\0', dout);
+                               bytes++;
+                       }
+#endif
+               }
+               if (hash_interval) {
+                       if (bytes < hashbytes)
+                               (void)putc('#', ttyout);
+                       (void)putc('\n', ttyout);
+               }
+               if (ferror(fin))
+                       warn("Reading `%s'", local);
+               if (ferror(dout)) {
+                       if (errno != EPIPE)
+                               warn("Writing to network");
+                       bytes = -1;
+               }
+               break;
+       }
+
+       progressmeter(1);
+       if (closefunc != NULL) {
+               (*closefunc)(fin);
+               fin = NULL;
+       }
+       (void)fclose(dout);
+       dout = NULL;
+       (void)getreply(0);
+       if (bytes > 0)
+               ptransfer(0);
+       goto cleanupsend;
+
+ abort:
+       (void)xsignal(SIGINT, oldintr);
+       oldintr = NULL;
+       if (!cpend) {
+               code = -1;
+               goto cleanupsend;
+       }
+       if (data >= 0) {
+               (void)close(data);
+               data = -1;
+       }
+       if (dout) {
+               (void)fclose(dout);
+               dout = NULL;
+       }
+       (void)getreply(0);
+       code = -1;
+       if (bytes > 0)
+               ptransfer(0);
+
+ cleanupsend:
+       if (oldintr)
+               (void)xsignal(SIGINT, oldintr);
+       if (oldintp)
+               (void)xsignal(SIGPIPE, oldintp);
+       if (data >= 0) {
+               (void)close(data);
+               data = -1;
+       }
+       if (closefunc != NULL && fin != NULL)
+               (*closefunc)(fin);
+       if (dout)
+               (void)fclose(dout);
+       progress = oprogress;
+       restart_point = 0;
+       bytes = 0;
+}
+
+void
+recvrequest(const char *cmd, const char *volatile local, const char *remote,
+           const char *lmode, int printnames, int ignorespecial)
+{
+       FILE *volatile fout;
+       FILE *volatile din;
+       int (*volatile closefunc)(FILE *);
+       sigfunc volatile oldintr;
+       sigfunc volatile oldintp;
+       int c, d;
+       int volatile is_retr;
+       int volatile tcrflag;
+       int volatile bare_lfs;
+       static size_t bufsize;
+       static char *buf;
+       off_t volatile hashbytes;
+       int hash_interval;
+       struct stat st;
+       time_t mtime;
+       struct timeval tval[2];
+       int oprogress;
+       int opreserve;
+
+#ifdef __minix
+       struct utimbuf utb;
+#endif
+
+       fout = NULL;
+       din = NULL;
+       hashbytes = mark;
+       direction = "received";
+       bytes = 0;
+       bare_lfs = 0;
+       filesize = -1;
+       oprogress = progress;
+       opreserve = preserve;
+       is_retr = (strcmp(cmd, "RETR") == 0);
+       if (is_retr && verbose && printnames) {
+               if (ignorespecial || *local != '-')
+                       fprintf(ttyout, "local: %s ", local);
+               if (remote)
+                       fprintf(ttyout, "remote: %s\n", remote);
+       }
+       if (proxy && is_retr) {
+               proxtrans(cmd, local, remote);
+               return;
+       }
+       closefunc = NULL;
+       oldintr = NULL;
+       oldintp = NULL;
+       tcrflag = !crflag && is_retr;
+       if (sigsetjmp(xferabort, 1)) {
+               while (cpend)
+                       (void)getreply(0);
+               code = -1;
+               goto cleanuprecv;
+       }
+       (void)xsignal(SIGQUIT, psummary);
+       oldintr = xsignal(SIGINT, abortxfer);
+       if (ignorespecial || (strcmp(local, "-") && *local != '|')) {
+               if (access(local, W_OK) < 0) {
+                       char *dir = strrchr(local, '/');
+
+                       if (errno != ENOENT && errno != EACCES) {
+                               warn("Can't access `%s'", local);
+                               code = -1;
+                               goto cleanuprecv;
+                       }
+                       if (dir != NULL)
+                               *dir = 0;
+                       d = access(dir == local ? "/" :
+                           dir ? local : ".", W_OK);
+                       if (dir != NULL)
+                               *dir = '/';
+                       if (d < 0) {
+                               warn("Can't access `%s'", local);
+                               code = -1;
+                               goto cleanuprecv;
+                       }
+                       if (!runique && errno == EACCES &&
+                           chmod(local, (S_IRUSR|S_IWUSR)) < 0) {
+                               warn("Can't chmod `%s'", local);
+                               code = -1;
+                               goto cleanuprecv;
+                       }
+                       if (runique && errno == EACCES &&
+                          (local = gunique(local)) == NULL) {
+                               code = -1;
+                               goto cleanuprecv;
+                       }
+               }
+               else if (runique && (local = gunique(local)) == NULL) {
+                       code = -1;
+                       goto cleanuprecv;
+               }
+       }
+       if (!is_retr) {
+               if (curtype != TYPE_A)
+                       changetype(TYPE_A, 0);
+       } else {
+               if (curtype != type)
+                       changetype(type, 0);
+               filesize = remotesize(remote, 0);
+               if (code == 421 || code == -1)
+                       goto cleanuprecv;
+       }
+       if (initconn()) {
+               code = -1;
+               goto cleanuprecv;
+       }
+       if (sigsetjmp(xferabort, 1))
+               goto abort;
+       if (is_retr && restart_point &&
+           command("REST " LLF, (LLT) restart_point) != CONTINUE)
+               goto cleanuprecv;
+       if (! EMPTYSTRING(remote)) {
+               if (command("%s %s", cmd, remote) != PRELIM)
+                       goto cleanuprecv;
+       } else {
+               if (command("%s", cmd) != PRELIM)
+                       goto cleanuprecv;
+       }
+       din = dataconn("r");
+       if (din == NULL)
+               goto abort;
+       if (!ignorespecial && strcmp(local, "-") == 0) {
+               fout = stdout;
+               progress = 0;
+               preserve = 0;
+       } else if (!ignorespecial && *local == '|') {
+               oldintp = xsignal(SIGPIPE, SIG_IGN);
+               fout = popen(local + 1, "w");
+               if (fout == NULL) {
+                       warn("Can't execute `%s'", local+1);
+                       goto abort;
+               }
+               progress = 0;
+               preserve = 0;
+               closefunc = pclose;
+       } else {
+               fout = fopen(local, lmode);
+               if (fout == NULL) {
+                       warn("Can't open `%s'", local);
+                       goto abort;
+               }
+               closefunc = fclose;
+       }
+
+       if (fstat(fileno(fout), &st) != -1 && !S_ISREG(st.st_mode)) {
+               progress = 0;
+               preserve = 0;
+       }
+       assert(rcvbuf_size > 0);
+       if ((size_t)rcvbuf_size > bufsize) {
+               if (buf)
+                       (void)free(buf);
+               bufsize = rcvbuf_size;
+               buf = ftp_malloc(bufsize);
+       }
+
+       progressmeter(-1);
+       hash_interval = (hash && (!progress || filesize < 0)) ? mark : 0;
+
+       switch (curtype) {
+
+       case TYPE_I:
+       case TYPE_L:
+               if (is_retr && restart_point &&
+                   lseek(fileno(fout), restart_point, SEEK_SET) < 0) {
+                       warn("Can't seek to restart `%s'", local);
+                       goto cleanuprecv;
+               }
+               c = copy_bytes(fileno(din), fileno(fout), buf, bufsize,
+                              rate_get, hash_interval);
+               if (c == 1) {
+                       if (errno != EPIPE)
+                               warn("Reading from network");
+                       bytes = -1;
+               } else if (c == 2) {
+                       warn("Writing `%s'", local);
+               }
+               break;
+
+       case TYPE_A:
+               if (is_retr && restart_point) {
+                       int ch;
+                       off_t i;
+
+                       if (fseeko(fout, (off_t)0, SEEK_SET) < 0)
+                               goto done;
+                       for (i = 0; i++ < restart_point;) {
+                               if ((ch = getc(fout)) == EOF)
+                                       goto done;
+                               if (ch == '\n')
+                                       i++;
+                       }
+                       if (fseeko(fout, (off_t)0, SEEK_CUR) < 0) {
+ done:
+                               warn("Can't seek to restart `%s'", local);
+                               goto cleanuprecv;
+                       }
+               }
+               while ((c = getc(din)) != EOF) {
+                       if (c == '\n')
+                               bare_lfs++;
+                       while (c == '\r') {
+                               while (hash_interval && bytes >= hashbytes) {
+                                       (void)putc('#', ttyout);
+                                       (void)fflush(ttyout);
+                                       hashbytes += mark;
+                               }
+                               bytes++;
+                               if ((c = getc(din)) != '\n' || tcrflag) {
+                                       if (ferror(fout))
+                                               goto break2;
+                                       (void)putc('\r', fout);
+                                       if (c == '\0') {
+                                               bytes++;
+                                               goto contin2;
+                                       }
+                                       if (c == EOF)
+                                               goto contin2;
+                               }
+                       }
+                       (void)putc(c, fout);
+                       bytes++;
+       contin2:        ;
+               }
+ break2:
+               if (hash_interval) {
+                       if (bytes < hashbytes)
+                               (void)putc('#', ttyout);
+                       (void)putc('\n', ttyout);
+               }
+               if (ferror(din)) {
+                       if (errno != EPIPE)
+                               warn("Reading from network");
+                       bytes = -1;
+               }
+               if (ferror(fout))
+                       warn("Writing `%s'", local);
+               break;
+       }
+
+       progressmeter(1);
+       if (closefunc != NULL) {
+               (*closefunc)(fout);
+               fout = NULL;
+       }
+       (void)fclose(din);
+       din = NULL;
+       (void)getreply(0);
+       if (bare_lfs) {
+               fprintf(ttyout,
+                   "WARNING! %d bare linefeeds received in ASCII mode.\n",
+                   bare_lfs);
+               fputs("File may not have transferred correctly.\n", ttyout);
+       }
+       if (bytes >= 0 && is_retr) {
+               if (bytes > 0)
+                       ptransfer(0);
+               if (preserve && (closefunc == fclose)) {
+                       mtime = remotemodtime(remote, 0);
+                       if (mtime != -1) {
+                               (void)gettimeofday(&tval[0], NULL);
+#ifdef __minix
+                               utb.actime = tval[0].tv_sec;
+                               utb.modtime = mtime;
+                               if (utime(local, &utb) == -1) {
+                                       fprintf(ttyout,
+                               "Can't change modification time on %s to %s",
+                                           local,
+                                           rfc2822time(localtime(&mtime)));
+                               }
+#else
+                               tval[1].tv_sec = mtime;
+                               tval[1].tv_usec = 0;
+                               if (utimes(local, tval) == -1) {
+                                       fprintf(ttyout,
+                               "Can't change modification time on %s to %s",
+                                           local,
+                                           rfc2822time(localtime(&mtime)));
+                               }
+#endif
+                       }
+               }
+       }
+       goto cleanuprecv;
+
+ abort:
+                       /*
+                        * abort using RFC 959 recommended IP,SYNC sequence
+                        */
+       if (! sigsetjmp(xferabort, 1)) {
+                       /* this is the first call */
+               (void)xsignal(SIGINT, abort_squared);
+               if (!cpend) {
+                       code = -1;
+                       goto cleanuprecv;
+               }
+               abort_remote(din);
+       }
+       code = -1;
+       if (bytes > 0)
+               ptransfer(0);
+
+ cleanuprecv:
+       if (oldintr)
+               (void)xsignal(SIGINT, oldintr);
+       if (oldintp)
+               (void)xsignal(SIGPIPE, oldintp);
+       if (data >= 0) {
+               (void)close(data);
+               data = -1;
+       }
+       if (closefunc != NULL && fout != NULL)
+               (*closefunc)(fout);
+       if (din)
+               (void)fclose(din);
+       progress = oprogress;
+       preserve = opreserve;
+       bytes = 0;
+}
+
+/*
+ * Need to start a listen on the data channel before we send the command,
+ * otherwise the server's connect may fail.
+ */
+int
+initconn(void)
+{
+       char *p, *a;
+       int result, tmpno = 0;
+       int on = 1;
+       int error;
+       unsigned int addr[16], port[2];
+       unsigned int af, hal, pal;
+       socklen_t len;
+       const char *pasvcmd = NULL;
+       int overbose;
+
+#ifdef INET6
+#ifndef NO_DEBUG
+       if (myctladdr.su_family == AF_INET6 && ftp_debug &&
+           (IN6_IS_ADDR_LINKLOCAL(&myctladdr.si_su.su_sin6.sin6_addr) ||
+            IN6_IS_ADDR_SITELOCAL(&myctladdr.si_su.su_sin6.sin6_addr))) {
+               warnx("Use of scoped addresses can be troublesome");
+       }
+#endif
+#endif
+
+ reinit:
+       if (passivemode) {
+               data_addr = myctladdr;
+               data = socket(data_addr.su_family, SOCK_STREAM, 0);
+               if (data < 0) {
+                       warn("Can't create socket for data connection");
+                       return (1);
+               }
+               if ((options & SO_DEBUG) &&
+                   setsockopt(data, SOL_SOCKET, SO_DEBUG,
+                               (void *)&on, sizeof(on)) == -1) {
+                       DWARN("setsockopt %s (ignored)", "SO_DEBUG");
+               }
+               result = COMPLETE + 1;
+               switch (data_addr.su_family) {
+               case AF_INET:
+                       if (epsv4 && !epsv4bad) {
+                               pasvcmd = "EPSV";
+                               overbose = verbose;
+                               if (ftp_debug == 0)
+                                       verbose = -1;
+                               result = command("EPSV");
+                               verbose = overbose;
+                               if (verbose > 0 &&
+                                   (result == COMPLETE || !connected))
+                                       fprintf(ttyout, "%s\n", reply_string);
+                               if (!connected)
+                                       return (1);
+                               /*
+                                * this code is to be friendly with broken
+                                * BSDI ftpd
+                                */
+                               if (code / 10 == 22 && code != 229) {
+                                       fputs(
+"wrong server: return code must be 229\n",
+                                               ttyout);
+                                       result = COMPLETE + 1;
+                               }
+                               if (result != COMPLETE) {
+                                       epsv4bad = 1;
+                                       DPRINTF("disabling epsv4 for this "
+                                           "connection\n");
+                               }
+                       }
+                       if (result != COMPLETE) {
+                               pasvcmd = "PASV";
+                               result = command("PASV");
+                               if (!connected)
+                                       return (1);
+                       }
+                       break;
+#ifdef INET6
+               case AF_INET6:
+                       if (epsv6 && !epsv6bad) {
+                               pasvcmd = "EPSV";
+                               overbose = verbose;
+                               if (ftp_debug == 0)
+                                       verbose = -1;
+                               result = command("EPSV");
+                               verbose = overbose;
+                               if (verbose > 0 &&
+                                   (result == COMPLETE || !connected))
+                                       fprintf(ttyout, "%s\n", reply_string);
+                               if (!connected)
+                                       return (1);
+                               /*
+                                * this code is to be friendly with
+                                * broken BSDI ftpd
+                                */
+                               if (code / 10 == 22 && code != 229) {
+                                       fputs(
+                                               "wrong server: return code must be 229\n",
+                                               ttyout);
+                                       result = COMPLETE + 1;
+                               }
+                               if (result != COMPLETE) {
+                                       epsv6bad = 1;
+                                       DPRINTF("disabling epsv6 for this "
+                                           "connection\n");
+                               }
+                       }
+                       if (result != COMPLETE) {
+                               pasvcmd = "LPSV";
+                               result = command("LPSV");
+                       }
+                       if (!connected)
+                               return (1);
+                       break;
+#endif
+               default:
+                       result = COMPLETE + 1;
+                       break;
+               }
+               if (result != COMPLETE) {
+                       if (activefallback) {
+                               (void)close(data);
+                               data = -1;
+                               passivemode = 0;
+#if 0
+                               activefallback = 0;
+#endif
+                               goto reinit;
+                       }
+                       fputs("Passive mode refused.\n", ttyout);
+                       goto bad;
+               }
+
+#define        pack2(var, off) \
+       (((var[(off) + 0] & 0xff) << 8) | ((var[(off) + 1] & 0xff) << 0))
+#define        pack4(var, off) \
+       (((var[(off) + 0] & 0xff) << 24) | ((var[(off) + 1] & 0xff) << 16) | \
+        ((var[(off) + 2] & 0xff) << 8) | ((var[(off) + 3] & 0xff) << 0))
+#define        UC(b)   (((int)b)&0xff)
+
+               /*
+                * What we've got at this point is a string of comma separated
+                * one-byte unsigned integer values, separated by commas.
+                */
+               if (strcmp(pasvcmd, "PASV") == 0) {
+                       if (data_addr.su_family != AF_INET) {
+                               fputs(
+    "Passive mode AF mismatch. Shouldn't happen!\n", ttyout);
+                               error = 1;
+                               goto bad;
+                       }
+                       if (code / 10 == 22 && code != 227) {
+                               fputs("wrong server: return code must be 227\n",
+                                       ttyout);
+                               error = 1;
+                               goto bad;
+                       }
+                       error = sscanf(pasv, "%u,%u,%u,%u,%u,%u",
+                                       &addr[0], &addr[1], &addr[2], &addr[3],
+                                       &port[0], &port[1]);
+                       if (error != 6) {
+                               fputs(
+"Passive mode address scan failure. Shouldn't happen!\n", ttyout);
+                               error = 1;
+                               goto bad;
+                       }
+                       error = 0;
+                       memset(&data_addr, 0, sizeof(data_addr));
+                       data_addr.su_family = AF_INET;
+                       data_addr.su_len = sizeof(struct sockaddr_in);
+                       data_addr.si_su.su_sin.sin_addr.s_addr =
+                           htonl(pack4(addr, 0));
+                       data_addr.su_port = htons(pack2(port, 0));
+               } else if (strcmp(pasvcmd, "LPSV") == 0) {
+                       if (code / 10 == 22 && code != 228) {
+                               fputs("wrong server: return code must be 228\n",
+                                       ttyout);
+                               error = 1;
+                               goto bad;
+                       }
+                       switch (data_addr.su_family) {
+                       case AF_INET:
+                               error = sscanf(pasv,
+"%u,%u,%u,%u,%u,%u,%u,%u,%u",
+                                       &af, &hal,
+                                       &addr[0], &addr[1], &addr[2], &addr[3],
+                                       &pal, &port[0], &port[1]);
+                               if (error != 9) {
+                                       fputs(
+"Passive mode address scan failure. Shouldn't happen!\n", ttyout);
+                                       error = 1;
+                                       goto bad;
+                               }
+                               if (af != 4 || hal != 4 || pal != 2) {
+                                       fputs(
+"Passive mode AF mismatch. Shouldn't happen!\n", ttyout);
+                                       error = 1;
+                                       goto bad;
+                               }
+
+                               error = 0;
+                               memset(&data_addr, 0, sizeof(data_addr));
+                               data_addr.su_family = AF_INET;
+                               data_addr.su_len = sizeof(struct sockaddr_in);
+                               data_addr.si_su.su_sin.sin_addr.s_addr =
+                                   htonl(pack4(addr, 0));
+                               data_addr.su_port = htons(pack2(port, 0));
+                               break;
+#ifdef INET6
+                       case AF_INET6:
+                               error = sscanf(pasv,
+"%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u",
+                                       &af, &hal,
+                                       &addr[0], &addr[1], &addr[2], &addr[3],
+                                       &addr[4], &addr[5], &addr[6], &addr[7],
+                                       &addr[8], &addr[9], &addr[10],
+                                       &addr[11], &addr[12], &addr[13],
+                                       &addr[14], &addr[15],
+                                       &pal, &port[0], &port[1]);
+                               if (error != 21) {
+                                       fputs(
+"Passive mode address scan failure. Shouldn't happen!\n", ttyout);
+                                       error = 1;
+                                       goto bad;
+                               }
+                               if (af != 6 || hal != 16 || pal != 2) {
+                                       fputs(
+"Passive mode AF mismatch. Shouldn't happen!\n", ttyout);
+                                       error = 1;
+                                       goto bad;
+                               }
+
+                               error = 0;
+                               memset(&data_addr, 0, sizeof(data_addr));
+                               data_addr.su_family = AF_INET6;
+                               data_addr.su_len = sizeof(struct sockaddr_in6);
+                           {
+                               size_t i;
+                               for (i = 0; i < sizeof(struct in6_addr); i++) {
+                                       data_addr.si_su.su_sin6.sin6_addr.s6_addr[i] =
+                                           UC(addr[i]);
+                               }
+                           }
+                               data_addr.su_port = htons(pack2(port, 0));
+                               break;
+#endif
+                       default:
+                               error = 1;
+                       }
+               } else if (strcmp(pasvcmd, "EPSV") == 0) {
+                       char delim[4];
+
+                       port[0] = 0;
+                       if (code / 10 == 22 && code != 229) {
+                               fputs("wrong server: return code must be 229\n",
+                                       ttyout);
+                               error = 1;
+                               goto bad;
+                       }
+                       if (sscanf(pasv, "%c%c%c%d%c", &delim[0],
+                                       &delim[1], &delim[2], &port[1],
+                                       &delim[3]) != 5) {
+                               fputs("parse error!\n", ttyout);
+                               error = 1;
+                               goto bad;
+                       }
+                       if (delim[0] != delim[1] || delim[0] != delim[2]
+                        || delim[0] != delim[3]) {
+                               fputs("parse error!\n", ttyout);
+                               error = 1;
+                               goto bad;
+                       }
+                       data_addr = hisctladdr;
+                       data_addr.su_port = htons(port[1]);
+               } else
+                       goto bad;
+
+               if (ftp_connect(data, (struct sockaddr *)&data_addr.si_su,
+                   data_addr.su_len, 1) < 0) {
+                       if (activefallback) {
+                               (void)close(data);
+                               data = -1;
+                               passivemode = 0;
+#if 0
+                               activefallback = 0;
+#endif
+                               goto reinit;
+                       }
+                       goto bad;
+               }
+#ifdef IPTOS_THROUGHPUT
+               if (data_addr.su_family == AF_INET) {
+                       on = IPTOS_THROUGHPUT;
+                       if (setsockopt(data, IPPROTO_IP, IP_TOS,
+                                       (void *)&on, sizeof(on)) == -1) {
+                               DWARN("setsockopt %s (ignored)",
+                                   "IPTOS_THROUGHPUT");
+                       }
+               }
+#endif
+               return (0);
+       }
+
+ noport:
+       data_addr = myctladdr;
+       if (sendport)
+               data_addr.su_port = 0;  /* let system pick one */
+       if (data != -1)
+               (void)close(data);
+       data = socket(data_addr.su_family, SOCK_STREAM, 0);
+       if (data < 0) {
+               warn("Can't create socket for data connection");
+               if (tmpno)
+                       sendport = 1;
+               return (1);
+       }
+       if (!sendport)
+               if (setsockopt(data, SOL_SOCKET, SO_REUSEADDR,
+                               (void *)&on, sizeof(on)) == -1) {
+                       warn("Can't set SO_REUSEADDR on data connection");
+                       goto bad;
+               }
+       if (bind(data, (struct sockaddr *)&data_addr.si_su,
+           data_addr.su_len) < 0) {
+               warn("Can't bind for data connection");
+               goto bad;
+       }
+       if ((options & SO_DEBUG) &&
+           setsockopt(data, SOL_SOCKET, SO_DEBUG,
+                       (void *)&on, sizeof(on)) == -1) {
+               DWARN("setsockopt %s (ignored)", "SO_DEBUG");
+       }
+       len = sizeof(data_addr.si_su);
+       memset((char *)&data_addr, 0, sizeof (data_addr));
+       if (getsockname(data, (struct sockaddr *)&data_addr.si_su, &len) == -1) {
+               warn("Can't determine my address of data connection");
+               goto bad;
+       }
+       data_addr.su_len = len;
+       if (ftp_listen(data, 1) < 0)
+               warn("Can't listen to data connection");
+
+       if (sendport) {
+               char hname[NI_MAXHOST], sname[NI_MAXSERV];
+               struct sockinet tmp;
+
+               switch (data_addr.su_family) {
+               case AF_INET:
+                       if (!epsv4 || epsv4bad) {
+                               result = COMPLETE + 1;
+                               break;
+                       }
+                       /* FALLTHROUGH */
+#ifdef INET6
+               case AF_INET6:
+                       if (!epsv6 || epsv6bad) {
+                               result = COMPLETE + 1;
+                               break;
+                       }
+#endif
+                       af = (data_addr.su_family == AF_INET) ? 1 : 2;
+                       tmp = data_addr;
+#ifdef INET6
+                       if (tmp.su_family == AF_INET6)
+                               tmp.si_su.su_sin6.sin6_scope_id = 0;
+#endif
+                       if (getnameinfo((struct sockaddr *)&tmp.si_su,
+                           tmp.su_len, hname, sizeof(hname), sname,
+                           sizeof(sname), NI_NUMERICHOST | NI_NUMERICSERV)) {
+                               result = ERROR;
+                       } else {
+                               overbose = verbose;
+                               if (ftp_debug == 0)
+                                       verbose = -1;
+                               result = command("EPRT |%u|%s|%s|", af, hname,
+                                   sname);
+                               verbose = overbose;
+                               if (verbose > 0 &&
+                                   (result == COMPLETE || !connected))
+                                       fprintf(ttyout, "%s\n", reply_string);
+                               if (!connected)
+                                       return (1);
+                               if (result != COMPLETE) {
+                                       epsv4bad = 1;
+                                       DPRINTF("disabling epsv4 for this "
+                                           "connection\n");
+                               }
+                       }
+                       break;
+               default:
+                       result = COMPLETE + 1;
+                       break;
+               }
+               if (result == COMPLETE)
+                       goto skip_port;
+
+               switch (data_addr.su_family) {
+               case AF_INET:
+                       a = (char *)&data_addr.si_su.su_sin.sin_addr;
+                       p = (char *)&data_addr.su_port;
+                       result = command("PORT %d,%d,%d,%d,%d,%d",
+                                UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
+                                UC(p[0]), UC(p[1]));
+                       break;
+#ifdef INET6
+               case AF_INET6: {
+                       uint8_t ua[sizeof(data_addr.si_su.su_sin6.sin6_addr)];
+                       uint8_t up[sizeof(data_addr.su_port)];
+
+                       memcpy(ua, &data_addr.si_su.su_sin6.sin6_addr,
+                           sizeof(ua));
+                       memcpy(up, &data_addr.su_port, sizeof(up));
+                       
+                       result = command(
+       "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+                                6, 16,
+                                 ua[0],  ua[1],  ua[2],  ua[3],
+                                 ua[4],  ua[5],  ua[6],  ua[7],
+                                 ua[8],  ua[9], ua[10], ua[11],
+                                ua[12], ua[13], ua[14], ua[15],
+                                2,
+                                up[0], up[1]);
+                       break;
+               }
+#endif
+               default:
+                       result = COMPLETE + 1; /* xxx */
+               }
+               if (!connected)
+                       return (1);
+       skip_port:
+
+               if (result == ERROR && sendport == -1) {
+                       sendport = 0;
+                       tmpno = 1;
+                       goto noport;
+               }
+               return (result != COMPLETE);
+       }
+       if (tmpno)
+               sendport = 1;
+#ifdef IPTOS_THROUGHPUT
+       if (data_addr.su_family == AF_INET) {
+               on = IPTOS_THROUGHPUT;
+               if (setsockopt(data, IPPROTO_IP, IP_TOS,
+                               (void *)&on, sizeof(on)) == -1) {
+                       DWARN("setsockopt %s (ignored)", "IPTOS_THROUGHPUT");
+               }
+       }
+#endif
+       return (0);
+ bad:
+       (void)close(data);
+       data = -1;
+       if (tmpno)
+               sendport = 1;
+       return (1);
+}
+
+FILE *
+dataconn(const char *lmode)
+{
+       struct sockinet from;
+       int             s, flags, rv, timeout;
+       struct timeval  endtime, now, td;
+       struct pollfd   pfd[1];
+       socklen_t       fromlen;
+
+       if (passivemode)        /* passive data connection */
+               return (fdopen(data, lmode));
+
+                               /* active mode data connection */
+
+       if ((flags = fcntl(data, F_GETFL, 0)) == -1)
+               goto dataconn_failed;           /* get current socket flags  */
+       if (fcntl(data, F_SETFL, flags | O_NONBLOCK) == -1)
+               goto dataconn_failed;           /* set non-blocking connect */
+
+               /* NOTE: we now must restore socket flags on successful exit */
+
+                               /* limit time waiting on listening socket */
+       pfd[0].fd = data;
+       pfd[0].events = POLLIN;
+       (void)gettimeofday(&endtime, NULL);     /* determine end time */
+       endtime.tv_sec += (quit_time > 0) ? quit_time: 60;
+                                               /* without -q, default to 60s */
+       do {
+               (void)gettimeofday(&now, NULL);
+               timersub(&endtime, &now, &td);
+               timeout = td.tv_sec * 1000 + td.tv_usec/1000;
+               if (timeout < 0)
+                       timeout = 0;
+               rv = ftp_poll(pfd, 1, timeout);
+       } while (rv == -1 && errno == EINTR);   /* loop until poll ! EINTR */
+       if (rv == -1) {
+               warn("Can't poll waiting before accept");
+               goto dataconn_failed;
+       }
+       if (rv == 0) {
+               warnx("Poll timeout waiting before accept");
+               goto dataconn_failed;
+       }
+
+                               /* (non-blocking) accept the connection */
+       fromlen = myctladdr.su_len;
+       do {
+               s = accept(data, (struct sockaddr *) &from.si_su, &fromlen);
+       } while (s == -1 && errno == EINTR);    /* loop until accept ! EINTR */
+       if (s == -1) {
+               warn("Can't accept data connection");
+               goto dataconn_failed;
+       }
+
+       (void)close(data);
+       data = s;
+       if (fcntl(data, F_SETFL, flags) == -1)  /* restore socket flags */
+               goto dataconn_failed;
+
+#ifdef IPTOS_THROUGHPUT
+       if (from.su_family == AF_INET) {
+               int tos = IPTOS_THROUGHPUT;
+               if (setsockopt(s, IPPROTO_IP, IP_TOS,
+                               (void *)&tos, sizeof(tos)) == -1) {
+                       DWARN("setsockopt %s (ignored)", "IPTOS_THROUGHPUT");
+               }
+       }
+#endif
+       return (fdopen(data, lmode));
+
+ dataconn_failed:
+       (void)close(data);
+       data = -1;
+       return (NULL);
+}
+
+void
+psabort(int notused)
+{
+       int oerrno = errno;
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       abrtflag++;
+       errno = oerrno;
+}
+
+void
+pswitch(int flag)
+{
+       sigfunc oldintr;
+       static struct comvars {
+               int connect;
+               char name[MAXHOSTNAMELEN];
+               struct sockinet mctl;
+               struct sockinet hctl;
+               FILE *in;
+               FILE *out;
+               int tpe;
+               int curtpe;
+               int cpnd;
+               int sunqe;
+               int runqe;
+               int mcse;
+               int ntflg;
+               char nti[17];
+               char nto[17];
+               int mapflg;
+               char mi[MAXPATHLEN];
+               char mo[MAXPATHLEN];
+       } proxstruct, tmpstruct;
+       struct comvars *ip, *op;
+
+       abrtflag = 0;
+       oldintr = xsignal(SIGINT, psabort);
+       if (flag) {
+               if (proxy)
+                       return;
+               ip = &tmpstruct;
+               op = &proxstruct;
+               proxy++;
+       } else {
+               if (!proxy)
+                       return;
+               ip = &proxstruct;
+               op = &tmpstruct;
+               proxy = 0;
+       }
+       ip->connect = connected;
+       connected = op->connect;
+       if (hostname)
+               (void)strlcpy(ip->name, hostname, sizeof(ip->name));
+       else
+               ip->name[0] = '\0';
+       hostname = op->name;
+       ip->hctl = hisctladdr;
+       hisctladdr = op->hctl;
+       ip->mctl = myctladdr;
+       myctladdr = op->mctl;
+       ip->in = cin;
+       cin = op->in;
+       ip->out = cout;
+       cout = op->out;
+       ip->tpe = type;
+       type = op->tpe;
+       ip->curtpe = curtype;
+       curtype = op->curtpe;
+       ip->cpnd = cpend;
+       cpend = op->cpnd;
+       ip->sunqe = sunique;
+       sunique = op->sunqe;
+       ip->runqe = runique;
+       runique = op->runqe;
+       ip->mcse = mcase;
+       mcase = op->mcse;
+       ip->ntflg = ntflag;
+       ntflag = op->ntflg;
+       (void)strlcpy(ip->nti, ntin, sizeof(ip->nti));
+       (void)strlcpy(ntin, op->nti, sizeof(ntin));
+       (void)strlcpy(ip->nto, ntout, sizeof(ip->nto));
+       (void)strlcpy(ntout, op->nto, sizeof(ntout));
+       ip->mapflg = mapflag;
+       mapflag = op->mapflg;
+       (void)strlcpy(ip->mi, mapin, sizeof(ip->mi));
+       (void)strlcpy(mapin, op->mi, sizeof(mapin));
+       (void)strlcpy(ip->mo, mapout, sizeof(ip->mo));
+       (void)strlcpy(mapout, op->mo, sizeof(mapout));
+       (void)xsignal(SIGINT, oldintr);
+       if (abrtflag) {
+               abrtflag = 0;
+               (*oldintr)(SIGINT);
+       }
+}
+
+__dead static void
+abortpt(int notused)
+{
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       ptabflg++;
+       mflag = 0;
+       abrtflag = 0;
+       siglongjmp(ptabort, 1);
+}
+
+void
+proxtrans(const char *cmd, const char *local, const char *remote)
+{
+       sigfunc volatile oldintr;
+       int prox_type, nfnd;
+       int volatile secndflag;
+       const char *volatile cmd2;
+
+       oldintr = NULL;
+       secndflag = 0;
+       if (strcmp(cmd, "RETR"))
+               cmd2 = "RETR";
+       else
+               cmd2 = runique ? "STOU" : "STOR";
+       if ((prox_type = type) == 0) {
+               if (unix_server && unix_proxy)
+                       prox_type = TYPE_I;
+               else
+                       prox_type = TYPE_A;
+       }
+       if (curtype != prox_type)
+               changetype(prox_type, 1);
+       if (command("PASV") != COMPLETE) {
+               fputs("proxy server does not support third party transfers.\n",
+                   ttyout);
+               return;
+       }
+       pswitch(0);
+       if (!connected) {
+               fputs("No primary connection.\n", ttyout);
+               pswitch(1);
+               code = -1;
+               return;
+       }
+       if (curtype != prox_type)
+               changetype(prox_type, 1);
+       if (command("PORT %s", pasv) != COMPLETE) {
+               pswitch(1);
+               return;
+       }
+       if (sigsetjmp(ptabort, 1))
+               goto abort;
+       oldintr = xsignal(SIGINT, abortpt);
+       if ((restart_point &&
+           (command("REST " LLF, (LLT) restart_point) != CONTINUE))
+           || (command("%s %s", cmd, remote) != PRELIM)) {
+               (void)xsignal(SIGINT, oldintr);
+               pswitch(1);
+               return;
+       }
+       sleep(2);
+       pswitch(1);
+       secndflag++;
+       if ((restart_point &&
+           (command("REST " LLF, (LLT) restart_point) != CONTINUE))
+           || (command("%s %s", cmd2, local) != PRELIM))
+               goto abort;
+       ptflag++;
+       (void)getreply(0);
+       pswitch(0);
+       (void)getreply(0);
+       (void)xsignal(SIGINT, oldintr);
+       pswitch(1);
+       ptflag = 0;
+       fprintf(ttyout, "local: %s remote: %s\n", local, remote);
+       return;
+ abort:
+       if (sigsetjmp(xferabort, 1)) {
+               (void)xsignal(SIGINT, oldintr);
+               return;
+       }
+       (void)xsignal(SIGINT, abort_squared);
+       ptflag = 0;
+       if (strcmp(cmd, "RETR") && !proxy)
+               pswitch(1);
+       else if (!strcmp(cmd, "RETR") && proxy)
+               pswitch(0);
+       if (!cpend && !secndflag) {  /* only here if cmd = "STOR" (proxy=1) */
+               if (command("%s %s", cmd2, local) != PRELIM) {
+                       pswitch(0);
+                       if (cpend)
+                               abort_remote(NULL);
+               }
+               pswitch(1);
+               if (ptabflg)
+                       code = -1;
+               (void)xsignal(SIGINT, oldintr);
+               return;
+       }
+       if (cpend)
+               abort_remote(NULL);
+       pswitch(!proxy);
+       if (!cpend && !secndflag) {  /* only if cmd = "RETR" (proxy=1) */
+               if (command("%s %s", cmd2, local) != PRELIM) {
+                       pswitch(0);
+                       if (cpend)
+                               abort_remote(NULL);
+                       pswitch(1);
+                       if (ptabflg)
+                               code = -1;
+                       (void)xsignal(SIGINT, oldintr);
+                       return;
+               }
+       }
+       if (cpend)
+               abort_remote(NULL);
+       pswitch(!proxy);
+       if (cpend) {
+               if ((nfnd = empty(cin, NULL, 10)) <= 0) {
+                       if (nfnd < 0)
+                               warn("Error aborting proxy command");
+                       if (ptabflg)
+                               code = -1;
+                       lostpeer(0);
+               }
+               (void)getreply(0);
+               (void)getreply(0);
+       }
+       if (proxy)
+               pswitch(0);
+       pswitch(1);
+       if (ptabflg)
+               code = -1;
+       (void)xsignal(SIGINT, oldintr);
+}
+
+void
+reset(int argc, char *argv[])
+{
+       int nfnd = 1;
+
+       if (argc == 0 && argv != NULL) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       while (nfnd > 0) {
+               if ((nfnd = empty(cin, NULL, 0)) < 0) {
+                       warn("Error resetting connection");
+                       code = -1;
+                       lostpeer(0);
+               } else if (nfnd)
+                       (void)getreply(0);
+       }
+}
+
+char *
+gunique(const char *local)
+{
+       static char new[MAXPATHLEN];
+       char *cp = strrchr(local, '/');
+       int d, count=0, len;
+       char ext = '1';
+
+       if (cp)
+               *cp = '\0';
+       d = access(cp == local ? "/" : cp ? local : ".", W_OK);
+       if (cp)
+               *cp = '/';
+       if (d < 0) {
+               warn("Can't access `%s'", local);
+               return (NULL);
+       }
+       len = strlcpy(new, local, sizeof(new));
+       cp = &new[len];
+       *cp++ = '.';
+       while (!d) {
+               if (++count == 100) {
+                       fputs("runique: can't find unique file name.\n",
+                           ttyout);
+                       return (NULL);
+               }
+               *cp++ = ext;
+               *cp = '\0';
+               if (ext == '9')
+                       ext = '0';
+               else
+                       ext++;
+               if ((d = access(new, F_OK)) < 0)
+                       break;
+               if (ext != '0')
+                       cp--;
+               else if (*(cp - 2) == '.')
+                       *(cp - 1) = '1';
+               else {
+                       *(cp - 2) = *(cp - 2) + 1;
+                       cp--;
+               }
+       }
+       return (new);
+}
+
+/*
+ * abort_squared --
+ *     aborts abort_remote(). lostpeer() is called because if the user is
+ *     too impatient to wait or there's another problem then ftp really
+ *     needs to get back to a known state.
+ */
+static void
+abort_squared(int dummy)
+{
+       char msgbuf[100];
+       size_t len;
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       len = strlcpy(msgbuf, "\nremote abort aborted; closing connection.\n",
+           sizeof(msgbuf));
+       write(fileno(ttyout), msgbuf, len);
+       lostpeer(0);
+       siglongjmp(xferabort, 1);
+}
+
+void
+abort_remote(FILE *din)
+{
+       char buf[BUFSIZ];
+       int nfnd;
+
+       if (cout == NULL) {
+               warnx("Lost control connection for abort");
+               if (ptabflg)
+                       code = -1;
+               lostpeer(0);
+               return;
+       }
+       /*
+        * send IAC in urgent mode instead of DM because 4.3BSD places oob mark
+        * after urgent byte rather than before as is protocol now
+        */
+       buf[0] = IAC;
+       buf[1] = IP;
+       buf[2] = IAC;
+       if (send(fileno(cout), buf, 3, MSG_OOB) != 3)
+               warn("Can't send abort message");
+       fprintf(cout, "%cABOR\r\n", DM);
+       (void)fflush(cout);
+       if ((nfnd = empty(cin, din, 10)) <= 0) {
+               if (nfnd < 0)
+                       warn("Can't send abort message");
+               if (ptabflg)
+                       code = -1;
+               lostpeer(0);
+       }
+       if (din && (nfnd & 2)) {
+               while (read(fileno(din), buf, BUFSIZ) > 0)
+                       continue;
+       }
+       if (getreply(0) == ERROR && code == 552) {
+               /* 552 needed for nic style abort */
+               (void)getreply(0);
+       }
+       (void)getreply(0);
+}
+
+/*
+ * Ensure that ai->ai_addr is NOT an IPv4 mapped address.
+ * IPv4 mapped address complicates too many things in FTP
+ * protocol handling, as FTP protocol is defined differently
+ * between IPv4 and IPv6.
+ *
+ * This may not be the best way to handle this situation,
+ * since the semantics of IPv4 mapped address is defined in
+ * the kernel.  There are configurations where we should use
+ * IPv4 mapped address as native IPv6 address, not as
+ * "an IPv6 address that embeds IPv4 address" (namely, SIIT).
+ *
+ * More complete solution would be to have an additional
+ * getsockopt to grab "real" peername/sockname.  "real"
+ * peername/sockname will be AF_INET if IPv4 mapped address
+ * is used to embed IPv4 address, and will be AF_INET6 if
+ * we use it as native.  What a mess!
+ */
+void
+ai_unmapped(struct addrinfo *ai)
+{
+#ifdef INET6
+       struct sockaddr_in6 *sin6;
+       struct sockaddr_in sin;
+       socklen_t len;
+
+       if (ai->ai_family != AF_INET6)
+               return;
+       if (ai->ai_addrlen != sizeof(struct sockaddr_in6) ||
+           sizeof(sin) > ai->ai_addrlen)
+               return;
+       sin6 = (struct sockaddr_in6 *)ai->ai_addr;
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
+               return;
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       len = sizeof(struct sockaddr_in);
+       memcpy(&sin.sin_addr, &sin6->sin6_addr.s6_addr[12],
+           sizeof(sin.sin_addr));
+       sin.sin_port = sin6->sin6_port;
+
+       ai->ai_family = AF_INET;
+#if defined(HAVE_STRUCT_SOCKADDR_IN_SIN_LEN)
+       sin.sin_len = len;
+#endif
+       memcpy(ai->ai_addr, &sin, len);
+       ai->ai_addrlen = len;
+#endif
+}
+
+#ifdef NO_USAGE
+void
+xusage(void)
+{
+       fputs("Usage error\n", ttyout);
+}
+#endif
diff --git a/usr.bin/ftp/ftp_var.h b/usr.bin/ftp/ftp_var.h
new file mode 100644 (file)
index 0000000..7e5040a
--- /dev/null
@@ -0,0 +1,354 @@
+/*     $NetBSD: ftp_var.h,v 1.82 2012/12/21 18:07:36 christos Exp $    */
+
+/*-
+ * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)ftp_var.h   8.4 (Berkeley) 10/9/94
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * FTP global variables.
+ */
+
+#ifdef SMALL
+#undef NO_EDITCOMPLETE
+#define        NO_EDITCOMPLETE
+#undef NO_PROGRESS
+#define        NO_PROGRESS
+#endif
+
+#include <sys/param.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <poll.h>
+
+#include <setjmp.h>
+#include <stringlist.h>
+
+#ifndef NO_EDITCOMPLETE
+#include <histedit.h>
+#endif /* !NO_EDITCOMPLETE */
+
+#include "extern.h"
+#include "progressbar.h"
+
+/*
+ * Format of command table.
+ */
+struct cmd {
+       const char      *c_name;        /* name of command */
+       const char      *c_help;        /* help string */
+       char            c_bell;         /* give bell when command completes */
+       char            c_conn;         /* must be connected to use command */
+       char            c_proxy;        /* proxy server may execute */
+#ifndef NO_EDITCOMPLETE
+       const char      *c_complete;    /* context sensitive completion list */
+#endif /* !NO_EDITCOMPLETE */
+       void            (*c_handler)(int, char **); /* function to call */
+};
+
+#define MAX_C_NAME     12              /* maximum length of cmd.c_name */
+
+/*
+ * Format of macro table
+ */
+struct macel {
+       char     mac_name[9];   /* macro name */
+       char    *mac_start;     /* start of macro in macbuf */
+       char    *mac_end;       /* end of macro in macbuf */
+};
+
+/*
+ * Format of option table
+ */
+struct option {
+       const char      *name;
+       char            *value;
+};
+
+/*
+ * Indices to features[]; an array containing status of remote server
+ * features; -1 not known (FEAT failed), 0 absent, 1 present.
+ */
+enum {
+       FEAT_FEAT = 0,          /* FEAT, OPTS */
+       FEAT_MDTM,              /* MDTM */
+       FEAT_MLST,              /* MLSD, MLST */
+       FEAT_REST_STREAM,       /* RESTart STREAM */
+       FEAT_SIZE,              /* SIZE */
+       FEAT_TVFS,              /* TVFS (not used) */
+       FEAT_max
+};
+
+
+/*
+ * Global defines
+ */
+#define        FTPBUFLEN       MAXPATHLEN + 200
+#define        MAX_IN_PORT_T   0xffffU
+
+#define        HASHBYTES       1024    /* default mark for `hash' command */
+#define        DEFAULTINCR     1024    /* default increment for `rate' command */
+
+#define        FTP_PORT        21      /* default if ! getservbyname("ftp/tcp") */
+#define        HTTP_PORT       80      /* default if ! getservbyname("http/tcp") */
+#define        HTTPS_PORT      443     /* default if ! getservbyname("https/tcp") */
+#ifndef        GATE_PORT
+#define        GATE_PORT       21      /* default if ! getservbyname("ftpgate/tcp") */
+#endif
+#ifndef        GATE_SERVER
+#define        GATE_SERVER     ""      /* default server */
+#endif
+
+#define        DEFAULTPAGER    "more"  /* default pager if $PAGER isn't set */
+#define        DEFAULTPROMPT   "ftp> " /* default prompt  if `set prompt' is empty */
+#define        DEFAULTRPROMPT  ""      /* default rprompt if `set rprompt' is empty */
+
+#define        TMPFILE         "ftpXXXXXXXXXX"
+
+
+#ifndef        GLOBAL
+#define        GLOBAL  extern
+#endif
+
+/*
+ * Options and other state info.
+ */
+GLOBAL int     trace;          /* trace packets exchanged */
+GLOBAL int     hash;           /* print # for each buffer transferred */
+GLOBAL int     mark;           /* number of bytes between hashes */
+GLOBAL int     sendport;       /* use PORT/LPRT cmd for each data connection */
+GLOBAL int     connected;      /* 1 = connected to server, -1 = logged in */
+GLOBAL int     interactive;    /* interactively prompt on m* cmds */
+GLOBAL int     confirmrest;    /* confirm rest of current m* cmd */
+GLOBAL int     ftp_debug;      /* debugging level */
+GLOBAL int     bell;           /* ring bell on cmd completion */
+GLOBAL int     doglob;         /* glob local file names */
+GLOBAL int     autologin;      /* establish user account on connection */
+GLOBAL int     proxy;          /* proxy server connection active */
+GLOBAL int     proxflag;       /* proxy connection exists */
+GLOBAL int     gatemode;       /* use gate-ftp */
+GLOBAL const char *gateserver; /* server to use for gate-ftp */
+GLOBAL int     sunique;        /* store files on server with unique name */
+GLOBAL int     runique;        /* store local files with unique name */
+GLOBAL int     mcase;          /* map upper to lower case for mget names */
+GLOBAL int     ntflag;         /* use ntin ntout tables for name translation */
+GLOBAL int     mapflag;        /* use mapin mapout templates on file names */
+GLOBAL int     preserve;       /* preserve modification time on files */
+GLOBAL int     code;           /* return/reply code for ftp command */
+GLOBAL int     crflag;         /* if 1, strip car. rets. on ascii gets */
+GLOBAL int     passivemode;    /* passive mode enabled */
+GLOBAL int     activefallback; /* fall back to active mode if passive fails */
+GLOBAL char   *altarg;         /* argv[1] with no shell-like preprocessing  */
+GLOBAL char    ntin[17];       /* input translation table */
+GLOBAL char    ntout[17];      /* output translation table */
+GLOBAL char    mapin[MAXPATHLEN]; /* input map template */
+GLOBAL char    mapout[MAXPATHLEN]; /* output map template */
+GLOBAL char    typename[32];   /* name of file transfer type */
+GLOBAL int     type;           /* requested file transfer type */
+GLOBAL int     curtype;        /* current file transfer type */
+GLOBAL char    structname[32]; /* name of file transfer structure */
+GLOBAL int     stru;           /* file transfer structure */
+GLOBAL char    formname[32];   /* name of file transfer format */
+GLOBAL int     form;           /* file transfer format */
+GLOBAL char    modename[32];   /* name of file transfer mode */
+GLOBAL int     mode;           /* file transfer mode */
+GLOBAL char    bytename[32];   /* local byte size in ascii */
+GLOBAL int     bytesize;       /* local byte size in binary */
+GLOBAL int     anonftp;        /* automatic anonymous login */
+GLOBAL int     dirchange;      /* remote directory changed by cd command */
+GLOBAL int     flushcache;     /* set HTTP cache flush headers with request */
+GLOBAL int     rate_get;       /* maximum get xfer rate */
+GLOBAL int     rate_get_incr;  /* increment for get xfer rate */
+GLOBAL int     rate_put;       /* maximum put xfer rate */
+GLOBAL int     rate_put_incr;  /* increment for put xfer rate */
+GLOBAL int     retry_connect;  /* seconds between retrying connection */
+GLOBAL const char *tmpdir;     /* temporary directory */
+GLOBAL int     epsv4;          /* use EPSV/EPRT on IPv4 connections */
+GLOBAL int     epsv4bad;       /* EPSV doesn't work on the current server */
+GLOBAL int     epsv6;          /* use EPSV/EPRT on IPv6 connections */
+GLOBAL int     epsv6bad;       /* EPSV doesn't work on the current server */
+GLOBAL int     editing;        /* command line editing enabled */
+GLOBAL int     features[FEAT_max];     /* remote FEATures supported */
+
+#ifndef NO_EDITCOMPLETE
+GLOBAL EditLine *el;           /* editline(3) status structure */
+GLOBAL History  *hist;         /* editline(3) history structure */
+GLOBAL char     *cursor_pos;   /* cursor position we're looking for */
+GLOBAL size_t    cursor_argc;  /* location of cursor in margv */
+GLOBAL size_t    cursor_argo;  /* offset of cursor in margv[cursor_argc] */
+#endif /* !NO_EDITCOMPLETE */
+
+GLOBAL char   *hostname;       /* name of host connected to */
+GLOBAL int     unix_server;    /* server is unix, can use binary for ascii */
+GLOBAL int     unix_proxy;     /* proxy is unix, can use binary for ascii */
+GLOBAL char    localcwd[MAXPATHLEN];   /* local dir */
+GLOBAL char    remotecwd[MAXPATHLEN];  /* remote dir */
+GLOBAL char   *username;       /* name of user logged in as. (dynamic) */
+
+GLOBAL sa_family_t family;     /* address family to use for connections */
+GLOBAL const char *ftpport;    /* port number to use for FTP connections */
+GLOBAL const char *httpport;   /* port number to use for HTTP connections */
+#ifdef WITH_SSL
+GLOBAL const char *httpsport;  /* port number to use for HTTPS connections */
+#endif
+GLOBAL const char *gateport;   /* port number to use for gateftp connections */
+GLOBAL struct addrinfo *bindai; /* local address to bind as */
+
+GLOBAL char   *outfile;        /* filename to output URLs to */
+GLOBAL int     restartautofetch; /* restart auto-fetch */
+
+GLOBAL char    line[FTPBUFLEN]; /* input line buffer */
+GLOBAL char    *stringbase;    /* current scan point in line buffer */
+GLOBAL char    argbuf[FTPBUFLEN]; /* argument storage buffer */
+GLOBAL char    *argbase;       /* current storage point in arg buffer */
+GLOBAL StringList *marg_sl;    /* stringlist containing margv */
+GLOBAL int     margc;          /* count of arguments on input line */
+#define        margv (marg_sl->sl_str) /* args parsed from input line */
+GLOBAL int     cpend;          /* flag: if != 0, then pending server reply */
+GLOBAL int     mflag;          /* flag: if != 0, then active multi command */
+
+GLOBAL int     options;        /* used during socket creation */
+
+GLOBAL int     sndbuf_size;    /* socket send buffer size */
+GLOBAL int     rcvbuf_size;    /* socket receive buffer size */
+
+GLOBAL int     macnum;         /* number of defined macros */
+GLOBAL struct macel macros[16];
+GLOBAL char    macbuf[4096];
+
+GLOBAL char    *localhome;             /* local home directory */
+GLOBAL char    *localname;             /* local user name */
+GLOBAL char     netrc[MAXPATHLEN];     /* path to .netrc file */
+GLOBAL char     reply_string[BUFSIZ];  /* first line of previous reply */
+GLOBAL void    (*reply_callback)(const char *);
+                                       /*
+                                        * function to call for each line in
+                                        * the server's reply except for the
+                                        * first (`xxx-') and last (`xxx ')
+                                        */
+
+GLOBAL volatile sig_atomic_t   sigint_raised;
+
+GLOBAL FILE    *cin;
+GLOBAL FILE    *cout;
+GLOBAL int      data;
+
+extern struct cmd      cmdtab[];
+extern struct option   optiontab[];
+
+
+#define        EMPTYSTRING(x)  ((x) == NULL || (*(x) == '\0'))
+#define        FREEPTR(x)      if ((x) != NULL) { free(x); (x) = NULL; }
+
+#ifdef BSD4_4
+# define HAVE_STRUCT_SOCKADDR_IN_SIN_LEN       1
+#endif
+
+#ifdef NO_LONG_LONG
+# define STRTOLL(x,y,z)        strtol(x,y,z)
+#else
+# define STRTOLL(x,y,z)        strtoll(x,y,z)
+#endif
+
+#ifdef NO_DEBUG
+#define DPRINTF(...)
+#define DWARN(...)
+#else
+#define DPRINTF(...)   if (ftp_debug) (void)fprintf(ttyout, __VA_ARGS__)
+#define DWARN(...)     if (ftp_debug) warn(__VA_ARGS__)
+#endif
+
+#define STRorNULL(s)   ((s) ? (s) : "<null>")
+
+#ifdef NO_USAGE
+void xusage(void);
+#define UPRINTF(...)   xusage()
+#else
+#define UPRINTF(...)   (void)fprintf(ttyout, __VA_ARGS__)
+#endif
diff --git a/usr.bin/ftp/main.c b/usr.bin/ftp/main.c
new file mode 100644 (file)
index 0000000..c84364d
--- /dev/null
@@ -0,0 +1,1057 @@
+/*     $NetBSD: main.c,v 1.122 2012/12/22 16:57:10 christos Exp $      */
+
+/*-
+ * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1985, 1989, 1993, 1994\
+ The Regents of the University of California.  All rights reserved.\
+  Copyright 1996-2008 The NetBSD Foundation, Inc.  All rights reserved");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c     8.6 (Berkeley) 10/9/94";
+#else
+__RCSID("$NetBSD: main.c,v 1.122 2012/12/22 16:57:10 christos Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * FTP User Program -- Command Interface.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <locale.h>
+
+#define        GLOBAL          /* force GLOBAL decls in ftp_var.h to be declared */
+#include "ftp_var.h"
+
+#define        FTP_PROXY       "ftp_proxy"     /* env var with FTP proxy location */
+#define        HTTP_PROXY      "http_proxy"    /* env var with HTTP proxy location */
+#define        HTTPS_PROXY     "https_proxy"   /* env var with HTTPS proxy location */
+#define        NO_PROXY        "no_proxy"      /* env var with list of non-proxied
+                                        * hosts, comma or space separated */
+
+__dead static void     usage(void);
+static void    setupoption(const char *, const char *, const char *);
+
+int
+main(int volatile argc, char **volatile argv)
+{
+       int ch, rval;
+       struct passwd *pw;
+       char *cp, *ep, *anonpass, *upload_path, *src_addr;
+       const char *anonuser;
+       int dumbterm, isupload;
+       size_t len;
+
+       tzset();
+       setlocale(LC_ALL, "");
+       setprogname(argv[0]);
+
+       sigint_raised = 0;
+
+       ftpport = "ftp";
+       httpport = "http";
+#ifdef WITH_SSL
+       httpsport = "https";
+#endif
+       gateport = NULL;
+       cp = getenv("FTPSERVERPORT");
+       if (cp != NULL)
+               gateport = cp;
+       else
+               gateport = "ftpgate";
+       doglob = 1;
+       interactive = 1;
+       autologin = 1;
+       passivemode = 1;
+       activefallback = 1;
+       preserve = 1;
+       verbose = 0;
+       progress = 0;
+       gatemode = 0;
+       data = -1;
+       outfile = NULL;
+       restartautofetch = 0;
+#ifndef NO_EDITCOMPLETE
+       editing = 0;
+       el = NULL;
+       hist = NULL;
+#endif
+       bytes = 0;
+       mark = HASHBYTES;
+       rate_get = 0;
+       rate_get_incr = DEFAULTINCR;
+       rate_put = 0;
+       rate_put_incr = DEFAULTINCR;
+#ifdef INET6
+       epsv4 = 1;
+       epsv6 = 1;      
+#else
+       epsv4 = 0;
+       epsv6 = 0;      
+#endif
+       epsv4bad = 0;
+       epsv6bad = 0;
+       src_addr = NULL;
+       upload_path = NULL;
+       isupload = 0;
+       reply_callback = NULL;
+#ifdef INET6
+       family = AF_UNSPEC;
+#else
+       family = AF_INET;       /* force AF_INET if no INET6 support */
+#endif
+
+       netrc[0] = '\0';
+       cp = getenv("NETRC");
+       if (cp != NULL && strlcpy(netrc, cp, sizeof(netrc)) >= sizeof(netrc))
+               errx(1, "$NETRC `%s': %s", cp, strerror(ENAMETOOLONG));
+
+       marg_sl = ftp_sl_init();
+       if ((tmpdir = getenv("TMPDIR")) == NULL)
+               tmpdir = _PATH_TMP;
+
+       /* Set default operation mode based on FTPMODE environment variable */
+       if ((cp = getenv("FTPMODE")) != NULL) {
+               if (strcasecmp(cp, "passive") == 0) {
+                       passivemode = 1;
+                       activefallback = 0;
+               } else if (strcasecmp(cp, "active") == 0) {
+                       passivemode = 0;
+                       activefallback = 0;
+               } else if (strcasecmp(cp, "gate") == 0) {
+                       gatemode = 1;
+               } else if (strcasecmp(cp, "auto") == 0) {
+                       passivemode = 1;
+                       activefallback = 1;
+               } else
+                       warnx("Unknown $FTPMODE `%s'; using defaults", cp);
+       }
+
+       if (strcmp(getprogname(), "pftp") == 0) {
+               passivemode = 1;
+               activefallback = 0;
+       } else if (strcmp(getprogname(), "gate-ftp") == 0)
+               gatemode = 1;
+
+       gateserver = getenv("FTPSERVER");
+       if (gateserver == NULL || *gateserver == '\0')
+               gateserver = GATE_SERVER;
+       if (gatemode) {
+               if (*gateserver == '\0') {
+                       warnx(
+"Neither $FTPSERVER nor GATE_SERVER is defined; disabling gate-ftp");
+                       gatemode = 0;
+               }
+       }
+
+       cp = getenv("TERM");
+       if (cp == NULL || strcmp(cp, "dumb") == 0)
+               dumbterm = 1;
+       else
+               dumbterm = 0;
+       fromatty = isatty(fileno(stdin));
+       ttyout = stdout;
+       if (isatty(fileno(ttyout))) {
+               verbose = 1;            /* verbose if to a tty */
+               if (! dumbterm) {
+#ifndef NO_EDITCOMPLETE
+                       if (fromatty)   /* editing mode on if tty is usable */
+                               editing = 1;
+#endif
+#ifndef NO_PROGRESS
+                       if (foregroundproc())
+                               progress = 1;   /* progress bar on if fg */
+#endif
+               }
+       }
+
+       while ((ch = getopt(argc, argv, "46AadefginN:o:pP:q:r:Rs:tT:u:vV")) != -1) {
+               switch (ch) {
+               case '4':
+                       family = AF_INET;
+                       break;
+
+               case '6':
+#ifdef INET6
+                       family = AF_INET6;
+#else
+                       warnx("INET6 support is not available; ignoring -6");
+#endif
+                       break;
+
+               case 'A':
+                       activefallback = 0;
+                       passivemode = 0;
+                       break;
+
+               case 'a':
+                       anonftp = 1;
+                       break;
+
+               case 'd':
+                       options |= SO_DEBUG;
+                       ftp_debug++;
+                       break;
+
+               case 'e':
+#ifndef NO_EDITCOMPLETE
+                       editing = 0;
+#endif
+                       break;
+
+               case 'f':
+                       flushcache = 1;
+                       break;
+
+               case 'g':
+                       doglob = 0;
+                       break;
+
+               case 'i':
+                       interactive = 0;
+                       break;
+
+               case 'n':
+                       autologin = 0;
+                       break;
+
+               case 'N':
+                       if (strlcpy(netrc, optarg, sizeof(netrc))
+                           >= sizeof(netrc))
+                               errx(1, "%s: %s", optarg,
+                                   strerror(ENAMETOOLONG));
+                       break;
+
+               case 'o':
+                       outfile = optarg;
+                       if (strcmp(outfile, "-") == 0)
+                               ttyout = stderr;
+                       break;
+
+               case 'p':
+                       passivemode = 1;
+                       activefallback = 0;
+                       break;
+
+               case 'P':
+                       ftpport = optarg;
+                       break;
+
+               case 'q':
+                       quit_time = strtol(optarg, &ep, 10);
+                       if (quit_time < 1 || *ep != '\0')
+                               errx(1, "Bad quit value: %s", optarg);
+                       break;
+
+               case 'r':
+                       retry_connect = strtol(optarg, &ep, 10);
+                       if (retry_connect < 1 || *ep != '\0')
+                               errx(1, "Bad retry value: %s", optarg);
+                       break;
+
+               case 'R':
+                       restartautofetch = 1;
+                       break;
+
+               case 's':
+                       src_addr = optarg;
+                       break;
+
+               case 't':
+                       trace = 1;
+                       break;
+
+               case 'T':
+               {
+                       int targc;
+                       char *targv[6], *oac;
+                       char cmdbuf[MAX_C_NAME];
+
+                               /* look for `dir,max[,incr]' */
+                       targc = 0;
+                       (void)strlcpy(cmdbuf, "-T", sizeof(cmdbuf));
+                       targv[targc++] = cmdbuf;
+                       oac = ftp_strdup(optarg);
+
+                       while ((cp = strsep(&oac, ",")) != NULL) {
+                               if (*cp == '\0') {
+                                       warnx("Bad throttle value `%s'",
+                                           optarg);
+                                       usage();
+                                       /* NOTREACHED */
+                               }
+                               targv[targc++] = cp;
+                               if (targc >= 5)
+                                       break;
+                       }
+                       if (parserate(targc, targv, 1) == -1)
+                               usage();
+                       free(oac);
+                       break;
+               }
+
+               case 'u':
+               {
+                       isupload = 1;
+                       interactive = 0;
+                       upload_path = ftp_strdup(optarg);
+
+                       break;
+               }
+
+               case 'v':
+                       progress = verbose = 1;
+                       break;
+
+               case 'V':
+                       progress = verbose = 0;
+                       break;
+
+               default:
+                       usage();
+               }
+       }
+                       /* set line buffering on ttyout */
+       setvbuf(ttyout, NULL, _IOLBF, 0);
+       argc -= optind;
+       argv += optind;
+
+       cpend = 0;      /* no pending replies */
+       proxy = 0;      /* proxy not active */
+       crflag = 1;     /* strip c.r. on ascii gets */
+       sendport = -1;  /* not using ports */
+
+       if (src_addr != NULL) {
+               struct addrinfo hints;
+               int error;
+
+               memset(&hints, 0, sizeof(hints));
+               hints.ai_family = family;
+               hints.ai_socktype = SOCK_STREAM;
+               hints.ai_flags = AI_PASSIVE;
+               error = getaddrinfo(src_addr, NULL, &hints, &bindai);
+               if (error) {
+                       errx(1, "Can't lookup `%s': %s", src_addr,
+                           (error == EAI_SYSTEM) ? strerror(errno)
+                                                 : gai_strerror(error));
+               }
+       }
+
+       /*
+        * Cache the user name and home directory.
+        */
+       localhome = NULL;
+       localname = NULL;
+       anonuser = "anonymous";
+       cp = getenv("HOME");
+       if (! EMPTYSTRING(cp))
+               localhome = ftp_strdup(cp);
+       pw = NULL;
+       cp = getlogin();
+       if (cp != NULL)
+               pw = getpwnam(cp);
+       if (pw == NULL)
+               pw = getpwuid(getuid());
+       if (pw != NULL) {
+               if (localhome == NULL && !EMPTYSTRING(pw->pw_dir))
+                       localhome = ftp_strdup(pw->pw_dir);
+               localname = ftp_strdup(pw->pw_name);
+               anonuser = localname;
+       }
+       if (netrc[0] == '\0' && localhome != NULL) {
+               if (strlcpy(netrc, localhome, sizeof(netrc)) >= sizeof(netrc) ||
+                   strlcat(netrc, "/.netrc", sizeof(netrc)) >= sizeof(netrc)) {
+                       warnx("%s/.netrc: %s", localhome,
+                           strerror(ENAMETOOLONG));
+                       netrc[0] = '\0';
+               }
+       }
+       if (localhome == NULL)
+               localhome = ftp_strdup("/");
+
+       /*
+        * Every anonymous FTP server I've encountered will accept the
+        * string "username@", and will append the hostname itself. We
+        * do this by default since many servers are picky about not
+        * having a FQDN in the anonymous password.
+        * - thorpej@NetBSD.org
+        */
+       len = strlen(anonuser) + 2;
+       anonpass = ftp_malloc(len);
+       (void)strlcpy(anonpass, anonuser, len);
+       (void)strlcat(anonpass, "@",      len);
+
+                       /*
+                        * set all the defaults for options defined in
+                        * struct option optiontab[]  declared in cmdtab.c
+                        */
+       setupoption("anonpass",         getenv("FTPANONPASS"),  anonpass);
+       setupoption("ftp_proxy",        getenv(FTP_PROXY),      "");
+       setupoption("http_proxy",       getenv(HTTP_PROXY),     "");
+       setupoption("https_proxy",      getenv(HTTPS_PROXY),    "");
+       setupoption("no_proxy",         getenv(NO_PROXY),       "");
+       setupoption("pager",            getenv("PAGER"),        DEFAULTPAGER);
+       setupoption("prompt",           getenv("FTPPROMPT"),    DEFAULTPROMPT);
+       setupoption("rprompt",          getenv("FTPRPROMPT"),   DEFAULTRPROMPT);
+
+       free(anonpass);
+
+       setttywidth(0);
+#ifdef SIGINFO
+       (void)xsignal(SIGINFO, psummary);
+#endif
+       (void)xsignal(SIGQUIT, psummary);
+       (void)xsignal(SIGUSR1, crankrate);
+       (void)xsignal(SIGUSR2, crankrate);
+       (void)xsignal(SIGWINCH, setttywidth);
+
+       if (argc > 0) {
+               if (isupload) {
+                       rval = auto_put(argc, argv, upload_path);
+ sigint_or_rval_exit:
+                       if (sigint_raised) {
+                               (void)xsignal(SIGINT, SIG_DFL);
+                               raise(SIGINT);
+                       }
+                       exit(rval);
+               } else if (strchr(argv[0], ':') != NULL
+                           && ! isipv6addr(argv[0])) {
+                       rval = auto_fetch(argc, argv);
+                       if (rval >= 0)          /* -1 == connected and cd-ed */
+                               goto sigint_or_rval_exit;
+               } else {
+                       char *xargv[4], *uuser, *host;
+                       char cmdbuf[MAXPATHLEN];
+
+                       if ((rval = sigsetjmp(toplevel, 1)))
+                               goto sigint_or_rval_exit;
+                       (void)xsignal(SIGINT, intr);
+                       (void)xsignal(SIGPIPE, lostpeer);
+                       uuser = NULL;
+                       host = argv[0];
+                       cp = strchr(host, '@');
+                       if (cp) {
+                               *cp = '\0';
+                               uuser = host;
+                               host = cp + 1;
+                       }
+                       (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf));
+                       xargv[0] = cmdbuf;
+                       xargv[1] = host;
+                       xargv[2] = argv[1];
+                       xargv[3] = NULL;
+                       do {
+                               int oautologin;
+
+                               oautologin = autologin;
+                               if (uuser != NULL) {
+                                       anonftp = 0;
+                                       autologin = 0;
+                               }
+                               setpeer(argc+1, xargv);
+                               autologin = oautologin;
+                               if (connected == 1 && uuser != NULL)
+                                       (void)ftp_login(host, uuser, NULL);
+                               if (!retry_connect)
+                                       break;
+                               if (!connected) {
+                                       macnum = 0;
+                                       fprintf(ttyout,
+                                           "Retrying in %d seconds...\n",
+                                           retry_connect);
+                                       sleep(retry_connect);
+                               }
+                       } while (!connected);
+                       retry_connect = 0; /* connected, stop hiding msgs */
+               }
+       }
+       if (isupload)
+               usage();
+
+#ifndef NO_EDITCOMPLETE
+       controlediting();
+#endif /* !NO_EDITCOMPLETE */
+
+       (void)sigsetjmp(toplevel, 1);
+       (void)xsignal(SIGINT, intr);
+       (void)xsignal(SIGPIPE, lostpeer);
+       for (;;)
+               cmdscanner();
+}
+
+/*
+ * Generate a prompt
+ */
+char *
+prompt(void)
+{
+       static char     **promptopt;
+       static char       buf[MAXPATHLEN];
+
+       if (promptopt == NULL) {
+               struct option *o;
+
+               o = getoption("prompt");
+               if (o == NULL)
+                       errx(1, "prompt: no such option `prompt'");
+               promptopt = &(o->value);
+       }
+       formatbuf(buf, sizeof(buf), *promptopt ? *promptopt : DEFAULTPROMPT);
+       return (buf);
+}
+
+/*
+ * Generate an rprompt
+ */
+char *
+rprompt(void)
+{
+       static char     **rpromptopt;
+       static char       buf[MAXPATHLEN];
+
+       if (rpromptopt == NULL) {
+               struct option *o;
+
+               o = getoption("rprompt");
+               if (o == NULL)
+                       errx(1, "rprompt: no such option `rprompt'");
+               rpromptopt = &(o->value);
+       }
+       formatbuf(buf, sizeof(buf), *rpromptopt ? *rpromptopt : DEFAULTRPROMPT);
+       return (buf);
+}
+
+/*
+ * Command parser.
+ */
+void
+cmdscanner(void)
+{
+       struct cmd      *c;
+       char            *p;
+#ifndef NO_EDITCOMPLETE
+       int              ch;
+       size_t           num;
+#endif
+       int              len;
+       char             cmdbuf[MAX_C_NAME];
+
+       for (;;) {
+#ifndef NO_EDITCOMPLETE
+               if (!editing) {
+#endif /* !NO_EDITCOMPLETE */
+                       if (fromatty) {
+                               fputs(prompt(), ttyout);
+                               p = rprompt();
+                               if (*p)
+                                       fprintf(ttyout, "%s ", p);
+                       }
+                       (void)fflush(ttyout);
+                       len = get_line(stdin, line, sizeof(line), NULL);
+                       switch (len) {
+                       case -1:        /* EOF */
+                       case -2:        /* error */
+                               if (fromatty)
+                                       putc('\n', ttyout);
+                               quit(0, NULL);
+                               /* NOTREACHED */
+                       case -3:        /* too long; try again */
+                               fputs("Sorry, input line is too long.\n",
+                                   ttyout);
+                               continue;
+                       case 0:         /* empty; try again */
+                               continue;
+                       default:        /* all ok */
+                               break;
+                       }
+#ifndef NO_EDITCOMPLETE
+               } else {
+                       const char *buf;
+                       HistEvent ev;
+                       cursor_pos = NULL;
+
+                       buf = el_gets(el, &ch);
+                       num = ch;
+                       if (buf == NULL || num == 0) {
+                               if (fromatty)
+                                       putc('\n', ttyout);
+                               quit(0, NULL);
+                       }
+                       if (num >= sizeof(line)) {
+                               fputs("Sorry, input line is too long.\n",
+                                   ttyout);
+                               break;
+                       }
+                       memcpy(line, buf, num);
+                       if (line[--num] == '\n') {
+                               line[num] = '\0';
+                               if (num == 0)
+                                       break;
+                       }
+                       history(hist, &ev, H_ENTER, buf);
+               }
+#endif /* !NO_EDITCOMPLETE */
+
+               makeargv();
+               if (margc == 0)
+                       continue;
+               c = getcmd(margv[0]);
+               if (c == (struct cmd *)-1) {
+                       fputs("?Ambiguous command.\n", ttyout);
+                       continue;
+               }
+               if (c == NULL) {
+#if !defined(NO_EDITCOMPLETE)
+                       /*
+                        * attempt to el_parse() unknown commands.
+                        * any command containing a ':' would be parsed
+                        * as "[prog:]cmd ...", and will result in a
+                        * false positive if prog != "ftp", so treat
+                        * such commands as invalid.
+                        */
+                       if (strchr(margv[0], ':') != NULL ||
+                           !editing ||
+                           el_parse(el, margc, (void *)margv) != 0)
+#endif /* !NO_EDITCOMPLETE */
+                               fputs("?Invalid command.\n", ttyout);
+                       continue;
+               }
+               if (c->c_conn && !connected) {
+                       fputs("Not connected.\n", ttyout);
+                       continue;
+               }
+               confirmrest = 0;
+               (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf));
+               margv[0] = cmdbuf;
+               (*c->c_handler)(margc, margv);
+               if (bell && c->c_bell)
+                       (void)putc('\007', ttyout);
+               if (c->c_handler != help)
+                       break;
+       }
+       (void)xsignal(SIGINT, intr);
+       (void)xsignal(SIGPIPE, lostpeer);
+}
+
+struct cmd *
+getcmd(const char *name)
+{
+       const char *p, *q;
+       struct cmd *c, *found;
+       int nmatches, longest;
+
+       if (name == NULL)
+               return (0);
+
+       longest = 0;
+       nmatches = 0;
+       found = 0;
+       for (c = cmdtab; (p = c->c_name) != NULL; c++) {
+               for (q = name; *q == *p++; q++)
+                       if (*q == 0)            /* exact match? */
+                               return (c);
+               if (!*q) {                      /* the name was a prefix */
+                       if (q - name > longest) {
+                               longest = q - name;
+                               nmatches = 1;
+                               found = c;
+                       } else if (q - name == longest)
+                               nmatches++;
+               }
+       }
+       if (nmatches > 1)
+               return ((struct cmd *)-1);
+       return (found);
+}
+
+/*
+ * Slice a string up into argc/argv.
+ */
+
+int slrflag;
+
+void
+makeargv(void)
+{
+       char *argp;
+
+       stringbase = line;              /* scan from first of buffer */
+       argbase = argbuf;               /* store from first of buffer */
+       slrflag = 0;
+       marg_sl->sl_cur = 0;            /* reset to start of marg_sl */
+       for (margc = 0; ; margc++) {
+               argp = slurpstring();
+               ftp_sl_add(marg_sl, argp);
+               if (argp == NULL)
+                       break;
+       }
+#ifndef NO_EDITCOMPLETE
+       if (cursor_pos == line) {
+               cursor_argc = 0;
+               cursor_argo = 0;
+       } else if (cursor_pos != NULL) {
+               cursor_argc = margc;
+               cursor_argo = strlen(margv[margc-1]);
+       }
+#endif /* !NO_EDITCOMPLETE */
+}
+
+#ifdef NO_EDITCOMPLETE
+#define        INC_CHKCURSOR(x)        (x)++
+#else  /* !NO_EDITCOMPLETE */
+#define        INC_CHKCURSOR(x)        { (x)++ ; \
+                               if (x == cursor_pos) { \
+                                       cursor_argc = margc; \
+                                       cursor_argo = ap-argbase; \
+                                       cursor_pos = NULL; \
+                               } }
+
+#endif /* !NO_EDITCOMPLETE */
+
+/*
+ * Parse string into argbuf;
+ * implemented with FSM to
+ * handle quoting and strings
+ */
+char *
+slurpstring(void)
+{
+       static char bangstr[2] = { '!', '\0' };
+       static char dollarstr[2] = { '$', '\0' };
+       int got_one = 0;
+       char *sb = stringbase;
+       char *ap = argbase;
+       char *tmp = argbase;            /* will return this if token found */
+
+       if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */
+               switch (slrflag) {      /* and $ as token for macro invoke */
+                       case 0:
+                               slrflag++;
+                               INC_CHKCURSOR(stringbase);
+                               return ((*sb == '!') ? bangstr : dollarstr);
+                               /* NOTREACHED */
+                       case 1:
+                               slrflag++;
+                               altarg = stringbase;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+S0:
+       switch (*sb) {
+
+       case '\0':
+               goto OUT;
+
+       case ' ':
+       case '\t':
+               INC_CHKCURSOR(sb);
+               goto S0;
+
+       default:
+               switch (slrflag) {
+                       case 0:
+                               slrflag++;
+                               break;
+                       case 1:
+                               slrflag++;
+                               altarg = sb;
+                               break;
+                       default:
+                               break;
+               }
+               goto S1;
+       }
+
+S1:
+       switch (*sb) {
+
+       case ' ':
+       case '\t':
+       case '\0':
+               goto OUT;       /* end of token */
+
+       case '\\':
+               INC_CHKCURSOR(sb);
+               goto S2;        /* slurp next character */
+
+       case '"':
+               INC_CHKCURSOR(sb);
+               goto S3;        /* slurp quoted string */
+
+       default:
+               *ap = *sb;      /* add character to token */
+               ap++;
+               INC_CHKCURSOR(sb);
+               got_one = 1;
+               goto S1;
+       }
+
+S2:
+       switch (*sb) {
+
+       case '\0':
+               goto OUT;
+
+       default:
+               *ap = *sb;
+               ap++;
+               INC_CHKCURSOR(sb);
+               got_one = 1;
+               goto S1;
+       }
+
+S3:
+       switch (*sb) {
+
+       case '\0':
+               goto OUT;
+
+       case '"':
+               INC_CHKCURSOR(sb);
+               goto S1;
+
+       default:
+               *ap = *sb;
+               ap++;
+               INC_CHKCURSOR(sb);
+               got_one = 1;
+               goto S3;
+       }
+
+OUT:
+       if (got_one)
+               *ap++ = '\0';
+       argbase = ap;                   /* update storage pointer */
+       stringbase = sb;                /* update scan pointer */
+       if (got_one) {
+               return (tmp);
+       }
+       switch (slrflag) {
+               case 0:
+                       slrflag++;
+                       break;
+               case 1:
+                       slrflag++;
+                       altarg = NULL;
+                       break;
+               default:
+                       break;
+       }
+       return (NULL);
+}
+
+/*
+ * Help/usage command.
+ * Call each command handler with argc == 0 and argv[0] == name.
+ */
+void
+help(int argc, char *argv[])
+{
+       struct cmd *c;
+       char *nargv[1], *cmd;
+       const char *p;
+       int isusage;
+
+       cmd = argv[0];
+       isusage = (strcmp(cmd, "usage") == 0);
+       if (argc == 0 || (isusage && argc == 1)) {
+               UPRINTF("usage: %s [command [...]]\n", cmd);
+               return;
+       }
+       if (argc == 1) {
+               StringList *buf;
+
+               buf = ftp_sl_init();
+               fprintf(ttyout,
+                   "%sommands may be abbreviated.  Commands are:\n\n",
+                   proxy ? "Proxy c" : "C");
+               for (c = cmdtab; (p = c->c_name) != NULL; c++)
+                       if (!proxy || c->c_proxy)
+                               ftp_sl_add(buf, ftp_strdup(p));
+               list_vertical(buf);
+               sl_free(buf, 1);
+               return;
+       }
+
+#define        HELPINDENT ((int) sizeof("disconnect"))
+
+       while (--argc > 0) {
+               char *arg;
+               char cmdbuf[MAX_C_NAME];
+
+               arg = *++argv;
+               c = getcmd(arg);
+               if (c == (struct cmd *)-1)
+                       fprintf(ttyout, "?Ambiguous %s command `%s'\n",
+                           cmd, arg);
+               else if (c == NULL)
+                       fprintf(ttyout, "?Invalid %s command `%s'\n",
+                           cmd, arg);
+               else {
+                       if (isusage) {
+                               (void)strlcpy(cmdbuf, c->c_name, sizeof(cmdbuf));
+                               nargv[0] = cmdbuf;
+                               (*c->c_handler)(0, nargv);
+                       } else
+                               fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
+                                   c->c_name, c->c_help);
+               }
+       }
+}
+
+struct option *
+getoption(const char *name)
+{
+       const char *p;
+       struct option *c;
+
+       if (name == NULL)
+               return (NULL);
+       for (c = optiontab; (p = c->name) != NULL; c++) {
+               if (strcasecmp(p, name) == 0)
+                       return (c);
+       }
+       return (NULL);
+}
+
+char *
+getoptionvalue(const char *name)
+{
+       struct option *c;
+
+       if (name == NULL)
+               errx(1, "getoptionvalue: invoked with NULL name");
+       c = getoption(name);
+       if (c != NULL)
+               return (c->value);
+       errx(1, "getoptionvalue: invoked with unknown option `%s'", name);
+       /* NOTREACHED */
+}
+
+static void
+setupoption(const char *name, const char *value, const char *defaultvalue)
+{
+       set_option(name, value ? value : defaultvalue, 0);
+}
+
+void
+usage(void)
+{
+       const char *progname = getprogname();
+
+       (void)fprintf(stderr,
+"usage: %s [-46AadefginpRtVv] [-N netrc] [-o outfile] [-P port] [-q quittime]\n"
+"           [-r retry] [-s srcaddr] [-T dir,max[,inc]]\n"
+"           [[user@]host [port]] [host:path[/]] [file:///file]\n"
+"           [ftp://[user[:pass]@]host[:port]/path[/]]\n"
+"           [http://[user[:pass]@]host[:port]/path] [...]\n"
+#ifdef WITH_SSL
+"           [https://[user[:pass]@]host[:port]/path] [...]\n"
+#endif
+"       %s -u URL file [...]\n", progname, progname);
+       exit(1);
+}
diff --git a/usr.bin/ftp/progressbar.c b/usr.bin/ftp/progressbar.c
new file mode 100644 (file)
index 0000000..2350776
--- /dev/null
@@ -0,0 +1,472 @@
+/*     $NetBSD: progressbar.c,v 1.22 2012/06/27 22:07:36 riastradh Exp $       */
+
+/*-
+ * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: progressbar.c,v 1.22 2012/06/27 22:07:36 riastradh Exp $");
+#endif /* not lint */
+
+/*
+ * FTP User Program -- Misc support routines
+ */
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#include "progressbar.h"
+
+#if !defined(NO_PROGRESS)
+/*
+ * return non-zero if we're the current foreground process
+ */
+int
+foregroundproc(void)
+{
+       static pid_t pgrp = -1;
+
+       if (pgrp == -1)
+               pgrp = getpgrp();
+
+       return (tcgetpgrp(fileno(ttyout)) == pgrp);
+}
+#endif /* !defined(NO_PROGRESS) */
+
+
+static void updateprogressmeter(int);
+
+/*
+ * SIGALRM handler to update the progress meter
+ */
+static void
+updateprogressmeter(int dummy)
+{
+       int oerrno = errno;
+
+       progressmeter(0);
+       errno = oerrno;
+}
+
+/*
+ * List of order of magnitude suffixes, per IEC 60027-2.
+ */
+static const char * const suffixes[] = {
+       "",     /* 2^0  (byte) */
+       "KiB",  /* 2^10 Kibibyte */
+       "MiB",  /* 2^20 Mebibyte */
+       "GiB",  /* 2^30 Gibibyte */
+       "TiB",  /* 2^40 Tebibyte */
+       "PiB",  /* 2^50 Pebibyte */
+       "EiB",  /* 2^60 Exbibyte */
+#if 0
+               /* The following are not necessary for signed 64-bit off_t */
+       "ZiB",  /* 2^70 Zebibyte */
+       "YiB",  /* 2^80 Yobibyte */
+#endif
+};
+#define NSUFFIXES      (int)(sizeof(suffixes) / sizeof(suffixes[0]))
+
+/*
+ * Display a transfer progress bar if progress is non-zero.
+ * SIGALRM is hijacked for use by this function.
+ * - Before the transfer, set filesize to size of file (or -1 if unknown),
+ *   and call with flag = -1. This starts the once per second timer,
+ *   and a call to updateprogressmeter() upon SIGALRM.
+ * - During the transfer, updateprogressmeter will call progressmeter
+ *   with flag = 0
+ * - After the transfer, call with flag = 1
+ */
+static struct timeval start;
+static struct timeval lastupdate;
+
+#define        BUFLEFT (sizeof(buf) - len)
+
+void
+progressmeter(int flag)
+{
+       static off_t lastsize;
+       off_t cursize;
+       struct timeval now, wait;
+#ifndef NO_PROGRESS
+       struct timeval td;
+       off_t abbrevsize, bytespersec;
+       double elapsed;
+       int ratio, i, remaining, barlength;
+
+                       /*
+                        * Work variables for progress bar.
+                        *
+                        * XXX: if the format of the progress bar changes
+                        *      (especially the number of characters in the
+                        *      `static' portion of it), be sure to update
+                        *      these appropriately.
+                        */
+#endif
+       size_t          len;
+       char            buf[256];       /* workspace for progress bar */
+#ifndef NO_PROGRESS
+#define        BAROVERHEAD     45              /* non `*' portion of progress bar */
+                                       /*
+                                        * stars should contain at least
+                                        * sizeof(buf) - BAROVERHEAD entries
+                                        */
+       static const char       stars[] =
+"*****************************************************************************"
+"*****************************************************************************"
+"*****************************************************************************";
+
+#endif
+
+       if (flag == -1) {
+               (void)gettimeofday(&start, NULL);
+               lastupdate = start;
+               lastsize = restart_point;
+       }
+
+       (void)gettimeofday(&now, NULL);
+       cursize = bytes + restart_point;
+       timersub(&now, &lastupdate, &wait);
+       if (cursize > lastsize) {
+               lastupdate = now;
+               lastsize = cursize;
+               wait.tv_sec = 0;
+       } else {
+#ifndef STANDALONE_PROGRESS
+               if (quit_time > 0 && wait.tv_sec > quit_time) {
+                       len = snprintf(buf, sizeof(buf), "\r\n%s: "
+                           "transfer aborted because stalled for %lu sec.\r\n",
+                           getprogname(), (unsigned long)wait.tv_sec);
+                       (void)write(fileno(ttyout), buf, len);
+                       alarmtimer(0);
+                       (void)xsignal(SIGALRM, SIG_DFL);
+                       siglongjmp(toplevel, 1);
+               }
+#endif /* !STANDALONE_PROGRESS */
+       }
+       /*
+        * Always set the handler even if we are not the foreground process.
+        */
+#ifdef STANDALONE_PROGRESS
+       if (progress) {
+#else
+       if (quit_time > 0 || progress) {
+#endif /* !STANDALONE_PROGRESS */
+               if (flag == -1) {
+                       (void)xsignal_restart(SIGALRM, updateprogressmeter, 1);
+                       alarmtimer(1);          /* set alarm timer for 1 Hz */
+               } else if (flag == 1) {
+                       alarmtimer(0);
+                       (void)xsignal(SIGALRM, SIG_DFL);
+               }
+       }
+#ifndef NO_PROGRESS
+       if (!progress)
+               return;
+       len = 0;
+
+       /*
+        * print progress bar only if we are foreground process.
+        */
+       if (! foregroundproc())
+               return;
+
+       len += snprintf(buf + len, BUFLEFT, "\r");
+       if (prefix)
+         len += snprintf(buf + len, BUFLEFT, "%s", prefix);
+       if (filesize > 0) {
+               ratio = (int)((double)cursize * 100.0 / (double)filesize);
+               ratio = MAX(ratio, 0);
+               ratio = MIN(ratio, 100);
+               len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio);
+
+                       /*
+                        * calculate the length of the `*' bar, ensuring that
+                        * the number of stars won't exceed the buffer size
+                        */
+               barlength = MIN((int)(sizeof(buf) - 1), ttywidth) - BAROVERHEAD;
+               if (prefix)
+                       barlength -= (int)strlen(prefix);
+               if (barlength > 0) {
+                       i = barlength * ratio / 100;
+                       len += snprintf(buf + len, BUFLEFT,
+                           "|%.*s%*s|", i, stars, (int)(barlength - i), "");
+               }
+       }
+
+       abbrevsize = cursize;
+       for (i = 0; abbrevsize >= 100000 && i < NSUFFIXES; i++)
+               abbrevsize >>= 10;
+       if (i == NSUFFIXES)
+               i--;
+       len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %-3s ",
+           (LLT)abbrevsize,
+           suffixes[i]);
+
+       timersub(&now, &start, &td);
+       elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
+
+       bytespersec = 0;
+       if (bytes > 0) {
+               bytespersec = bytes;
+               if (elapsed > 0.0)
+                       bytespersec /= elapsed;
+       }
+       for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++)
+               bytespersec >>= 10;
+       len += snprintf(buf + len, BUFLEFT,
+           " " LLFP("3") ".%02d %.2sB/s ",
+           (LLT)(bytespersec / 1024),
+           (int)((bytespersec % 1024) * 100 / 1024),
+           suffixes[i]);
+
+       if (filesize > 0) {
+               if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
+                       len += snprintf(buf + len, BUFLEFT, "   --:-- ETA");
+               } else if (wait.tv_sec >= STALLTIME) {
+                       len += snprintf(buf + len, BUFLEFT, " - stalled -");
+               } else {
+                       remaining = (int)
+                           ((filesize - restart_point) / (bytes / elapsed) -
+                           elapsed);
+                       if (remaining >= 100 * SECSPERHOUR)
+                               len += snprintf(buf + len, BUFLEFT,
+                                   "   --:-- ETA");
+                       else {
+                               i = remaining / SECSPERHOUR;
+                               if (i)
+                                       len += snprintf(buf + len, BUFLEFT,
+                                           "%2d:", i);
+                               else
+                                       len += snprintf(buf + len, BUFLEFT,
+                                           "   ");
+                               i = remaining % SECSPERHOUR;
+                               len += snprintf(buf + len, BUFLEFT,
+                                   "%02d:%02d ETA", i / 60, i % 60);
+                       }
+               }
+       }
+       if (flag == 1)
+               len += snprintf(buf + len, BUFLEFT, "\n");
+       (void)write(fileno(ttyout), buf, len);
+
+#endif /* !NO_PROGRESS */
+}
+
+#ifndef STANDALONE_PROGRESS
+/*
+ * Display transfer statistics.
+ * Requires start to be initialised by progressmeter(-1),
+ * direction to be defined by xfer routines, and filesize and bytes
+ * to be updated by xfer routines
+ * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr
+ * instead of ttyout.
+ */
+void
+ptransfer(int siginfo)
+{
+       struct timeval now, td, wait;
+       double elapsed;
+       off_t bytespersec;
+       int remaining, hh, i;
+       size_t len;
+
+       char buf[256];          /* Work variable for transfer status. */
+
+       if (!verbose && !progress && !siginfo)
+               return;
+
+       (void)gettimeofday(&now, NULL);
+       timersub(&now, &start, &td);
+       elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
+       bytespersec = 0;
+       if (bytes > 0) {
+               bytespersec = bytes;
+               if (elapsed > 0.0)
+                       bytespersec /= elapsed;
+       }
+       len = 0;
+       len += snprintf(buf + len, BUFLEFT, LLF " byte%s %s in ",
+           (LLT)bytes, bytes == 1 ? "" : "s", direction);
+       remaining = (int)elapsed;
+       if (remaining > SECSPERDAY) {
+               int days;
+
+               days = remaining / SECSPERDAY;
+               remaining %= SECSPERDAY;
+               len += snprintf(buf + len, BUFLEFT,
+                   "%d day%s ", days, days == 1 ? "" : "s");
+       }
+       hh = remaining / SECSPERHOUR;
+       remaining %= SECSPERHOUR;
+       if (hh)
+               len += snprintf(buf + len, BUFLEFT, "%2d:", hh);
+       len += snprintf(buf + len, BUFLEFT,
+           "%02d:%02d ", remaining / 60, remaining % 60);
+
+       for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++)
+               bytespersec >>= 10;
+       if (i == NSUFFIXES)
+               i--;
+       len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %.2sB/s)",
+           (LLT)(bytespersec / 1024),
+           (int)((bytespersec % 1024) * 100 / 1024),
+           suffixes[i]);
+
+       if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
+           && bytes + restart_point <= filesize) {
+               remaining = (int)((filesize - restart_point) /
+                                 (bytes / elapsed) - elapsed);
+               hh = remaining / SECSPERHOUR;
+               remaining %= SECSPERHOUR;
+               len += snprintf(buf + len, BUFLEFT, "  ETA: ");
+               if (hh)
+                       len += snprintf(buf + len, BUFLEFT, "%2d:", hh);
+               len += snprintf(buf + len, BUFLEFT, "%02d:%02d",
+                   remaining / 60, remaining % 60);
+               timersub(&now, &lastupdate, &wait);
+               if (wait.tv_sec >= STALLTIME)
+                       len += snprintf(buf + len, BUFLEFT, "  (stalled)");
+       }
+       len += snprintf(buf + len, BUFLEFT, "\n");
+       (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len);
+}
+
+/*
+ * SIG{INFO,QUIT} handler to print transfer stats if a transfer is in progress
+ */
+void
+psummary(int notused)
+{
+       int oerrno = errno;
+
+       if (bytes > 0) {
+               if (fromatty)
+                       write(fileno(ttyout), "\n", 1);
+               ptransfer(1);
+       }
+       errno = oerrno;
+}
+#endif /* !STANDALONE_PROGRESS */
+
+
+/*
+ * Set the SIGALRM interval timer for wait seconds, 0 to disable.
+ */
+void
+alarmtimer(int wait)
+{
+       struct itimerval itv;
+
+       itv.it_value.tv_sec = wait;
+       itv.it_value.tv_usec = 0;
+       itv.it_interval = itv.it_value;
+       setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+
+/*
+ * Install a POSIX signal handler, allowing the invoker to set whether
+ * the signal should be restartable or not
+ */
+sigfunc
+xsignal_restart(int sig, sigfunc func, int restartable)
+{
+       struct sigaction act, oact;
+       act.sa_handler = func;
+
+       sigemptyset(&act.sa_mask);
+#if defined(SA_RESTART)                        /* 4.4BSD, Posix(?), SVR4 */
+       act.sa_flags = restartable ? SA_RESTART : 0;
+#elif defined(SA_INTERRUPT)            /* SunOS 4.x */
+       act.sa_flags = restartable ? 0 : SA_INTERRUPT;
+#else
+#error "system must have SA_RESTART or SA_INTERRUPT"
+#endif
+       if (sigaction(sig, &act, &oact) < 0)
+               return (SIG_ERR);
+       return (oact.sa_handler);
+}
+
+/*
+ * Install a signal handler with the `restartable' flag set dependent upon
+ * which signal is being set. (This is a wrapper to xsignal_restart())
+ */
+sigfunc
+xsignal(int sig, sigfunc func)
+{
+       int restartable;
+
+       /*
+        * Some signals print output or change the state of the process.
+        * There should be restartable, so that reads and writes are
+        * not affected.  Some signals should cause program flow to change;
+        * these signals should not be restartable, so that the system call
+        * will return with EINTR, and the program will go do something
+        * different.  If the signal handler calls longjmp() or siglongjmp(),
+        * it doesn't matter if it's restartable.
+        */
+
+       switch(sig) {
+#ifdef SIGINFO
+       case SIGINFO:
+#endif
+       case SIGQUIT:
+       case SIGUSR1:
+       case SIGUSR2:
+       case SIGWINCH:
+               restartable = 1;
+               break;
+
+       case SIGALRM:
+       case SIGINT:
+       case SIGPIPE:
+               restartable = 0;
+               break;
+
+       default:
+               /*
+                * This is unpleasant, but I don't know what would be better.
+                * Right now, this "can't happen"
+                */
+               errx(1, "xsignal_restart: called with signal %d", sig);
+       }
+
+       return(xsignal_restart(sig, func, restartable));
+}
diff --git a/usr.bin/ftp/progressbar.h b/usr.bin/ftp/progressbar.h
new file mode 100644 (file)
index 0000000..f627e88
--- /dev/null
@@ -0,0 +1,92 @@
+/*     $NetBSD: progressbar.h,v 1.8 2009/04/12 10:18:52 lukem Exp $    */
+
+/*-
+ * Copyright (c) 1996-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STANDALONE_PROGRESS
+#include <setjmp.h>
+#endif /* !STANDALONE_PROGRESS */
+
+#ifndef        GLOBAL
+#define        GLOBAL  extern
+#endif
+
+
+#define        STALLTIME       5       /* # of seconds of no xfer before "stalling" */
+
+typedef void (*sigfunc)(int);
+
+
+GLOBAL FILE   *ttyout;         /* stdout, or stderr if retrieving to stdout */
+
+GLOBAL int     progress;       /* display transfer progress bar */
+GLOBAL int     ttywidth;       /* width of tty */
+
+GLOBAL off_t   bytes;          /* current # of bytes read */
+GLOBAL off_t   filesize;       /* size of file being transferred */
+GLOBAL off_t   restart_point;  /* offset to restart transfer */
+GLOBAL char   *prefix;         /* Text written left of progress bar */
+
+
+#ifndef        STANDALONE_PROGRESS
+GLOBAL int     fromatty;       /* input is from a terminal */
+GLOBAL int     verbose;        /* print messages coming back from server */
+GLOBAL int     quit_time;      /* maximum time to wait if stalled */
+
+GLOBAL const char  *direction; /* direction transfer is occurring */
+
+GLOBAL sigjmp_buf toplevel;    /* non-local goto stuff for cmd scanner */
+#endif /* !STANDALONE_PROGRESS */
+
+int    foregroundproc(void);
+void   alarmtimer(int);
+void   progressmeter(int);
+sigfunc        xsignal(int, sigfunc);
+sigfunc        xsignal_restart(int, sigfunc, int);
+
+#ifndef STANDALONE_PROGRESS
+void   psummary(int);
+void   ptransfer(int);
+#endif /* !STANDALONE_PROGRESS */
+
+#ifdef NO_LONG_LONG
+# define LLF           "%ld"
+# define LLFP(x)       "%" x "ld"
+# define LLT           long
+# define ULLF          "%lu"
+# define ULLFP(x)      "%" x "lu"
+# define ULLT          unsigned long
+#else
+# define LLF           "%lld"
+# define LLFP(x)       "%" x "lld"
+# define LLT           long long
+# define ULLF          "%llu"
+# define ULLFP(x)      "%" x "llu"
+# define ULLT          unsigned long long
+#endif
diff --git a/usr.bin/ftp/ruserpass.c b/usr.bin/ftp/ruserpass.c
new file mode 100644 (file)
index 0000000..e434052
--- /dev/null
@@ -0,0 +1,318 @@
+/*     $NetBSD: ruserpass.c,v 1.33 2007/04/17 05:52:04 lukem Exp $     */
+
+/*
+ * Copyright (c) 1985, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ruserpass.c        8.4 (Berkeley) 4/27/95";
+#else
+__RCSID("$NetBSD: ruserpass.c,v 1.33 2007/04/17 05:52:04 lukem Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ftp_var.h"
+
+static int token(void);
+static FILE *cfile;
+
+#define        DEFAULT 1
+#define        LOGIN   2
+#define        PASSWD  3
+#define        ACCOUNT 4
+#define        MACDEF  5
+#define        ID      10
+#define        MACH    11
+
+static char tokval[100];
+
+static struct toktab {
+       const char *tokstr;
+       int tval;
+} toktab[] = {
+       { "default",    DEFAULT },
+       { "login",      LOGIN },
+       { "password",   PASSWD },
+       { "passwd",     PASSWD },
+       { "account",    ACCOUNT },
+       { "machine",    MACH },
+       { "macdef",     MACDEF },
+       { NULL,         0 }
+};
+
+int
+ruserpass(const char *host, char **aname, char **apass, char **aacct)
+{
+       char *tmp;
+       const char *mydomain;
+       char myname[MAXHOSTNAMELEN + 1];
+       int t, i, c, usedefault = 0;
+       struct stat stb;
+
+       if (netrc[0] == '\0')
+               return (0);
+       cfile = fopen(netrc, "r");
+       if (cfile == NULL) {
+               if (errno != ENOENT)
+                       warn("Can't read `%s'", netrc);
+               return (0);
+       }
+       if (gethostname(myname, sizeof(myname)) < 0)
+               myname[0] = '\0';
+       myname[sizeof(myname) - 1] = '\0';
+       if ((mydomain = strchr(myname, '.')) == NULL)
+               mydomain = "";
+ next:
+       while ((t = token()) > 0) switch(t) {
+
+       case DEFAULT:
+               usedefault = 1;
+               /* FALL THROUGH */
+
+       case MACH:
+               if (!usedefault) {
+                       if ((t = token()) == -1)
+                               goto bad;
+                       if (t != ID)
+                               continue;
+                       /*
+                        * Allow match either for user's input host name
+                        * or official hostname.  Also allow match of
+                        * incompletely-specified host in local domain.
+                        */
+                       if (strcasecmp(host, tokval) == 0)
+                               goto match;
+                       if (strcasecmp(hostname, tokval) == 0)
+                               goto match;
+                       if ((tmp = strchr(hostname, '.')) != NULL &&
+                           strcasecmp(tmp, mydomain) == 0 &&
+                           strncasecmp(hostname, tokval, tmp-hostname) == 0 &&
+                           tokval[tmp - hostname] == '\0')
+                               goto match;
+                       if ((tmp = strchr(host, '.')) != NULL &&
+                           strcasecmp(tmp, mydomain) == 0 &&
+                           strncasecmp(host, tokval, tmp - host) == 0 &&
+                           tokval[tmp - host] == '\0')
+                               goto match;
+                       continue;
+               }
+       match:
+               while ((t = token()) > 0 &&
+                   t != MACH && t != DEFAULT) switch(t) {
+
+               case LOGIN:
+                       if ((t = token()) == -1)
+                               goto bad;
+                       if (t) {
+                               if (*aname == NULL)
+                                       *aname = ftp_strdup(tokval);
+                               else {
+                                       if (strcmp(*aname, tokval))
+                                               goto next;
+                               }
+                       }
+                       break;
+               case PASSWD:
+                       if ((*aname == NULL || strcmp(*aname, "anonymous")) &&
+                           fstat(fileno(cfile), &stb) >= 0 &&
+                           (stb.st_mode & 077) != 0) {
+       warnx("Error: .netrc file is readable by others");
+       warnx("Remove password or make file unreadable by others");
+                               goto bad;
+                       }
+                       if ((t = token()) == -1)
+                               goto bad;
+                       if (t && *apass == NULL)
+                               *apass = ftp_strdup(tokval);
+                       break;
+               case ACCOUNT:
+                       if (fstat(fileno(cfile), &stb) >= 0
+                           && (stb.st_mode & 077) != 0) {
+       warnx("Error: .netrc file is readable by others");
+       warnx("Remove account or make file unreadable by others");
+                               goto bad;
+                       }
+                       if ((t = token()) == -1)
+                               goto bad;
+                       if (t && *aacct == NULL)
+                               *aacct = ftp_strdup(tokval);
+                       break;
+               case MACDEF:
+                       if (proxy) {
+                               (void)fclose(cfile);
+                               return (0);
+                       }
+                       while ((c = getc(cfile)) != EOF)
+                               if (c != ' ' && c != '\t')
+                                       break;
+                       if (c == EOF || c == '\n') {
+                               fputs("Missing macdef name argument.\n",
+                                   ttyout);
+                               goto bad;
+                       }
+                       if (macnum == 16) {
+                               fputs(
+                           "Limit of 16 macros have already been defined.\n",
+                                   ttyout);
+                               goto bad;
+                       }
+                       tmp = macros[macnum].mac_name;
+                       *tmp++ = c;
+                       for (i = 0; i < 8 && (c = getc(cfile)) != EOF &&
+                           !isspace(c); ++i) {
+                               *tmp++ = c;
+                       }
+                       if (c == EOF) {
+                               fputs(
+                           "Macro definition missing null line terminator.\n",
+                                   ttyout);
+                               goto bad;
+                       }
+                       *tmp = '\0';
+                       if (c != '\n') {
+                               while ((c = getc(cfile)) != EOF && c != '\n');
+                       }
+                       if (c == EOF) {
+                               fputs(
+                           "Macro definition missing null line terminator.\n",
+                                   ttyout);
+                               goto bad;
+                       }
+                       if (macnum == 0) {
+                               macros[macnum].mac_start = macbuf;
+                       }
+                       else {
+                               macros[macnum].mac_start =
+                                   macros[macnum-1].mac_end + 1;
+                       }
+                       tmp = macros[macnum].mac_start;
+                       while (tmp != macbuf + 4096) {
+                               if ((c = getc(cfile)) == EOF) {
+                                       fputs(
+                           "Macro definition missing null line terminator.\n",
+                                           ttyout);
+                                       goto bad;
+                               }
+                               *tmp = c;
+                               if (*tmp == '\n') {
+                                       if (tmp == macros[macnum].mac_start) {
+                                               macros[macnum++].mac_end = tmp;
+                                               break;
+                                       } else if (*(tmp - 1) == '\0') {
+                                               macros[macnum++].mac_end =
+                                                   tmp - 1;
+                                               break;
+                                       }
+                                       *tmp = '\0';
+                               }
+                               tmp++;
+                       }
+                       if (tmp == macbuf + 4096) {
+                               fputs("4K macro buffer exceeded.\n",
+                                   ttyout);
+                               goto bad;
+                       }
+                       break;
+               default:
+                       warnx("Unknown .netrc keyword `%s'", tokval);
+                       break;
+               }
+               goto done;
+       }
+ done:
+       if (t == -1)
+               goto bad;
+       (void)fclose(cfile);
+       return (0);
+ bad:
+       (void)fclose(cfile);
+       return (-1);
+}
+
+static int
+token(void)
+{
+       char *cp;
+       int c;
+       struct toktab *t;
+
+       if (feof(cfile) || ferror(cfile))
+               return (0);
+       while ((c = getc(cfile)) != EOF &&
+           (c == '\n' || c == '\t' || c == ' ' || c == ','))
+               continue;
+       if (c == EOF)
+               return (0);
+       cp = tokval;
+       if (c == '"') {
+               while ((c = getc(cfile)) != EOF && c != '"') {
+                       if (c == '\\')
+                               if ((c = getc(cfile)) == EOF)
+                                       break;
+                       *cp++ = c;
+                       if (cp == tokval + sizeof(tokval)) {
+                               warnx("Token in .netrc too long");
+                               return (-1);
+                       }
+               }
+       } else {
+               *cp++ = c;
+               while ((c = getc(cfile)) != EOF
+                   && c != '\n' && c != '\t' && c != ' ' && c != ',') {
+                       if (c == '\\')
+                               if ((c = getc(cfile)) == EOF)
+                                       break;
+                       *cp++ = c;
+                       if (cp == tokval + sizeof(tokval)) {
+                               warnx("Token in .netrc too long");
+                               return (-1);
+                       }
+               }
+       }
+       *cp = 0;
+       if (tokval[0] == 0)
+               return (0);
+       for (t = toktab; t->tokstr; t++)
+               if (!strcmp(t->tokstr, tokval))
+                       return (t->tval);
+       return (ID);
+}
diff --git a/usr.bin/ftp/ssl.c b/usr.bin/ftp/ssl.c
new file mode 100644 (file)
index 0000000..b34c864
--- /dev/null
@@ -0,0 +1,608 @@
+/*     $NetBSD: ssl.c,v 1.2 2012/12/24 22:12:28 christos Exp $ */
+
+/*-
+ * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2008, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: common.c,v 1.53 2007/12/19 00:26:36 des Exp $
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: ssl.c,v 1.2 2012/12/24 22:12:28 christos Exp $");
+#endif
+
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/param.h>
+#include <sys/select.h>
+#include <sys/uio.h>
+
+#include <netinet/tcp.h>
+#include <netinet/in.h>
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "ssl.h"
+
+extern int quit_time, verbose, ftp_debug;
+extern FILE *ttyout;
+
+struct fetch_connect {
+       int                      sd;            /* file/socket descriptor */
+       char                    *buf;           /* buffer */
+       size_t                   bufsize;       /* buffer size */
+       size_t                   bufpos;        /* position of buffer */
+       size_t                   buflen;        /* length of buffer contents */
+       struct {                                /* data cached after an
+                                                  interrupted read */
+               char    *buf;
+               size_t   size;
+               size_t   pos;
+               size_t   len;
+       } cache;
+       int                      issock;
+       int                      iserr;
+       int                      iseof;
+       SSL                     *ssl;           /* SSL handle */
+};
+
+/*
+ * Write a vector to a connection w/ timeout
+ * Note: can modify the iovec.
+ */
+static ssize_t
+fetch_writev(struct fetch_connect *conn, struct iovec *iov, int iovcnt)
+{
+       struct timeval now, timeout, delta;
+       fd_set writefds;
+       ssize_t len, total;
+       int r;
+
+       if (quit_time > 0) {
+               FD_ZERO(&writefds);
+               gettimeofday(&timeout, NULL);
+               timeout.tv_sec += quit_time;
+       }
+
+       total = 0;
+       while (iovcnt > 0) {
+               while (quit_time > 0 && !FD_ISSET(conn->sd, &writefds)) {
+                       FD_SET(conn->sd, &writefds);
+                       gettimeofday(&now, NULL);
+                       delta.tv_sec = timeout.tv_sec - now.tv_sec;
+                       delta.tv_usec = timeout.tv_usec - now.tv_usec;
+                       if (delta.tv_usec < 0) {
+                               delta.tv_usec += 1000000;
+                               delta.tv_sec--;
+                       }
+                       if (delta.tv_sec < 0) {
+                               errno = ETIMEDOUT;
+                               return -1;
+                       }
+                       errno = 0;
+                       r = select(conn->sd + 1, NULL, &writefds, NULL, &delta);
+                       if (r == -1) {
+                               if (errno == EINTR)
+                                       continue;
+                               return -1;
+                       }
+               }
+               errno = 0;
+               if (conn->ssl != NULL)
+                       len = SSL_write(conn->ssl, iov->iov_base, iov->iov_len);
+               else
+                       len = writev(conn->sd, iov, iovcnt);
+               if (len == 0) {
+                       /* we consider a short write a failure */
+                       /* XXX perhaps we shouldn't in the SSL case */
+                       errno = EPIPE;
+                       return -1;
+               }
+               if (len < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       return -1;
+               }
+               total += len;
+               while (iovcnt > 0 && len >= (ssize_t)iov->iov_len) {
+                       len -= iov->iov_len;
+                       iov++;
+                       iovcnt--;
+               }
+               if (iovcnt > 0) {
+                       iov->iov_len -= len;
+                       iov->iov_base = (char *)iov->iov_base + len;
+               }
+       }
+       return total;
+}
+
+/*
+ * Write to a connection w/ timeout
+ */
+static int
+fetch_write(struct fetch_connect *conn, const char *str, size_t len)
+{
+       struct iovec iov[1];
+
+       iov[0].iov_base = (char *)__UNCONST(str);
+       iov[0].iov_len = len;
+       return fetch_writev(conn, iov, 1);
+}
+
+/*
+ * Send a formatted line; optionally echo to terminal
+ */
+int
+fetch_printf(struct fetch_connect *conn, const char *fmt, ...)
+{
+       va_list ap;
+       size_t len;
+       char *msg;
+       int r;
+
+       va_start(ap, fmt);
+       len = vasprintf(&msg, fmt, ap);
+       va_end(ap);
+
+       if (msg == NULL) {
+               errno = ENOMEM;
+               return -1;
+       }
+
+       r = fetch_write(conn, msg, len);
+       free(msg);
+       return r;
+}
+
+int
+fetch_fileno(struct fetch_connect *conn)
+{
+
+       return conn->sd;
+}
+
+int
+fetch_error(struct fetch_connect *conn)
+{
+
+       return conn->iserr;
+}
+
+static void
+fetch_clearerr(struct fetch_connect *conn)
+{
+
+       conn->iserr = 0;
+}
+
+int
+fetch_flush(struct fetch_connect *conn)
+{
+       int v;
+
+       if (conn->issock) {
+#ifdef TCP_NOPUSH
+               v = 0;
+               setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &v, sizeof(v));
+#endif
+               v = 1;
+               setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v));
+       }
+       return 0;
+}
+
+/*ARGSUSED*/
+struct fetch_connect *
+fetch_open(const char *fname, const char *fmode)
+{
+       struct fetch_connect *conn;
+       int fd;
+
+       fd = open(fname, O_RDONLY); /* XXX: fmode */
+       if (fd < 0)
+               return NULL;
+
+       if ((conn = calloc(1, sizeof(*conn))) == NULL) {
+               close(fd);
+               return NULL;
+       }
+
+       conn->sd = fd;
+       conn->issock = 0;
+       return conn;
+}
+
+/*ARGSUSED*/
+struct fetch_connect *
+fetch_fdopen(int sd, const char *fmode)
+{
+       struct fetch_connect *conn;
+#if defined(SO_NOSIGPIPE) || defined(TCP_NOPUSH)
+       int opt = 1;
+#endif
+
+       if ((conn = calloc(1, sizeof(*conn))) == NULL)
+               return NULL;
+
+       conn->sd = sd;
+       conn->issock = 1;
+       fcntl(sd, F_SETFD, FD_CLOEXEC);
+#ifdef SO_NOSIGPIPE
+       setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
+#endif
+#ifdef TCP_NOPUSH
+       setsockopt(sd, IPPROTO_TCP, TCP_NOPUSH, &opt, sizeof(opt));
+#endif
+       return conn;
+}
+
+int
+fetch_close(struct fetch_connect *conn)
+{
+       int rv = 0;
+
+       if (conn != NULL) {
+               fetch_flush(conn);
+               SSL_free(conn->ssl);
+               rv = close(conn->sd);
+               if (rv < 0) {
+                       errno = rv;
+                       rv = EOF;
+               }
+               free(conn->cache.buf);
+               free(conn->buf);
+               free(conn);
+       }
+       return rv;
+}
+
+#define FETCH_READ_WAIT                -2
+#define FETCH_READ_ERROR       -1
+
+static ssize_t
+fetch_ssl_read(SSL *ssl, void *buf, size_t len)
+{
+       ssize_t rlen;
+       int ssl_err;
+
+       rlen = SSL_read(ssl, buf, len);
+       if (rlen < 0) {
+               ssl_err = SSL_get_error(ssl, rlen);
+               if (ssl_err == SSL_ERROR_WANT_READ ||
+                   ssl_err == SSL_ERROR_WANT_WRITE) {
+                       return FETCH_READ_WAIT;
+               }
+               ERR_print_errors_fp(ttyout);
+               return FETCH_READ_ERROR;
+       }
+       return rlen;
+}
+
+static ssize_t
+fetch_nonssl_read(int sd, void *buf, size_t len)
+{
+       ssize_t rlen;
+
+       rlen = read(sd, buf, len);
+       if (rlen < 0) {
+               if (errno == EAGAIN || errno == EINTR)
+                       return FETCH_READ_WAIT;
+               return FETCH_READ_ERROR;
+       }
+       return rlen;
+}
+
+/*
+ * Cache some data that was read from a socket but cannot be immediately
+ * returned because of an interrupted system call.
+ */
+static int
+fetch_cache_data(struct fetch_connect *conn, char *src, size_t nbytes)
+{
+
+       if (conn->cache.size < nbytes) {
+               char *tmp = realloc(conn->cache.buf, nbytes);
+               if (tmp == NULL)
+                       return -1;
+
+               conn->cache.buf = tmp;
+               conn->cache.size = nbytes;
+       }
+
+       memcpy(conn->cache.buf, src, nbytes);
+       conn->cache.len = nbytes;
+       conn->cache.pos = 0;
+       return 0;
+}
+
+ssize_t
+fetch_read(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
+{
+       struct timeval now, timeout, delta;
+       fd_set readfds;
+       ssize_t rlen, total;
+       size_t len;
+       char *start, *buf;
+
+       if (quit_time > 0) {
+               gettimeofday(&timeout, NULL);
+               timeout.tv_sec += quit_time;
+       }
+
+       total = 0;
+       start = buf = ptr;
+       len = size * nmemb;
+
+       if (conn->cache.len > 0) {
+               /*
+                * The last invocation of fetch_read was interrupted by a
+                * signal after some data had been read from the socket. Copy
+                * the cached data into the supplied buffer before trying to
+                * read from the socket again.
+                */
+               total = (conn->cache.len < len) ? conn->cache.len : len;
+               memcpy(buf, conn->cache.buf, total);
+
+               conn->cache.len -= total;
+               conn->cache.pos += total;
+               len -= total;
+               buf += total;
+       }
+
+       while (len > 0) {
+               /*
+                * The socket is non-blocking.  Instead of the canonical
+                * select() -> read(), we do the following:
+                *
+                * 1) call read() or SSL_read().
+                * 2) if an error occurred, return -1.
+                * 3) if we received data but we still expect more,
+                *    update our counters and loop.
+                * 4) if read() or SSL_read() signaled EOF, return.
+                * 5) if we did not receive any data but we're not at EOF,
+                *    call select().
+                *
+                * In the SSL case, this is necessary because if we
+                * receive a close notification, we have to call
+                * SSL_read() one additional time after we've read
+                * everything we received.
+                *
+                * In the non-SSL case, it may improve performance (very
+                * slightly) when reading small amounts of data.
+                */
+               if (conn->ssl != NULL)
+                       rlen = fetch_ssl_read(conn->ssl, buf, len);
+               else
+                       rlen = fetch_nonssl_read(conn->sd, buf, len);
+               if (rlen == 0) {
+                       break;
+               } else if (rlen > 0) {
+                       len -= rlen;
+                       buf += rlen;
+                       total += rlen;
+                       continue;
+               } else if (rlen == FETCH_READ_ERROR) {
+                       if (errno == EINTR)
+                               fetch_cache_data(conn, start, total);
+                       return -1;
+               }
+               FD_ZERO(&readfds);
+               while (!FD_ISSET(conn->sd, &readfds)) {
+                       FD_SET(conn->sd, &readfds);
+                       if (quit_time > 0) {
+                               gettimeofday(&now, NULL);
+                               if (!timercmp(&timeout, &now, >)) {
+                                       errno = ETIMEDOUT;
+                                       return -1;
+                               }
+                               timersub(&timeout, &now, &delta);
+                       }
+                       errno = 0;
+                       if (select(conn->sd + 1, &readfds, NULL, NULL,
+                               quit_time > 0 ? &delta : NULL) < 0) {
+                               if (errno == EINTR)
+                                       continue;
+                               return -1;
+                       }
+               }
+       }
+       return total;
+}
+
+#define MIN_BUF_SIZE 1024
+
+/*
+ * Read a line of text from a connection w/ timeout
+ */
+char *
+fetch_getln(char *str, int size, struct fetch_connect *conn)
+{
+       size_t tmpsize;
+       ssize_t len;
+       char c;
+
+       if (conn->buf == NULL) {
+               if ((conn->buf = malloc(MIN_BUF_SIZE)) == NULL) {
+                       errno = ENOMEM;
+                       conn->iserr = 1;
+                       return NULL;
+               }
+               conn->bufsize = MIN_BUF_SIZE;
+       }
+
+       if (conn->iserr || conn->iseof)
+               return NULL;
+
+       if (conn->buflen - conn->bufpos > 0)
+               goto done;
+
+       conn->buf[0] = '\0';
+       conn->bufpos = 0;
+       conn->buflen = 0;
+       do {
+               len = fetch_read(&c, sizeof(c), 1, conn);
+               if (len == -1) {
+                       conn->iserr = 1;
+                       return NULL;
+               }
+               if (len == 0) {
+                       conn->iseof = 1;
+                       break;
+               }
+               conn->buf[conn->buflen++] = c;
+               if (conn->buflen == conn->bufsize) {
+                       char *tmp = conn->buf;
+                       tmpsize = conn->bufsize * 2 + 1;
+                       if ((tmp = realloc(tmp, tmpsize)) == NULL) {
+                               errno = ENOMEM;
+                               conn->iserr = 1;
+                               return NULL;
+                       }
+                       conn->buf = tmp;
+                       conn->bufsize = tmpsize;
+               }
+       } while (c != '\n');
+
+       if (conn->buflen == 0)
+               return NULL;
+ done:
+       tmpsize = MIN(size - 1, (int)(conn->buflen - conn->bufpos));
+       memcpy(str, conn->buf + conn->bufpos, tmpsize);
+       str[tmpsize] = '\0';
+       conn->bufpos += tmpsize;
+       return str;
+}
+
+int
+fetch_getline(struct fetch_connect *conn, char *buf, size_t buflen,
+    const char **errormsg)
+{
+       size_t len;
+       int rv;
+
+       if (fetch_getln(buf, buflen, conn) == NULL) {
+               if (conn->iseof) {      /* EOF */
+                       rv = -2;
+                       if (errormsg)
+                               *errormsg = "\nEOF received";
+               } else {                /* error */
+                       rv = -1;
+                       if (errormsg)
+                               *errormsg = "Error encountered";
+               }
+               fetch_clearerr(conn);
+               return rv;
+       }
+       len = strlen(buf);
+       if (buf[len - 1] == '\n') {     /* clear any trailing newline */
+               buf[--len] = '\0';
+       } else if (len == buflen - 1) { /* line too long */
+               while (1) {
+                       char c;
+                       ssize_t rlen = fetch_read(&c, sizeof(c), 1, conn);
+                       if (rlen <= 0 || c == '\n')
+                               break;
+               }
+               if (errormsg)
+                       *errormsg = "Input line is too long";
+               fetch_clearerr(conn);
+               return -3;
+       }
+       if (errormsg)
+               *errormsg = NULL;
+       return len;
+}
+
+void *
+fetch_start_ssl(int sock)
+{
+       SSL *ssl;
+       SSL_CTX *ctx;
+       int ret, ssl_err;
+
+       /* Init the SSL library and context */
+       if (!SSL_library_init()){
+               fprintf(ttyout, "SSL library init failed\n");
+               return NULL;
+       }
+
+       SSL_load_error_strings();
+
+       ctx = SSL_CTX_new(SSLv23_client_method());
+       SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+
+       ssl = SSL_new(ctx);
+       if (ssl == NULL){
+               fprintf(ttyout, "SSL context creation failed\n");
+               SSL_CTX_free(ctx);
+               return NULL;
+       }
+       SSL_set_fd(ssl, sock);
+       while ((ret = SSL_connect(ssl)) == -1) {
+               ssl_err = SSL_get_error(ssl, ret);
+               if (ssl_err != SSL_ERROR_WANT_READ &&
+                   ssl_err != SSL_ERROR_WANT_WRITE) {
+                       ERR_print_errors_fp(ttyout);
+                       SSL_free(ssl);
+                       return NULL;
+               }
+       }
+
+       if (ftp_debug && verbose) {
+               X509 *cert;
+               X509_NAME *name;
+               char *str;
+
+               fprintf(ttyout, "SSL connection established using %s\n",
+                   SSL_get_cipher(ssl));
+               cert = SSL_get_peer_certificate(ssl);
+               name = X509_get_subject_name(cert);
+               str = X509_NAME_oneline(name, 0, 0);
+               fprintf(ttyout, "Certificate subject: %s\n", str);
+               free(str);
+               name = X509_get_issuer_name(cert);
+               str = X509_NAME_oneline(name, 0, 0);
+               fprintf(ttyout, "Certificate issuer: %s\n", str);
+               free(str);
+       }
+
+       return ssl;
+}
+
+
+void
+fetch_set_ssl(struct fetch_connect *conn, void *ssl)
+{
+       conn->ssl = ssl;
+}
diff --git a/usr.bin/ftp/ssl.h b/usr.bin/ftp/ssl.h
new file mode 100644 (file)
index 0000000..9ecc759
--- /dev/null
@@ -0,0 +1,62 @@
+/*     $NetBSD: ssl.h,v 1.1 2012/12/21 18:07:36 christos Exp $ */
+
+/*-
+ * Copyright (c) 2012 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifdef WITH_SSL
+
+#define FETCH struct fetch_connect
+struct fetch_connect;
+
+int fetch_printf(struct fetch_connect *, const char *fmt, ...);
+int fetch_fileno(struct fetch_connect *);
+int fetch_error(struct fetch_connect *);
+int fetch_flush(struct fetch_connect *);
+struct fetch_connect *fetch_open(const char *, const char *);
+struct fetch_connect *fetch_fdopen(int, const char *);
+int fetch_close(struct fetch_connect *);
+ssize_t fetch_read(void *, size_t, size_t, struct fetch_connect *);
+char *fetch_getln(char *, int, struct fetch_connect *);
+int fetch_getline(struct fetch_connect *, char *, size_t, const char **);
+void fetch_set_ssl(struct fetch_connect *, void *);
+void *fetch_start_ssl(int);
+
+#else  /* !WITH_SSL */
+
+#define FETCH FILE
+
+#define        fetch_printf    fprintf
+#define        fetch_fileno    fileno
+#define        fetch_error     ferror
+#define        fetch_flush     fflush
+#define        fetch_open      fopen
+#define        fetch_fdopen    fdopen
+#define        fetch_close     fclose
+#define        fetch_read      fread
+#define        fetch_getln     fgets
+#define        fetch_getline   get_line
+#define        fetch_set_ssl(a, b)
+
+#endif /* !WITH_SSL */
diff --git a/usr.bin/ftp/util.c b/usr.bin/ftp/util.c
new file mode 100644 (file)
index 0000000..ee610c6
--- /dev/null
@@ -0,0 +1,1543 @@
+/*     $NetBSD: util.c,v 1.158 2013/02/19 23:29:15 dsl Exp $   */
+
+/*-
+ * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
+ * NASA Ames Research Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: util.c,v 1.158 2013/02/19 23:29:15 dsl Exp $");
+#endif /* not lint */
+
+/*
+ * FTP User Program -- Misc support routines
+ */
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <arpa/ftp.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <signal.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#include "ftp_var.h"
+
+/*
+ * Connect to peer server and auto-login, if possible.
+ */
+void
+setpeer(int argc, char *argv[])
+{
+       char *host;
+       const char *port;
+
+       if (argc == 0)
+               goto usage;
+       if (connected) {
+               fprintf(ttyout, "Already connected to %s, use close first.\n",
+                   hostname);
+               code = -1;
+               return;
+       }
+       if (argc < 2)
+               (void)another(&argc, &argv, "to");
+       if (argc < 2 || argc > 3) {
+ usage:
+               UPRINTF("usage: %s host-name [port]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (gatemode)
+               port = gateport;
+       else
+               port = ftpport;
+       if (argc > 2)
+               port = argv[2];
+
+       if (gatemode) {
+               if (gateserver == NULL || *gateserver == '\0')
+                       errx(1, "main: gateserver not defined");
+               host = hookup(gateserver, port);
+       } else
+               host = hookup(argv[1], port);
+
+       if (host) {
+               if (gatemode && verbose) {
+                       fprintf(ttyout,
+                           "Connecting via pass-through server %s\n",
+                           gateserver);
+               }
+
+               connected = 1;
+               /*
+                * Set up defaults for FTP.
+                */
+               (void)strlcpy(typename, "ascii", sizeof(typename));
+               type = TYPE_A;
+               curtype = TYPE_A;
+               (void)strlcpy(formname, "non-print", sizeof(formname));
+               form = FORM_N;
+               (void)strlcpy(modename, "stream", sizeof(modename));
+               mode = MODE_S;
+               (void)strlcpy(structname, "file", sizeof(structname));
+               stru = STRU_F;
+               (void)strlcpy(bytename, "8", sizeof(bytename));
+               bytesize = 8;
+               if (autologin)
+                       (void)ftp_login(argv[1], NULL, NULL);
+       }
+}
+
+static void
+parse_feat(const char *fline)
+{
+
+                       /*
+                        * work-around broken ProFTPd servers that can't
+                        * even obey RFC 2389.
+                        */
+       while (*fline && isspace((int)*fline))
+               fline++;
+
+       if (strcasecmp(fline, "MDTM") == 0)
+               features[FEAT_MDTM] = 1;
+       else if (strncasecmp(fline, "MLST", sizeof("MLST") - 1) == 0) {
+               features[FEAT_MLST] = 1;
+       } else if (strcasecmp(fline, "REST STREAM") == 0)
+               features[FEAT_REST_STREAM] = 1;
+       else if (strcasecmp(fline, "SIZE") == 0)
+               features[FEAT_SIZE] = 1;
+       else if (strcasecmp(fline, "TVFS") == 0)
+               features[FEAT_TVFS] = 1;
+}
+
+/*
+ * Determine the remote system type (SYST) and features (FEAT).
+ * Call after a successful login (i.e, connected = -1)
+ */
+void
+getremoteinfo(void)
+{
+       int overbose, i;
+
+       overbose = verbose;
+       if (ftp_debug == 0)
+               verbose = -1;
+
+                       /* determine remote system type */
+       if (command("SYST") == COMPLETE) {
+               if (overbose) {
+                       int os_len = strcspn(reply_string + 4, " \r\n\t");
+                       if (os_len > 1 && reply_string[4 + os_len - 1] == '.')
+                               os_len--;
+                       fprintf(ttyout, "Remote system type is %.*s.\n",
+                           os_len, reply_string + 4);
+               }
+               /*
+                * Decide whether we should default to bninary.
+                * Traditionally checked for "215 UNIX Type: L8", but
+                * some printers report "Linux" ! so be more forgiving.
+                * In reality we probably almost never want text any more.
+                */
+               if (!strncasecmp(reply_string + 4, "unix", 4) ||
+                   !strncasecmp(reply_string + 4, "linux", 5)) {
+                       if (proxy)
+                               unix_proxy = 1;
+                       else
+                               unix_server = 1;
+                       /*
+                        * Set type to 0 (not specified by user),
+                        * meaning binary by default, but don't bother
+                        * telling server.  We can use binary
+                        * for text files unless changed by the user.
+                        */
+                       type = 0;
+                       (void)strlcpy(typename, "binary", sizeof(typename));
+                       if (overbose)
+                           fprintf(ttyout,
+                               "Using %s mode to transfer files.\n",
+                               typename);
+               } else {
+                       if (proxy)
+                               unix_proxy = 0;
+                       else
+                               unix_server = 0;
+                       if (overbose &&
+                           !strncmp(reply_string, "215 TOPS20", 10))
+                               fputs(
+"Remember to set tenex mode when transferring binary files from this machine.\n",
+                                   ttyout);
+               }
+       }
+
+                       /* determine features (if any) */
+       for (i = 0; i < FEAT_max; i++)
+               features[i] = -1;
+       reply_callback = parse_feat;
+       if (command("FEAT") == COMPLETE) {
+               for (i = 0; i < FEAT_max; i++) {
+                       if (features[i] == -1)
+                               features[i] = 0;
+               }
+               features[FEAT_FEAT] = 1;
+       } else
+               features[FEAT_FEAT] = 0;
+#ifndef NO_DEBUG
+       if (ftp_debug) {
+#define DEBUG_FEAT(x) fprintf(ttyout, "features[" #x "] = %d\n", features[(x)])
+               DEBUG_FEAT(FEAT_FEAT);
+               DEBUG_FEAT(FEAT_MDTM);
+               DEBUG_FEAT(FEAT_MLST);
+               DEBUG_FEAT(FEAT_REST_STREAM);
+               DEBUG_FEAT(FEAT_SIZE);
+               DEBUG_FEAT(FEAT_TVFS);
+#undef DEBUG_FEAT
+       }
+#endif
+       reply_callback = NULL;
+
+       verbose = overbose;
+}
+
+/*
+ * Reset the various variables that indicate connection state back to
+ * disconnected settings.
+ * The caller is responsible for issuing any commands to the remote server
+ * to perform a clean shutdown before this is invoked.
+ */
+void
+cleanuppeer(void)
+{
+
+       if (cout)
+               (void)fclose(cout);
+       cout = NULL;
+       connected = 0;
+       unix_server = 0;
+       unix_proxy = 0;
+                       /*
+                        * determine if anonftp was specifically set with -a
+                        * (1), or implicitly set by auto_fetch() (2). in the
+                        * latter case, disable after the current xfer
+                        */
+       if (anonftp == 2)
+               anonftp = 0;
+       data = -1;
+       epsv4bad = 0;
+       epsv6bad = 0;
+       if (username)
+               free(username);
+       username = NULL;
+       if (!proxy)
+               macnum = 0;
+}
+
+/*
+ * Top-level signal handler for interrupted commands.
+ */
+void
+intr(int signo)
+{
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       siglongjmp(toplevel, 1);
+}
+
+/*
+ * Signal handler for lost connections; cleanup various elements of
+ * the connection state, and call cleanuppeer() to finish it off.
+ */
+void
+lostpeer(int dummy)
+{
+       int oerrno = errno;
+
+       alarmtimer(0);
+       if (connected) {
+               if (cout != NULL) {
+                       (void)shutdown(fileno(cout), 1+1);
+                       (void)fclose(cout);
+                       cout = NULL;
+               }
+               if (data >= 0) {
+                       (void)shutdown(data, 1+1);
+                       (void)close(data);
+                       data = -1;
+               }
+               connected = 0;
+       }
+       pswitch(1);
+       if (connected) {
+               if (cout != NULL) {
+                       (void)shutdown(fileno(cout), 1+1);
+                       (void)fclose(cout);
+                       cout = NULL;
+               }
+               connected = 0;
+       }
+       proxflag = 0;
+       pswitch(0);
+       cleanuppeer();
+       errno = oerrno;
+}
+
+
+/*
+ * Login to remote host, using given username & password if supplied.
+ * Return non-zero if successful.
+ */
+int
+ftp_login(const char *host, const char *luser, const char *lpass)
+{
+       char tmp[80];
+       char *fuser, *pass, *facct, *p;
+       char emptypass[] = "";
+       const char *errormsg;
+       int n, aflag, rval, nlen;
+
+       aflag = rval = 0;
+       fuser = pass = facct = NULL;
+       if (luser)
+               fuser = ftp_strdup(luser);
+       if (lpass)
+               pass = ftp_strdup(lpass);
+
+       DPRINTF("ftp_login: user `%s' pass `%s' host `%s'\n",
+           STRorNULL(fuser), STRorNULL(pass), STRorNULL(host));
+
+       /*
+        * Set up arguments for an anonymous FTP session, if necessary.
+        */
+       if (anonftp) {
+               FREEPTR(fuser);
+               fuser = ftp_strdup("anonymous");        /* as per RFC 1635 */
+               FREEPTR(pass);
+               pass = ftp_strdup(getoptionvalue("anonpass"));
+       }
+
+       if (ruserpass(host, &fuser, &pass, &facct) < 0) {
+               code = -1;
+               goto cleanup_ftp_login;
+       }
+
+       while (fuser == NULL) {
+               if (localname)
+                       fprintf(ttyout, "Name (%s:%s): ", host, localname);
+               else
+                       fprintf(ttyout, "Name (%s): ", host);
+               errormsg = NULL;
+               nlen = get_line(stdin, tmp, sizeof(tmp), &errormsg);
+               if (nlen < 0) {
+                       fprintf(ttyout, "%s; %s aborted.\n", errormsg, "login");
+                       code = -1;
+                       goto cleanup_ftp_login;
+               } else if (nlen == 0) {
+                       fuser = ftp_strdup(localname);
+               } else {
+                       fuser = ftp_strdup(tmp);
+               }
+       }
+
+       if (gatemode) {
+               char *nuser;
+               size_t len;
+
+               len = strlen(fuser) + 1 + strlen(host) + 1;
+               nuser = ftp_malloc(len);
+               (void)strlcpy(nuser, fuser, len);
+               (void)strlcat(nuser, "@",  len);
+               (void)strlcat(nuser, host, len);
+               FREEPTR(fuser);
+               fuser = nuser;
+       }
+
+       n = command("USER %s", fuser);
+       if (n == CONTINUE) {
+               if (pass == NULL) {
+                       p = getpass("Password: ");
+                       if (p == NULL)
+                               p = emptypass;
+                       pass = ftp_strdup(p);
+                       memset(p, 0, strlen(p));
+               }
+               n = command("PASS %s", pass);
+               memset(pass, 0, strlen(pass));
+       }
+       if (n == CONTINUE) {
+               aflag++;
+               if (facct == NULL) {
+                       p = getpass("Account: ");
+                       if (p == NULL)
+                               p = emptypass;
+                       facct = ftp_strdup(p);
+                       memset(p, 0, strlen(p));
+               }
+               if (facct[0] == '\0') {
+                       warnx("Login failed");
+                       goto cleanup_ftp_login;
+               }
+               n = command("ACCT %s", facct);
+               memset(facct, 0, strlen(facct));
+       }
+       if ((n != COMPLETE) ||
+           (!aflag && facct != NULL && command("ACCT %s", facct) != COMPLETE)) {
+               warnx("Login failed");
+               goto cleanup_ftp_login;
+       }
+       rval = 1;
+       username = ftp_strdup(fuser);
+       if (proxy)
+               goto cleanup_ftp_login;
+
+       connected = -1;
+       getremoteinfo();
+       for (n = 0; n < macnum; ++n) {
+               if (!strcmp("init", macros[n].mac_name)) {
+                       (void)strlcpy(line, "$init", sizeof(line));
+                       makeargv();
+                       domacro(margc, margv);
+                       break;
+               }
+       }
+       updatelocalcwd();
+       updateremotecwd();
+
+ cleanup_ftp_login:
+       FREEPTR(fuser);
+       if (pass != NULL)
+               memset(pass, 0, strlen(pass));
+       FREEPTR(pass);
+       if (facct != NULL)
+               memset(facct, 0, strlen(facct));
+       FREEPTR(facct);
+       return (rval);
+}
+
+/*
+ * `another' gets another argument, and stores the new argc and argv.
+ * It reverts to the top level (via intr()) on EOF/error.
+ *
+ * Returns false if no new arguments have been added.
+ */
+int
+another(int *pargc, char ***pargv, const char *aprompt)
+{
+       const char      *errormsg;
+       int             ret, nlen;
+       size_t          len;
+
+       len = strlen(line);
+       if (len >= sizeof(line) - 3) {
+               fputs("Sorry, arguments too long.\n", ttyout);
+               intr(0);
+       }
+       fprintf(ttyout, "(%s) ", aprompt);
+       line[len++] = ' ';
+       errormsg = NULL;
+       nlen = get_line(stdin, line + len, sizeof(line)-len, &errormsg);
+       if (nlen < 0) {
+               fprintf(ttyout, "%s; %s aborted.\n", errormsg, "operation");
+               intr(0);
+       }
+       len += nlen;
+       makeargv();
+       ret = margc > *pargc;
+       *pargc = margc;
+       *pargv = margv;
+       return (ret);
+}
+
+/*
+ * glob files given in argv[] from the remote server.
+ * if errbuf isn't NULL, store error messages there instead
+ * of writing to the screen.
+ */
+char *
+remglob(char *argv[], int doswitch, const char **errbuf)
+{
+       static char buf[MAXPATHLEN];
+       static FILE *ftemp = NULL;
+       static char **args;
+       char temp[MAXPATHLEN];
+       int oldverbose, oldhash, oldprogress, fd;
+       char *cp;
+       const char *rmode;
+       size_t len;
+
+       if (!mflag || !connected) {
+               if (!doglob)
+                       args = NULL;
+               else {
+                       if (ftemp) {
+                               (void)fclose(ftemp);
+                               ftemp = NULL;
+                       }
+               }
+               return (NULL);
+       }
+       if (!doglob) {
+               if (args == NULL)
+                       args = argv;
+               if ((cp = *++args) == NULL)
+                       args = NULL;
+               return (cp);
+       }
+       if (ftemp == NULL) {
+               len = strlcpy(temp, tmpdir, sizeof(temp));
+               if (temp[len - 1] != '/')
+                       (void)strlcat(temp, "/", sizeof(temp));
+               (void)strlcat(temp, TMPFILE, sizeof(temp));
+               if ((fd = mkstemp(temp)) < 0) {
+                       warn("Unable to create temporary file `%s'", temp);
+                       return (NULL);
+               }
+               close(fd);
+               oldverbose = verbose;
+               verbose = (errbuf != NULL) ? -1 : 0;
+               oldhash = hash;
+               oldprogress = progress;
+               hash = 0;
+               progress = 0;
+               if (doswitch)
+                       pswitch(!proxy);
+               for (rmode = "w"; *++argv != NULL; rmode = "a")
+                       recvrequest("NLST", temp, *argv, rmode, 0, 0);
+               if ((code / 100) != COMPLETE) {
+                       if (errbuf != NULL)
+                               *errbuf = reply_string;
+               }
+               if (doswitch)
+                       pswitch(!proxy);
+               verbose = oldverbose;
+               hash = oldhash;
+               progress = oldprogress;
+               ftemp = fopen(temp, "r");
+               (void)unlink(temp);
+               if (ftemp == NULL) {
+                       if (errbuf == NULL)
+                               warnx("Can't find list of remote files");
+                       else
+                               *errbuf =
+                                   "Can't find list of remote files";
+                       return (NULL);
+               }
+       }
+       if (fgets(buf, sizeof(buf), ftemp) == NULL) {
+               (void)fclose(ftemp);
+               ftemp = NULL;
+               return (NULL);
+       }
+       if ((cp = strchr(buf, '\n')) != NULL)
+               *cp = '\0';
+       return (buf);
+}
+
+/*
+ * Glob a local file name specification with the expectation of a single
+ * return value. Can't control multiple values being expanded from the
+ * expression, we return only the first.
+ * Returns NULL on error, or a pointer to a buffer containing the filename
+ * that's the caller's responsiblity to free(3) when finished with.
+ */
+char *
+globulize(const char *pattern)
+{
+       glob_t gl;
+       int flags;
+       char *p;
+
+       if (!doglob)
+               return (ftp_strdup(pattern));
+
+       flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
+       memset(&gl, 0, sizeof(gl));
+       if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
+               warnx("Glob pattern `%s' not found", pattern);
+               globfree(&gl);
+               return (NULL);
+       }
+       p = ftp_strdup(gl.gl_pathv[0]);
+       globfree(&gl);
+       return (p);
+}
+
+/*
+ * determine size of remote file
+ */
+off_t
+remotesize(const char *file, int noisy)
+{
+       int overbose, r;
+       off_t size;
+
+       overbose = verbose;
+       size = -1;
+       if (ftp_debug == 0)
+               verbose = -1;
+       if (! features[FEAT_SIZE]) {
+               if (noisy)
+                       fprintf(ttyout,
+                           "SIZE is not supported by remote server.\n");
+               goto cleanup_remotesize;
+       }
+       r = command("SIZE %s", file);
+       if (r == COMPLETE) {
+               char *cp, *ep;
+
+               cp = strchr(reply_string, ' ');
+               if (cp != NULL) {
+                       cp++;
+                       size = STRTOLL(cp, &ep, 10);
+                       if (*ep != '\0' && !isspace((unsigned char)*ep))
+                               size = -1;
+               }
+       } else {
+               if (r == ERROR && code == 500 && features[FEAT_SIZE] == -1)
+                       features[FEAT_SIZE] = 0;
+               if (noisy && ftp_debug == 0) {
+                       fputs(reply_string, ttyout);
+                       putc('\n', ttyout);
+               }
+       }
+ cleanup_remotesize:
+       verbose = overbose;
+       return (size);
+}
+
+/*
+ * determine last modification time (in GMT) of remote file
+ */
+time_t
+remotemodtime(const char *file, int noisy)
+{
+       int     overbose, ocode, r;
+       time_t  rtime;
+
+       overbose = verbose;
+       ocode = code;
+       rtime = -1;
+       if (ftp_debug == 0)
+               verbose = -1;
+       if (! features[FEAT_MDTM]) {
+               if (noisy)
+                       fprintf(ttyout,
+                           "MDTM is not supported by remote server.\n");
+               goto cleanup_parse_time;
+       }
+       r = command("MDTM %s", file);
+       if (r == COMPLETE) {
+               struct tm timebuf;
+               char *timestr, *frac;
+
+               /*
+                * time-val = 14DIGIT [ "." 1*DIGIT ]
+                *              YYYYMMDDHHMMSS[.sss]
+                * mdtm-response = "213" SP time-val CRLF / error-response
+                */
+               timestr = reply_string + 4;
+
+                                       /*
+                                        * parse fraction.
+                                        * XXX: ignored for now
+                                        */
+               frac = strchr(timestr, '\r');
+               if (frac != NULL)
+                       *frac = '\0';
+               frac = strchr(timestr, '.');
+               if (frac != NULL)
+                       *frac++ = '\0';
+               if (strlen(timestr) == 15 && strncmp(timestr, "191", 3) == 0) {
+                       /*
+                        * XXX: Workaround for lame ftpd's that return
+                        *      `19100' instead of `2000'
+                        */
+                       fprintf(ttyout,
+           "Y2K warning! Incorrect time-val `%s' received from server.\n",
+                           timestr);
+                       timestr++;
+                       timestr[0] = '2';
+                       timestr[1] = '0';
+                       fprintf(ttyout, "Converted to `%s'\n", timestr);
+               }
+               memset(&timebuf, 0, sizeof(timebuf));
+               if (strlen(timestr) != 14 ||
+                   (strptime(timestr, "%Y%m%d%H%M%S", &timebuf) == NULL)) {
+ bad_parse_time:
+                       fprintf(ttyout, "Can't parse time `%s'.\n", timestr);
+                       goto cleanup_parse_time;
+               }
+               timebuf.tm_isdst = -1;
+               rtime = timegm(&timebuf);
+               if (rtime == -1) {
+                       if (noisy || ftp_debug != 0)
+                               goto bad_parse_time;
+                       else
+                               goto cleanup_parse_time;
+               } else {
+                       DPRINTF("remotemodtime: parsed time `%s' as " LLF
+                           ", %s",
+                           timestr, (LLT)rtime,
+                           rfc2822time(localtime(&rtime)));
+               }
+       } else {
+               if (r == ERROR && code == 500 && features[FEAT_MDTM] == -1)
+                       features[FEAT_MDTM] = 0;
+               if (noisy && ftp_debug == 0) {
+                       fputs(reply_string, ttyout);
+                       putc('\n', ttyout);
+               }
+       }
+ cleanup_parse_time:
+       verbose = overbose;
+       if (rtime == -1)
+               code = ocode;
+       return (rtime);
+}
+
+/*
+ * Format tm in an RFC 2822 compatible manner, with a trailing \n.
+ * Returns a pointer to a static string containing the result.
+ */
+const char *
+rfc2822time(const struct tm *tm)
+{
+       static char result[50];
+
+       if (strftime(result, sizeof(result),
+           "%a, %d %b %Y %H:%M:%S %z\n", tm) == 0)
+               errx(1, "Can't convert RFC 2822 time: buffer too small");
+       return result;
+}
+
+/*
+ * Parse HTTP-date as per RFC 2616.
+ * Return a pointer to the next character of the consumed date string,
+ * or NULL if failed.
+ */
+const char *
+parse_rfc2616time(struct tm *parsed, const char *httpdate)
+{
+       const char *t;
+       const char *curlocale;
+
+       /* The representation of %a depends on the current locale. */
+       curlocale = setlocale(LC_TIME, NULL);
+       (void)setlocale(LC_TIME, "C");
+                                                               /* RFC 1123 */
+       if ((t = strptime(httpdate, "%a, %d %b %Y %H:%M:%S GMT", parsed)) ||
+                                                               /* RFC 850 */
+           (t = strptime(httpdate, "%a, %d-%b-%y %H:%M:%S GMT", parsed)) ||
+                                                               /* asctime */
+           (t = strptime(httpdate, "%a, %b %d %H:%M:%S %Y", parsed))) {
+               ;                       /* do nothing */
+       }
+       (void)setlocale(LC_TIME, curlocale);
+       return t;
+}
+
+/*
+ * Update global `localcwd', which contains the state of the local cwd
+ */
+void
+updatelocalcwd(void)
+{
+
+       if (getcwd(localcwd, sizeof(localcwd)) == NULL)
+               localcwd[0] = '\0';
+       DPRINTF("updatelocalcwd: got `%s'\n", localcwd);
+}
+
+/*
+ * Update global `remotecwd', which contains the state of the remote cwd
+ */
+void
+updateremotecwd(void)
+{
+       int      overbose, ocode;
+       size_t   i;
+       char    *cp;
+
+       overbose = verbose;
+       ocode = code;
+       if (ftp_debug == 0)
+               verbose = -1;
+       if (command("PWD") != COMPLETE)
+               goto badremotecwd;
+       cp = strchr(reply_string, ' ');
+       if (cp == NULL || cp[0] == '\0' || cp[1] != '"')
+               goto badremotecwd;
+       cp += 2;
+       for (i = 0; *cp && i < sizeof(remotecwd) - 1; i++, cp++) {
+               if (cp[0] == '"') {
+                       if (cp[1] == '"')
+                               cp++;
+                       else
+                               break;
+               }
+               remotecwd[i] = *cp;
+       }
+       remotecwd[i] = '\0';
+       DPRINTF("updateremotecwd: got `%s'\n", remotecwd);
+       goto cleanupremotecwd;
+ badremotecwd:
+       remotecwd[0]='\0';
+ cleanupremotecwd:
+       verbose = overbose;
+       code = ocode;
+}
+
+/*
+ * Ensure file is in or under dir.
+ * Returns 1 if so, 0 if not (or an error occurred).
+ */
+int
+fileindir(const char *file, const char *dir)
+{
+       char    parentdirbuf[PATH_MAX+1], *parentdir;
+       char    realdir[PATH_MAX+1];
+       size_t  dirlen;
+
+                                       /* determine parent directory of file */
+       (void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
+       parentdir = dirname(parentdirbuf);
+       if (strcmp(parentdir, ".") == 0)
+               return 1;               /* current directory is ok */
+
+                                       /* find the directory */
+       if (realpath(parentdir, realdir) == NULL) {
+               warn("Unable to determine real path of `%s'", parentdir);
+               return 0;
+       }
+       if (realdir[0] != '/')          /* relative result is ok */
+               return 1;
+       dirlen = strlen(dir);
+       if (strncmp(realdir, dir, dirlen) == 0 &&
+           (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
+               return 1;
+       return 0;
+}
+
+/*
+ * List words in stringlist, vertically arranged
+ */
+void
+list_vertical(StringList *sl)
+{
+       size_t i, j;
+       size_t columns, lines;
+       char *p;
+       size_t w, width;
+
+       width = 0;
+
+       for (i = 0 ; i < sl->sl_cur ; i++) {
+               w = strlen(sl->sl_str[i]);
+               if (w > width)
+                       width = w;
+       }
+       width = (width + 8) &~ 7;
+
+       columns = ttywidth / width;
+       if (columns == 0)
+               columns = 1;
+       lines = (sl->sl_cur + columns - 1) / columns;
+       for (i = 0; i < lines; i++) {
+               for (j = 0; j < columns; j++) {
+                       p = sl->sl_str[j * lines + i];
+                       if (p)
+                               fputs(p, ttyout);
+                       if (j * lines + i + lines >= sl->sl_cur) {
+                               putc('\n', ttyout);
+                               break;
+                       }
+                       if (p) {
+                               w = strlen(p);
+                               while (w < width) {
+                                       w = (w + 8) &~ 7;
+                                       (void)putc('\t', ttyout);
+                               }
+                       }
+               }
+       }
+}
+
+/*
+ * Update the global ttywidth value, using TIOCGWINSZ.
+ */
+void
+setttywidth(int a)
+{
+       struct winsize winsize;
+       int oerrno = errno;
+
+       if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1 &&
+           winsize.ws_col != 0)
+               ttywidth = winsize.ws_col;
+       else
+               ttywidth = 80;
+       errno = oerrno;
+}
+
+/*
+ * Change the rate limit up (SIGUSR1) or down (SIGUSR2)
+ */
+void
+crankrate(int sig)
+{
+
+       switch (sig) {
+       case SIGUSR1:
+               if (rate_get)
+                       rate_get += rate_get_incr;
+               if (rate_put)
+                       rate_put += rate_put_incr;
+               break;
+       case SIGUSR2:
+               if (rate_get && rate_get > rate_get_incr)
+                       rate_get -= rate_get_incr;
+               if (rate_put && rate_put > rate_put_incr)
+                       rate_put -= rate_put_incr;
+               break;
+       default:
+               err(1, "crankrate invoked with unknown signal: %d", sig);
+       }
+}
+
+
+/*
+ * Setup or cleanup EditLine structures
+ */
+#ifndef NO_EDITCOMPLETE
+void
+controlediting(void)
+{
+       if (editing && el == NULL && hist == NULL) {
+               HistEvent ev;
+               int editmode;
+
+               el = el_init(getprogname(), stdin, ttyout, stderr);
+               /* init editline */
+               hist = history_init();          /* init the builtin history */
+               history(hist, &ev, H_SETSIZE, 100);/* remember 100 events */
+               el_set(el, EL_HIST, history, hist);     /* use history */
+
+               el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */
+               el_set(el, EL_PROMPT, prompt);  /* set the prompt functions */
+               el_set(el, EL_RPROMPT, rprompt);
+
+               /* add local file completion, bind to TAB */
+               el_set(el, EL_ADDFN, "ftp-complete",
+                   "Context sensitive argument completion",
+                   complete);
+               el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
+               el_source(el, NULL);    /* read ~/.editrc */
+               if ((el_get(el, EL_EDITMODE, &editmode) != -1) && editmode == 0)
+                       editing = 0;    /* the user doesn't want editing,
+                                        * so disable, and let statement
+                                        * below cleanup */
+               else
+                       el_set(el, EL_SIGNAL, 1);
+       }
+       if (!editing) {
+               if (hist) {
+                       history_end(hist);
+                       hist = NULL;
+               }
+               if (el) {
+                       el_end(el);
+                       el = NULL;
+               }
+       }
+}
+#endif /* !NO_EDITCOMPLETE */
+
+/*
+ * Convert the string `arg' to an int, which may have an optional SI suffix
+ * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise.
+ */
+int
+strsuftoi(const char *arg)
+{
+       char *cp;
+       long val;
+
+       if (!isdigit((unsigned char)arg[0]))
+               return (-1);
+
+       val = strtol(arg, &cp, 10);
+       if (cp != NULL) {
+               if (cp[0] != '\0' && cp[1] != '\0')
+                        return (-1);
+               switch (tolower((unsigned char)cp[0])) {
+               case '\0':
+               case 'b':
+                       break;
+               case 'k':
+                       val <<= 10;
+                       break;
+               case 'm':
+                       val <<= 20;
+                       break;
+               case 'g':
+                       val <<= 30;
+                       break;
+               default:
+                       return (-1);
+               }
+       }
+       if (val < 0 || val > INT_MAX)
+               return (-1);
+
+       return (val);
+}
+
+/*
+ * Set up socket buffer sizes before a connection is made.
+ */
+void
+setupsockbufsize(int sock)
+{
+       socklen_t slen;
+
+       if (0 == rcvbuf_size) {
+               slen = sizeof(rcvbuf_size);
+               if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF,
+                   (void *)&rcvbuf_size, &slen) == -1)
+                       err(1, "Unable to determine rcvbuf size");
+               if (rcvbuf_size <= 0)
+                       rcvbuf_size = 8 * 1024;
+               if (rcvbuf_size > 8 * 1024 * 1024)
+                       rcvbuf_size = 8 * 1024 * 1024;
+               DPRINTF("setupsockbufsize: rcvbuf_size determined as %d\n",
+                   rcvbuf_size);
+       }
+       if (0 == sndbuf_size) {
+               slen = sizeof(sndbuf_size);
+               if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF,
+                   (void *)&sndbuf_size, &slen) == -1)
+                       err(1, "Unable to determine sndbuf size");
+               if (sndbuf_size <= 0)
+                       sndbuf_size = 8 * 1024;
+               if (sndbuf_size > 8 * 1024 * 1024)
+                       sndbuf_size = 8 * 1024 * 1024;
+               DPRINTF("setupsockbufsize: sndbuf_size determined as %d\n",
+                   sndbuf_size);
+       }
+
+       if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
+           (void *)&sndbuf_size, sizeof(sndbuf_size)) == -1)
+               warn("Unable to set sndbuf size %d", sndbuf_size);
+
+       if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
+           (void *)&rcvbuf_size, sizeof(rcvbuf_size)) == -1)
+               warn("Unable to set rcvbuf size %d", rcvbuf_size);
+}
+
+/*
+ * Copy characters from src into dst, \ quoting characters that require it
+ */
+void
+ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
+{
+       size_t  di, si;
+
+       di = si = 0;
+       while (src[si] != '\0' && di < dstlen && si < srclen) {
+               switch (src[si]) {
+               case '\\':
+               case ' ':
+               case '\t':
+               case '\r':
+               case '\n':
+               case '"':
+                       /*
+                        * Need room for two characters and NUL, avoiding
+                        * incomplete escape sequences at end of dst.
+                        */
+                       if (di >= dstlen - 3)
+                               break;
+                       dst[di++] = '\\';
+                       /* FALLTHROUGH */
+               default:
+                       dst[di] = src[si++];
+                       if (di < dstlen)
+                               di++;
+               }
+       }
+       dst[di] = '\0';
+}
+
+/*
+ * Copy src into buf (which is len bytes long), expanding % sequences.
+ */
+void
+formatbuf(char *buf, size_t len, const char *src)
+{
+       const char      *p, *p2, *q;
+       size_t           i;
+       int              op, updirs, pdirs;
+
+#define ADDBUF(x) do { \
+               if (i >= len - 1) \
+                       goto endbuf; \
+               buf[i++] = (x); \
+       } while (0)
+
+       p = src;
+       for (i = 0; *p; p++) {
+               if (*p != '%') {
+                       ADDBUF(*p);
+                       continue;
+               }
+               p++;
+
+               switch (op = *p) {
+
+               case '/':
+               case '.':
+               case 'c':
+                       p2 = connected ? remotecwd : "";
+                       updirs = pdirs = 0;
+
+                       /* option to determine fixed # of dirs from path */
+                       if (op == '.' || op == 'c') {
+                               int skip;
+
+                               q = p2;
+                               while (*p2)             /* calc # of /'s */
+                                       if (*p2++ == '/')
+                                               updirs++;
+                               if (p[1] == '0') {      /* print <x> or ... */
+                                       pdirs = 1;
+                                       p++;
+                               }
+                               if (p[1] >= '1' && p[1] <= '9') {
+                                                       /* calc # to skip  */
+                                       skip = p[1] - '0';
+                                       p++;
+                               } else
+                                       skip = 1;
+
+                               updirs -= skip;
+                               while (skip-- > 0) {
+                                       while ((p2 > q) && (*p2 != '/'))
+                                               p2--;   /* back up */
+                                       if (skip && p2 > q)
+                                               p2--;
+                               }
+                               if (*p2 == '/' && p2 != q)
+                                       p2++;
+                       }
+
+                       if (updirs > 0 && pdirs) {
+                               if (i >= len - 5)
+                                       break;
+                               if (op == '.') {
+                                       ADDBUF('.');
+                                       ADDBUF('.');
+                                       ADDBUF('.');
+                               } else {
+                                       ADDBUF('/');
+                                       ADDBUF('<');
+                                       if (updirs > 9) {
+                                               ADDBUF('9');
+                                               ADDBUF('+');
+                                       } else
+                                               ADDBUF('0' + updirs);
+                                       ADDBUF('>');
+                               }
+                       }
+                       for (; *p2; p2++)
+                               ADDBUF(*p2);
+                       break;
+
+               case 'M':
+               case 'm':
+                       for (p2 = connected && hostname ? hostname : "-";
+                           *p2 ; p2++) {
+                               if (op == 'm' && *p2 == '.')
+                                       break;
+                               ADDBUF(*p2);
+                       }
+                       break;
+
+               case 'n':
+                       for (p2 = connected ? username : "-"; *p2 ; p2++)
+                               ADDBUF(*p2);
+                       break;
+
+               case '%':
+                       ADDBUF('%');
+                       break;
+
+               default:                /* display unknown codes literally */
+                       ADDBUF('%');
+                       ADDBUF(op);
+                       break;
+
+               }
+       }
+ endbuf:
+       buf[i] = '\0';
+}
+
+/*
+ * Determine if given string is an IPv6 address or not.
+ * Return 1 for yes, 0 for no
+ */
+int
+isipv6addr(const char *addr)
+{
+       int rv = 0;
+#ifdef INET6
+       struct addrinfo hints, *res;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_INET6;
+       hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+       hints.ai_flags = AI_NUMERICHOST;
+       if (getaddrinfo(addr, "0", &hints, &res) != 0)
+               rv = 0;
+       else {
+               rv = 1;
+               freeaddrinfo(res);
+       }
+       DPRINTF("isipv6addr: got %d for %s\n", rv, addr);
+#endif
+       return (rv == 1) ? 1 : 0;
+}
+
+/*
+ * Read a line from the FILE stream into buf/buflen using fgets(), so up
+ * to buflen-1 chars will be read and the result will be NUL terminated.
+ * If the line has a trailing newline it will be removed.
+ * If the line is too long, excess characters will be read until
+ * newline/EOF/error.
+ * If EOF/error occurs or a too-long line is encountered and errormsg
+ * isn't NULL, it will be changed to a description of the problem.
+ * (The EOF message has a leading \n for cosmetic purposes).
+ * Returns:
+ *     >=0     length of line (excluding trailing newline) if all ok
+ *     -1      error occurred
+ *     -2      EOF encountered
+ *     -3      line was too long
+ */
+int
+get_line(FILE *stream, char *buf, size_t buflen, const char **errormsg)
+{
+       int     rv, ch;
+       size_t  len;
+
+       if (fgets(buf, buflen, stream) == NULL) {
+               if (feof(stream)) {     /* EOF */
+                       rv = -2;
+                       if (errormsg)
+                               *errormsg = "\nEOF received";
+               } else  {               /* error */
+                       rv = -1;
+                       if (errormsg)
+                               *errormsg = "Error encountered";
+               }
+               clearerr(stream);
+               return rv;
+       }
+       len = strlen(buf);
+       if (buf[len-1] == '\n') {       /* clear any trailing newline */
+               buf[--len] = '\0';
+       } else if (len == buflen-1) {   /* line too long */
+               while ((ch = getchar()) != '\n' && ch != EOF)
+                       continue;
+               if (errormsg)
+                       *errormsg = "Input line is too long";
+               clearerr(stream);
+               return -3;
+       }
+       if (errormsg)
+               *errormsg = NULL;
+       return len;
+}
+
+/*
+ * Internal version of connect(2); sets socket buffer sizes,
+ * binds to a specific local address (if set), and
+ * supports a connection timeout using a non-blocking connect(2) with
+ * a poll(2).
+ * Socket fcntl flags are temporarily updated to include O_NONBLOCK;
+ * these will not be reverted on connection failure.
+ * Returns 0 on success, or -1 upon failure (with an appropriate
+ * error message displayed.)
+ */
+int
+ftp_connect(int sock, const struct sockaddr *name, socklen_t namelen, int pe)
+{
+       int             flags, rv, timeout, error;
+       socklen_t       slen;
+       struct timeval  endtime, now, td;
+       struct pollfd   pfd[1];
+       char            hname[NI_MAXHOST];
+       char            sname[NI_MAXSERV];
+
+       setupsockbufsize(sock);
+       if (getnameinfo(name, namelen,
+           hname, sizeof(hname), sname, sizeof(sname),
+           NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+               strlcpy(hname, "?", sizeof(hname));
+               strlcpy(sname, "?", sizeof(sname));
+       }
+
+       if (bindai != NULL) {                   /* bind to specific addr */
+               struct addrinfo *ai;
+
+               for (ai = bindai; ai != NULL; ai = ai->ai_next) {
+                       if (ai->ai_family == name->sa_family)
+                               break;
+               }
+               if (ai == NULL)
+                       ai = bindai;
+               if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+                       char    bname[NI_MAXHOST];
+                       int     saveerr;
+
+                       saveerr = errno;
+                       if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
+                           bname, sizeof(bname), NULL, 0, NI_NUMERICHOST) != 0)
+                               strlcpy(bname, "?", sizeof(bname));
+                       errno = saveerr;
+                       warn("Can't bind to `%s'", bname);
+                       return -1;
+               }
+       }
+
+                                               /* save current socket flags */
+       if ((flags = fcntl(sock, F_GETFL, 0)) == -1) {
+               warn("Can't %s socket flags for connect to `%s:%s'",
+                   "save", hname, sname);
+               return -1;
+       }
+                                               /* set non-blocking connect */
+       if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
+               warn("Can't set socket non-blocking for connect to `%s:%s'",
+                   hname, sname);
+               return -1;
+       }
+
+       /* NOTE: we now must restore socket flags on successful exit */
+
+       pfd[0].fd = sock;
+       pfd[0].events = POLLIN|POLLOUT;
+
+       if (quit_time > 0) {                    /* want a non default timeout */
+               (void)gettimeofday(&endtime, NULL);
+               endtime.tv_sec += quit_time;    /* determine end time */
+       }
+
+       rv = connect(sock, name, namelen);      /* inititate the connection */
+       if (rv == -1) {                         /* connection error */
+               if (errno != EINPROGRESS) {     /* error isn't "please wait" */
+                       if (pe || (errno != EHOSTUNREACH))
+ connecterror:
+                               warn("Can't connect to `%s:%s'", hname, sname);
+                       return -1;
+               }
+
+                                               /* connect EINPROGRESS; wait */
+               do {
+                       if (quit_time > 0) {    /* determine timeout */
+                               (void)gettimeofday(&now, NULL);
+                               timersub(&endtime, &now, &td);
+                               timeout = td.tv_sec * 1000 + td.tv_usec/1000;
+                               if (timeout < 0)
+                                       timeout = 0;
+                       } else {
+                               timeout = INFTIM;
+                       }
+                       pfd[0].revents = 0;
+                       rv = ftp_poll(pfd, 1, timeout);
+                                               /* loop until poll ! EINTR */
+               } while (rv == -1 && errno == EINTR);
+
+               if (rv == 0) {                  /* poll (connect) timed out */
+                       errno = ETIMEDOUT;
+                       goto connecterror;
+               }
+
+               if (rv == -1) {                 /* poll error */
+                       goto connecterror;
+               } else if (pfd[0].revents & (POLLIN|POLLOUT)) {
+                       slen = sizeof(error);   /* OK, or pending error */
+                       if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
+                           &error, &slen) == -1) {
+                                               /* Solaris pending error */
+                               goto connecterror;
+                       } else if (error != 0) {
+                               errno = error;  /* BSD pending error */
+                               goto connecterror;
+                       }
+               } else {
+                       errno = EBADF;          /* this shouldn't happen ... */
+                       goto connecterror;
+               }
+       }
+
+       if (fcntl(sock, F_SETFL, flags) == -1) {
+                                               /* restore socket flags */
+               warn("Can't %s socket flags for connect to `%s:%s'",
+                   "restore", hname, sname);
+               return -1;
+       }
+       return 0;
+}
+
+/*
+ * Internal version of listen(2); sets socket buffer sizes first.
+ */
+int
+ftp_listen(int sock, int backlog)
+{
+
+       setupsockbufsize(sock);
+       return (listen(sock, backlog));
+}
+
+/*
+ * Internal version of poll(2), to allow reimplementation by select(2)
+ * on platforms without the former.
+ */
+int
+ftp_poll(struct pollfd *fds, int nfds, int timeout)
+{
+       return poll(fds, nfds, timeout);
+}
+
+/*
+ * malloc() with inbuilt error checking
+ */
+void *
+ftp_malloc(size_t size)
+{
+       void *p;
+
+       p = malloc(size);
+       if (p == NULL)
+               err(1, "Unable to allocate %ld bytes of memory", (long)size);
+       return (p);
+}
+
+/*
+ * sl_init() with inbuilt error checking
+ */
+StringList *
+ftp_sl_init(void)
+{
+       StringList *p;
+
+       p = sl_init();
+       if (p == NULL)
+               err(1, "Unable to allocate memory for stringlist");
+       return (p);
+}
+
+/*
+ * sl_add() with inbuilt error checking
+ */
+void
+ftp_sl_add(StringList *sl, char *i)
+{
+
+       if (sl_add(sl, i) == -1)
+               err(1, "Unable to add `%s' to stringlist", i);
+}
+
+/*
+ * strdup() with inbuilt error checking
+ */
+char *
+ftp_strdup(const char *str)
+{
+       char *s;
+
+       if (str == NULL)
+               errx(1, "ftp_strdup: called with NULL argument");
+       s = strdup(str);
+       if (s == NULL)
+               err(1, "Unable to allocate memory for string copy");
+       return (s);
+}
diff --git a/usr.bin/ftp/version.h b/usr.bin/ftp/version.h
new file mode 100644 (file)
index 0000000..9d94b43
--- /dev/null
@@ -0,0 +1,38 @@
+/*     $NetBSD: version.h,v 1.83 2013/02/06 16:37:20 christos Exp $    */
+
+/*-
+ * Copyright (c) 1999-2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FTP_PRODUCT
+#define        FTP_PRODUCT     "NetBSD-ftp"
+#endif
+
+#ifndef FTP_VERSION
+#define        FTP_VERSION     "20121224"
+#endif