[BACK]Return to main.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ftp

Diff for /src/usr.bin/ftp/main.c between version 1.128 and 1.129

version 1.128, 2019/05/15 13:42:40 version 1.129, 2019/05/16 12:44:18
Line 1 
Line 1 
 /*      $OpenBSD$ */  /*      $OpenBSD$       */
   /*      $NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $  */
   
 /*  /*
  * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>   * Copyright (C) 1997 and 1998 WIDE Project.
    * All rights reserved.
  *   *
  * Permission to use, copy, modify, and distribute this software for any   * Redistribution and use in source and binary forms, with or without
  * purpose with or without fee is hereby granted, provided that the above   * modification, are permitted provided that the following conditions
  * copyright notice and this permission notice appear in all copies.   * 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.
  *   *
  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES   * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES   * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.   * 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>  /*
    * 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 User Program -- Command Interface.
    */
 #include <sys/types.h>  #include <sys/types.h>
 #include <sys/queue.h>  
 #include <sys/stat.h>  
 #include <sys/socket.h>  #include <sys/socket.h>
 #include <sys/wait.h>  
   
   #include <ctype.h>
 #include <err.h>  #include <err.h>
 #include <errno.h>  
 #include <fcntl.h>  #include <fcntl.h>
 #include <imsg.h>  #include <netdb.h>
 #include <libgen.h>  #include <pwd.h>
 #include <signal.h>  
 #include <stdio.h>  #include <stdio.h>
   #include <errno.h>
 #include <stdlib.h>  #include <stdlib.h>
 #include <string.h>  #include <string.h>
 #include <unistd.h>  #include <unistd.h>
   
 #include "ftp.h"  #include <tls.h>
 #include "xmalloc.h"  
   
 static int               auto_fetch(int, char **);  #include "cmds.h"
 static void              child(int, int, char **);  #include "ftp_var.h"
 static int               parent(int, pid_t);  
 static struct url       *proxy_parse(const char *);  
 static struct url       *get_proxy(int);  
 static void              re_exec(int, int, char **);  
 static void              validate_output_fname(struct url *, const char *);  
 static __dead void       usage(void);  
   
 struct imsgbuf           child_ibuf;  int connect_timeout;
 const char              *useragent = "OpenBSD ftp";  
 int                      activemode, family = AF_UNSPEC, io_debug;  
 int                      progressmeter, verbose = 1;  
 volatile sig_atomic_t    interrupted = 0;  
 FILE                    *msgout = stdout;  
   
 static const char       *title;  #ifndef NOSSL
 static char             *tls_options, *oarg;  char * const ssl_verify_opts[] = {
 static int               connect_timeout, resume;  #define SSL_CAFILE              0
           "cafile",
   #define SSL_CAPATH              1
           "capath",
   #define SSL_CIPHERS             2
           "ciphers",
   #define SSL_DONTVERIFY          3
           "dont",
   #define SSL_DOVERIFY            4
           "do",
   #define SSL_VERIFYDEPTH         5
           "depth",
   #define SSL_MUSTSTAPLE          6
           "muststaple",
   #define SSL_NOVERIFYTIME        7
           "noverifytime",
   #define SSL_SESSION             8
           "session",
           NULL
   };
   
   struct tls_config *tls_config;
   int tls_session_fd = -1;
   
   static void
   process_ssl_options(char *cp)
   {
           const char *errstr;
           long long depth;
           char *str;
   
           while (*cp) {
                   switch (getsubopt(&cp, ssl_verify_opts, &str)) {
                   case SSL_CAFILE:
                           if (str == NULL)
                                   errx(1, "missing CA file");
                           if (tls_config_set_ca_file(tls_config, str) != 0)
                                   errx(1, "tls ca file failed: %s",
                                       tls_config_error(tls_config));
                           break;
                   case SSL_CAPATH:
                           if (str == NULL)
                                   errx(1, "missing CA directory path");
                           if (tls_config_set_ca_path(tls_config, str) != 0)
                                   errx(1, "tls ca path failed: %s",
                                       tls_config_error(tls_config));
                           break;
                   case SSL_CIPHERS:
                           if (str == NULL)
                                   errx(1, "missing cipher list");
                           if (tls_config_set_ciphers(tls_config, str) != 0)
                                   errx(1, "tls ciphers failed: %s",
                                       tls_config_error(tls_config));
                           break;
                   case SSL_DONTVERIFY:
                           tls_config_insecure_noverifycert(tls_config);
                           tls_config_insecure_noverifyname(tls_config);
                           break;
                   case SSL_DOVERIFY:
                           tls_config_verify(tls_config);
                           break;
                   case SSL_VERIFYDEPTH:
                           if (str == NULL)
                                   errx(1, "missing depth");
                           depth = strtonum(str, 0, INT_MAX, &errstr);
                           if (errstr)
                                   errx(1, "certificate validation depth is %s",
                                       errstr);
                           tls_config_set_verify_depth(tls_config, (int)depth);
                           break;
                   case SSL_MUSTSTAPLE:
                           tls_config_ocsp_require_stapling(tls_config);
                           break;
                   case SSL_NOVERIFYTIME:
                           tls_config_insecure_noverifytime(tls_config);
                           break;
                   case SSL_SESSION:
                           if (str == NULL)
                                   errx(1, "missing session file");
                           if ((tls_session_fd = open(str, O_RDWR|O_CREAT,
                               0600)) == -1)
                                   err(1, "failed to open or create session file "
                                       "'%s'", str);
                           if (tls_config_set_session_fd(tls_config,
                               tls_session_fd) == -1)
                                   errx(1, "failed to set session: %s",
                                       tls_config_error(tls_config));
                           break;
                   default:
                           errx(1, "unknown -S suboption `%s'",
                               suboptarg ? suboptarg : "");
                           /* NOTREACHED */
                   }
           }
   }
   #endif /* !NOSSL */
   
   int family = PF_UNSPEC;
   int pipeout;
   
 int  int
 main(int argc, char **argv)  main(volatile int argc, char *argv[])
 {  {
         const char       *e;          int ch, rval;
         char            **save_argv, *term;  #ifndef SMALL
         int               ch, csock, dumb_terminal, rexec, save_argc;          int top;
   #endif
           struct passwd *pw = NULL;
           char *cp, homedir[PATH_MAX];
           char *outfile = NULL;
           const char *errstr;
           int dumb_terminal = 0;
   
         if (isatty(fileno(stdin)) != 1)          ftpport = "ftp";
                 verbose = 0;          httpport = "http";
   #ifndef NOSSL
           httpsport = "https";
   #endif /* !NOSSL */
           gateport = getenv("FTPSERVERPORT");
           if (gateport == NULL || *gateport == '\0')
                   gateport = "ftpgate";
           doglob = 1;
           interactive = 1;
           autologin = 1;
           passivemode = 1;
           activefallback = 1;
           preserve = 1;
           verbose = 0;
           progress = 0;
           gatemode = 0;
   #ifndef NOSSL
           cookiefile = NULL;
   #endif /* NOSSL */
   #ifndef SMALL
           editing = 0;
           el = NULL;
           hist = NULL;
           resume = 0;
           srcaddr = NULL;
           marg_sl = sl_init();
   #endif /* !SMALL */
           mark = HASHBYTES;
           epsv4 = 1;
           epsv4bad = 0;
   
         io_debug = getenv("IO_DEBUG") != NULL;          /* Set default operation mode based on FTPMODE environment variable */
         term = getenv("TERM");          if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') {
         dumb_terminal = (term == NULL || *term == '\0' ||                  if (strcmp(cp, "passive") == 0) {
             !strcmp(term, "dumb") || !strcmp(term, "emacs") ||                          passivemode = 1;
             !strcmp(term, "su"));                          activefallback = 0;
         if (isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) && !dumb_terminal)                  } else if (strcmp(cp, "active") == 0) {
                 progressmeter = 1;                          passivemode = 0;
                           activefallback = 0;
                   } else if (strcmp(cp, "gate") == 0) {
                           gatemode = 1;
                   } else if (strcmp(cp, "auto") == 0) {
                           passivemode = 1;
                           activefallback = 1;
                   } else
                           warnx("unknown FTPMODE: %s.  Using defaults", cp);
           }
   
         csock = rexec = 0;          if (strcmp(__progname, "gate-ftp") == 0)
         save_argc = argc;                  gatemode = 1;
         save_argv = argv;          gateserver = getenv("FTPSERVER");
           if (gateserver == NULL)
                   gateserver = "";
           if (gatemode) {
                   if (*gateserver == '\0') {
                           warnx(
   "Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp");
                           gatemode = 0;
                   }
           }
   
           cp = getenv("TERM");
           dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") ||
               !strcmp(cp, "emacs") || !strcmp(cp, "su"));
           fromatty = isatty(fileno(stdin));
           if (fromatty) {
                   verbose = 1;            /* verbose if from a tty */
   #ifndef SMALL
                   if (!dumb_terminal)
                           editing = 1;    /* editing mode on if tty is usable */
   #endif /* !SMALL */
           }
   
           ttyout = stdout;
           if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc())
                   progress = 1;           /* progress bar on if tty is usable */
   
   #ifndef NOSSL
           cookiefile = getenv("http_cookies");
           if (tls_init() != 0)
                   errx(1, "tls init failed");
           if (tls_config == NULL) {
                   tls_config = tls_config_new();
                   if (tls_config == NULL)
                           errx(1, "tls config failed");
                   if (tls_config_set_protocols(tls_config,
                       TLS_PROTOCOLS_ALL) != 0)
                           errx(1, "tls set protocols failed: %s",
                               tls_config_error(tls_config));
                   if (tls_config_set_ciphers(tls_config, "legacy") != 0)
                           errx(1, "tls set ciphers failed: %s",
                               tls_config_error(tls_config));
           }
   #endif /* !NOSSL */
   
           httpuseragent = NULL;
   
         while ((ch = getopt(argc, argv,          while ((ch = getopt(argc, argv,
             "46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vVw:xz:")) != -1) {                      "46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vVw:")) != -1) {
                 switch (ch) {                  switch (ch) {
                 case '4':                  case '4':
                         family = AF_INET;                          family = PF_INET;
                         break;                          break;
                 case '6':                  case '6':
                         family = AF_INET6;                          family = PF_INET6;
                         break;                          break;
                 case 'A':                  case 'A':
                         activemode = 1;                          activefallback = 0;
                           passivemode = 0;
                         break;                          break;
   
                   case 'a':
                           anonftp = 1;
                           break;
   
                 case 'C':                  case 'C':
   #ifndef SMALL
                         resume = 1;                          resume = 1;
   #endif /* !SMALL */
                         break;                          break;
   
                   case 'c':
   #ifndef SMALL
                           cookiefile = optarg;
   #endif /* !SMALL */
                           break;
   
                 case 'D':                  case 'D':
                         title = optarg;                          action = optarg;
                         break;                          break;
                 case 'o':                  case 'd':
                         oarg = optarg;  #ifndef SMALL
                         if (!strlen(oarg))                          options |= SO_DEBUG;
                                 oarg = NULL;                          debug++;
   #endif /* !SMALL */
                         break;                          break;
                 case 'M':  
                         progressmeter = 0;                  case 'E':
                           epsv4 = 0;
                         break;                          break;
                 case 'm':  
                         progressmeter = 1;                  case 'e':
   #ifndef SMALL
                           editing = 0;
   #endif /* !SMALL */
                         break;                          break;
                 case 'S':  
                         tls_options = optarg;                  case 'g':
                           doglob = 0;
                         break;                          break;
                 case 'U':  
                         useragent = optarg;                  case 'i':
                           interactive = 0;
                         break;                          break;
                 case 'V':  
                         verbose = 0;                  case 'k':
                           keep_alive_timeout = strtonum(optarg, 0, INT_MAX,
                               &errstr);
                           if (errstr != NULL) {
                                   warnx("keep alive amount is %s: %s", errstr,
                                           optarg);
                                   usage();
                           }
                         break;                          break;
                 case 'v':                  case 'M':
                         verbose = 1;                          progress = 0;
                         break;                          break;
                 case 'w':                  case 'm':
                         connect_timeout = strtonum(optarg, 0, 200, &e);                          progress = -1;
                         if (e)  
                                 errx(1, "-w: %s", e);  
                         break;                          break;
                 /* options for internal use only */  
                 case 'x':                  case 'n':
                         rexec = 1;                          autologin = 0;
                         break;                          break;
                 case 'z':  
                         csock = strtonum(optarg, 3, getdtablesize() - 1, &e);                  case 'o':
                         if (e)                          outfile = optarg;
                                 errx(1, "-z: %s", e);                          if (*outfile == '\0') {
                                   pipeout = 0;
                                   outfile = NULL;
                                   ttyout = stdout;
                           } else {
                                   pipeout = strcmp(outfile, "-") == 0;
                                   ttyout = pipeout ? stderr : stdout;
                           }
                         break;                          break;
                 /* Ignoring all remaining options */  
                 case 'a':  
                 case 'c':  
                 case 'd':  
                 case 'E':  
                 case 'e':  
                 case 'g':  
                 case 'i':  
                 case 'k':  
                 case 'n':  
                 case 'P':  
                 case 'p':                  case 'p':
                           passivemode = 1;
                           activefallback = 0;
                           break;
   
                   case 'P':
                           ftpport = optarg;
                           break;
   
                 case 'r':                  case 'r':
                           retry_connect = strtonum(optarg, 0, INT_MAX, &errstr);
                           if (errstr != NULL) {
                                   warnx("retry amount is %s: %s", errstr,
                                           optarg);
                                   usage();
                           }
                           break;
   
                   case 'S':
   #ifndef NOSSL
                           process_ssl_options(optarg);
   #endif /* !NOSSL */
                           break;
   
                 case 's':                  case 's':
   #ifndef SMALL
                           srcaddr = optarg;
   #endif /* !SMALL */
                           break;
   
                 case 't':                  case 't':
                           trace = 1;
                         break;                          break;
   
   #ifndef SMALL
                   case 'U':
                           free (httpuseragent);
                           if (strcspn(optarg, "\r\n") != strlen(optarg))
                                   errx(1, "Invalid User-Agent: %s.", optarg);
                           if (asprintf(&httpuseragent, "User-Agent: %s",
                               optarg) == -1)
                                   errx(1, "Can't allocate memory for HTTP(S) "
                                       "User-Agent");
                           break;
   #endif /* !SMALL */
   
                   case 'v':
                           verbose = 1;
                           break;
   
                   case 'V':
                           verbose = 0;
                           break;
   
                   case 'w':
                           connect_timeout = strtonum(optarg, 0, 200, &errstr);
                           if (errstr)
                                   errx(1, "-w: %s", errstr);
                           break;
                 default:                  default:
                         usage();                          usage();
                 }                  }
Line 156 
Line 452 
         argc -= optind;          argc -= optind;
         argv += optind;          argv += optind;
   
         if (rexec)  #ifndef NOSSL
                 child(csock, argc, argv);          cookie_load();
   #endif /* !NOSSL */
           if (httpuseragent == NULL)
                   httpuseragent = HTTP_USER_AGENT;
   
 #ifndef SMALL          cpend = 0;      /* no pending replies */
         struct url      *url;          proxy = 0;      /* proxy not active */
           crflag = 1;     /* strip c.r. on ascii gets */
           sendport = -1;  /* not using ports */
           /*
            * Set up the home directory in case we're globbing.
            */
           cp = getlogin();
           if (cp != NULL) {
                   pw = getpwnam(cp);
           }
           if (pw == NULL)
                   pw = getpwuid(getuid());
           if (pw != NULL) {
                   (void)strlcpy(homedir, pw->pw_dir, sizeof homedir);
                   home = homedir;
           }
   
         switch (argc) {          setttywidth(0);
         case 0:          (void)signal(SIGWINCH, setttywidth);
                 cmd(NULL, NULL, NULL);  
                 return 0;  
         case 1:  
         case 2:  
                 switch (scheme_lookup(argv[0])) {  
                 case -1:  
                         cmd(argv[0], argv[1], NULL);  
                         return 0;  
                 case S_FTP:  
                         if ((url = url_parse(argv[0])) == NULL)  
                                 exit(1);  
   
                         if (url->path &&          if (argc > 0) {
                             url->path[strlen(url->path) - 1] != '/')                  if (isurl(argv[0])) {
                                 break; /* auto fetch */                          if (pipeout) {
   #ifndef SMALL
                         cmd(url->host, url->port, url->path);                                  if (pledge("stdio rpath dns tty inet proc exec fattr",
                         return 0;                                      NULL) == -1)
                 }                                          err(1, "pledge");
                 break;  
         }  
 #else  #else
         if (argc == 0)                                  if (pledge("stdio rpath dns tty inet fattr",
                 usage();                                      NULL) == -1)
                                           err(1, "pledge");
 #endif  #endif
                           } else {
   #ifndef SMALL
                                   if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr",
                                       NULL) == -1)
                                           err(1, "pledge");
   #else
                                   if (pledge("stdio rpath wpath cpath dns tty inet fattr",
                                       NULL) == -1)
                                           err(1, "pledge");
   #endif
                           }
   
         return auto_fetch(save_argc, save_argv);                          rval = auto_fetch(argc, argv, outfile);
                           if (rval >= 0)          /* -1 == connected and cd-ed */
                                   exit(rval);
                   } else {
   #ifndef SMALL
                           char *xargv[5];
   
                           if (setjmp(toplevel))
                                   exit(0);
                           (void)signal(SIGINT, (sig_t)intr);
                           (void)signal(SIGPIPE, (sig_t)lostpeer);
                           xargv[0] = __progname;
                           xargv[1] = argv[0];
                           xargv[2] = argv[1];
                           xargv[3] = argv[2];
                           xargv[4] = NULL;
                           do {
                                   setpeer(argc+1, xargv);
                                   if (!retry_connect)
                                           break;
                                   if (!connected) {
                                           macnum = 0;
                                           fputs("Retrying...\n", ttyout);
                                           sleep(retry_connect);
                                   }
                           } while (!connected);
                           retry_connect = 0; /* connected, stop hiding msgs */
   #endif /* !SMALL */
                   }
           }
   #ifndef SMALL
           controlediting();
           top = setjmp(toplevel) == 0;
           if (top) {
                   (void)signal(SIGINT, (sig_t)intr);
                   (void)signal(SIGPIPE, (sig_t)lostpeer);
           }
           for (;;) {
                   cmdscanner(top);
                   top = 1;
           }
   #else /* !SMALL */
           usage();
   #endif /* !SMALL */
 }  }
   
 static int  void
 auto_fetch(int sargc, char **sargv)  intr(void)
 {  {
         pid_t   pid;          int save_errno = errno;
         int     sp[2];  
   
         if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) != 0)          write(fileno(ttyout), "\n\r", 2);
                 err(1, "socketpair");          alarmtimer(0);
   
         switch (pid = fork()) {          errno = save_errno;
         case -1:          longjmp(toplevel, 1);
                 err(1, "fork");  
         case 0:  
                 close(sp[0]);  
                 re_exec(sp[1], sargc, sargv);  
         }  
   
         close(sp[1]);  
         return parent(sp[0], pid);  
 }  }
   
 static void  void
 re_exec(int sock, int argc, char **argv)  lostpeer(void)
 {  {
         char    **nargv, *sock_str;          int save_errno = errno;
         int       i, j, nargc;  
   
         nargc = argc + 4;          alarmtimer(0);
         nargv = xcalloc(nargc, sizeof(*nargv));          if (connected) {
         xasprintf(&sock_str, "%d", sock);                  if (cout != NULL) {
         i = 0;                          (void)shutdown(fileno(cout), SHUT_RDWR);
         nargv[i++] = argv[0];                          (void)fclose(cout);
         nargv[i++] = "-z";                          cout = NULL;
         nargv[i++] = sock_str;                  }
         nargv[i++] = "-x";                  if (data >= 0) {
         for (j = 1; j < argc; j++)                          (void)shutdown(data, SHUT_RDWR);
                 nargv[i++] = argv[j];                          (void)close(data);
                           data = -1;
                   }
                   connected = 0;
           }
           pswitch(1);
           if (connected) {
                   if (cout != NULL) {
                           (void)shutdown(fileno(cout), SHUT_RDWR);
                           (void)fclose(cout);
                           cout = NULL;
                   }
                   connected = 0;
           }
           proxflag = 0;
           pswitch(0);
           errno = save_errno;
   }
   
         execvp(nargv[0], nargv);  #ifndef SMALL
         err(1, "execvp");  /*
    * Generate a prompt
    */
   char *
   prompt(void)
   {
           return ("ftp> ");
 }  }
   
 static int  /*
 parent(int sock, pid_t child_pid)   * Command parser.
    */
   void
   cmdscanner(int top)
 {  {
         struct imsgbuf  ibuf;          struct cmd *c;
         struct imsg     imsg;          int num;
         struct stat     sb;          HistEvent hev;
         off_t           offset;  
         int             fd, save_errno, sig, status;  
   
         setproctitle("%s", "parent");          if (!top && !editing)
         if (pledge("stdio cpath rpath wpath sendfd", NULL) == -1)                  (void)putc('\n', ttyout);
                 err(1, "pledge");  
   
         imsg_init(&ibuf, sock);  
         for (;;) {          for (;;) {
                 if (read_message(&ibuf, &imsg) == 0)                  if (!editing) {
                         break;                          if (fromatty) {
                                   fputs(prompt(), ttyout);
                                   (void)fflush(ttyout);
                           }
                           if (fgets(line, sizeof(line), stdin) == NULL)
                                   quit(0, 0);
                           num = strlen(line);
                           if (num == 0)
                                   break;
                           if (line[--num] == '\n') {
                                   if (num == 0)
                                           break;
                                   line[num] = '\0';
                           } else if (num == sizeof(line) - 2) {
                                   fputs("sorry, input line too long.\n", ttyout);
                                   while ((num = getchar()) != '\n' && num != EOF)
                                           /* void */;
                                   break;
                           } /* else it was a line without a newline */
                   } else {
                           const char *buf;
                           cursor_pos = NULL;
   
                 if (imsg.hdr.type != IMSG_OPEN)                          if ((buf = el_gets(el, &num)) == NULL || num == 0) {
                         errx(1, "%s: IMSG_OPEN expected", __func__);                                  putc('\n', ttyout);
                                   fflush(ttyout);
                 offset = 0;                                  quit(0, 0);
                 fd = open(imsg.data, imsg.hdr.peerid, 0666);                          }
                 save_errno = errno;                          if (buf[--num] == '\n') {
                 if (fd != -1 && fstat(fd, &sb) == 0) {                                  if (num == 0)
                         if (sb.st_mode & S_IFDIR) {                                          break;
                                 close(fd);                          }
                                 fd = -1;                          if (num >= sizeof(line)) {
                                 save_errno = EISDIR;                                  fputs("sorry, input line too long.\n", ttyout);
                         } else                                  break;
                                 offset = sb.st_size;                          }
                           memcpy(line, buf, (size_t)num);
                           line[num] = '\0';
                           history(hist, &hev, H_ENTER, buf);
                 }                  }
   
                 send_message(&ibuf, IMSG_OPEN, save_errno,                  makeargv();
                     &offset, sizeof offset, fd);                  if (margc == 0)
                 imsg_free(&imsg);                          continue;
                   c = getcmd(margv[0]);
                   if (c == (struct cmd *)-1) {
                           fputs("?Ambiguous command.\n", ttyout);
                           continue;
                   }
                   if (c == 0) {
                           /*
                            * Give editline(3) a shot at unknown commands.
                            * XXX - bogus commands with a colon in
                            *       them will not elicit an error.
                            */
                           if (editing &&
                               el_parse(el, margc, (const char **)margv) != 0)
                                   fputs("?Invalid command.\n", ttyout);
                           continue;
                   }
                   if (c->c_conn && !connected) {
                           fputs("Not connected.\n", ttyout);
                           continue;
                   }
                   confirmrest = 0;
                   (*c->c_handler)(margc, margv);
                   if (bell && c->c_bell)
                           (void)putc('\007', ttyout);
                   if (c->c_handler != help)
                           break;
         }          }
           (void)signal(SIGINT, (sig_t)intr);
           (void)signal(SIGPIPE, (sig_t)lostpeer);
   }
   
         close(sock);  struct cmd *
         if (waitpid(child_pid, &status, 0) == -1 && errno != ECHILD)  getcmd(const char *name)
                 err(1, "wait");  {
           const char *p, *q;
           struct cmd *c, *found;
           int nmatches, longest;
   
         sig = WTERMSIG(status);          if (name == NULL)
         if (WIFSIGNALED(status) && sig != SIGPIPE)                  return (0);
                 errx(1, "child terminated: signal %d", sig);  
   
         return WEXITSTATUS(status);          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);
 }  }
   
 static void  /*
 child(int sock, int argc, char **argv)   * Slice a string up into argc/argv.
 {   */
         struct url      *url;  
         FILE            *dst_fp;  
         char            *p;  
         off_t            offset, sz;  
         int              fd, i, tostdout;  
   
         setproctitle("%s", "child");  int slrflag;
 #ifndef NOSSL  
         https_init(tls_options);  
 #endif  
         if (pledge("stdio inet dns recvfd tty", NULL) == -1)  
                 err(1, "pledge");  
         if (!progressmeter && pledge("stdio inet dns recvfd", NULL) == -1)  
                 err(1, "pledge");  
   
         imsg_init(&child_ibuf, sock);  void
         tostdout = oarg && (strcmp(oarg, "-") == 0);  makeargv(void)
         if (tostdout)  {
                 msgout = stderr;          char *argp;
         if (resume && tostdout)  
                 errx(1, "can't append to stdout");  
   
         for (i = 0; i < argc; i++) {          stringbase = line;              /* scan from first of buffer */
                 fd = -1;          argbase = argbuf;               /* store from first of buffer */
                 offset = sz = 0;          slrflag = 0;
           marg_sl->sl_cur = 0;            /* reset to start of marg_sl */
           for (margc = 0; ; margc++) {
                   argp = slurpstring();
                   sl_add(marg_sl, argp);
                   if (argp == NULL)
                           break;
           }
           if (cursor_pos == line) {
                   cursor_argc = 0;
                   cursor_argo = 0;
           } else if (cursor_pos != NULL) {
                   cursor_argc = margc;
                   cursor_argo = strlen(margv[margc-1]);
           }
   }
   
                 if ((url = url_parse(argv[i])) == NULL)  #define INC_CHKCURSOR(x)        { (x)++ ; \
                         exit(1);                                  if (x == cursor_pos) { \
                                           cursor_argc = margc; \
                                           cursor_argo = ap-argbase; \
                                           cursor_pos = NULL; \
                                   } }
   
                 validate_output_fname(url, argv[i]);  /*
                 url_connect(url, get_proxy(url->scheme), connect_timeout);   * Parse string into argbuf;
                 if (resume)   * implemented with FSM to
                         fd = fd_request(url->fname, O_WRONLY|O_APPEND, &offset);   * handle quoting and strings
    */
   char *
   slurpstring(void)
   {
           int got_one = 0;
           char *sb = stringbase;
           char *ap = argbase;
           char *tmp = argbase;            /* will return this if token found */
   
                 url = url_request(url, get_proxy(url->scheme), &offset, &sz);          if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */
                 if (resume && offset == 0 && fd != -1)                  switch (slrflag) {      /* and $ as token for macro invoke */
                         if (ftruncate(fd, 0) != 0)                          case 0:
                                 err(1, "ftruncate");                                  slrflag++;
                                   INC_CHKCURSOR(stringbase);
                                   return ((*sb == '!') ? "!" : "$");
                                   /* NOTREACHED */
                           case 1:
                                   slrflag++;
                                   altarg = stringbase;
                                   break;
                           default:
                                   break;
                   }
           }
   
                 if (fd == -1 && !tostdout &&  S0:
                     (fd = fd_request(url->fname,          switch (*sb) {
                     O_CREAT|O_TRUNC|O_WRONLY, NULL)) == -1)  
                         err(1, "Can't open file %s", url->fname);  
   
                 if (tostdout) {          case '\0':
                         dst_fp = stdout;                  goto OUT;
                 } else if ((dst_fp = fdopen(fd, "w")) == NULL)  
                         err(1, "%s: fdopen", __func__);  
   
                 init_stats(sz, &offset);          case ' ':
                 if (progressmeter) {          case '\t':
                         p = basename(url->path);                  INC_CHKCURSOR(sb);
                         start_progress_meter(p, title);                  goto S0;
   
           default:
                   switch (slrflag) {
                           case 0:
                                   slrflag++;
                                   break;
                           case 1:
                                   slrflag++;
                                   altarg = sb;
                                   break;
                           default:
                                   break;
                 }                  }
                   goto S1;
           }
   
                 url_save(url, dst_fp, &offset);  S1:
                 if (progressmeter)          switch (*sb) {
                         stop_progress_meter();  
                 finish_stats();  
   
                 if (!tostdout)          case ' ':
                         fclose(dst_fp);          case '\t':
           case '\0':
                   goto OUT;       /* end of token */
   
                 url_close(url);          case '\\':
                 url_free(url);                  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;
         }          }
   
         exit(0);  S2:
 }          switch (*sb) {
   
 static struct url *          case '\0':
 get_proxy(int scheme)                  goto OUT;
 {  
         static struct url       *ftp_proxy, *http_proxy;  
   
         switch (scheme) {  
         case S_HTTP:  
         case S_HTTPS:  
                 if (http_proxy)  
                         return http_proxy;  
                 else  
                         return (http_proxy = proxy_parse("http_proxy"));  
         case S_FTP:  
                 if (ftp_proxy)  
                         return ftp_proxy;  
                 else  
                         return (ftp_proxy = proxy_parse("ftp_proxy"));  
         default:          default:
                 return NULL;                  *ap = *sb;
                   ap++;
                   INC_CHKCURSOR(sb);
                   got_one = 1;
                   goto S1;
         }          }
 }  
   
 static void  S3:
 validate_output_fname(struct url *url, const char *name)          switch (*sb) {
 {  
         url->fname = xstrdup(oarg ? oarg : basename(url->path));  
         if (strcmp(url->fname, "/") == 0)  
                 errx(1, "No filename after host (use -o): %s", name);  
   
         if (strcmp(url->fname, ".") == 0)          case '\0':
                 errx(1, "No '/' after host (use -o): %s", name);                  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 = (char *) 0;
                           break;
                   default:
                           break;
           }
           return (NULL);
 }  }
   
 static struct url *  /*
 proxy_parse(const char *name)   * Help command.
    * Call each command handler with argc == 0 and argv[0] == name.
    */
   void
   help(int argc, char *argv[])
 {  {
         struct url      *proxy;          struct cmd *c;
         char            *str;  
   
         if ((str = getenv(name)) == NULL)          if (argc == 1) {
                 return NULL;                  StringList *buf;
   
         if (strlen(str) == 0)                  buf = sl_init();
                 return NULL;                  fprintf(ttyout, "%sommands may be abbreviated.  Commands are:\n\n",
                       proxy ? "Proxy c" : "C");
                   for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
                           if (c->c_name && (!proxy || c->c_proxy))
                                   sl_add(buf, c->c_name);
                   list_vertical(buf);
                   sl_free(buf, 0);
                   return;
           }
   
         if ((proxy = url_parse(str)) == NULL)  #define HELPINDENT ((int) sizeof("disconnect"))
                 exit(1);  
   
         if (proxy->scheme != S_HTTP)          while (--argc > 0) {
                 errx(1, "Malformed proxy URL: %s", str);                  char *arg;
   
         return proxy;                  arg = *++argv;
                   c = getcmd(arg);
                   if (c == (struct cmd *)-1)
                           fprintf(ttyout, "?Ambiguous help command %s\n", arg);
                   else if (c == NULL)
                           fprintf(ttyout, "?Invalid help command %s\n", arg);
                   else
                           fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
                                   c->c_name, c->c_help);
           }
 }  }
   #endif /* !SMALL */
   
 static __dead void  __dead void
 usage(void)  usage(void)
 {  {
         fprintf(stderr, "usage:\t%s [-46AVv] [-D title] [host [port]]\n"          fprintf(stderr, "usage: "
             "\t%s [-46ACMmVv] [-D title] [-o output] [-S tls_options]\n"  #ifndef SMALL
             "\t\t[-U useragent] [-w seconds] url ...\n", getprogname(),              "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] "
              getprogname());              "[-r seconds]\n"
               "           [-s srcaddr] [host [port]]\n"
               "       ftp [-C] [-o output] [-s srcaddr]\n"
               "           ftp://[user:password@]host[:port]/file[/] ...\n"
               "       ftp [-C] [-c cookie] [-o output] [-S ssl_options] "
               "[-s srcaddr]\n"
               "           [-U useragent] [-w seconds] "
               "http[s]://[user:password@]host[:port]/file ...\n"
               "       ftp [-C] [-o output] [-s srcaddr] file:file ...\n"
               "       ftp [-C] [-o output] [-s srcaddr] host:/file[/] ...\n"
   #else /* !SMALL */
               "ftp [-o output] "
               "ftp://[user:password@]host[:port]/file[/] ...\n"
   #ifndef NOSSL
               "       ftp [-o output] [-S ssl_options] [-w seconds] "
               "http[s]://[user:password@]host[:port]/file ...\n"
   #else
               "       ftp [-o output] [-w seconds] http://host[:port]/file ...\n"
   #endif /* NOSSL */
               "       ftp [-o output] file:file ...\n"
               "       ftp [-o output] host:/file[/] ...\n"
   #endif /* !SMALL */
               );
         exit(1);          exit(1);
 }  }

Legend:
Removed from v.1.128  
changed lines
  Added in v.1.129