"Fossies" - the Fresh Open Source Software Archive

Member "tnftp-20200705/src/fetch.c" (4 Jul 2020, 57139 Bytes) of package /linux/privat/tnftp-20200705.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "fetch.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 20151004_vs_20200705.

    1 /*  $NetBSD: fetch.c,v 1.24 2020/07/04 09:59:07 lukem Exp $ */
    2 /*  from    NetBSD: fetch.c,v 1.231 2019/04/04 00:36:09 christos Exp    */
    3 
    4 /*-
    5  * Copyright (c) 1997-2015 The NetBSD Foundation, Inc.
    6  * All rights reserved.
    7  *
    8  * This code is derived from software contributed to The NetBSD Foundation
    9  * by Luke Mewburn.
   10  *
   11  * This code is derived from software contributed to The NetBSD Foundation
   12  * by Scott Aaron Bamford.
   13  *
   14  * This code is derived from software contributed to The NetBSD Foundation
   15  * by Thomas Klausner.
   16  *
   17  * Redistribution and use in source and binary forms, with or without
   18  * modification, are permitted provided that the following conditions
   19  * are met:
   20  * 1. Redistributions of source code must retain the above copyright
   21  *    notice, this list of conditions and the following disclaimer.
   22  * 2. Redistributions in binary form must reproduce the above copyright
   23  *    notice, this list of conditions and the following disclaimer in the
   24  *    documentation and/or other materials provided with the distribution.
   25  *
   26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
   27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
   30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   36  * POSSIBILITY OF SUCH DAMAGE.
   37  */
   38 
   39 #include "tnftp.h"
   40 
   41 #if 0   /* tnftp */
   42 
   43 #include <sys/cdefs.h>
   44 #ifndef lint
   45 __RCSID(" NetBSD: fetch.c,v 1.231 2019/04/04 00:36:09 christos Exp  ");
   46 #endif /* not lint */
   47 
   48 /*
   49  * FTP User Program -- Command line file retrieval
   50  */
   51 
   52 #include <sys/types.h>
   53 #include <sys/param.h>
   54 #include <sys/socket.h>
   55 #include <sys/stat.h>
   56 #include <sys/time.h>
   57 
   58 #include <netinet/in.h>
   59 
   60 #include <arpa/ftp.h>
   61 #include <arpa/inet.h>
   62 
   63 #include <assert.h>
   64 #include <ctype.h>
   65 #include <err.h>
   66 #include <errno.h>
   67 #include <netdb.h>
   68 #include <fcntl.h>
   69 #include <stdio.h>
   70 #include <stdlib.h>
   71 #include <string.h>
   72 #include <unistd.h>
   73 #include <time.h>
   74 
   75 #endif  /* tnftp */
   76 
   77 #include "ssl.h"
   78 #include "ftp_var.h"
   79 #include "version.h"
   80 
   81 typedef enum {
   82     UNKNOWN_URL_T=-1,
   83     HTTP_URL_T,
   84     HTTPS_URL_T,
   85     FTP_URL_T,
   86     FILE_URL_T,
   87     CLASSIC_URL_T
   88 } url_t;
   89 
   90 struct authinfo {
   91     char *auth;
   92     char *user;
   93     char *pass;
   94 };
   95 
   96 struct urlinfo {
   97     char *host;
   98     char *port;
   99     char *path;
  100     url_t utype;
  101     in_port_t portnum;
  102 };
  103 
  104 struct posinfo {
  105     off_t rangestart;
  106     off_t rangeend;
  107     off_t entitylen;
  108 };
  109 
  110 __dead static void  aborthttp(int);
  111 __dead static void  timeouthttp(int);
  112 #ifndef NO_AUTH
  113 static int  auth_url(const char *, char **, const struct authinfo *);
  114 static void base64_encode(const unsigned char *, size_t, unsigned char *);
  115 #endif
  116 static int  go_fetch(const char *);
  117 static int  fetch_ftp(const char *);
  118 static int  fetch_url(const char *, const char *, char *, char *);
  119 static const char *match_token(const char **, const char *);
  120 static int  parse_url(const char *, const char *, struct urlinfo *,
  121     struct authinfo *);
  122 static void url_decode(char *);
  123 static void freeauthinfo(struct authinfo *);
  124 static void freeurlinfo(struct urlinfo *);
  125 
  126 static int  redirect_loop;
  127 
  128 
  129 #define STRNEQUAL(a,b)  (strncasecmp((a), (b), sizeof((b))-1) == 0)
  130 #define ISLWS(x)    ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t')
  131 #define SKIPLWS(x)  do { while (ISLWS((*x))) x++; } while (0)
  132 
  133 
  134 #define ABOUT_URL   "about:"    /* propaganda */
  135 #define FILE_URL    "file://"   /* file URL prefix */
  136 #define FTP_URL     "ftp://"    /* ftp URL prefix */
  137 #define HTTP_URL    "http://"   /* http URL prefix */
  138 #ifdef WITH_SSL
  139 #define HTTPS_URL   "https://"  /* https URL prefix */
  140 
  141 #define IS_HTTP_TYPE(urltype) \
  142     (((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T))
  143 #else
  144 #define IS_HTTP_TYPE(urltype) \
  145     ((urltype) == HTTP_URL_T)
  146 #endif
  147 
  148 /*
  149  * Determine if token is the next word in buf (case insensitive).
  150  * If so, advance buf past the token and any trailing LWS, and
  151  * return a pointer to the token (in buf).  Otherwise, return NULL.
  152  * token may be preceded by LWS.
  153  * token must be followed by LWS or NUL.  (I.e, don't partial match).
  154  */
  155 static const char *
  156 match_token(const char **buf, const char *token)
  157 {
  158     const char  *p, *orig;
  159     size_t      tlen;
  160 
  161     tlen = strlen(token);
  162     p = *buf;
  163     SKIPLWS(p);
  164     orig = p;
  165     if (strncasecmp(p, token, tlen) != 0)
  166         return NULL;
  167     p += tlen;
  168     if (*p != '\0' && !ISLWS(*p))
  169         return NULL;
  170     SKIPLWS(p);
  171     orig = *buf;
  172     *buf = p;
  173     return orig;
  174 }
  175 
  176 static void
  177 initposinfo(struct posinfo *pi)
  178 {
  179     pi->rangestart = pi->rangeend = pi->entitylen = -1;
  180 }
  181 
  182 static void
  183 initauthinfo(struct authinfo *ai, char *auth)
  184 {
  185     ai->auth = auth;
  186     ai->user = ai->pass = 0;
  187 }
  188 
  189 static void
  190 freeauthinfo(struct authinfo *a)
  191 {
  192     FREEPTR(a->user);
  193     if (a->pass != NULL)
  194         memset(a->pass, 0, strlen(a->pass));
  195     FREEPTR(a->pass);
  196 }
  197 
  198 static void
  199 initurlinfo(struct urlinfo *ui)
  200 {
  201     ui->host = ui->port = ui->path = 0;
  202     ui->utype = UNKNOWN_URL_T;
  203     ui->portnum = 0;
  204 }
  205 
  206 static void
  207 copyurlinfo(struct urlinfo *dui, struct urlinfo *sui)
  208 {
  209     dui->host = ftp_strdup(sui->host);
  210     dui->port = ftp_strdup(sui->port);
  211     dui->path = ftp_strdup(sui->path);
  212     dui->utype = sui->utype;
  213     dui->portnum = sui->portnum;
  214 }
  215 
  216 static void
  217 freeurlinfo(struct urlinfo *ui)
  218 {
  219     FREEPTR(ui->host);
  220     FREEPTR(ui->port);
  221     FREEPTR(ui->path);
  222 }
  223 
  224 #ifndef NO_AUTH
  225 /*
  226  * Generate authorization response based on given authentication challenge.
  227  * Returns -1 if an error occurred, otherwise 0.
  228  * Sets response to a malloc(3)ed string; caller should free.
  229  */
  230 static int
  231 auth_url(const char *challenge, char **response, const struct authinfo *auth)
  232 {
  233     const char  *cp, *scheme, *errormsg;
  234     char        *ep, *clear, *realm;
  235     char         uuser[BUFSIZ], *gotpass;
  236     const char  *upass;
  237     int      rval;
  238     size_t       len, clen, rlen;
  239 
  240     *response = NULL;
  241     clear = realm = NULL;
  242     rval = -1;
  243     cp = challenge;
  244     scheme = "Basic";   /* only support Basic authentication */
  245     gotpass = NULL;
  246 
  247     DPRINTF("auth_url: challenge `%s'\n", challenge);
  248 
  249     if (! match_token(&cp, scheme)) {
  250         warnx("Unsupported authentication challenge `%s'",
  251             challenge);
  252         goto cleanup_auth_url;
  253     }
  254 
  255 #define REALM "realm=\""
  256     if (STRNEQUAL(cp, REALM))
  257         cp += sizeof(REALM) - 1;
  258     else {
  259         warnx("Unsupported authentication challenge `%s'",
  260             challenge);
  261         goto cleanup_auth_url;
  262     }
  263 /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */
  264     if ((ep = strchr(cp, '\"')) != NULL) {
  265         len = ep - cp;
  266         realm = (char *)ftp_malloc(len + 1);
  267         (void)strlcpy(realm, cp, len + 1);
  268     } else {
  269         warnx("Unsupported authentication challenge `%s'",
  270             challenge);
  271         goto cleanup_auth_url;
  272     }
  273 
  274     fprintf(ttyout, "Username for `%s': ", realm);
  275     if (auth->user != NULL) {
  276         (void)strlcpy(uuser, auth->user, sizeof(uuser));
  277         fprintf(ttyout, "%s\n", uuser);
  278     } else {
  279         (void)fflush(ttyout);
  280         if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) {
  281             warnx("%s; can't authenticate", errormsg);
  282             goto cleanup_auth_url;
  283         }
  284     }
  285     if (auth->pass != NULL)
  286         upass = auth->pass;
  287     else {
  288         gotpass = getpass("Password: ");
  289         if (gotpass == NULL) {
  290             warnx("Can't read password");
  291             goto cleanup_auth_url;
  292         }
  293         upass = gotpass;
  294     }
  295 
  296     clen = strlen(uuser) + strlen(upass) + 2;   /* user + ":" + pass + "\0" */
  297     clear = (char *)ftp_malloc(clen);
  298     (void)strlcpy(clear, uuser, clen);
  299     (void)strlcat(clear, ":", clen);
  300     (void)strlcat(clear, upass, clen);
  301     if (gotpass)
  302         memset(gotpass, 0, strlen(gotpass));
  303 
  304                         /* scheme + " " + enc + "\0" */
  305     rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
  306     *response = ftp_malloc(rlen);
  307     (void)strlcpy(*response, scheme, rlen);
  308     len = strlcat(*response, " ", rlen);
  309             /* use  `clen - 1'  to not encode the trailing NUL */
  310     base64_encode((unsigned char *)clear, clen - 1,
  311         (unsigned char *)*response + len);
  312     memset(clear, 0, clen);
  313     rval = 0;
  314 
  315  cleanup_auth_url:
  316     FREEPTR(clear);
  317     FREEPTR(realm);
  318     return (rval);
  319 }
  320 
  321 /*
  322  * Encode len bytes starting at clear using base64 encoding into encoded,
  323  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
  324  */
  325 static void
  326 base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded)
  327 {
  328     static const unsigned char enc[] =
  329         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  330     unsigned char   *cp;
  331     size_t   i;
  332 
  333     cp = encoded;
  334     for (i = 0; i < len; i += 3) {
  335         *(cp++) = enc[((clear[i + 0] >> 2))];
  336         *(cp++) = enc[((clear[i + 0] << 4) & 0x30)
  337                 | ((clear[i + 1] >> 4) & 0x0f)];
  338         *(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
  339                 | ((clear[i + 2] >> 6) & 0x03)];
  340         *(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
  341     }
  342     *cp = '\0';
  343     while (i-- > len)
  344         *(--cp) = '=';
  345 }
  346 #endif
  347 
  348 /*
  349  * Decode %xx escapes in given string, `in-place'.
  350  */
  351 static void
  352 url_decode(char *url)
  353 {
  354     unsigned char *p, *q;
  355 
  356     if (EMPTYSTRING(url))
  357         return;
  358     p = q = (unsigned char *)url;
  359 
  360 #define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
  361     while (*p) {
  362         if (p[0] == '%'
  363             && p[1] && isxdigit((unsigned char)p[1])
  364             && p[2] && isxdigit((unsigned char)p[2])) {
  365             *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
  366             p+=3;
  367         } else
  368             *q++ = *p++;
  369     }
  370     *q = '\0';
  371 }
  372 
  373 
  374 /*
  375  * Parse URL of form (per RFC 3986):
  376  *  <type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
  377  * Returns -1 if a parse error occurred, otherwise 0.
  378  * It's the caller's responsibility to url_decode() the returned
  379  * user, pass and path.
  380  *
  381  * Sets type to url_t, each of the given char ** pointers to a
  382  * malloc(3)ed strings of the relevant section, and port to
  383  * the number given, or ftpport if ftp://, or httpport if http://.
  384  *
  385  * XXX: this is not totally RFC 3986 compliant; <path> will have the
  386  * leading `/' unless it's an ftp:// URL, as this makes things easier
  387  * for file:// and http:// URLs.  ftp:// URLs have the `/' between the
  388  * host and the URL-path removed, but any additional leading slashes
  389  * in the URL-path are retained (because they imply that we should
  390  * later do "CWD" with a null argument).
  391  *
  392  * Examples:
  393  *   input URL           output path
  394  *   ---------           -----------
  395  *  "http://host"           "/"
  396  *  "http://host/"          "/"
  397  *  "http://host/path"      "/path"
  398  *  "file://host/dir/file"      "dir/file"
  399  *  "ftp://host"            ""
  400  *  "ftp://host/"           ""
  401  *  "ftp://host//"          "/"
  402  *  "ftp://host/dir/file"       "dir/file"
  403  *  "ftp://host//dir/file"      "/dir/file"
  404  */
  405 
  406 static int
  407 parse_url(const char *url, const char *desc, struct urlinfo *ui,
  408     struct authinfo *auth) 
  409 {
  410     const char  *origurl, *tport;
  411     char        *cp, *ep, *thost;
  412     size_t       len;
  413 
  414     if (url == NULL || desc == NULL || ui == NULL || auth == NULL)
  415         errx(1, "parse_url: invoked with NULL argument!");
  416     DPRINTF("parse_url: %s `%s'\n", desc, url);
  417 
  418     origurl = url;
  419     tport = NULL;
  420 
  421     if (STRNEQUAL(url, HTTP_URL)) {
  422         url += sizeof(HTTP_URL) - 1;
  423         ui->utype = HTTP_URL_T;
  424         ui->portnum = HTTP_PORT;
  425         tport = httpport;
  426     } else if (STRNEQUAL(url, FTP_URL)) {
  427         url += sizeof(FTP_URL) - 1;
  428         ui->utype = FTP_URL_T;
  429         ui->portnum = FTP_PORT;
  430         tport = ftpport;
  431     } else if (STRNEQUAL(url, FILE_URL)) {
  432         url += sizeof(FILE_URL) - 1;
  433         ui->utype = FILE_URL_T;
  434         tport = "";
  435 #ifdef WITH_SSL
  436     } else if (STRNEQUAL(url, HTTPS_URL)) {
  437         url += sizeof(HTTPS_URL) - 1;
  438         ui->utype = HTTPS_URL_T;
  439         ui->portnum = HTTPS_PORT;
  440         tport = httpsport;
  441 #endif
  442     } else {
  443         warnx("Invalid %s `%s'", desc, url);
  444  cleanup_parse_url:
  445         freeauthinfo(auth);
  446         freeurlinfo(ui);
  447         return (-1);
  448     }
  449 
  450     if (*url == '\0')
  451         return (0);
  452 
  453             /* find [user[:pass]@]host[:port] */
  454     ep = strchr(url, '/');
  455     if (ep == NULL)
  456         thost = ftp_strdup(url);
  457     else {
  458         len = ep - url;
  459         thost = (char *)ftp_malloc(len + 1);
  460         (void)strlcpy(thost, url, len + 1);
  461         if (ui->utype == FTP_URL_T) /* skip first / for ftp URLs */
  462             ep++;
  463         ui->path = ftp_strdup(ep);
  464     }
  465 
  466     cp = strchr(thost, '@');    /* look for user[:pass]@ in URLs */
  467     if (cp != NULL) {
  468         if (ui->utype == FTP_URL_T)
  469             anonftp = 0;    /* disable anonftp */
  470         auth->user = thost;
  471         *cp = '\0';
  472         thost = ftp_strdup(cp + 1);
  473         cp = strchr(auth->user, ':');
  474         if (cp != NULL) {
  475             *cp = '\0';
  476             auth->pass = ftp_strdup(cp + 1);
  477         }
  478         url_decode(auth->user);
  479         if (auth->pass)
  480             url_decode(auth->pass);
  481     }
  482 
  483 #ifdef INET6
  484             /*
  485              * Check if thost is an encoded IPv6 address, as per
  486              * RFC 3986:
  487              *  `[' ipv6-address ']'
  488              */
  489     if (*thost == '[') {
  490         cp = thost + 1;
  491         if ((ep = strchr(cp, ']')) == NULL ||
  492             (ep[1] != '\0' && ep[1] != ':')) {
  493             warnx("Invalid address `%s' in %s `%s'",
  494                 thost, desc, origurl);
  495             goto cleanup_parse_url;
  496         }
  497         len = ep - cp;      /* change `[xyz]' -> `xyz' */
  498         memmove(thost, thost + 1, len);
  499         thost[len] = '\0';
  500         if (! isipv6addr(thost)) {
  501             warnx("Invalid IPv6 address `%s' in %s `%s'",
  502                 thost, desc, origurl);
  503             goto cleanup_parse_url;
  504         }
  505         cp = ep + 1;
  506         if (*cp == ':')
  507             cp++;
  508         else
  509             cp = NULL;
  510     } else
  511 #endif /* INET6 */
  512         if ((cp = strchr(thost, ':')) != NULL)
  513             *cp++ = '\0';
  514     ui->host = thost;
  515 
  516             /* look for [:port] */
  517     if (cp != NULL) {
  518         unsigned long   nport;
  519 
  520         nport = strtoul(cp, &ep, 10);
  521         if (*cp == '\0' || *ep != '\0' ||
  522             nport < 1 || nport > MAX_IN_PORT_T) {
  523             warnx("Unknown port `%s' in %s `%s'",
  524                 cp, desc, origurl);
  525             goto cleanup_parse_url;
  526         }
  527         ui->portnum = nport;
  528         tport = cp;
  529     }
  530 
  531     if (tport != NULL)
  532         ui->port = ftp_strdup(tport);
  533     if (ui->path == NULL) {
  534         const char *emptypath = "/";
  535         if (ui->utype == FTP_URL_T) /* skip first / for ftp URLs */
  536             emptypath++;
  537         ui->path = ftp_strdup(emptypath);
  538     }
  539 
  540     DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) "
  541         "path `%s'\n",
  542         STRorNULL(auth->user), STRorNULL(auth->pass),
  543         STRorNULL(ui->host), STRorNULL(ui->port),
  544         ui->portnum ? ui->portnum : -1, STRorNULL(ui->path));
  545 
  546     return (0);
  547 }
  548 
  549 sigjmp_buf  httpabort;
  550 
  551 static int
  552 ftp_socket(const struct urlinfo *ui, void **ssl)
  553 {
  554     struct addrinfo hints, *res, *res0 = NULL;
  555     int error;
  556     int s;
  557     const char *host = ui->host;
  558     const char *port = ui->port;
  559 
  560     if (ui->utype != HTTPS_URL_T)
  561         ssl = NULL;
  562 
  563     memset(&hints, 0, sizeof(hints));
  564     hints.ai_flags = 0;
  565     hints.ai_family = family;
  566     hints.ai_socktype = SOCK_STREAM;
  567     hints.ai_protocol = 0;
  568 
  569     error = getaddrinfo(host, port, &hints, &res0);
  570     if (error) {
  571         warnx("Can't LOOKUP `%s:%s': %s", host, port,
  572             (error == EAI_SYSTEM) ? strerror(errno)
  573                       : gai_strerror(error));
  574         return -1;
  575     }
  576 
  577     if (res0->ai_canonname)
  578         host = res0->ai_canonname;
  579 
  580     s = -1;
  581     if (ssl)
  582         *ssl = NULL;
  583     for (res = res0; res; res = res->ai_next) {
  584         char    hname[NI_MAXHOST], sname[NI_MAXSERV];
  585 
  586         ai_unmapped(res);
  587         if (getnameinfo(res->ai_addr, res->ai_addrlen,
  588             hname, sizeof(hname), sname, sizeof(sname),
  589             NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
  590             strlcpy(hname, "?", sizeof(hname));
  591             strlcpy(sname, "?", sizeof(sname));
  592         }
  593 
  594         if (verbose && res0->ai_next) {
  595 #ifdef INET6
  596             if(res->ai_family == AF_INET6) {
  597                 fprintf(ttyout, "Trying [%s]:%s ...\n",
  598                     hname, sname);
  599             } else {
  600 #endif
  601                 fprintf(ttyout, "Trying %s:%s ...\n",
  602                     hname, sname);
  603 #ifdef INET6
  604             }
  605 #endif
  606         }
  607 
  608         s = socket(res->ai_family, SOCK_STREAM, res->ai_protocol);
  609         if (s < 0) {
  610             warn(
  611                 "Can't create socket for connection to "
  612                 "`%s:%s'", hname, sname);
  613             continue;
  614         }
  615 
  616         if (ftp_connect(s, res->ai_addr, res->ai_addrlen,
  617             verbose || !res->ai_next) < 0) {
  618             close(s);
  619             s = -1;
  620             continue;
  621         }
  622 
  623 #ifdef WITH_SSL
  624         if (ssl) {
  625             if ((*ssl = fetch_start_ssl(s, host)) == NULL) {
  626                 close(s);
  627                 s = -1;
  628                 continue;
  629             }
  630         }
  631 #endif
  632         break;
  633     }
  634     if (res0)
  635         freeaddrinfo(res0);
  636     return s;
  637 }
  638 
  639 static int
  640 handle_noproxy(const char *host, in_port_t portnum)
  641 {
  642 
  643     char *cp, *ep, *np, *np_copy, *np_iter, *no_proxy;
  644     unsigned long np_port;
  645     size_t hlen, plen;
  646     int isproxy = 1;
  647 
  648     /* check URL against list of no_proxied sites */
  649     no_proxy = getoptionvalue("no_proxy");
  650     if (EMPTYSTRING(no_proxy))
  651         return isproxy;
  652 
  653     np_iter = np_copy = ftp_strdup(no_proxy);
  654     hlen = strlen(host);
  655     while ((cp = strsep(&np_iter, " ,")) != NULL) {
  656         if (*cp == '\0')
  657             continue;
  658         if ((np = strrchr(cp, ':')) != NULL) {
  659             *np++ =  '\0';
  660             np_port = strtoul(np, &ep, 10);
  661             if (*np == '\0' || *ep != '\0')
  662                 continue;
  663             if (np_port != portnum)
  664                 continue;
  665         }
  666         plen = strlen(cp);
  667         if (hlen < plen)
  668             continue;
  669         if (strncasecmp(host + hlen - plen, cp, plen) == 0) {
  670             isproxy = 0;
  671             break;
  672         }
  673     }
  674     FREEPTR(np_copy);
  675     return isproxy;
  676 }
  677 
  678 static int
  679 handle_proxy(const char *url, const char *penv, struct urlinfo *ui,
  680     struct authinfo *pauth)
  681 {
  682     struct urlinfo pui;
  683 
  684     if (isipv6addr(ui->host) && strchr(ui->host, '%') != NULL) {
  685         warnx("Scoped address notation `%s' disallowed via web proxy",
  686             ui->host);
  687         return -1;
  688     }
  689 
  690     initurlinfo(&pui);
  691     if (parse_url(penv, "proxy URL", &pui, pauth) == -1)
  692         return -1;
  693 
  694     if ((!IS_HTTP_TYPE(pui.utype) && pui.utype != FTP_URL_T) ||
  695         EMPTYSTRING(pui.host) ||
  696         (! EMPTYSTRING(pui.path) && strcmp(pui.path, "/") != 0)) {
  697         warnx("Malformed proxy URL `%s'", penv);
  698         freeurlinfo(&pui);
  699         return -1;
  700     }
  701 
  702     FREEPTR(pui.path);
  703     pui.path = ftp_strdup(url);
  704 
  705     freeurlinfo(ui);
  706     *ui = pui;
  707 
  708     return 0;
  709 }
  710 
  711 static void
  712 print_host(FETCH *fin, const struct urlinfo *ui)
  713 {
  714     char *h, *p;
  715 
  716     if (strchr(ui->host, ':') == NULL) {
  717         fetch_printf(fin, "Host: %s", ui->host);
  718     } else {
  719         /*
  720          * strip off IPv6 scope identifier, since it is
  721          * local to the node
  722          */
  723         h = ftp_strdup(ui->host);
  724         if (isipv6addr(h) && (p = strchr(h, '%')) != NULL)
  725             *p = '\0';
  726 
  727         fetch_printf(fin, "Host: [%s]", h);
  728         free(h);
  729     }
  730 
  731     if ((ui->utype == HTTP_URL_T && ui->portnum != HTTP_PORT) ||
  732         (ui->utype == HTTPS_URL_T && ui->portnum != HTTPS_PORT))
  733         fetch_printf(fin, ":%u", ui->portnum);
  734     fetch_printf(fin, "\r\n");
  735 }
  736 
  737 static void
  738 print_agent(FETCH *fin)
  739 {
  740     const char *useragent;
  741     if ((useragent = getenv("FTPUSERAGENT")) != NULL) {
  742         fetch_printf(fin, "User-Agent: %s\r\n", useragent);
  743     } else {
  744         fetch_printf(fin, "User-Agent: %s/%s\r\n",
  745             FTP_PRODUCT, FTP_VERSION);
  746     }
  747 }
  748 
  749 static void
  750 print_cache(FETCH *fin, int isproxy)
  751 {
  752     fetch_printf(fin, isproxy ?
  753         "Pragma: no-cache\r\n" :
  754         "Cache-Control: no-cache\r\n");
  755 }
  756 
  757 static int
  758 print_get(FETCH *fin, int hasleading, int isproxy, const struct urlinfo *oui,
  759     const struct urlinfo *ui)
  760 {
  761     const char *leading = hasleading ? ", " : "  (";
  762 
  763     if (isproxy) {
  764         if (verbose) {
  765             fprintf(ttyout, "%svia %s:%u", leading,
  766                 ui->host, ui->portnum);
  767             leading = ", ";
  768             hasleading++;
  769         }
  770         fetch_printf(fin, "GET %s HTTP/1.0\r\n", ui->path);
  771         print_host(fin, oui);
  772         return hasleading;
  773     }
  774 
  775     fetch_printf(fin, "GET %s HTTP/1.1\r\n", ui->path);
  776     print_host(fin, ui);
  777     fetch_printf(fin, "Accept: */*\r\n");
  778     fetch_printf(fin, "Connection: close\r\n");
  779     if (restart_point) {
  780         fputs(leading, ttyout);
  781         fetch_printf(fin, "Range: bytes=" LLF "-\r\n",
  782             (LLT)restart_point);
  783         fprintf(ttyout, "restarting at " LLF, (LLT)restart_point);
  784         hasleading++;
  785     }
  786     return hasleading;
  787 }
  788 
  789 static void
  790 getmtime(const char *cp, time_t *mtime)
  791 {
  792     struct tm parsed;
  793     const char *t;
  794 
  795     memset(&parsed, 0, sizeof(parsed));
  796     t = parse_rfc2616time(&parsed, cp);
  797 
  798     if (t == NULL)
  799         return;
  800 
  801     parsed.tm_isdst = -1;
  802     if (*t == '\0')
  803         *mtime = timegm(&parsed);
  804 
  805 #ifndef NO_DEBUG
  806     if (ftp_debug && *mtime != -1) {
  807         fprintf(ttyout, "parsed time as: %s",
  808             rfc2822time(localtime(mtime)));
  809     }
  810 #endif
  811 }
  812 
  813 static int
  814 print_proxy(FETCH *fin, int hasleading, const char *wwwauth,
  815     const char *proxyauth)
  816 {
  817     const char *leading = hasleading ? ", " : "  (";
  818 
  819     if (wwwauth) {
  820         if (verbose) {
  821             fprintf(ttyout, "%swith authorization", leading);
  822             hasleading++;
  823         }
  824         fetch_printf(fin, "Authorization: %s\r\n", wwwauth);
  825     }
  826     if (proxyauth) {
  827         if (verbose) {
  828             fprintf(ttyout, "%swith proxy authorization", leading);
  829             hasleading++;
  830         }
  831         fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
  832     }
  833     return hasleading;
  834 }
  835 
  836 #ifdef WITH_SSL
  837 static void
  838 print_connect(FETCH *fin, const struct urlinfo *ui)
  839 {
  840     char hname[NI_MAXHOST], *p;
  841     const char *h;
  842 
  843     if (isipv6addr(ui->host)) {
  844         /*
  845          * strip off IPv6 scope identifier,
  846          * since it is local to the node
  847          */
  848         if ((p = strchr(ui->host, '%')) == NULL)
  849             snprintf(hname, sizeof(hname), "[%s]", ui->host);
  850         else
  851             snprintf(hname, sizeof(hname), "[%.*s]",
  852                 (int)(p - ui->host), ui->host);
  853         h = hname;
  854     } else
  855         h = ui->host;
  856 
  857     fetch_printf(fin, "CONNECT %s:%d HTTP/1.1\r\n", h, ui->portnum);
  858     fetch_printf(fin, "Host: %s:%d\r\n", h, ui->portnum);
  859 }
  860 #endif
  861 
  862 #define C_OK 0
  863 #define C_CLEANUP 1
  864 #define C_IMPROPER 2
  865 
  866 static int
  867 getresponseline(FETCH *fin, char *buf, size_t buflen, int *len)
  868 {
  869     const char *errormsg;
  870 
  871     alarmtimer(quit_time ? quit_time : 60);
  872     *len = fetch_getline(fin, buf, buflen, &errormsg);
  873     alarmtimer(0);
  874     if (*len < 0) {
  875         if (*errormsg == '\n')
  876             errormsg++;
  877         warnx("Receiving HTTP reply: %s", errormsg);
  878         return C_CLEANUP;
  879     }
  880     while (*len > 0 && (ISLWS(buf[*len-1])))
  881         buf[--*len] = '\0';
  882 
  883     if (*len)
  884         DPRINTF("%s: received `%s'\n", __func__, buf);
  885     return C_OK;
  886 }
  887 
  888 static int
  889 getresponse(FETCH *fin, char **cp, size_t buflen, int *hcode)
  890 {
  891     int len, rv;
  892     char *ep, *buf = *cp;
  893 
  894     *hcode = 0;
  895     if ((rv = getresponseline(fin, buf, buflen, &len)) != C_OK)
  896         return rv;
  897 
  898     /* Determine HTTP response code */
  899     *cp = strchr(buf, ' ');
  900     if (*cp == NULL)
  901         return C_IMPROPER;
  902 
  903     (*cp)++;
  904 
  905     *hcode = strtol(*cp, &ep, 10);
  906     if (*ep != '\0' && !isspace((unsigned char)*ep))
  907         return C_IMPROPER;
  908 
  909     return C_OK;
  910 }
  911 
  912 static int
  913 parse_posinfo(const char **cp, struct posinfo *pi)
  914 {
  915     char *ep;
  916     if (!match_token(cp, "bytes"))
  917         return -1;
  918 
  919     if (**cp == '*')
  920         (*cp)++;
  921     else {
  922         pi->rangestart = STRTOLL(*cp, &ep, 10);
  923         if (pi->rangestart < 0 || *ep != '-')
  924             return -1;
  925         *cp = ep + 1;
  926         pi->rangeend = STRTOLL(*cp, &ep, 10);
  927         if (pi->rangeend < 0 || pi->rangeend < pi->rangestart)
  928             return -1;
  929         *cp = ep;
  930     }
  931     if (**cp != '/')
  932         return -1;
  933     (*cp)++;
  934     if (**cp == '*')
  935         (*cp)++;
  936     else {
  937         pi->entitylen = STRTOLL(*cp, &ep, 10);
  938         if (pi->entitylen < 0)
  939             return -1;
  940         *cp = ep;
  941     }
  942     if (**cp != '\0')
  943         return -1;
  944 
  945 #ifndef NO_DEBUG
  946     if (ftp_debug) {
  947         fprintf(ttyout, "parsed range as: ");
  948         if (pi->rangestart == -1)
  949             fprintf(ttyout, "*");
  950         else
  951             fprintf(ttyout, LLF "-" LLF, (LLT)pi->rangestart,
  952                 (LLT)pi->rangeend);
  953         fprintf(ttyout, "/" LLF "\n", (LLT)pi->entitylen);
  954     }
  955 #endif
  956     return 0;
  957 }
  958 
  959 #ifndef NO_AUTH
  960 static void
  961 do_auth(int hcode, const char *url, const char *penv, struct authinfo *wauth,
  962     struct authinfo *pauth, char **auth, const char *message,
  963     volatile int *rval)
  964 {
  965     struct authinfo aauth;
  966     char *response;
  967 
  968     if (hcode == 401)
  969         aauth = *wauth;
  970     else
  971         aauth = *pauth;
  972 
  973     if (verbose || aauth.auth == NULL ||
  974         aauth.user == NULL || aauth.pass == NULL)
  975         fprintf(ttyout, "%s\n", message);
  976     if (EMPTYSTRING(*auth)) {
  977         warnx("No authentication challenge provided by server");
  978         return;
  979     }
  980 
  981     if (aauth.auth != NULL) {
  982         char reply[10];
  983 
  984         fprintf(ttyout, "Authorization failed. Retry (y/n)? ");
  985         if (get_line(stdin, reply, sizeof(reply), NULL) < 0) {
  986             return;
  987         }
  988         if (tolower((unsigned char)reply[0]) != 'y')
  989             return;
  990 
  991         aauth.user = NULL;
  992         aauth.pass = NULL;
  993     }
  994 
  995     if (auth_url(*auth, &response, &aauth) == 0) {
  996         *rval = fetch_url(url, penv,
  997             hcode == 401 ? pauth->auth : response,
  998             hcode == 401 ? response: wauth->auth);
  999         memset(response, 0, strlen(response));
 1000         FREEPTR(response);
 1001     }
 1002 }
 1003 #endif
 1004 
 1005 static int
 1006 negotiate_connection(FETCH *fin, const char *url, const char *penv,
 1007     struct posinfo *pi, time_t *mtime, struct authinfo *wauth,
 1008     struct authinfo *pauth, volatile int *rval, volatile int *ischunked,
 1009     char **auth)
 1010 {
 1011     int         len, hcode, rv;
 1012     char            buf[FTPBUFLEN], *ep;
 1013     const char      *cp, *token;
 1014     char            *location, *message;
 1015 
 1016     *auth = message = location = NULL;
 1017 
 1018     /* Read the response */
 1019     ep = buf;
 1020     switch (getresponse(fin, &ep, sizeof(buf), &hcode)) {
 1021     case C_CLEANUP:
 1022         goto cleanup_fetch_url;
 1023     case C_IMPROPER:
 1024         goto improper;
 1025     case C_OK:
 1026         message = ftp_strdup(ep);
 1027         break;
 1028     }
 1029 
 1030     /* Read the rest of the header. */
 1031 
 1032     for (;;) {
 1033         if ((rv = getresponseline(fin, buf, sizeof(buf), &len)) != C_OK)
 1034             goto cleanup_fetch_url;
 1035         if (len == 0)
 1036             break;
 1037 
 1038     /*
 1039      * Look for some headers
 1040      */
 1041 
 1042         cp = buf;
 1043 
 1044         if (match_token(&cp, "Content-Length:")) {
 1045             filesize = STRTOLL(cp, &ep, 10);
 1046             if (filesize < 0 || *ep != '\0')
 1047                 goto improper;
 1048             DPRINTF("%s: parsed len as: " LLF "\n",
 1049                 __func__, (LLT)filesize);
 1050 
 1051         } else if (match_token(&cp, "Content-Range:")) {
 1052             if (parse_posinfo(&cp, pi) == -1)
 1053                 goto improper;
 1054             if (! restart_point) {
 1055                 warnx(
 1056                 "Received unexpected Content-Range header");
 1057                 goto cleanup_fetch_url;
 1058             }
 1059 
 1060         } else if (match_token(&cp, "Last-Modified:")) {
 1061             getmtime(cp, mtime);
 1062 
 1063         } else if (match_token(&cp, "Location:")) {
 1064             location = ftp_strdup(cp);
 1065             DPRINTF("%s: parsed location as `%s'\n",
 1066                 __func__, cp);
 1067 
 1068         } else if (match_token(&cp, "Transfer-Encoding:")) {
 1069             if (match_token(&cp, "binary")) {
 1070                 warnx(
 1071         "Bogus transfer encoding `binary' (fetching anyway)");
 1072                 continue;
 1073             }
 1074             if (! (token = match_token(&cp, "chunked"))) {
 1075                 warnx(
 1076                 "Unsupported transfer encoding `%s'",
 1077                     token);
 1078                 goto cleanup_fetch_url;
 1079             }
 1080             (*ischunked)++;
 1081             DPRINTF("%s: using chunked encoding\n",
 1082                 __func__);
 1083 
 1084         } else if (match_token(&cp, "Proxy-Authenticate:")
 1085             || match_token(&cp, "WWW-Authenticate:")) {
 1086             if (! (token = match_token(&cp, "Basic"))) {
 1087                 DPRINTF("%s: skipping unknown auth "
 1088                     "scheme `%s'\n", __func__, token);
 1089                 continue;
 1090             }
 1091             FREEPTR(*auth);
 1092             *auth = ftp_strdup(token);
 1093             DPRINTF("%s: parsed auth as `%s'\n",
 1094                 __func__, cp);
 1095         }
 1096 
 1097     }
 1098             /* finished parsing header */
 1099 
 1100     switch (hcode) {
 1101     case 200:
 1102         break;
 1103     case 206:
 1104         if (! restart_point) {
 1105             warnx("Not expecting partial content header");
 1106             goto cleanup_fetch_url;
 1107         }
 1108         break;
 1109     case 300:
 1110     case 301:
 1111     case 302:
 1112     case 303:
 1113     case 305:
 1114     case 307:
 1115         if (EMPTYSTRING(location)) {
 1116             warnx(
 1117             "No redirection Location provided by server");
 1118             goto cleanup_fetch_url;
 1119         }
 1120         if (redirect_loop++ > 5) {
 1121             warnx("Too many redirections requested");
 1122             goto cleanup_fetch_url;
 1123         }
 1124         if (hcode == 305) {
 1125             if (verbose)
 1126                 fprintf(ttyout, "Redirected via %s\n",
 1127                     location);
 1128             *rval = fetch_url(url, location,
 1129                 pauth->auth, wauth->auth);
 1130         } else {
 1131             if (verbose)
 1132                 fprintf(ttyout, "Redirected to %s\n",
 1133                     location);
 1134             *rval = go_fetch(location);
 1135         }
 1136         goto cleanup_fetch_url;
 1137 #ifndef NO_AUTH
 1138     case 401:
 1139     case 407:
 1140         do_auth(hcode, url, penv, wauth, pauth, auth, message, rval);
 1141         goto cleanup_fetch_url;
 1142 #endif
 1143     default:
 1144         if (message)
 1145             warnx("Error retrieving file `%s'", message);
 1146         else
 1147             warnx("Unknown error retrieving file");
 1148         goto cleanup_fetch_url;
 1149     }
 1150     rv = C_OK;
 1151     goto out;
 1152 
 1153 cleanup_fetch_url:
 1154     rv = C_CLEANUP;
 1155     goto out;
 1156 improper:
 1157     rv = C_IMPROPER;
 1158     goto out;
 1159 out:
 1160     FREEPTR(message);
 1161     FREEPTR(location);
 1162     return rv;
 1163 }       /* end of ftp:// or http:// specific setup */
 1164 
 1165 #ifdef WITH_SSL
 1166 static int
 1167 connectmethod(FETCH *fin, const char *url, const char *penv,
 1168     struct urlinfo *oui, struct urlinfo *ui, struct authinfo *wauth,
 1169     struct authinfo *pauth, char **auth, int *hasleading, volatile int *rval)
 1170 {
 1171     void *ssl;
 1172     int hcode, rv;
 1173     const char *cp;
 1174     char buf[FTPBUFLEN], *ep;
 1175     char *message = NULL;
 1176 
 1177     print_connect(fin, oui);
 1178 
 1179     print_agent(fin);
 1180     *hasleading = print_proxy(fin, *hasleading, NULL, pauth->auth);
 1181 
 1182     if (verbose && *hasleading)
 1183         fputs(")\n", ttyout);
 1184     *hasleading = 0;
 1185 
 1186     fetch_printf(fin, "\r\n");
 1187     if (fetch_flush(fin) == EOF) {
 1188         warn("Writing HTTP request");
 1189         alarmtimer(0);
 1190         goto cleanup_fetch_url;
 1191     }
 1192     alarmtimer(0);
 1193 
 1194     /* Read the response */
 1195     ep = buf;
 1196     switch (getresponse(fin, &ep, sizeof(buf), &hcode)) {
 1197     case C_CLEANUP:
 1198         goto cleanup_fetch_url;
 1199     case C_IMPROPER:
 1200         goto improper;
 1201     case C_OK:
 1202         message = ftp_strdup(ep);
 1203         break;
 1204     }
 1205         
 1206     for (;;) {
 1207         int len;
 1208         if (getresponseline(fin, buf, sizeof(buf), &len) != C_OK)
 1209             goto cleanup_fetch_url;
 1210         if (len == 0)
 1211             break;
 1212 
 1213         cp = buf;
 1214         if (match_token(&cp, "Proxy-Authenticate:")) {
 1215             const char *token;
 1216             if (!(token = match_token(&cp, "Basic"))) {
 1217                 DPRINTF(
 1218                     "%s: skipping unknown auth scheme `%s'\n",
 1219                     __func__, token);
 1220                 continue;
 1221             }
 1222             FREEPTR(*auth);
 1223             *auth = ftp_strdup(token);
 1224             DPRINTF("%s: parsed auth as " "`%s'\n", __func__, cp);
 1225         }
 1226     }
 1227 
 1228     /* finished parsing header */
 1229     switch (hcode) {
 1230     case 200:
 1231         break;
 1232 #ifndef NO_AUTH
 1233     case 407:
 1234         do_auth(hcode, url, penv, wauth, pauth, auth, message, rval);
 1235         goto cleanup_fetch_url;
 1236 #endif
 1237     default:
 1238         if (message)
 1239             warnx("Error proxy connect " "`%s'", message);
 1240         else
 1241             warnx("Unknown error proxy " "connect");
 1242         goto cleanup_fetch_url;
 1243     }
 1244 
 1245     if ((ssl = fetch_start_ssl(fetch_fileno(fin), oui->host)) == NULL)
 1246         goto cleanup_fetch_url;
 1247     fetch_set_ssl(fin, ssl);
 1248 
 1249     rv = C_OK;
 1250     goto out;
 1251 improper:
 1252     rv = C_IMPROPER;
 1253     goto out;
 1254 cleanup_fetch_url:
 1255     rv = C_CLEANUP;
 1256     goto out;
 1257 out:
 1258     FREEPTR(message);
 1259     return rv;
 1260 }
 1261 #endif
 1262 
 1263 /*
 1264  * Retrieve URL, via a proxy if necessary, using HTTP.
 1265  * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
 1266  * http_proxy/https_proxy as appropriate.
 1267  * Supports HTTP redirects.
 1268  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
 1269  * is still open (e.g, ftp xfer with trailing /)
 1270  */
 1271 static int
 1272 fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 1273 {
 1274     sigfunc volatile    oldint;
 1275     sigfunc volatile    oldpipe;
 1276     sigfunc volatile    oldalrm;
 1277     sigfunc volatile    oldquit;
 1278     int volatile        s;
 1279     struct stat     sb;
 1280     int volatile        isproxy;
 1281     int volatile        rval, ischunked;
 1282     size_t          flen;
 1283     static size_t       bufsize;
 1284     static char     *xferbuf;
 1285     const char      *cp;
 1286     char            *ep;
 1287     char            *volatile auth;
 1288     char            *volatile savefile;
 1289     char            *volatile location;
 1290     char            *volatile message;
 1291     char            *volatile decodedpath;
 1292     struct authinfo     wauth, pauth;
 1293     struct posinfo      pi;
 1294     off_t           hashbytes;
 1295     int         (*volatile closefunc)(FILE *);
 1296     FETCH           *volatile fin;
 1297     FILE            *volatile fout;
 1298     const char      *volatile penv = proxyenv;
 1299     struct urlinfo      ui, oui;
 1300     time_t          mtime;
 1301     void            *ssl = NULL;
 1302 
 1303     DPRINTF("%s: `%s' proxyenv `%s'\n", __func__, url, STRorNULL(penv));
 1304 
 1305     oldquit = oldalrm = oldint = oldpipe = NULL;
 1306     closefunc = NULL;
 1307     fin = NULL;
 1308     fout = NULL;
 1309     s = -1;
 1310     savefile = NULL;
 1311     auth = location = message = NULL;
 1312     ischunked = isproxy = 0;
 1313     rval = 1;
 1314 
 1315     initurlinfo(&ui);
 1316     initurlinfo(&oui);
 1317     initauthinfo(&wauth, wwwauth);
 1318     initauthinfo(&pauth, proxyauth);
 1319 
 1320     decodedpath = NULL;
 1321 
 1322     if (sigsetjmp(httpabort, 1))
 1323         goto cleanup_fetch_url;
 1324 
 1325     if (parse_url(url, "URL", &ui, &wauth) == -1)
 1326         goto cleanup_fetch_url;
 1327 
 1328     copyurlinfo(&oui, &ui);
 1329 
 1330     if (ui.utype == FILE_URL_T && ! EMPTYSTRING(ui.host)
 1331         && strcasecmp(ui.host, "localhost") != 0) {
 1332         warnx("No support for non local file URL `%s'", url);
 1333         goto cleanup_fetch_url;
 1334     }
 1335 
 1336     if (EMPTYSTRING(ui.path)) {
 1337         if (ui.utype == FTP_URL_T) {
 1338             rval = fetch_ftp(url);
 1339             goto cleanup_fetch_url;
 1340         }
 1341         if (!IS_HTTP_TYPE(ui.utype) || outfile == NULL)  {
 1342             warnx("Invalid URL (no file after host) `%s'", url);
 1343             goto cleanup_fetch_url;
 1344         }
 1345     }
 1346 
 1347     decodedpath = ftp_strdup(ui.path);
 1348     url_decode(decodedpath);
 1349 
 1350     if (outfile)
 1351         savefile = outfile;
 1352     else {
 1353         cp = strrchr(decodedpath, '/');     /* find savefile */
 1354         if (cp != NULL)
 1355             savefile = ftp_strdup(cp + 1);
 1356         else
 1357             savefile = ftp_strdup(decodedpath);
 1358         /*
 1359          * Use the first URL we requested not the name after a
 1360          * possible redirect, but careful to save it because our
 1361          * "safety" check is the match to outfile.
 1362          */
 1363         outfile = ftp_strdup(savefile);
 1364     }
 1365     DPRINTF("%s: savefile `%s'\n", __func__, savefile);
 1366     if (EMPTYSTRING(savefile)) {
 1367         if (ui.utype == FTP_URL_T) {
 1368             rval = fetch_ftp(url);
 1369             goto cleanup_fetch_url;
 1370         }
 1371         warnx("No file after directory (you must specify an "
 1372             "output file) `%s'", url);
 1373         goto cleanup_fetch_url;
 1374     }
 1375 
 1376     restart_point = 0;
 1377     filesize = -1;
 1378     initposinfo(&pi);
 1379     mtime = -1;
 1380     if (restartautofetch) {
 1381         if (stat(savefile, &sb) == 0)
 1382             restart_point = sb.st_size;
 1383     }
 1384     if (ui.utype == FILE_URL_T) {       /* file:// URLs */
 1385         direction = "copied";
 1386         fin = fetch_open(decodedpath, "r");
 1387         if (fin == NULL) {
 1388             warn("Can't open `%s'", decodedpath);
 1389             goto cleanup_fetch_url;
 1390         }
 1391         if (fstat(fetch_fileno(fin), &sb) == 0) {
 1392             mtime = sb.st_mtime;
 1393             filesize = sb.st_size;
 1394         }
 1395         if (restart_point) {
 1396             if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) < 0) {
 1397                 warn("Can't seek to restart `%s'",
 1398                     decodedpath);
 1399                 goto cleanup_fetch_url;
 1400             }
 1401         }
 1402         if (verbose) {
 1403             fprintf(ttyout, "Copying %s", decodedpath);
 1404             if (restart_point)
 1405                 fprintf(ttyout, " (restarting at " LLF ")",
 1406                     (LLT)restart_point);
 1407             fputs("\n", ttyout);
 1408         }
 1409         if (0 == rcvbuf_size) {
 1410             rcvbuf_size = 8 * 1024; /* XXX */
 1411         }
 1412     } else {                /* ftp:// or http:// URLs */
 1413         int hasleading;
 1414 
 1415         if (penv == NULL) {
 1416 #ifdef WITH_SSL
 1417             if (ui.utype == HTTPS_URL_T)
 1418                 penv = getoptionvalue("https_proxy");
 1419 #endif
 1420             if (penv == NULL && IS_HTTP_TYPE(ui.utype))
 1421                 penv = getoptionvalue("http_proxy");
 1422             else if (ui.utype == FTP_URL_T)
 1423                 penv = getoptionvalue("ftp_proxy");
 1424         }
 1425         direction = "retrieved";
 1426         if (! EMPTYSTRING(penv)) {          /* use proxy */
 1427 
 1428             isproxy = handle_noproxy(ui.host, ui.portnum);
 1429 
 1430             if (isproxy == 0 && ui.utype == FTP_URL_T) {
 1431                 rval = fetch_ftp(url);
 1432                 goto cleanup_fetch_url;
 1433             }
 1434 
 1435             if (isproxy) {
 1436                 if (restart_point) {
 1437                     warnx(
 1438                         "Can't restart via proxy URL `%s'",
 1439                         penv);
 1440                     goto cleanup_fetch_url;
 1441                 }
 1442                 if (handle_proxy(url, penv, &ui, &pauth) < 0)
 1443                     goto cleanup_fetch_url;
 1444             }
 1445         } /* ! EMPTYSTRING(penv) */
 1446 
 1447         s = ftp_socket(&ui, &ssl);
 1448         if (s < 0) {
 1449             warnx("Can't connect to `%s:%s'", ui.host, ui.port);
 1450             goto cleanup_fetch_url;
 1451         }
 1452 
 1453         oldalrm = xsignal(SIGALRM, timeouthttp);
 1454         alarmtimer(quit_time ? quit_time : 60);
 1455         fin = fetch_fdopen(s, "r+");
 1456         fetch_set_ssl(fin, ssl);
 1457         alarmtimer(0);
 1458 
 1459         alarmtimer(quit_time ? quit_time : 60);
 1460         /*
 1461          * Construct and send the request.
 1462          */
 1463         if (verbose)
 1464             fprintf(ttyout, "Requesting %s\n", url);
 1465 
 1466         hasleading = 0;
 1467 #ifdef WITH_SSL
 1468         if (isproxy && oui.utype == HTTPS_URL_T) {
 1469             switch (connectmethod(fin, url, penv, &oui, &ui,
 1470                 &wauth, &pauth, __UNVOLATILE(&auth), &hasleading,
 1471                 &rval)) {
 1472             case C_CLEANUP:
 1473                 goto cleanup_fetch_url;
 1474             case C_IMPROPER:
 1475                 goto improper;
 1476             case C_OK:
 1477                 break;
 1478             default:
 1479                 abort();
 1480             }
 1481         }
 1482 #endif
 1483 
 1484         hasleading = print_get(fin, hasleading, isproxy, &oui, &ui);
 1485 
 1486         if (flushcache)
 1487             print_cache(fin, isproxy);
 1488 
 1489         print_agent(fin);
 1490         hasleading = print_proxy(fin, hasleading, wauth.auth,
 1491              auth ? NULL : pauth.auth);
 1492         if (hasleading) {
 1493             hasleading = 0;
 1494             if (verbose)
 1495                 fputs(")\n", ttyout);
 1496         }
 1497 
 1498         fetch_printf(fin, "\r\n");
 1499         if (fetch_flush(fin) == EOF) {
 1500             warn("Writing HTTP request");
 1501             alarmtimer(0);
 1502             goto cleanup_fetch_url;
 1503         }
 1504         alarmtimer(0);
 1505 
 1506         switch (negotiate_connection(fin, url, penv, &pi,
 1507             &mtime, &wauth, &pauth, &rval, &ischunked,
 1508             __UNVOLATILE(&auth))) {
 1509         case C_OK:
 1510             break;
 1511         case C_CLEANUP:
 1512             goto cleanup_fetch_url;
 1513         case C_IMPROPER:
 1514             goto improper;
 1515         default:
 1516             abort();
 1517         }
 1518     }
 1519 
 1520     /* Open the output file. */
 1521 
 1522     /*
 1523      * Only trust filenames with special meaning if they came from
 1524      * the command line
 1525      */
 1526     if (outfile == savefile) {
 1527         if (strcmp(savefile, "-") == 0) {
 1528             fout = stdout;
 1529         } else if (*savefile == '|') {
 1530             oldpipe = xsignal(SIGPIPE, SIG_IGN);
 1531             fout = popen(savefile + 1, "w");
 1532             if (fout == NULL) {
 1533                 warn("Can't execute `%s'", savefile + 1);
 1534                 goto cleanup_fetch_url;
 1535             }
 1536             closefunc = pclose;
 1537         }
 1538     }
 1539     if (fout == NULL) {
 1540         if ((pi.rangeend != -1 && pi.rangeend <= restart_point) ||
 1541             (pi.rangestart == -1 &&
 1542             filesize != -1 && filesize <= restart_point)) {
 1543             /* already done */
 1544             if (verbose)
 1545                 fprintf(ttyout, "already done\n");
 1546             rval = 0;
 1547             goto cleanup_fetch_url;
 1548         }
 1549         if (restart_point && pi.rangestart != -1) {
 1550             if (pi.entitylen != -1)
 1551                 filesize = pi.entitylen;
 1552             if (pi.rangestart != restart_point) {
 1553                 warnx(
 1554                     "Size of `%s' differs from save file `%s'",
 1555                     url, savefile);
 1556                 goto cleanup_fetch_url;
 1557             }
 1558             fout = fopen(savefile, "a");
 1559         } else
 1560             fout = fopen(savefile, "w");
 1561         if (fout == NULL) {
 1562             warn("Can't open `%s'", savefile);
 1563             goto cleanup_fetch_url;
 1564         }
 1565         closefunc = fclose;
 1566     }
 1567 
 1568             /* Trap signals */
 1569     oldquit = xsignal(SIGQUIT, psummary);
 1570     oldint = xsignal(SIGINT, aborthttp);
 1571 
 1572     assert(rcvbuf_size > 0);
 1573     if ((size_t)rcvbuf_size > bufsize) {
 1574         if (xferbuf)
 1575             (void)free(xferbuf);
 1576         bufsize = rcvbuf_size;
 1577         xferbuf = ftp_malloc(bufsize);
 1578     }
 1579 
 1580     bytes = 0;
 1581     hashbytes = mark;
 1582     if (oldalrm) {
 1583         (void)xsignal(SIGALRM, oldalrm);
 1584         oldalrm = NULL;
 1585     }
 1586     progressmeter(-1);
 1587 
 1588             /* Finally, suck down the file. */
 1589     do {
 1590         long chunksize;
 1591         short lastchunk;
 1592 
 1593         chunksize = 0;
 1594         lastchunk = 0;
 1595                     /* read chunk-size */
 1596         if (ischunked) {
 1597             if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
 1598                 warnx("Unexpected EOF reading chunk-size");
 1599                 goto cleanup_fetch_url;
 1600             }
 1601             errno = 0;
 1602             chunksize = strtol(xferbuf, &ep, 16);
 1603             if (ep == xferbuf) {
 1604                 warnx("Invalid chunk-size");
 1605                 goto cleanup_fetch_url;
 1606             }
 1607             if (errno == ERANGE || chunksize < 0) {
 1608                 errno = ERANGE;
 1609                 warn("Chunk-size `%.*s'",
 1610                     (int)(ep-xferbuf), xferbuf);
 1611                 goto cleanup_fetch_url;
 1612             }
 1613 
 1614                 /*
 1615                  * XXX: Work around bug in Apache 1.3.9 and
 1616                  *  1.3.11, which incorrectly put trailing
 1617                  *  space after the chunk-size.
 1618                  */
 1619             while (*ep == ' ')
 1620                 ep++;
 1621 
 1622                     /* skip [ chunk-ext ] */
 1623             if (*ep == ';') {
 1624                 while (*ep && *ep != '\r')
 1625                     ep++;
 1626             }
 1627 
 1628             if (strcmp(ep, "\r\n") != 0) {
 1629                 warnx("Unexpected data following chunk-size");
 1630                 goto cleanup_fetch_url;
 1631             }
 1632             DPRINTF("%s: got chunk-size of " LLF "\n", __func__,
 1633                 (LLT)chunksize);
 1634             if (chunksize == 0) {
 1635                 lastchunk = 1;
 1636                 goto chunkdone;
 1637             }
 1638         }
 1639                     /* transfer file or chunk */
 1640         while (1) {
 1641             struct timeval then, now, td;
 1642             volatile off_t bufrem;
 1643 
 1644             if (rate_get)
 1645                 (void)gettimeofday(&then, NULL);
 1646             bufrem = rate_get ? rate_get : (off_t)bufsize;
 1647             if (ischunked)
 1648                 bufrem = MIN(chunksize, bufrem);
 1649             while (bufrem > 0) {
 1650                 size_t nr = MIN((off_t)bufsize, bufrem);
 1651                 flen = fetch_read(xferbuf, sizeof(char),
 1652                     nr, fin);
 1653                 if (flen == 0) {
 1654                     if (fetch_error(fin))
 1655                         goto chunkerror;
 1656                     goto chunkdone;
 1657                 }
 1658                 bytes += flen;
 1659                 bufrem -= flen;
 1660                 if (fwrite(xferbuf, sizeof(char), flen, fout)
 1661                     != flen) {
 1662                     warn("Writing `%s'", savefile);
 1663                     goto cleanup_fetch_url;
 1664                 }
 1665                 if (hash && !progress) {
 1666                     while (bytes >= hashbytes) {
 1667                         (void)putc('#', ttyout);
 1668                         hashbytes += mark;
 1669                     }
 1670                     (void)fflush(ttyout);
 1671                 }
 1672                 if (ischunked) {
 1673                     chunksize -= flen;
 1674                     if (chunksize <= 0)
 1675                         break;
 1676                 }
 1677             }
 1678             if (rate_get) {
 1679                 while (1) {
 1680                     (void)gettimeofday(&now, NULL);
 1681                     timersub(&now, &then, &td);
 1682                     if (td.tv_sec > 0)
 1683                         break;
 1684                     usleep(1000000 - td.tv_usec);
 1685                 }
 1686             }
 1687             if (ischunked && chunksize <= 0)
 1688                 break;
 1689         }
 1690                     /* read CRLF after chunk*/
 1691  chunkdone:
 1692         if (ischunked) {
 1693             if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
 1694                 alarmtimer(0);
 1695                 warnx("Unexpected EOF reading chunk CRLF");
 1696                 goto cleanup_fetch_url;
 1697             }
 1698             if (strcmp(xferbuf, "\r\n") != 0) {
 1699                 warnx("Unexpected data following chunk");
 1700                 goto cleanup_fetch_url;
 1701             }
 1702             if (lastchunk)
 1703                 break;
 1704         }
 1705     } while (ischunked);
 1706 
 1707 /* XXX: deal with optional trailer & CRLF here? */
 1708 chunkerror:
 1709     if (hash && !progress && bytes > 0) {
 1710         if (bytes < mark)
 1711             (void)putc('#', ttyout);
 1712         (void)putc('\n', ttyout);
 1713     }
 1714     if (fetch_error(fin)) {
 1715         warn("Reading file");
 1716         goto cleanup_fetch_url;
 1717     }
 1718     progressmeter(1);
 1719     (void)fflush(fout);
 1720     if (closefunc == fclose && mtime != -1) {
 1721         struct timeval tval[2];
 1722 
 1723         (void)gettimeofday(&tval[0], NULL);
 1724         tval[1].tv_sec = mtime;
 1725         tval[1].tv_usec = 0;
 1726         (*closefunc)(fout);
 1727         fout = NULL;
 1728 
 1729         if (utimes(savefile, tval) == -1) {
 1730             fprintf(ttyout,
 1731                 "Can't change modification time to %s",
 1732                 rfc2822time(localtime(&mtime)));
 1733         }
 1734     }
 1735     if (bytes > 0)
 1736         ptransfer(0);
 1737     bytes = 0;
 1738 
 1739     rval = 0;
 1740     goto cleanup_fetch_url;
 1741 
 1742  improper:
 1743     warnx("Improper response from `%s:%s'", ui.host, ui.port);
 1744 
 1745  cleanup_fetch_url:
 1746     if (oldint)
 1747         (void)xsignal(SIGINT, oldint);
 1748     if (oldpipe)
 1749         (void)xsignal(SIGPIPE, oldpipe);
 1750     if (oldalrm)
 1751         (void)xsignal(SIGALRM, oldalrm);
 1752     if (oldquit)
 1753         (void)xsignal(SIGQUIT, oldpipe);
 1754     if (fin != NULL)
 1755         fetch_close(fin);
 1756     else if (s != -1)
 1757         close(s);
 1758     if (closefunc != NULL && fout != NULL)
 1759         (*closefunc)(fout);
 1760     if (savefile != outfile)
 1761         FREEPTR(savefile);
 1762     freeurlinfo(&ui);
 1763     freeurlinfo(&oui);
 1764     freeauthinfo(&wauth);
 1765     freeauthinfo(&pauth);
 1766     FREEPTR(decodedpath);
 1767     FREEPTR(auth);
 1768     FREEPTR(location);
 1769     FREEPTR(message);
 1770     return (rval);
 1771 }
 1772 
 1773 /*
 1774  * Abort a HTTP retrieval
 1775  */
 1776 static void
 1777 aborthttp(int notused)
 1778 {
 1779     char msgbuf[100];
 1780     int len;
 1781 
 1782     sigint_raised = 1;
 1783     alarmtimer(0);
 1784     if (fromatty) {
 1785         len = snprintf(msgbuf, sizeof(msgbuf),
 1786             "\n%s: HTTP fetch aborted.\n", getprogname());
 1787         if (len > 0)
 1788             write(fileno(ttyout), msgbuf, len);
 1789     }
 1790     siglongjmp(httpabort, 1);
 1791 }
 1792 
 1793 static void
 1794 timeouthttp(int notused)
 1795 {
 1796     char msgbuf[100];
 1797     int len;
 1798 
 1799     alarmtimer(0);
 1800     if (fromatty) {
 1801         len = snprintf(msgbuf, sizeof(msgbuf),
 1802             "\n%s: HTTP fetch timeout.\n", getprogname());
 1803         if (len > 0)
 1804             write(fileno(ttyout), msgbuf, len);
 1805     }
 1806     siglongjmp(httpabort, 1);
 1807 }
 1808 
 1809 /*
 1810  * Retrieve ftp URL or classic ftp argument using FTP.
 1811  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
 1812  * is still open (e.g, ftp xfer with trailing /)
 1813  */
 1814 static int
 1815 fetch_ftp(const char *url)
 1816 {
 1817     char        *cp, *xargv[5], rempath[MAXPATHLEN];
 1818     char        *dir, *file;
 1819     char         cmdbuf[MAXPATHLEN];
 1820     char         dirbuf[4];
 1821     int      dirhasglob, filehasglob, rval, transtype, xargc;
 1822     int      oanonftp, oautologin;
 1823     struct authinfo  auth;
 1824     struct urlinfo   ui;
 1825 
 1826     DPRINTF("fetch_ftp: `%s'\n", url);
 1827     dir = file = NULL;
 1828     rval = 1;
 1829     transtype = TYPE_I;
 1830 
 1831     initurlinfo(&ui);
 1832     initauthinfo(&auth, NULL);
 1833 
 1834     if (STRNEQUAL(url, FTP_URL)) {
 1835         if ((parse_url(url, "URL", &ui, &auth) == -1) ||
 1836             (auth.user != NULL && *auth.user == '\0') ||
 1837             EMPTYSTRING(ui.host)) {
 1838             warnx("Invalid URL `%s'", url);
 1839             goto cleanup_fetch_ftp;
 1840         }
 1841         /*
 1842          * Note: Don't url_decode(path) here.  We need to keep the
 1843          * distinction between "/" and "%2F" until later.
 1844          */
 1845 
 1846                     /* check for trailing ';type=[aid]' */
 1847         if (! EMPTYSTRING(ui.path) && (cp = strrchr(ui.path, ';')) != NULL) {
 1848             if (strcasecmp(cp, ";type=a") == 0)
 1849                 transtype = TYPE_A;
 1850             else if (strcasecmp(cp, ";type=i") == 0)
 1851                 transtype = TYPE_I;
 1852             else if (strcasecmp(cp, ";type=d") == 0) {
 1853                 warnx(
 1854                 "Directory listing via a URL is not supported");
 1855                 goto cleanup_fetch_ftp;
 1856             } else {
 1857                 warnx("Invalid suffix `%s' in URL `%s'", cp,
 1858                     url);
 1859                 goto cleanup_fetch_ftp;
 1860             }
 1861             *cp = 0;
 1862         }
 1863     } else {            /* classic style `[user@]host:[file]' */
 1864         ui.utype = CLASSIC_URL_T;
 1865         ui.host = ftp_strdup(url);
 1866         cp = strchr(ui.host, '@');
 1867         if (cp != NULL) {
 1868             *cp = '\0';
 1869             auth.user = ui.host;
 1870             anonftp = 0;    /* disable anonftp */
 1871             ui.host = ftp_strdup(cp + 1);
 1872         }
 1873         cp = strchr(ui.host, ':');
 1874         if (cp != NULL) {
 1875             *cp = '\0';
 1876             ui.path = ftp_strdup(cp + 1);
 1877         }
 1878     }
 1879     if (EMPTYSTRING(ui.host))
 1880         goto cleanup_fetch_ftp;
 1881 
 1882             /* Extract the file and (if present) directory name. */
 1883     dir = ui.path;
 1884     if (! EMPTYSTRING(dir)) {
 1885         /*
 1886          * If we are dealing with classic `[user@]host:[path]' syntax,
 1887          * then a path of the form `/file' (resulting from input of the
 1888          * form `host:/file') means that we should do "CWD /" before
 1889          * retrieving the file.  So we set dir="/" and file="file".
 1890          *
 1891          * But if we are dealing with URLs like `ftp://host/path' then
 1892          * a path of the form `/file' (resulting from a URL of the form
 1893          * `ftp://host//file') means that we should do `CWD ' (with an
 1894          * empty argument) before retrieving the file.  So we set
 1895          * dir="" and file="file".
 1896          *
 1897          * If the path does not contain / at all, we set dir=NULL.
 1898          * (We get a path without any slashes if we are dealing with
 1899          * classic `[user@]host:[file]' or URL `ftp://host/file'.)
 1900          *
 1901          * In all other cases, we set dir to a string that does not
 1902          * include the final '/' that separates the dir part from the
 1903          * file part of the path.  (This will be the empty string if
 1904          * and only if we are dealing with a path of the form `/file'
 1905          * resulting from an URL of the form `ftp://host//file'.)
 1906          */
 1907         cp = strrchr(dir, '/');
 1908         if (cp == dir && ui.utype == CLASSIC_URL_T) {
 1909             file = cp + 1;
 1910             (void)strlcpy(dirbuf, "/", sizeof(dirbuf));
 1911             dir = dirbuf;
 1912         } else if (cp != NULL) {
 1913             *cp++ = '\0';
 1914             file = cp;
 1915         } else {
 1916             file = dir;
 1917             dir = NULL;
 1918         }
 1919     } else
 1920         dir = NULL;
 1921     if (ui.utype == FTP_URL_T && file != NULL) {
 1922         url_decode(file);
 1923         /* but still don't url_decode(dir) */
 1924     }
 1925     DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s "
 1926         "path `%s' dir `%s' file `%s'\n",
 1927         STRorNULL(auth.user), STRorNULL(auth.pass),
 1928         STRorNULL(ui.host), STRorNULL(ui.port),
 1929         STRorNULL(ui.path), STRorNULL(dir), STRorNULL(file));
 1930 
 1931     dirhasglob = filehasglob = 0;
 1932     if (doglob &&
 1933         (ui.utype == CLASSIC_URL_T || ui.utype == FTP_URL_T)) {
 1934         if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
 1935             dirhasglob = 1;
 1936         if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
 1937             filehasglob = 1;
 1938     }
 1939 
 1940             /* Set up the connection */
 1941     oanonftp = anonftp;
 1942     if (connected)
 1943         disconnect(0, NULL);
 1944     anonftp = oanonftp;
 1945     (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf));
 1946     xargv[0] = cmdbuf;
 1947     xargv[1] = ui.host;
 1948     xargv[2] = NULL;
 1949     xargc = 2;
 1950     if (ui.port) {
 1951         xargv[2] = ui.port;
 1952         xargv[3] = NULL;
 1953         xargc = 3;
 1954     }
 1955     oautologin = autologin;
 1956         /* don't autologin in setpeer(), use ftp_login() below */
 1957     autologin = 0;
 1958     setpeer(xargc, xargv);
 1959     autologin = oautologin;
 1960     if ((connected == 0) ||
 1961         (connected == 1 && !ftp_login(ui.host, auth.user, auth.pass))) {
 1962         warnx("Can't connect or login to host `%s:%s'",
 1963             ui.host, ui.port ? ui.port : "?");
 1964         goto cleanup_fetch_ftp;
 1965     }
 1966 
 1967     switch (transtype) {
 1968     case TYPE_A:
 1969         setascii(1, xargv);
 1970         break;
 1971     case TYPE_I:
 1972         setbinary(1, xargv);
 1973         break;
 1974     default:
 1975         errx(1, "fetch_ftp: unknown transfer type %d", transtype);
 1976     }
 1977 
 1978         /*
 1979          * Change directories, if necessary.
 1980          *
 1981          * Note: don't use EMPTYSTRING(dir) below, because
 1982          * dir=="" means something different from dir==NULL.
 1983          */
 1984     if (dir != NULL && !dirhasglob) {
 1985         char *nextpart;
 1986 
 1987         /*
 1988          * If we are dealing with a classic `[user@]host:[path]'
 1989          * (urltype is CLASSIC_URL_T) then we have a raw directory
 1990          * name (not encoded in any way) and we can change
 1991          * directories in one step.
 1992          *
 1993          * If we are dealing with an `ftp://host/path' URL
 1994          * (urltype is FTP_URL_T), then RFC 3986 says we need to
 1995          * send a separate CWD command for each unescaped "/"
 1996          * in the path, and we have to interpret %hex escaping
 1997          * *after* we find the slashes.  It's possible to get
 1998          * empty components here, (from multiple adjacent
 1999          * slashes in the path) and RFC 3986 says that we should
 2000          * still do `CWD ' (with a null argument) in such cases.
 2001          *
 2002          * Many ftp servers don't support `CWD ', so if there's an
 2003          * error performing that command, bail out with a descriptive
 2004          * message.
 2005          *
 2006          * Examples:
 2007          *
 2008          * host:            dir="", urltype=CLASSIC_URL_T
 2009          *      logged in (to default directory)
 2010          * host:file            dir=NULL, urltype=CLASSIC_URL_T
 2011          *      "RETR file"
 2012          * host:dir/            dir="dir", urltype=CLASSIC_URL_T
 2013          *      "CWD dir", logged in
 2014          * ftp://host/          dir="", urltype=FTP_URL_T
 2015          *      logged in (to default directory)
 2016          * ftp://host/dir/      dir="dir", urltype=FTP_URL_T
 2017          *      "CWD dir", logged in
 2018          * ftp://host/file      dir=NULL, urltype=FTP_URL_T
 2019          *      "RETR file"
 2020          * ftp://host//file     dir="", urltype=FTP_URL_T
 2021          *      "CWD ", "RETR file"
 2022          * host:/file           dir="/", urltype=CLASSIC_URL_T
 2023          *      "CWD /", "RETR file"
 2024          * ftp://host///file        dir="/", urltype=FTP_URL_T
 2025          *      "CWD ", "CWD ", "RETR file"
 2026          * ftp://host/%2F/file      dir="%2F", urltype=FTP_URL_T
 2027          *      "CWD /", "RETR file"
 2028          * ftp://host/foo/file      dir="foo", urltype=FTP_URL_T
 2029          *      "CWD foo", "RETR file"
 2030          * ftp://host/foo/bar/file  dir="foo/bar"
 2031          *      "CWD foo", "CWD bar", "RETR file"
 2032          * ftp://host//foo/bar/file dir="/foo/bar"
 2033          *      "CWD ", "CWD foo", "CWD bar", "RETR file"
 2034          * ftp://host/foo//bar/file dir="foo//bar"
 2035          *      "CWD foo", "CWD ", "CWD bar", "RETR file"
 2036          * ftp://host/%2F/foo/bar/file  dir="%2F/foo/bar"
 2037          *      "CWD /", "CWD foo", "CWD bar", "RETR file"
 2038          * ftp://host/%2Ffoo/bar/file   dir="%2Ffoo/bar"
 2039          *      "CWD /foo", "CWD bar", "RETR file"
 2040          * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar"
 2041          *      "CWD /foo/bar", "RETR file"
 2042          * ftp://host/%2Ffoo%2Fbar%2Ffile   dir=NULL
 2043          *      "RETR /foo/bar/file"
 2044          *
 2045          * Note that we don't need `dir' after this point.
 2046          */
 2047         do {
 2048             if (ui.utype == FTP_URL_T) {
 2049                 nextpart = strchr(dir, '/');
 2050                 if (nextpart) {
 2051                     *nextpart = '\0';
 2052                     nextpart++;
 2053                 }
 2054                 url_decode(dir);
 2055             } else
 2056                 nextpart = NULL;
 2057             DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n",
 2058                 STRorNULL(dir), STRorNULL(nextpart));
 2059             if (ui.utype == FTP_URL_T || *dir != '\0') {
 2060                 (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf));
 2061                 xargv[0] = cmdbuf;
 2062                 xargv[1] = dir;
 2063                 xargv[2] = NULL;
 2064                 dirchange = 0;
 2065                 cd(2, xargv);
 2066                 if (! dirchange) {
 2067                     if (*dir == '\0' && code == 500)
 2068                         fprintf(stderr,
 2069 "\n"
 2070 "ftp: The `CWD ' command (without a directory), which is required by\n"
 2071 "     RFC 3986 to support the empty directory in the URL pathname (`//'),\n"
 2072 "     conflicts with the server's conformance to RFC 959.\n"
 2073 "     Try the same URL without the `//' in the URL pathname.\n"
 2074 "\n");
 2075                     goto cleanup_fetch_ftp;
 2076                 }
 2077             }
 2078             dir = nextpart;
 2079         } while (dir != NULL);
 2080     }
 2081 
 2082     if (EMPTYSTRING(file)) {
 2083         rval = -1;
 2084         goto cleanup_fetch_ftp;
 2085     }
 2086 
 2087     if (dirhasglob) {
 2088         (void)strlcpy(rempath, dir, sizeof(rempath));
 2089         (void)strlcat(rempath, "/", sizeof(rempath));
 2090         (void)strlcat(rempath, file,    sizeof(rempath));
 2091         file = rempath;
 2092     }
 2093 
 2094             /* Fetch the file(s). */
 2095     xargc = 2;
 2096     (void)strlcpy(cmdbuf, "get", sizeof(cmdbuf));
 2097     xargv[0] = cmdbuf;
 2098     xargv[1] = file;
 2099     xargv[2] = NULL;
 2100     if (dirhasglob || filehasglob) {
 2101         int ointeractive;
 2102 
 2103         ointeractive = interactive;
 2104         interactive = 0;
 2105         if (restartautofetch)
 2106             (void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf));
 2107         else
 2108             (void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf));
 2109         xargv[0] = cmdbuf;
 2110         mget(xargc, xargv);
 2111         interactive = ointeractive;
 2112     } else {
 2113         char *destfile = outfile;
 2114         if (destfile == NULL) {
 2115             cp = strrchr(file, '/');    /* find savefile */
 2116             if (cp != NULL)
 2117                 destfile = cp + 1;
 2118             else
 2119                 destfile = file;
 2120         }
 2121         xargv[2] = (char *)destfile;
 2122         xargv[3] = NULL;
 2123         xargc++;
 2124         if (restartautofetch)
 2125             reget(xargc, xargv);
 2126         else
 2127             get(xargc, xargv);
 2128     }
 2129 
 2130     if ((code / 100) == COMPLETE)
 2131         rval = 0;
 2132 
 2133  cleanup_fetch_ftp:
 2134     freeurlinfo(&ui);
 2135     freeauthinfo(&auth);
 2136     return (rval);
 2137 }
 2138 
 2139 /*
 2140  * Retrieve the given file to outfile.
 2141  * Supports arguments of the form:
 2142  *  "host:path", "ftp://host/path"  if $ftpproxy, call fetch_url() else
 2143  *                  call fetch_ftp()
 2144  *  "http://host/path"      call fetch_url() to use HTTP
 2145  *  "file:///path"          call fetch_url() to copy
 2146  *  "about:..."         print a message
 2147  *
 2148  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
 2149  * is still open (e.g, ftp xfer with trailing /)
 2150  */
 2151 static int
 2152 go_fetch(const char *url)
 2153 {
 2154     char *proxyenv;
 2155     char *p;
 2156 
 2157 #ifndef NO_ABOUT
 2158     /*
 2159      * Check for about:*
 2160      */
 2161     if (STRNEQUAL(url, ABOUT_URL)) {
 2162         url += sizeof(ABOUT_URL) -1;
 2163         if (strcasecmp(url, "ftp") == 0 ||
 2164             strcasecmp(url, "tnftp") == 0) {
 2165             fputs(
 2166 "This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n"
 2167 "for the NetBSD project.  Execute `man ftp' for more details.\n", ttyout);
 2168         } else if (strcasecmp(url, "lukem") == 0) {
 2169             fputs(
 2170 "Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
 2171 "Please email feedback to <lukem@NetBSD.org>.\n", ttyout);
 2172         } else if (strcasecmp(url, "netbsd") == 0) {
 2173             fputs(
 2174 "NetBSD is a freely available and redistributable UNIX-like operating system.\n"
 2175 "For more information, see http://www.NetBSD.org/\n", ttyout);
 2176         } else if (strcasecmp(url, "version") == 0) {
 2177             fprintf(ttyout, "Version: %s %s%s\n",
 2178                 FTP_PRODUCT, FTP_VERSION,
 2179 #ifdef INET6
 2180                 ""
 2181 #else
 2182                 " (-IPv6)"
 2183 #endif
 2184             );
 2185         } else {
 2186             fprintf(ttyout, "`%s' is an interesting topic.\n", url);
 2187         }
 2188         fputs("\n", ttyout);
 2189         return (0);
 2190     }
 2191 #endif
 2192 
 2193     /*
 2194      * Check for file:// and http:// URLs.
 2195      */
 2196     if (STRNEQUAL(url, HTTP_URL)
 2197 #ifdef WITH_SSL
 2198         || STRNEQUAL(url, HTTPS_URL)
 2199 #endif
 2200         || STRNEQUAL(url, FILE_URL))
 2201         return (fetch_url(url, NULL, NULL, NULL));
 2202 
 2203     /*
 2204      * If it contains "://" but does not begin with ftp://
 2205      * or something that was already handled, then it's
 2206      * unsupported.
 2207      *
 2208      * If it contains ":" but not "://" then we assume the
 2209      * part before the colon is a host name, not an URL scheme,
 2210      * so we don't try to match that here.
 2211      */
 2212     if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL))
 2213         errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url);
 2214 
 2215     /*
 2216      * Try FTP URL-style and host:file arguments next.
 2217      * If ftpproxy is set with an FTP URL, use fetch_url()
 2218      * Othewise, use fetch_ftp().
 2219      */
 2220     proxyenv = getoptionvalue("ftp_proxy");
 2221     if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL))
 2222         return (fetch_url(url, NULL, NULL, NULL));
 2223 
 2224     return (fetch_ftp(url));
 2225 }
 2226 
 2227 /*
 2228  * Retrieve multiple files from the command line,
 2229  * calling go_fetch() for each file.
 2230  *
 2231  * If an ftp path has a trailing "/", the path will be cd-ed into and
 2232  * the connection remains open, and the function will return -1
 2233  * (to indicate the connection is alive).
 2234  * If an error occurs the return value will be the offset+1 in
 2235  * argv[] of the file that caused a problem (i.e, argv[x]
 2236  * returns x+1)
 2237  * Otherwise, 0 is returned if all files retrieved successfully.
 2238  */
 2239 int
 2240 auto_fetch(int argc, char *argv[])
 2241 {
 2242     volatile int    argpos, rval;
 2243 
 2244     argpos = rval = 0;
 2245 
 2246     if (sigsetjmp(toplevel, 1)) {
 2247         if (connected)
 2248             disconnect(0, NULL);
 2249         if (rval > 0)
 2250             rval = argpos + 1;
 2251         return (rval);
 2252     }
 2253     (void)xsignal(SIGINT, intr);
 2254     (void)xsignal(SIGPIPE, lostpeer);
 2255 
 2256     /*
 2257      * Loop through as long as there's files to fetch.
 2258      */
 2259     for (; (rval == 0) && (argpos < argc); argpos++) {
 2260         if (strchr(argv[argpos], ':') == NULL)
 2261             break;
 2262         redirect_loop = 0;
 2263         if (!anonftp)
 2264             anonftp = 2;    /* Handle "automatic" transfers. */
 2265         rval = go_fetch(argv[argpos]);
 2266         if (outfile != NULL && strcmp(outfile, "-") != 0
 2267             && outfile[0] != '|') {
 2268             FREEPTR(outfile);
 2269         }
 2270         if (rval > 0)
 2271             rval = argpos + 1;
 2272     }
 2273 
 2274     if (connected && rval != -1)
 2275         disconnect(0, NULL);
 2276     return (rval);
 2277 }
 2278 
 2279 
 2280 /*
 2281  * Upload multiple files from the command line.
 2282  *
 2283  * If an error occurs the return value will be the offset+1 in
 2284  * argv[] of the file that caused a problem (i.e, argv[x]
 2285  * returns x+1)
 2286  * Otherwise, 0 is returned if all files uploaded successfully.
 2287  */
 2288 int
 2289 auto_put(int argc, char **argv, const char *uploadserver)
 2290 {
 2291     char    *uargv[4], *path, *pathsep;
 2292     int  uargc, rval, argpos;
 2293     size_t   len;
 2294     char     cmdbuf[MAX_C_NAME];
 2295 
 2296     (void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf));
 2297     uargv[0] = cmdbuf;
 2298     uargv[1] = argv[0];
 2299     uargc = 2;
 2300     uargv[2] = uargv[3] = NULL;
 2301     pathsep = NULL;
 2302     rval = 1;
 2303 
 2304     DPRINTF("auto_put: target `%s'\n", uploadserver);
 2305 
 2306     path = ftp_strdup(uploadserver);
 2307     len = strlen(path);
 2308     if (path[len - 1] != '/' && path[len - 1] != ':') {
 2309             /*
 2310              * make sure we always pass a directory to auto_fetch
 2311              */
 2312         if (argc > 1) {     /* more than one file to upload */
 2313             len = strlen(uploadserver) + 2; /* path + "/" + "\0" */
 2314             free(path);
 2315             path = (char *)ftp_malloc(len);
 2316             (void)strlcpy(path, uploadserver, len);
 2317             (void)strlcat(path, "/", len);
 2318         } else {        /* single file to upload */
 2319             (void)strlcpy(cmdbuf, "put", sizeof(cmdbuf));
 2320             uargv[0] = cmdbuf;
 2321             pathsep = strrchr(path, '/');
 2322             if (pathsep == NULL) {
 2323                 pathsep = strrchr(path, ':');
 2324                 if (pathsep == NULL) {
 2325                     warnx("Invalid URL `%s'", path);
 2326                     goto cleanup_auto_put;
 2327                 }
 2328                 pathsep++;
 2329                 uargv[2] = ftp_strdup(pathsep);
 2330                 pathsep[0] = '/';
 2331             } else
 2332                 uargv[2] = ftp_strdup(pathsep + 1);
 2333             pathsep[1] = '\0';
 2334             uargc++;
 2335         }
 2336     }
 2337     DPRINTF("auto_put: URL `%s' argv[2] `%s'\n",
 2338         path, STRorNULL(uargv[2]));
 2339 
 2340             /* connect and cwd */
 2341     rval = auto_fetch(1, &path);
 2342     if(rval >= 0)
 2343         goto cleanup_auto_put;
 2344 
 2345     rval = 0;
 2346 
 2347             /* target filename provided; upload 1 file */
 2348             /* XXX : is this the best way? */
 2349     if (uargc == 3) {
 2350         uargv[1] = argv[0];
 2351         put(uargc, uargv);
 2352         if ((code / 100) != COMPLETE)
 2353             rval = 1;
 2354     } else {    /* otherwise a target dir: upload all files to it */
 2355         for(argpos = 0; argv[argpos] != NULL; argpos++) {
 2356             uargv[1] = argv[argpos];
 2357             mput(uargc, uargv);
 2358             if ((code / 100) != COMPLETE) {
 2359                 rval = argpos + 1;
 2360                 break;
 2361             }
 2362         }
 2363     }
 2364 
 2365  cleanup_auto_put:
 2366     free(path);
 2367     FREEPTR(uargv[2]);
 2368     return (rval);
 2369 }