[BACK]Return to fetch.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / usr.bin / ftp

Annotation of src/usr.bin/ftp/fetch.c, Revision 1.178

1.178   ! lukem       1: /*     $NetBSD: fetch.c,v 1.177 2007/05/15 23:54:18 lukem Exp $        */
1.1       lukem       2:
                      3: /*-
1.174     lukem       4:  * Copyright (c) 1997-2007 The NetBSD Foundation, Inc.
1.1       lukem       5:  * All rights reserved.
                      6:  *
                      7:  * This code is derived from software contributed to The NetBSD Foundation
1.54      lukem       8:  * by Luke Mewburn.
1.1       lukem       9:  *
1.114     lukem      10:  * This code is derived from software contributed to The NetBSD Foundation
                     11:  * by Scott Aaron Bamford.
                     12:  *
1.1       lukem      13:  * Redistribution and use in source and binary forms, with or without
                     14:  * modification, are permitted provided that the following conditions
                     15:  * are met:
                     16:  * 1. Redistributions of source code must retain the above copyright
                     17:  *    notice, this list of conditions and the following disclaimer.
                     18:  * 2. Redistributions in binary form must reproduce the above copyright
                     19:  *    notice, this list of conditions and the following disclaimer in the
                     20:  *    documentation and/or other materials provided with the distribution.
                     21:  * 3. All advertising materials mentioning features or use of this software
                     22:  *    must display the following acknowledgement:
1.84      lukem      23:  *     This product includes software developed by the NetBSD
                     24:  *     Foundation, Inc. and its contributors.
1.1       lukem      25:  * 4. Neither the name of The NetBSD Foundation nor the names of its
                     26:  *    contributors may be used to endorse or promote products derived
                     27:  *    from this software without specific prior written permission.
                     28:  *
                     29:  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
                     30:  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
                     31:  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
1.14      lukem      32:  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
                     33:  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1.1       lukem      34:  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
                     35:  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
                     36:  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
                     37:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
                     38:  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
                     39:  * POSSIBILITY OF SUCH DAMAGE.
                     40:  */
                     41:
1.12      lukem      42: #include <sys/cdefs.h>
1.1       lukem      43: #ifndef lint
1.178   ! lukem      44: __RCSID("$NetBSD: fetch.c,v 1.177 2007/05/15 23:54:18 lukem Exp $");
1.1       lukem      45: #endif /* not lint */
                     46:
                     47: /*
                     48:  * FTP User Program -- Command line file retrieval
                     49:  */
                     50:
                     51: #include <sys/types.h>
                     52: #include <sys/param.h>
                     53: #include <sys/socket.h>
1.30      lukem      54: #include <sys/stat.h>
1.32      lukem      55: #include <sys/time.h>
1.1       lukem      56:
                     57: #include <netinet/in.h>
                     58:
1.2       lukem      59: #include <arpa/ftp.h>
1.1       lukem      60: #include <arpa/inet.h>
                     61:
                     62: #include <ctype.h>
                     63: #include <err.h>
1.22      lukem      64: #include <errno.h>
1.1       lukem      65: #include <netdb.h>
                     66: #include <fcntl.h>
                     67: #include <stdio.h>
                     68: #include <stdlib.h>
                     69: #include <string.h>
                     70: #include <unistd.h>
1.57      christos   71: #include <time.h>
1.1       lukem      72:
                     73: #include "ftp_var.h"
1.100     lukem      74: #include "version.h"
1.1       lukem      75:
1.27      lukem      76: typedef enum {
                     77:        UNKNOWN_URL_T=-1,
                     78:        HTTP_URL_T,
                     79:        FTP_URL_T,
1.53      lukem      80:        FILE_URL_T,
                     81:        CLASSIC_URL_T
1.27      lukem      82: } url_t;
                     83:
1.111     lukem      84: void           aborthttp(int);
1.147     christos   85: #ifndef NO_AUTH
1.111     lukem      86: static int     auth_url(const char *, char **, const char *, const char *);
1.154     lukem      87: static void    base64_encode(const unsigned char *, size_t, unsigned char *);
1.147     christos   88: #endif
1.111     lukem      89: static int     go_fetch(const char *);
                     90: static int     fetch_ftp(const char *);
                     91: static int     fetch_url(const char *, const char *, char *, char *);
1.154     lukem      92: static const char *match_token(const char **, const char *);
1.111     lukem      93: static int     parse_url(const char *, const char *, url_t *, char **,
                     94:                            char **, char **, char **, in_port_t *, char **);
                     95: static void    url_decode(char *);
1.12      lukem      96:
1.42      lukem      97: static int     redirect_loop;
                     98:
1.12      lukem      99:
1.150     lukem     100: #define        STRNEQUAL(a,b)  (strncasecmp((a), (b), sizeof((b))-1) == 0)
                    101: #define        ISLWS(x)        ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t')
                    102: #define        SKIPLWS(x)      do { while (ISLWS((*x))) x++; } while (0)
                    103:
                    104:
1.25      lukem     105: #define        ABOUT_URL       "about:"        /* propaganda */
                    106: #define        FILE_URL        "file://"       /* file URL prefix */
1.1       lukem     107: #define        FTP_URL         "ftp://"        /* ftp URL prefix */
                    108: #define        HTTP_URL        "http://"       /* http URL prefix */
                    109:
                    110:
1.154     lukem     111: /*
                    112:  * Determine if token is the next word in buf (case insensitive).
                    113:  * If so, advance buf past the token and any trailing LWS, and
                    114:  * return a pointer to the token (in buf).  Otherwise, return NULL.
1.172     christos  115:  * token may be preceded by LWS.
1.154     lukem     116:  * token must be followed by LWS or NUL.  (I.e, don't partial match).
                    117:  */
                    118: static const char *
                    119: match_token(const char **buf, const char *token)
                    120: {
                    121:        const char      *p, *orig;
                    122:        size_t          tlen;
                    123:
                    124:        tlen = strlen(token);
                    125:        p = *buf;
                    126:        SKIPLWS(p);
                    127:        orig = p;
                    128:        if (strncasecmp(p, token, tlen) != 0)
                    129:                return NULL;
                    130:        p += tlen;
                    131:        if (*p != '\0' && !ISLWS(*p))
                    132:                return NULL;
                    133:        SKIPLWS(p);
                    134:        orig = *buf;
                    135:        *buf = p;
                    136:        return orig;
                    137: }
                    138:
1.147     christos  139: #ifndef NO_AUTH
1.27      lukem     140: /*
1.44      lukem     141:  * Generate authorization response based on given authentication challenge.
                    142:  * Returns -1 if an error occurred, otherwise 0.
                    143:  * Sets response to a malloc(3)ed string; caller should free.
                    144:  */
                    145: static int
1.111     lukem     146: auth_url(const char *challenge, char **response, const char *guser,
                    147:        const char *gpass)
1.44      lukem     148: {
1.162     lukem     149:        const char      *cp, *scheme, *errormsg;
1.154     lukem     150:        char            *ep, *clear, *realm;
1.82      lukem     151:        char             user[BUFSIZ], *pass;
                    152:        int              rval;
                    153:        size_t           len, clen, rlen;
1.44      lukem     154:
                    155:        *response = NULL;
1.154     lukem     156:        clear = realm = NULL;
1.44      lukem     157:        rval = -1;
1.154     lukem     158:        cp = challenge;
                    159:        scheme = "Basic";       /* only support Basic authentication */
1.44      lukem     160:
1.163     christos  161:        DPRINTF("auth_url: challenge `%s'\n", challenge);
1.44      lukem     162:
1.154     lukem     163:        if (! match_token(&cp, scheme)) {
1.175     lukem     164:                warnx("Unsupported authentication challenge `%s'",
1.44      lukem     165:                    challenge);
                    166:                goto cleanup_auth_url;
                    167:        }
                    168:
1.91      lukem     169: #define        REALM "realm=\""
1.150     lukem     170:        if (STRNEQUAL(cp, REALM))
1.44      lukem     171:                cp += sizeof(REALM) - 1;
                    172:        else {
1.175     lukem     173:                warnx("Unsupported authentication challenge `%s'",
1.44      lukem     174:                    challenge);
                    175:                goto cleanup_auth_url;
                    176:        }
1.154     lukem     177: /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */
1.44      lukem     178:        if ((ep = strchr(cp, '\"')) != NULL) {
                    179:                size_t len = ep - cp;
                    180:
1.166     christos  181:                realm = (char *)ftp_malloc(len + 1);
1.78      lukem     182:                (void)strlcpy(realm, cp, len + 1);
1.44      lukem     183:        } else {
1.175     lukem     184:                warnx("Unsupported authentication challenge `%s'",
1.44      lukem     185:                    challenge);
                    186:                goto cleanup_auth_url;
                    187:        }
                    188:
1.154     lukem     189:        fprintf(ttyout, "Username for `%s': ", realm);
                    190:        if (guser != NULL) {
1.78      lukem     191:                (void)strlcpy(user, guser, sizeof(user));
1.154     lukem     192:                fprintf(ttyout, "%s\n", user);
                    193:        } else {
1.54      lukem     194:                (void)fflush(ttyout);
1.162     lukem     195:                if (getline(stdin, user, sizeof(user), &errormsg) < 0) {
                    196:                        warnx("%s; can't authenticate", errormsg);
1.54      lukem     197:                        goto cleanup_auth_url;
1.90      lukem     198:                }
1.54      lukem     199:        }
                    200:        if (gpass != NULL)
                    201:                pass = (char *)gpass;
1.174     lukem     202:        else {
1.54      lukem     203:                pass = getpass("Password: ");
1.174     lukem     204:                if (pass == NULL) {
                    205:                        warnx("Can't read password");
                    206:                        goto cleanup_auth_url;
                    207:                }
                    208:        }
1.44      lukem     209:
1.72      lukem     210:        clen = strlen(user) + strlen(pass) + 2; /* user + ":" + pass + "\0" */
1.166     christos  211:        clear = (char *)ftp_malloc(clen);
1.78      lukem     212:        (void)strlcpy(clear, user, clen);
                    213:        (void)strlcat(clear, ":", clen);
                    214:        (void)strlcat(clear, pass, clen);
1.54      lukem     215:        if (gpass == NULL)
1.80      lukem     216:                memset(pass, 0, strlen(pass));
1.44      lukem     217:
1.64      lukem     218:                                                /* scheme + " " + enc + "\0" */
1.74      lukem     219:        rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
1.166     christos  220:        *response = (char *)ftp_malloc(rlen);
1.78      lukem     221:        (void)strlcpy(*response, scheme, rlen);
1.72      lukem     222:        len = strlcat(*response, " ", rlen);
1.152     lukem     223:                        /* use  `clen - 1'  to not encode the trailing NUL */
1.158     lukem     224:        base64_encode((unsigned char *)clear, clen - 1,
                    225:            (unsigned char *)*response + len);
1.80      lukem     226:        memset(clear, 0, clen);
1.44      lukem     227:        rval = 0;
                    228:
1.118     lukem     229:  cleanup_auth_url:
1.44      lukem     230:        FREEPTR(clear);
                    231:        FREEPTR(realm);
                    232:        return (rval);
                    233: }
                    234:
                    235: /*
                    236:  * Encode len bytes starting at clear using base64 encoding into encoded,
                    237:  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
                    238:  */
1.124     lukem     239: static void
1.154     lukem     240: base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded)
1.44      lukem     241: {
1.154     lukem     242:        static const unsigned char enc[] =
1.44      lukem     243:            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1.154     lukem     244:        unsigned char   *cp;
1.82      lukem     245:        int      i;
1.44      lukem     246:
                    247:        cp = encoded;
                    248:        for (i = 0; i < len; i += 3) {
                    249:                *(cp++) = enc[((clear[i + 0] >> 2))];
                    250:                *(cp++) = enc[((clear[i + 0] << 4) & 0x30)
                    251:                            | ((clear[i + 1] >> 4) & 0x0f)];
                    252:                *(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
                    253:                            | ((clear[i + 2] >> 6) & 0x03)];
                    254:                *(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
                    255:        }
                    256:        *cp = '\0';
                    257:        while (i-- > len)
                    258:                *(--cp) = '=';
                    259: }
1.147     christos  260: #endif
1.44      lukem     261:
1.50      lukem     262: /*
                    263:  * Decode %xx escapes in given string, `in-place'.
                    264:  */
                    265: static void
1.111     lukem     266: url_decode(char *url)
1.50      lukem     267: {
                    268:        unsigned char *p, *q;
                    269:
                    270:        if (EMPTYSTRING(url))
                    271:                return;
1.75      lukem     272:        p = q = (unsigned char *)url;
1.50      lukem     273:
1.91      lukem     274: #define        HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
1.50      lukem     275:        while (*p) {
                    276:                if (p[0] == '%'
                    277:                    && p[1] && isxdigit((unsigned char)p[1])
                    278:                    && p[2] && isxdigit((unsigned char)p[2])) {
                    279:                        *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
                    280:                        p+=3;
                    281:                } else
                    282:                        *q++ = *p++;
                    283:        }
                    284:        *q = '\0';
                    285: }
                    286:
1.44      lukem     287:
                    288: /*
1.177     lukem     289:  * Parse URL of form (per RFC3986):
1.145     lukem     290:  *     <type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
1.27      lukem     291:  * Returns -1 if a parse error occurred, otherwise 0.
1.50      lukem     292:  * It's the caller's responsibility to url_decode() the returned
                    293:  * user, pass and path.
1.63      lukem     294:  *
1.27      lukem     295:  * Sets type to url_t, each of the given char ** pointers to a
                    296:  * malloc(3)ed strings of the relevant section, and port to
1.41      lukem     297:  * the number given, or ftpport if ftp://, or httpport if http://.
1.52      lukem     298:  *
1.177     lukem     299:  * XXX: this is not totally RFC3986 compliant; <path> will have the
1.53      lukem     300:  * leading `/' unless it's an ftp:// URL, as this makes things easier
1.177     lukem     301:  * for file:// and http:// URLs.  ftp:// URLs have the `/' between the
1.141     wiz       302:  * host and the URL-path removed, but any additional leading slashes
                    303:  * in the URL-path are retained (because they imply that we should
1.53      lukem     304:  * later do "CWD" with a null argument).
                    305:  *
                    306:  * Examples:
1.141     wiz       307:  *      input URL                       output path
1.53      lukem     308:  *      ---------                       -----------
1.177     lukem     309:  *     "http://host"                   "/"
                    310:  *     "http://host/"                  "/"
                    311:  *     "http://host/path"              "/path"
1.63      lukem     312:  *     "file://host/dir/file"          "dir/file"
1.177     lukem     313:  *     "ftp://host"                    ""
1.63      lukem     314:  *     "ftp://host/"                   ""
1.177     lukem     315:  *     "ftp://host//"                  "/"
                    316:  *     "ftp://host/dir/file"           "dir/file"
1.53      lukem     317:  *     "ftp://host//dir/file"          "/dir/file"
1.27      lukem     318:  */
                    319: static int
1.111     lukem     320: parse_url(const char *url, const char *desc, url_t *type,
                    321:                char **user, char **pass, char **host, char **port,
                    322:                in_port_t *portnum, char **path)
1.27      lukem     323: {
1.82      lukem     324:        const char      *origurl;
                    325:        char            *cp, *ep, *thost, *tport;
                    326:        size_t           len;
1.27      lukem     327:
                    328:        if (url == NULL || desc == NULL || type == NULL || user == NULL
1.82      lukem     329:            || pass == NULL || host == NULL || port == NULL || portnum == NULL
                    330:            || path == NULL)
1.27      lukem     331:                errx(1, "parse_url: invoked with NULL argument!");
                    332:
1.82      lukem     333:        origurl = url;
1.27      lukem     334:        *type = UNKNOWN_URL_T;
1.63      lukem     335:        *user = *pass = *host = *port = *path = NULL;
1.82      lukem     336:        *portnum = 0;
1.63      lukem     337:        tport = NULL;
1.27      lukem     338:
1.150     lukem     339:        if (STRNEQUAL(url, HTTP_URL)) {
1.27      lukem     340:                url += sizeof(HTTP_URL) - 1;
                    341:                *type = HTTP_URL_T;
1.82      lukem     342:                *portnum = HTTP_PORT;
1.63      lukem     343:                tport = httpport;
1.150     lukem     344:        } else if (STRNEQUAL(url, FTP_URL)) {
1.27      lukem     345:                url += sizeof(FTP_URL) - 1;
                    346:                *type = FTP_URL_T;
1.82      lukem     347:                *portnum = FTP_PORT;
1.63      lukem     348:                tport = ftpport;
1.150     lukem     349:        } else if (STRNEQUAL(url, FILE_URL)) {
1.27      lukem     350:                url += sizeof(FILE_URL) - 1;
                    351:                *type = FILE_URL_T;
                    352:        } else {
                    353:                warnx("Invalid %s `%s'", desc, url);
1.118     lukem     354:  cleanup_parse_url:
1.27      lukem     355:                FREEPTR(*user);
1.162     lukem     356:                if (*pass != NULL)
                    357:                        memset(*pass, 0, strlen(*pass));
1.27      lukem     358:                FREEPTR(*pass);
                    359:                FREEPTR(*host);
1.60      itojun    360:                FREEPTR(*port);
1.27      lukem     361:                FREEPTR(*path);
                    362:                return (-1);
                    363:        }
                    364:
                    365:        if (*url == '\0')
                    366:                return (0);
                    367:
                    368:                        /* find [user[:pass]@]host[:port] */
                    369:        ep = strchr(url, '/');
                    370:        if (ep == NULL)
1.166     christos  371:                thost = ftp_strdup(url);
1.27      lukem     372:        else {
1.50      lukem     373:                len = ep - url;
1.166     christos  374:                thost = (char *)ftp_malloc(len + 1);
1.78      lukem     375:                (void)strlcpy(thost, url, len + 1);
1.53      lukem     376:                if (*type == FTP_URL_T) /* skip first / for ftp URLs */
                    377:                        ep++;
1.166     christos  378:                *path = ftp_strdup(ep);
1.27      lukem     379:        }
                    380:
1.63      lukem     381:        cp = strchr(thost, '@');        /* look for user[:pass]@ in URLs */
1.54      lukem     382:        if (cp != NULL) {
                    383:                if (*type == FTP_URL_T)
                    384:                        anonftp = 0;    /* disable anonftp */
1.27      lukem     385:                *user = thost;
                    386:                *cp = '\0';
1.166     christos  387:                thost = ftp_strdup(cp + 1);
1.27      lukem     388:                cp = strchr(*user, ':');
                    389:                if (cp != NULL) {
                    390:                        *cp = '\0';
1.166     christos  391:                        *pass = ftp_strdup(cp + 1);
1.27      lukem     392:                }
1.154     lukem     393:                url_decode(*user);
                    394:                if (*pass)
                    395:                        url_decode(*pass);
1.63      lukem     396:        }
                    397:
                    398: #ifdef INET6
                    399:                        /*
                    400:                         * Check if thost is an encoded IPv6 address, as per
1.177     lukem     401:                         * RFC3986:
1.63      lukem     402:                         *      `[' ipv6-address ']'
                    403:                         */
                    404:        if (*thost == '[') {
                    405:                cp = thost + 1;
                    406:                if ((ep = strchr(cp, ']')) == NULL ||
1.106     itojun    407:                    (ep[1] != '\0' && ep[1] != ':')) {
1.63      lukem     408:                        warnx("Invalid address `%s' in %s `%s'",
1.82      lukem     409:                            thost, desc, origurl);
1.63      lukem     410:                        goto cleanup_parse_url;
                    411:                }
1.103     lukem     412:                len = ep - cp;          /* change `[xyz]' -> `xyz' */
1.63      lukem     413:                memmove(thost, thost + 1, len);
                    414:                thost[len] = '\0';
                    415:                if (! isipv6addr(thost)) {
                    416:                        warnx("Invalid IPv6 address `%s' in %s `%s'",
1.82      lukem     417:                            thost, desc, origurl);
1.63      lukem     418:                        goto cleanup_parse_url;
                    419:                }
                    420:                cp = ep + 1;
                    421:                if (*cp == ':')
                    422:                        cp++;
                    423:                else
                    424:                        cp = NULL;
1.27      lukem     425:        } else
1.63      lukem     426: #endif /* INET6 */
1.177     lukem     427:                if ((cp = strchr(thost, ':')) != NULL)
                    428:                        *cp++ =  '\0';
1.63      lukem     429:        *host = thost;
1.60      itojun    430:
1.27      lukem     431:                        /* look for [:port] */
                    432:        if (cp != NULL) {
1.82      lukem     433:                long    nport;
1.27      lukem     434:
1.122     lukem     435:                nport = parseport(cp, -1);
                    436:                if (nport == -1) {
                    437:                        warnx("Unknown port `%s' in %s `%s'",
                    438:                            cp, desc, origurl);
1.27      lukem     439:                        goto cleanup_parse_url;
                    440:                }
1.82      lukem     441:                *portnum = nport;
1.63      lukem     442:                tport = cp;
1.27      lukem     443:        }
1.82      lukem     444:
1.95      lukem     445:        if (tport != NULL)
1.166     christos  446:                *port = ftp_strdup(tport);
1.177     lukem     447:        if (*path == NULL) {
                    448:                const char *emptypath = "/";
                    449:                if (*type == FTP_URL_T) /* skip first / for ftp URLs */
                    450:                        emptypath++;
                    451:                *path = ftp_strdup(emptypath);
                    452:        }
1.27      lukem     453:
1.163     christos  454:        DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) "
                    455:            "path `%s'\n",
                    456:            *user ? *user : "<null>", *pass ? *pass : "<null>",
                    457:            *host ? *host : "<null>", *port ? *port : "<null>",
                    458:            *portnum ? *portnum : -1, *path ? *path : "<null>");
1.27      lukem     459:
                    460:        return (0);
                    461: }
                    462:
1.89      lukem     463: sigjmp_buf     httpabort;
1.2       lukem     464:
1.1       lukem     465: /*
1.50      lukem     466:  * Retrieve URL, via a proxy if necessary, using HTTP.
                    467:  * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
                    468:  * http_proxy as appropriate.
                    469:  * Supports HTTP redirects.
1.127     tacha     470:  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1.41      lukem     471:  * is still open (e.g, ftp xfer with trailing /)
1.1       lukem     472:  */
1.12      lukem     473: static int
1.111     lukem     474: fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
1.1       lukem     475: {
1.98      itojun    476:        struct addrinfo         hints, *res, *res0 = NULL;
1.66      christos  477:        int                     error;
1.98      itojun    478:        char                    hbuf[NI_MAXHOST];
1.173     christos  479:        sigfunc volatile        oldintr;
                    480:        sigfunc volatile        oldintp;
                    481:        int volatile            s;
1.95      lukem     482:        struct stat             sb;
1.173     christos  483:        int volatile            ischunked;
                    484:        int volatile            isproxy;
                    485:        int volatile            rval;
                    486:        int volatile            hcode;
1.42      lukem     487:        size_t                  len;
1.73      lukem     488:        static size_t           bufsize;
                    489:        static char             *xferbuf;
1.154     lukem     490:        const char              *cp, *token;
1.173     christos  491:        char                    *ep;
1.176     lukem     492:        char                    buf[FTPBUFLEN];
                    493:        const char              *errormsg;
1.173     christos  494:        char                    *volatile savefile;
                    495:        char                    *volatile auth;
                    496:        char                    *volatile location;
                    497:        char                    *volatile message;
                    498:        char                    *user, *pass, *host, *port, *path;
                    499:        char                    *volatile decodedpath;
1.144     lukem     500:        char                    *puser, *ppass, *useragent;
1.95      lukem     501:        off_t                   hashbytes, rangestart, rangeend, entitylen;
1.173     christos  502:        int                     (*volatile closefunc)(FILE *);
                    503:        FILE                    *volatile fin;
                    504:        FILE                    *volatile fout;
1.42      lukem     505:        time_t                  mtime;
                    506:        url_t                   urltype;
1.60      itojun    507:        in_port_t               portnum;
1.1       lukem     508:
1.86      lukem     509:        oldintr = oldintp = NULL;
1.22      lukem     510:        closefunc = NULL;
1.24      lukem     511:        fin = fout = NULL;
1.1       lukem     512:        s = -1;
1.176     lukem     513:        savefile = NULL;
1.44      lukem     514:        auth = location = message = NULL;
1.52      lukem     515:        ischunked = isproxy = hcode = 0;
1.42      lukem     516:        rval = 1;
1.54      lukem     517:        user = pass = host = path = decodedpath = puser = ppass = NULL;
1.1       lukem     518:
1.82      lukem     519:        if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port,
                    520:            &portnum, &path) == -1)
1.42      lukem     521:                goto cleanup_fetch_url;
1.5       lukem     522:
1.27      lukem     523:        if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
                    524:            && strcasecmp(host, "localhost") != 0) {
                    525:                warnx("No support for non local file URL `%s'", url);
1.42      lukem     526:                goto cleanup_fetch_url;
1.9       lukem     527:        }
1.27      lukem     528:
1.9       lukem     529:        if (EMPTYSTRING(path)) {
1.42      lukem     530:                if (urltype == FTP_URL_T) {
1.52      lukem     531:                        rval = fetch_ftp(url);
1.42      lukem     532:                        goto cleanup_fetch_url;
                    533:                }
1.27      lukem     534:                if (urltype != HTTP_URL_T || outfile == NULL)  {
                    535:                        warnx("Invalid URL (no file after host) `%s'", url);
1.42      lukem     536:                        goto cleanup_fetch_url;
1.27      lukem     537:                }
1.9       lukem     538:        }
1.1       lukem     539:
1.166     christos  540:        decodedpath = ftp_strdup(path);
1.50      lukem     541:        url_decode(decodedpath);
                    542:
1.22      lukem     543:        if (outfile)
1.166     christos  544:                savefile = ftp_strdup(outfile);
1.22      lukem     545:        else {
1.50      lukem     546:                cp = strrchr(decodedpath, '/');         /* find savefile */
1.29      lukem     547:                if (cp != NULL)
1.166     christos  548:                        savefile = ftp_strdup(cp + 1);
1.22      lukem     549:                else
1.166     christos  550:                        savefile = ftp_strdup(decodedpath);
1.22      lukem     551:        }
1.9       lukem     552:        if (EMPTYSTRING(savefile)) {
1.42      lukem     553:                if (urltype == FTP_URL_T) {
1.52      lukem     554:                        rval = fetch_ftp(url);
1.42      lukem     555:                        goto cleanup_fetch_url;
                    556:                }
1.175     lukem     557:                warnx("No file after directory (you must specify an "
1.140     grant     558:                    "output file) `%s'", url);
1.42      lukem     559:                goto cleanup_fetch_url;
1.52      lukem     560:        } else {
1.163     christos  561:                DPRINTF("savefile `%s'\n", savefile);
1.9       lukem     562:        }
1.1       lukem     563:
1.95      lukem     564:        restart_point = 0;
1.25      lukem     565:        filesize = -1;
1.95      lukem     566:        rangestart = rangeend = entitylen = -1;
1.25      lukem     567:        mtime = -1;
1.95      lukem     568:        if (restartautofetch) {
                    569:                if (strcmp(savefile, "-") != 0 && *savefile != '|' &&
                    570:                    stat(savefile, &sb) == 0)
                    571:                        restart_point = sb.st_size;
                    572:        }
1.25      lukem     573:        if (urltype == FILE_URL_T) {            /* file:// URLs */
                    574:                direction = "copied";
1.50      lukem     575:                fin = fopen(decodedpath, "r");
1.25      lukem     576:                if (fin == NULL) {
1.175     lukem     577:                        warn("Can't open `%s'", decodedpath);
1.42      lukem     578:                        goto cleanup_fetch_url;
1.5       lukem     579:                }
1.25      lukem     580:                if (fstat(fileno(fin), &sb) == 0) {
                    581:                        mtime = sb.st_mtime;
                    582:                        filesize = sb.st_size;
                    583:                }
1.95      lukem     584:                if (restart_point) {
                    585:                        if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
1.175     lukem     586:                                warn("Can't seek to restart `%s'",
1.95      lukem     587:                                    decodedpath);
                    588:                                goto cleanup_fetch_url;
                    589:                        }
                    590:                }
                    591:                if (verbose) {
                    592:                        fprintf(ttyout, "Copying %s", decodedpath);
                    593:                        if (restart_point)
1.121     lukem     594:                                fprintf(ttyout, " (restarting at " LLF ")",
                    595:                                    (LLT)restart_point);
1.95      lukem     596:                        fputs("\n", ttyout);
                    597:                }
1.25      lukem     598:        } else {                                /* ftp:// or http:// URLs */
1.65      lukem     599:                char *leading;
                    600:                int hasleading;
                    601:
1.42      lukem     602:                if (proxyenv == NULL) {
                    603:                        if (urltype == HTTP_URL_T)
1.91      lukem     604:                                proxyenv = getoptionvalue("http_proxy");
1.42      lukem     605:                        else if (urltype == FTP_URL_T)
1.91      lukem     606:                                proxyenv = getoptionvalue("ftp_proxy");
1.42      lukem     607:                }
1.25      lukem     608:                direction = "retrieved";
1.91      lukem     609:                if (! EMPTYSTRING(proxyenv)) {                  /* use proxy */
1.27      lukem     610:                        url_t purltype;
1.54      lukem     611:                        char *phost, *ppath;
1.91      lukem     612:                        char *pport, *no_proxy;
1.27      lukem     613:
                    614:                        isproxy = 1;
                    615:
                    616:                                /* check URL against list of no_proxied sites */
1.91      lukem     617:                        no_proxy = getoptionvalue("no_proxy");
                    618:                        if (! EMPTYSTRING(no_proxy)) {
1.171     elad      619:                                char *np, *np_copy, *np_iter;
1.27      lukem     620:                                long np_port;
                    621:                                size_t hlen, plen;
                    622:
1.171     elad      623:                                np_iter = np_copy = ftp_strdup(no_proxy);
1.27      lukem     624:                                hlen = strlen(host);
1.171     elad      625:                                while ((cp = strsep(&np_iter, " ,")) != NULL) {
1.27      lukem     626:                                        if (*cp == '\0')
                    627:                                                continue;
1.60      itojun    628:                                        if ((np = strrchr(cp, ':')) != NULL) {
1.27      lukem     629:                                                *np = '\0';
                    630:                                                np_port =
                    631:                                                    strtol(np + 1, &ep, 10);
                    632:                                                if (*ep != '\0')
                    633:                                                        continue;
1.82      lukem     634:                                                if (np_port != portnum)
1.27      lukem     635:                                                        continue;
                    636:                                        }
                    637:                                        plen = strlen(cp);
1.109     lukem     638:                                        if (hlen < plen)
                    639:                                                continue;
1.27      lukem     640:                                        if (strncasecmp(host + hlen - plen,
                    641:                                            cp, plen) == 0) {
                    642:                                                isproxy = 0;
                    643:                                                break;
                    644:                                        }
                    645:                                }
                    646:                                FREEPTR(np_copy);
1.127     tacha     647:                                if (isproxy == 0 && urltype == FTP_URL_T) {
                    648:                                        rval = fetch_ftp(url);
                    649:                                        goto cleanup_fetch_url;
                    650:                                }
1.25      lukem     651:                        }
1.1       lukem     652:
1.27      lukem     653:                        if (isproxy) {
                    654:                                if (parse_url(proxyenv, "proxy URL", &purltype,
1.82      lukem     655:                                    &puser, &ppass, &phost, &pport, &portnum,
                    656:                                    &ppath) == -1)
1.42      lukem     657:                                        goto cleanup_fetch_url;
1.27      lukem     658:
                    659:                                if ((purltype != HTTP_URL_T
                    660:                                     && purltype != FTP_URL_T) ||
                    661:                                    EMPTYSTRING(phost) ||
                    662:                                    (! EMPTYSTRING(ppath)
                    663:                                     && strcmp(ppath, "/") != 0)) {
                    664:                                        warnx("Malformed proxy URL `%s'",
                    665:                                            proxyenv);
                    666:                                        FREEPTR(phost);
1.60      itojun    667:                                        FREEPTR(pport);
1.27      lukem     668:                                        FREEPTR(ppath);
1.42      lukem     669:                                        goto cleanup_fetch_url;
1.27      lukem     670:                                }
1.112     itojun    671:                                if (isipv6addr(host) &&
                    672:                                    strchr(host, '%') != NULL) {
                    673:                                        warnx(
                    674: "Scoped address notation `%s' disallowed via web proxy",
                    675:                                            host);
                    676:                                        FREEPTR(phost);
                    677:                                        FREEPTR(pport);
                    678:                                        FREEPTR(ppath);
                    679:                                        goto cleanup_fetch_url;
                    680:                                }
1.12      lukem     681:
1.27      lukem     682:                                FREEPTR(host);
                    683:                                host = phost;
1.62      tron      684:                                FREEPTR(port);
                    685:                                port = pport;
1.27      lukem     686:                                FREEPTR(path);
1.166     christos  687:                                path = ftp_strdup(url);
1.27      lukem     688:                                FREEPTR(ppath);
                    689:                        }
1.91      lukem     690:                } /* ! EMPTYSTRING(proxyenv) */
1.25      lukem     691:
1.60      itojun    692:                memset(&hints, 0, sizeof(hints));
1.65      lukem     693:                hints.ai_flags = 0;
1.131     lukem     694:                hints.ai_family = family;
1.60      itojun    695:                hints.ai_socktype = SOCK_STREAM;
                    696:                hints.ai_protocol = 0;
1.122     lukem     697:                error = getaddrinfo(host, NULL, &hints, &res0);
1.60      itojun    698:                if (error) {
1.175     lukem     699:                        warnx("Can't lookup `%s': %s", host,
                    700:                            (error == EAI_SYSTEM) ? strerror(errno)
                    701:                                                  : gai_strerror(error));
1.60      itojun    702:                        goto cleanup_fetch_url;
                    703:                }
1.98      itojun    704:                if (res0->ai_canonname)
                    705:                        host = res0->ai_canonname;
1.60      itojun    706:
1.98      itojun    707:                s = -1;
                    708:                for (res = res0; res; res = res->ai_next) {
1.113     itojun    709:                        ai_unmapped(res);
1.98      itojun    710:                        if (getnameinfo(res->ai_addr, res->ai_addrlen,
1.142     itojun    711:                            hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
1.175     lukem     712:                                strlcpy(hbuf, "?", sizeof(hbuf));
1.98      itojun    713:
1.161     lukem     714:                        if (verbose && res0->ai_next) {
1.98      itojun    715:                                fprintf(ttyout, "Trying %s...\n", hbuf);
1.161     lukem     716:                        }
1.98      itojun    717:
1.122     lukem     718:                        ((struct sockaddr_in *)res->ai_addr)->sin_port =
                    719:                            htons(portnum);
1.125     lukem     720:                        s = socket(res->ai_family, SOCK_STREAM,
                    721:                            res->ai_protocol);
1.60      itojun    722:                        if (s < 0) {
1.175     lukem     723:                                warn(
                    724:                                  "Can't create socket for connection to `%s'",
                    725:                                    hbuf);
1.98      itojun    726:                                continue;
1.60      itojun    727:                        }
                    728:
1.166     christos  729:                        if (ftp_connect(s, res->ai_addr, res->ai_addrlen) < 0) {
1.60      itojun    730:                                close(s);
1.98      itojun    731:                                s = -1;
                    732:                                continue;
1.60      itojun    733:                        }
1.98      itojun    734:
                    735:                        /* success */
1.60      itojun    736:                        break;
                    737:                }
1.98      itojun    738:
                    739:                if (s < 0) {
1.175     lukem     740:                        warnx("Can't connect to `%s'", host);
1.98      itojun    741:                        goto cleanup_fetch_url;
                    742:                }
1.24      lukem     743:
1.25      lukem     744:                fin = fdopen(s, "r+");
                    745:                /*
1.27      lukem     746:                 * Construct and send the request.
1.25      lukem     747:                 */
1.65      lukem     748:                if (verbose)
                    749:                        fprintf(ttyout, "Requesting %s\n", url);
                    750:                leading = "  (";
                    751:                hasleading = 0;
1.27      lukem     752:                if (isproxy) {
1.65      lukem     753:                        if (verbose) {
                    754:                                fprintf(ttyout, "%svia %s:%s", leading,
                    755:                                    host, port);
                    756:                                leading = ", ";
                    757:                                hasleading++;
                    758:                        }
1.44      lukem     759:                        fprintf(fin, "GET %s HTTP/1.0\r\n", path);
1.47      lukem     760:                        if (flushcache)
                    761:                                fprintf(fin, "Pragma: no-cache\r\n");
1.27      lukem     762:                } else {
                    763:                        fprintf(fin, "GET %s HTTP/1.1\r\n", path);
1.106     itojun    764:                        if (strchr(host, ':')) {
1.112     itojun    765:                                char *h, *p;
                    766:
                    767:                                /*
                    768:                                 * strip off IPv6 scope identifier, since it is
                    769:                                 * local to the node
                    770:                                 */
1.166     christos  771:                                h = ftp_strdup(host);
1.112     itojun    772:                                if (isipv6addr(h) &&
                    773:                                    (p = strchr(h, '%')) != NULL) {
                    774:                                        *p = '\0';
                    775:                                }
1.134     lukem     776:                                fprintf(fin, "Host: [%s]", h);
1.112     itojun    777:                                free(h);
1.106     itojun    778:                        } else
1.134     lukem     779:                                fprintf(fin, "Host: %s", host);
                    780:                        if (portnum != HTTP_PORT)
                    781:                                fprintf(fin, ":%u", portnum);
                    782:                        fprintf(fin, "\r\n");
1.40      explorer  783:                        fprintf(fin, "Accept: */*\r\n");
1.42      lukem     784:                        fprintf(fin, "Connection: close\r\n");
1.95      lukem     785:                        if (restart_point) {
                    786:                                fputs(leading, ttyout);
1.121     lukem     787:                                fprintf(fin, "Range: bytes=" LLF "-\r\n",
                    788:                                    (LLT)restart_point);
                    789:                                fprintf(ttyout, "restarting at " LLF,
                    790:                                    (LLT)restart_point);
1.95      lukem     791:                                leading = ", ";
                    792:                                hasleading++;
                    793:                        }
1.47      lukem     794:                        if (flushcache)
                    795:                                fprintf(fin, "Cache-Control: no-cache\r\n");
1.25      lukem     796:                }
1.144     lukem     797:                if ((useragent=getenv("FTPUSERAGENT")) != NULL) {
                    798:                        fprintf(fin, "User-Agent: %s\r\n", useragent);
                    799:                } else {
                    800:                        fprintf(fin, "User-Agent: %s/%s\r\n",
                    801:                            FTP_PRODUCT, FTP_VERSION);
                    802:                }
1.44      lukem     803:                if (wwwauth) {
1.65      lukem     804:                        if (verbose) {
                    805:                                fprintf(ttyout, "%swith authorization",
                    806:                                    leading);
                    807:                                leading = ", ";
                    808:                                hasleading++;
                    809:                        }
1.44      lukem     810:                        fprintf(fin, "Authorization: %s\r\n", wwwauth);
                    811:                }
1.46      lukem     812:                if (proxyauth) {
1.65      lukem     813:                        if (verbose) {
1.49      lukem     814:                                fprintf(ttyout,
1.65      lukem     815:                                    "%swith proxy authorization", leading);
                    816:                                leading = ", ";
                    817:                                hasleading++;
                    818:                        }
1.44      lukem     819:                        fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
1.46      lukem     820:                }
1.65      lukem     821:                if (verbose && hasleading)
                    822:                        fputs(")\n", ttyout);
1.44      lukem     823:                fprintf(fin, "\r\n");
1.25      lukem     824:                if (fflush(fin) == EOF) {
                    825:                        warn("Writing HTTP request");
1.42      lukem     826:                        goto cleanup_fetch_url;
1.25      lukem     827:                }
1.1       lukem     828:
1.25      lukem     829:                                /* Read the response */
1.176     lukem     830:                len = getline(fin, buf, sizeof(buf), &errormsg);
                    831:                if (len < 0) {
                    832:                        if (*errormsg == '\n')
                    833:                                errormsg++;
                    834:                        warnx("Receiving HTTP reply: %s", errormsg);
1.42      lukem     835:                        goto cleanup_fetch_url;
1.24      lukem     836:                }
1.150     lukem     837:                while (len > 0 && (ISLWS(buf[len-1])))
1.24      lukem     838:                        buf[--len] = '\0';
1.163     christos  839:                DPRINTF("received `%s'\n", buf);
1.1       lukem     840:
1.42      lukem     841:                                /* Determine HTTP response code */
1.25      lukem     842:                cp = strchr(buf, ' ');
                    843:                if (cp == NULL)
                    844:                        goto improper;
                    845:                else
                    846:                        cp++;
1.42      lukem     847:                hcode = strtol(cp, &ep, 10);
                    848:                if (*ep != '\0' && !isspace((unsigned char)*ep))
                    849:                        goto improper;
1.166     christos  850:                message = ftp_strdup(cp);
1.25      lukem     851:
                    852:                                /* Read the rest of the header. */
                    853:                while (1) {
1.176     lukem     854:                        len = getline(fin, buf, sizeof(buf), &errormsg);
                    855:                        if (len < 0) {
                    856:                                if (*errormsg == '\n')
                    857:                                        errormsg++;
                    858:                                warnx("Receiving HTTP reply: %s", errormsg);
1.42      lukem     859:                                goto cleanup_fetch_url;
1.25      lukem     860:                        }
1.150     lukem     861:                        while (len > 0 && (ISLWS(buf[len-1])))
1.25      lukem     862:                                buf[--len] = '\0';
                    863:                        if (len == 0)
                    864:                                break;
1.163     christos  865:                        DPRINTF("received `%s'\n", buf);
1.25      lukem     866:
1.154     lukem     867:                /*
                    868:                 * Look for some headers
                    869:                 */
                    870:
1.25      lukem     871:                        cp = buf;
1.42      lukem     872:
1.154     lukem     873:                        if (match_token(&cp, "Content-Length:")) {
1.119     lukem     874:                                filesize = STRTOLL(cp, &ep, 10);
1.95      lukem     875:                                if (filesize < 0 || *ep != '\0')
1.25      lukem     876:                                        goto improper;
1.163     christos  877:                                DPRINTF("parsed len as: " LLF "\n",
                    878:                                    (LLT)filesize);
1.42      lukem     879:
1.154     lukem     880:                        } else if (match_token(&cp, "Content-Range:")) {
                    881:                                if (! match_token(&cp, "bytes"))
1.150     lukem     882:                                        goto improper;
1.154     lukem     883:
                    884:                                if (*cp == '*')
                    885:                                        cp++;
1.128     yamt      886:                                else {
                    887:                                        rangestart = STRTOLL(cp, &ep, 10);
                    888:                                        if (rangestart < 0 || *ep != '-')
                    889:                                                goto improper;
                    890:                                        cp = ep + 1;
                    891:                                        rangeend = STRTOLL(cp, &ep, 10);
                    892:                                        if (rangeend < 0 || rangeend < rangestart)
                    893:                                                goto improper;
1.154     lukem     894:                                        cp = ep;
1.128     yamt      895:                                }
1.154     lukem     896:                                if (*cp != '/')
1.95      lukem     897:                                        goto improper;
1.154     lukem     898:                                cp++;
                    899:                                if (*cp == '*')
                    900:                                        cp++;
1.128     yamt      901:                                else {
                    902:                                        entitylen = STRTOLL(cp, &ep, 10);
                    903:                                        if (entitylen < 0)
                    904:                                                goto improper;
1.154     lukem     905:                                        cp = ep;
1.128     yamt      906:                                }
1.154     lukem     907:                                if (*cp != '\0')
1.95      lukem     908:                                        goto improper;
                    909:
1.163     christos  910: #ifndef NO_DEBUG
1.167     christos  911:                                if (ftp_debug) {
1.128     yamt      912:                                        fprintf(ttyout, "parsed range as: ");
                    913:                                        if (rangestart == -1)
                    914:                                                fprintf(ttyout, "*");
                    915:                                        else
                    916:                                                fprintf(ttyout, LLF "-" LLF,
                    917:                                                    (LLT)rangestart,
                    918:                                                    (LLT)rangeend);
                    919:                                        fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
                    920:                                }
1.163     christos  921: #endif
1.95      lukem     922:                                if (! restart_point) {
                    923:                                        warnx(
                    924:                                    "Received unexpected Content-Range header");
                    925:                                        goto cleanup_fetch_url;
                    926:                                }
                    927:
1.154     lukem     928:                        } else if (match_token(&cp, "Last-Modified:")) {
1.25      lukem     929:                                struct tm parsed;
                    930:                                char *t;
                    931:
1.178   ! lukem     932:                                memset(&parsed, 0, sizeof(parsed));
1.177     lukem     933:                                                        /* RFC1123 */
1.25      lukem     934:                                if ((t = strptime(cp,
                    935:                                                "%a, %d %b %Y %H:%M:%S GMT",
                    936:                                                &parsed))
1.177     lukem     937:                                                        /* RFC0850 */
1.25      lukem     938:                                    || (t = strptime(cp,
                    939:                                                "%a, %d-%b-%y %H:%M:%S GMT",
                    940:                                                &parsed))
                    941:                                                        /* asctime */
                    942:                                    || (t = strptime(cp,
                    943:                                                "%a, %b %d %H:%M:%S %Y",
                    944:                                                &parsed))) {
1.38      lukem     945:                                        parsed.tm_isdst = -1;
1.25      lukem     946:                                        if (*t == '\0')
1.76      lukem     947:                                                mtime = timegm(&parsed);
1.163     christos  948: #ifndef NO_DEBUG
1.167     christos  949:                                        if (ftp_debug && mtime != -1) {
1.25      lukem     950:                                                fprintf(ttyout,
                    951:                                                    "parsed date as: %s",
                    952:                                                    ctime(&mtime));
1.38      lukem     953:                                        }
1.163     christos  954: #endif
1.25      lukem     955:                                }
1.42      lukem     956:
1.154     lukem     957:                        } else if (match_token(&cp, "Location:")) {
1.166     christos  958:                                location = ftp_strdup(cp);
1.163     christos  959:                                DPRINTF("parsed location as `%s'\n", cp);
1.44      lukem     960:
1.154     lukem     961:                        } else if (match_token(&cp, "Transfer-Encoding:")) {
                    962:                                if (match_token(&cp, "binary")) {
1.109     lukem     963:                                        warnx(
1.175     lukem     964:                        "Bogus transfer encoding `binary' (fetching anyway)");
1.109     lukem     965:                                        continue;
                    966:                                }
1.154     lukem     967:                                if (! (token = match_token(&cp, "chunked"))) {
1.45      lukem     968:                                        warnx(
1.175     lukem     969:                                    "Unsupported transfer encoding `%s'",
1.154     lukem     970:                                            token);
1.45      lukem     971:                                        goto cleanup_fetch_url;
                    972:                                }
                    973:                                ischunked++;
1.163     christos  974:                                DPRINTF("using chunked encoding\n");
1.45      lukem     975:
1.154     lukem     976:                        } else if (match_token(&cp, "Proxy-Authenticate:")
                    977:                                || match_token(&cp, "WWW-Authenticate:")) {
                    978:                                if (! (token = match_token(&cp, "Basic"))) {
1.163     christos  979:                                        DPRINTF(
1.154     lukem     980:                                "skipping unknown auth scheme `%s'\n",
                    981:                                                    token);
                    982:                                        continue;
                    983:                                }
1.44      lukem     984:                                FREEPTR(auth);
1.166     christos  985:                                auth = ftp_strdup(token);
1.163     christos  986:                                DPRINTF("parsed auth as `%s'\n", cp);
1.25      lukem     987:                        }
1.44      lukem     988:
1.24      lukem     989:                }
1.54      lukem     990:                                /* finished parsing header */
1.1       lukem     991:
1.52      lukem     992:                switch (hcode) {
                    993:                case 200:
                    994:                        break;
1.95      lukem     995:                case 206:
                    996:                        if (! restart_point) {
                    997:                                warnx("Not expecting partial content header");
                    998:                                goto cleanup_fetch_url;
                    999:                        }
                   1000:                        break;
1.52      lukem    1001:                case 300:
                   1002:                case 301:
                   1003:                case 302:
                   1004:                case 303:
                   1005:                case 305:
1.165     christos 1006:                case 307:
1.52      lukem    1007:                        if (EMPTYSTRING(location)) {
                   1008:                                warnx(
                   1009:                                "No redirection Location provided by server");
                   1010:                                goto cleanup_fetch_url;
                   1011:                        }
                   1012:                        if (redirect_loop++ > 5) {
                   1013:                                warnx("Too many redirections requested");
                   1014:                                goto cleanup_fetch_url;
                   1015:                        }
                   1016:                        if (hcode == 305) {
                   1017:                                if (verbose)
                   1018:                                        fprintf(ttyout, "Redirected via %s\n",
                   1019:                                            location);
                   1020:                                rval = fetch_url(url, location,
                   1021:                                    proxyauth, wwwauth);
                   1022:                        } else {
                   1023:                                if (verbose)
                   1024:                                        fprintf(ttyout, "Redirected to %s\n",
                   1025:                                            location);
                   1026:                                rval = go_fetch(location);
                   1027:                        }
1.42      lukem    1028:                        goto cleanup_fetch_url;
1.147     christos 1029: #ifndef NO_AUTH
1.52      lukem    1030:                case 401:
                   1031:                case 407:
                   1032:                    {
                   1033:                        char **authp;
1.54      lukem    1034:                        char *auser, *apass;
1.52      lukem    1035:
1.54      lukem    1036:                        if (hcode == 401) {
                   1037:                                authp = &wwwauth;
                   1038:                                auser = user;
                   1039:                                apass = pass;
                   1040:                        } else {
                   1041:                                authp = &proxyauth;
                   1042:                                auser = puser;
                   1043:                                apass = ppass;
                   1044:                        }
1.149     lukem    1045:                        if (verbose || *authp == NULL ||
                   1046:                            auser == NULL || apass == NULL)
                   1047:                                fprintf(ttyout, "%s\n", message);
                   1048:                        if (EMPTYSTRING(auth)) {
                   1049:                                warnx(
                   1050:                            "No authentication challenge provided by server");
                   1051:                                goto cleanup_fetch_url;
                   1052:                        }
1.52      lukem    1053:                        if (*authp != NULL) {
                   1054:                                char reply[10];
                   1055:
                   1056:                                fprintf(ttyout,
                   1057:                                    "Authorization failed. Retry (y/n)? ");
1.162     lukem    1058:                                if (getline(stdin, reply, sizeof(reply), NULL)
                   1059:                                    < 0) {
1.52      lukem    1060:                                        goto cleanup_fetch_url;
1.90      lukem    1061:                                }
1.153     dsl      1062:                                if (tolower((unsigned char)reply[0]) != 'y')
                   1063:                                        goto cleanup_fetch_url;
1.54      lukem    1064:                                auser = NULL;
                   1065:                                apass = NULL;
1.52      lukem    1066:                        }
1.54      lukem    1067:                        if (auth_url(auth, authp, auser, apass) == 0) {
1.52      lukem    1068:                                rval = fetch_url(url, proxyenv,
                   1069:                                    proxyauth, wwwauth);
1.80      lukem    1070:                                memset(*authp, 0, strlen(*authp));
1.52      lukem    1071:                                FREEPTR(*authp);
                   1072:                        }
1.42      lukem    1073:                        goto cleanup_fetch_url;
1.52      lukem    1074:                    }
1.147     christos 1075: #endif
1.52      lukem    1076:                default:
                   1077:                        if (message)
1.175     lukem    1078:                                warnx("Error retrieving file `%s'", message);
1.52      lukem    1079:                        else
                   1080:                                warnx("Unknown error retrieving file");
1.44      lukem    1081:                        goto cleanup_fetch_url;
                   1082:                }
1.52      lukem    1083:        }               /* end of ftp:// or http:// specific setup */
1.42      lukem    1084:
1.22      lukem    1085:                        /* Open the output file. */
                   1086:        if (strcmp(savefile, "-") == 0) {
                   1087:                fout = stdout;
                   1088:        } else if (*savefile == '|') {
1.77      lukem    1089:                oldintp = xsignal(SIGPIPE, SIG_IGN);
1.22      lukem    1090:                fout = popen(savefile + 1, "w");
                   1091:                if (fout == NULL) {
1.175     lukem    1092:                        warn("Can't execute `%s'", savefile + 1);
1.42      lukem    1093:                        goto cleanup_fetch_url;
1.22      lukem    1094:                }
                   1095:                closefunc = pclose;
                   1096:        } else {
1.129     yamt     1097:                if ((rangeend != -1 && rangeend <= restart_point) ||
                   1098:                    (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
                   1099:                        /* already done */
                   1100:                        if (verbose)
                   1101:                                fprintf(ttyout, "already done\n");
                   1102:                        rval = 0;
                   1103:                        goto cleanup_fetch_url;
                   1104:                }
                   1105:                if (restart_point && rangestart != -1) {
1.95      lukem    1106:                        if (entitylen != -1)
                   1107:                                filesize = entitylen;
1.129     yamt     1108:                        if (rangestart != restart_point) {
1.95      lukem    1109:                                warnx(
                   1110:                                    "Size of `%s' differs from save file `%s'",
                   1111:                                    url, savefile);
                   1112:                                goto cleanup_fetch_url;
                   1113:                        }
                   1114:                        fout = fopen(savefile, "a");
                   1115:                } else
                   1116:                        fout = fopen(savefile, "w");
1.22      lukem    1117:                if (fout == NULL) {
1.27      lukem    1118:                        warn("Can't open `%s'", savefile);
1.42      lukem    1119:                        goto cleanup_fetch_url;
1.22      lukem    1120:                }
                   1121:                closefunc = fclose;
1.1       lukem    1122:        }
                   1123:
1.22      lukem    1124:                        /* Trap signals */
1.88      lukem    1125:        if (sigsetjmp(httpabort, 1))
1.42      lukem    1126:                goto cleanup_fetch_url;
1.86      lukem    1127:        (void)xsignal(SIGQUIT, psummary);
1.77      lukem    1128:        oldintr = xsignal(SIGINT, aborthttp);
1.2       lukem    1129:
1.73      lukem    1130:        if (rcvbuf_size > bufsize) {
                   1131:                if (xferbuf)
                   1132:                        (void)free(xferbuf);
                   1133:                bufsize = rcvbuf_size;
1.166     christos 1134:                xferbuf = ftp_malloc(bufsize);
1.73      lukem    1135:        }
                   1136:
1.1       lukem    1137:        bytes = 0;
                   1138:        hashbytes = mark;
                   1139:        progressmeter(-1);
1.2       lukem    1140:
1.24      lukem    1141:                        /* Finally, suck down the file. */
1.45      lukem    1142:        do {
1.77      lukem    1143:                long chunksize;
1.45      lukem    1144:
                   1145:                chunksize = 0;
                   1146:                                        /* read chunksize */
                   1147:                if (ischunked) {
1.73      lukem    1148:                        if (fgets(xferbuf, bufsize, fin) == NULL) {
1.45      lukem    1149:                                warnx("Unexpected EOF reading chunksize");
                   1150:                                goto cleanup_fetch_url;
                   1151:                        }
1.73      lukem    1152:                        chunksize = strtol(xferbuf, &ep, 16);
1.103     lukem    1153:
                   1154:                                /*
1.123     lukem    1155:                                 * XXX: Work around bug in Apache 1.3.9 and
                   1156:                                 *      1.3.11, which incorrectly put trailing
                   1157:                                 *      space after the chunksize.
1.103     lukem    1158:                                 */
1.123     lukem    1159:                        while (*ep == ' ')
1.103     lukem    1160:                                ep++;
                   1161:
1.45      lukem    1162:                        if (strcmp(ep, "\r\n") != 0) {
                   1163:                                warnx("Unexpected data following chunksize");
                   1164:                                goto cleanup_fetch_url;
                   1165:                        }
1.163     christos 1166:                        DPRINTF("got chunksize of " LLF "\n", (LLT)chunksize);
1.45      lukem    1167:                        if (chunksize == 0)
                   1168:                                break;
                   1169:                }
1.59      lukem    1170:                                        /* transfer file or chunk */
                   1171:                while (1) {
                   1172:                        struct timeval then, now, td;
                   1173:                        off_t bufrem;
                   1174:
1.71      lukem    1175:                        if (rate_get)
                   1176:                                (void)gettimeofday(&then, NULL);
1.73      lukem    1177:                        bufrem = rate_get ? rate_get : bufsize;
1.101     lukem    1178:                        if (ischunked)
                   1179:                                bufrem = MIN(chunksize, bufrem);
1.59      lukem    1180:                        while (bufrem > 0) {
1.73      lukem    1181:                                len = fread(xferbuf, sizeof(char),
1.101     lukem    1182:                                    MIN(bufsize, bufrem), fin);
1.59      lukem    1183:                                if (len <= 0)
                   1184:                                        goto chunkdone;
                   1185:                                bytes += len;
                   1186:                                bufrem -= len;
1.73      lukem    1187:                                if (fwrite(xferbuf, sizeof(char), len, fout)
1.59      lukem    1188:                                    != len) {
                   1189:                                        warn("Writing `%s'", savefile);
                   1190:                                        goto cleanup_fetch_url;
                   1191:                                }
1.101     lukem    1192:                                if (hash && !progress) {
                   1193:                                        while (bytes >= hashbytes) {
                   1194:                                                (void)putc('#', ttyout);
                   1195:                                                hashbytes += mark;
                   1196:                                        }
                   1197:                                        (void)fflush(ttyout);
                   1198:                                }
                   1199:                                if (ischunked) {
                   1200:                                        chunksize -= len;
                   1201:                                        if (chunksize <= 0)
                   1202:                                                break;
1.45      lukem    1203:                                }
1.59      lukem    1204:                        }
                   1205:                        if (rate_get) {
                   1206:                                while (1) {
                   1207:                                        (void)gettimeofday(&now, NULL);
                   1208:                                        timersub(&now, &then, &td);
                   1209:                                        if (td.tv_sec > 0)
                   1210:                                                break;
                   1211:                                        usleep(1000000 - td.tv_usec);
                   1212:                                }
                   1213:                        }
1.101     lukem    1214:                        if (ischunked && chunksize <= 0)
                   1215:                                break;
1.1       lukem    1216:                }
1.45      lukem    1217:                                        /* read CRLF after chunk*/
1.59      lukem    1218:  chunkdone:
1.45      lukem    1219:                if (ischunked) {
1.73      lukem    1220:                        if (fgets(xferbuf, bufsize, fin) == NULL)
1.45      lukem    1221:                                break;
1.73      lukem    1222:                        if (strcmp(xferbuf, "\r\n") != 0) {
1.45      lukem    1223:                                warnx("Unexpected data following chunk");
                   1224:                                goto cleanup_fetch_url;
1.1       lukem    1225:                        }
                   1226:                }
1.45      lukem    1227:        } while (ischunked);
1.1       lukem    1228:        if (hash && !progress && bytes > 0) {
                   1229:                if (bytes < mark)
1.22      lukem    1230:                        (void)putc('#', ttyout);
                   1231:                (void)putc('\n', ttyout);
1.1       lukem    1232:        }
1.25      lukem    1233:        if (ferror(fin)) {
                   1234:                warn("Reading file");
1.42      lukem    1235:                goto cleanup_fetch_url;
1.1       lukem    1236:        }
1.2       lukem    1237:        progressmeter(1);
1.25      lukem    1238:        (void)fflush(fout);
                   1239:        if (closefunc == fclose && mtime != -1) {
                   1240:                struct timeval tval[2];
                   1241:
                   1242:                (void)gettimeofday(&tval[0], NULL);
                   1243:                tval[1].tv_sec = mtime;
                   1244:                tval[1].tv_usec = 0;
1.31      lukem    1245:                (*closefunc)(fout);
                   1246:                fout = NULL;
                   1247:
                   1248:                if (utimes(savefile, tval) == -1) {
1.25      lukem    1249:                        fprintf(ttyout,
                   1250:                            "Can't change modification time to %s",
                   1251:                            asctime(localtime(&mtime)));
                   1252:                }
                   1253:        }
                   1254:        if (bytes > 0)
                   1255:                ptransfer(0);
1.136     lukem    1256:        bytes = 0;
1.1       lukem    1257:
1.42      lukem    1258:        rval = 0;
                   1259:        goto cleanup_fetch_url;
1.11      lukem    1260:
1.118     lukem    1261:  improper:
1.27      lukem    1262:        warnx("Improper response from `%s'", host);
1.11      lukem    1263:
1.118     lukem    1264:  cleanup_fetch_url:
1.86      lukem    1265:        if (oldintr)
                   1266:                (void)xsignal(SIGINT, oldintr);
                   1267:        if (oldintp)
                   1268:                (void)xsignal(SIGPIPE, oldintp);
1.24      lukem    1269:        if (fin != NULL)
                   1270:                fclose(fin);
                   1271:        else if (s != -1)
1.1       lukem    1272:                close(s);
1.22      lukem    1273:        if (closefunc != NULL && fout != NULL)
                   1274:                (*closefunc)(fout);
1.156     lukem    1275:        if (res0)
                   1276:                freeaddrinfo(res0);
1.29      lukem    1277:        FREEPTR(savefile);
1.27      lukem    1278:        FREEPTR(user);
1.162     lukem    1279:        if (pass != NULL)
                   1280:                memset(pass, 0, strlen(pass));
1.27      lukem    1281:        FREEPTR(pass);
                   1282:        FREEPTR(host);
1.60      itojun   1283:        FREEPTR(port);
1.27      lukem    1284:        FREEPTR(path);
1.50      lukem    1285:        FREEPTR(decodedpath);
1.54      lukem    1286:        FREEPTR(puser);
1.162     lukem    1287:        if (ppass != NULL)
                   1288:                memset(ppass, 0, strlen(ppass));
1.54      lukem    1289:        FREEPTR(ppass);
1.44      lukem    1290:        FREEPTR(auth);
1.42      lukem    1291:        FREEPTR(location);
                   1292:        FREEPTR(message);
                   1293:        return (rval);
1.1       lukem    1294: }
                   1295:
                   1296: /*
1.50      lukem    1297:  * Abort a HTTP retrieval
1.2       lukem    1298:  */
                   1299: void
1.111     lukem    1300: aborthttp(int notused)
1.2       lukem    1301: {
1.88      lukem    1302:        char msgbuf[100];
1.159     lukem    1303:        size_t len;
1.2       lukem    1304:
1.148     lukem    1305:        sigint_raised = 1;
1.2       lukem    1306:        alarmtimer(0);
1.88      lukem    1307:        len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
                   1308:        write(fileno(ttyout), msgbuf, len);
                   1309:        siglongjmp(httpabort, 1);
1.2       lukem    1310: }
                   1311:
                   1312: /*
1.50      lukem    1313:  * Retrieve ftp URL or classic ftp argument using FTP.
1.63      lukem    1314:  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1.42      lukem    1315:  * is still open (e.g, ftp xfer with trailing /)
                   1316:  */
                   1317: static int
1.111     lukem    1318: fetch_ftp(const char *url)
1.42      lukem    1319: {
                   1320:        char            *cp, *xargv[5], rempath[MAXPATHLEN];
                   1321:        char            *host, *path, *dir, *file, *user, *pass;
1.60      itojun   1322:        char            *port;
1.160     lukem    1323:        int              dirhasglob, filehasglob, rval, type, xargc;
                   1324:        int              oanonftp, oautologin;
1.82      lukem    1325:        in_port_t        portnum;
                   1326:        url_t            urltype;
1.42      lukem    1327:
                   1328:        host = path = dir = file = user = pass = NULL;
1.60      itojun   1329:        port = NULL;
1.42      lukem    1330:        rval = 1;
1.53      lukem    1331:        type = TYPE_I;
1.42      lukem    1332:
1.150     lukem    1333:        if (STRNEQUAL(url, FTP_URL)) {
1.42      lukem    1334:                if ((parse_url(url, "URL", &urltype, &user, &pass,
1.82      lukem    1335:                    &host, &port, &portnum, &path) == -1) ||
1.42      lukem    1336:                    (user != NULL && *user == '\0') ||
                   1337:                    EMPTYSTRING(host)) {
                   1338:                        warnx("Invalid URL `%s'", url);
                   1339:                        goto cleanup_fetch_ftp;
                   1340:                }
1.53      lukem    1341:                /*
                   1342:                 * Note: Don't url_decode(path) here.  We need to keep the
                   1343:                 * distinction between "/" and "%2F" until later.
                   1344:                 */
                   1345:
                   1346:                                        /* check for trailing ';type=[aid]' */
1.63      lukem    1347:                if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
1.53      lukem    1348:                        if (strcasecmp(cp, ";type=a") == 0)
                   1349:                                type = TYPE_A;
                   1350:                        else if (strcasecmp(cp, ";type=i") == 0)
                   1351:                                type = TYPE_I;
                   1352:                        else if (strcasecmp(cp, ";type=d") == 0) {
                   1353:                                warnx(
                   1354:                            "Directory listing via a URL is not supported");
                   1355:                                goto cleanup_fetch_ftp;
                   1356:                        } else {
                   1357:                                warnx("Invalid suffix `%s' in URL `%s'", cp,
                   1358:                                    url);
                   1359:                                goto cleanup_fetch_ftp;
                   1360:                        }
                   1361:                        *cp = 0;
                   1362:                }
1.97      lukem    1363:        } else {                        /* classic style `[user@]host:[file]' */
1.53      lukem    1364:                urltype = CLASSIC_URL_T;
1.166     christos 1365:                host = ftp_strdup(url);
1.97      lukem    1366:                cp = strchr(host, '@');
                   1367:                if (cp != NULL) {
                   1368:                        *cp = '\0';
                   1369:                        user = host;
                   1370:                        anonftp = 0;    /* disable anonftp */
1.166     christos 1371:                        host = ftp_strdup(cp + 1);
1.97      lukem    1372:                }
1.42      lukem    1373:                cp = strchr(host, ':');
                   1374:                if (cp != NULL) {
                   1375:                        *cp = '\0';
1.166     christos 1376:                        path = ftp_strdup(cp + 1);
1.42      lukem    1377:                }
                   1378:        }
                   1379:        if (EMPTYSTRING(host))
                   1380:                goto cleanup_fetch_ftp;
                   1381:
1.50      lukem    1382:                        /* Extract the file and (if present) directory name. */
1.42      lukem    1383:        dir = path;
                   1384:        if (! EMPTYSTRING(dir)) {
1.53      lukem    1385:                /*
1.97      lukem    1386:                 * If we are dealing with classic `[user@]host:[path]' syntax,
                   1387:                 * then a path of the form `/file' (resulting from input of the
                   1388:                 * form `host:/file') means that we should do "CWD /" before
                   1389:                 * retrieving the file.  So we set dir="/" and file="file".
1.53      lukem    1390:                 *
1.97      lukem    1391:                 * But if we are dealing with URLs like `ftp://host/path' then
                   1392:                 * a path of the form `/file' (resulting from a URL of the form
                   1393:                 * `ftp://host//file') means that we should do `CWD ' (with an
                   1394:                 * empty argument) before retrieving the file.  So we set
1.53      lukem    1395:                 * dir="" and file="file".
                   1396:                 *
1.97      lukem    1397:                 * If the path does not contain / at all, we set dir=NULL.
                   1398:                 * (We get a path without any slashes if we are dealing with
                   1399:                 * classic `[user@]host:[file]' or URL `ftp://host/file'.)
1.53      lukem    1400:                 *
1.97      lukem    1401:                 * In all other cases, we set dir to a string that does not
                   1402:                 * include the final '/' that separates the dir part from the
                   1403:                 * file part of the path.  (This will be the empty string if
                   1404:                 * and only if we are dealing with a path of the form `/file'
                   1405:                 * resulting from an URL of the form `ftp://host//file'.)
1.53      lukem    1406:                 */
1.42      lukem    1407:                cp = strrchr(dir, '/');
1.53      lukem    1408:                if (cp == dir && urltype == CLASSIC_URL_T) {
1.50      lukem    1409:                        file = cp + 1;
                   1410:                        dir = "/";
                   1411:                } else if (cp != NULL) {
1.42      lukem    1412:                        *cp++ = '\0';
                   1413:                        file = cp;
                   1414:                } else {
                   1415:                        file = dir;
                   1416:                        dir = NULL;
                   1417:                }
1.63      lukem    1418:        } else
                   1419:                dir = NULL;
1.53      lukem    1420:        if (urltype == FTP_URL_T && file != NULL) {
1.157     lukem    1421:                url_decode(file);
1.53      lukem    1422:                /* but still don't url_decode(dir) */
                   1423:        }
1.163     christos 1424:        DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s "
                   1425:            "path `%s' dir `%s' file `%s'\n",
                   1426:            user ? user : "<null>", pass ? pass : "<null>",
                   1427:            host ? host : "<null>", port ? port : "<null>",
                   1428:            path ? path : "<null>",
                   1429:            dir ? dir : "<null>", file ? file : "<null>");
1.42      lukem    1430:
                   1431:        dirhasglob = filehasglob = 0;
1.53      lukem    1432:        if (doglob && urltype == CLASSIC_URL_T) {
1.42      lukem    1433:                if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
                   1434:                        dirhasglob = 1;
                   1435:                if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
                   1436:                        filehasglob = 1;
                   1437:        }
                   1438:
1.50      lukem    1439:                        /* Set up the connection */
1.160     lukem    1440:        oanonftp = anonftp;
1.50      lukem    1441:        if (connected)
                   1442:                disconnect(0, NULL);
1.160     lukem    1443:        anonftp = oanonftp;
1.126     cgd      1444:        xargv[0] = (char *)getprogname();       /* XXX discards const */
1.50      lukem    1445:        xargv[1] = host;
                   1446:        xargv[2] = NULL;
                   1447:        xargc = 2;
                   1448:        if (port) {
1.60      itojun   1449:                xargv[2] = port;
1.50      lukem    1450:                xargv[3] = NULL;
                   1451:                xargc = 3;
                   1452:        }
                   1453:        oautologin = autologin;
1.116     lukem    1454:                /* don't autologin in setpeer(), use ftp_login() below */
                   1455:        autologin = 0;
1.50      lukem    1456:        setpeer(xargc, xargv);
                   1457:        autologin = oautologin;
1.116     lukem    1458:        if ((connected == 0) ||
                   1459:            (connected == 1 && !ftp_login(host, user, pass))) {
1.50      lukem    1460:                warnx("Can't connect or login to host `%s'", host);
                   1461:                goto cleanup_fetch_ftp;
                   1462:        }
1.42      lukem    1463:
1.53      lukem    1464:        switch (type) {
                   1465:        case TYPE_A:
1.118     lukem    1466:                setascii(1, xargv);
1.53      lukem    1467:                break;
                   1468:        case TYPE_I:
1.118     lukem    1469:                setbinary(1, xargv);
1.53      lukem    1470:                break;
                   1471:        default:
1.91      lukem    1472:                errx(1, "fetch_ftp: unknown transfer type %d", type);
1.53      lukem    1473:        }
                   1474:
1.63      lukem    1475:                /*
                   1476:                 * Change directories, if necessary.
                   1477:                 *
                   1478:                 * Note: don't use EMPTYSTRING(dir) below, because
                   1479:                 * dir=="" means something different from dir==NULL.
                   1480:                 */
1.53      lukem    1481:        if (dir != NULL && !dirhasglob) {
                   1482:                char *nextpart;
1.42      lukem    1483:
1.53      lukem    1484:                /*
1.97      lukem    1485:                 * If we are dealing with a classic `[user@]host:[path]'
                   1486:                 * (urltype is CLASSIC_URL_T) then we have a raw directory
1.53      lukem    1487:                 * name (not encoded in any way) and we can change
                   1488:                 * directories in one step.
                   1489:                 *
                   1490:                 * If we are dealing with an `ftp://host/path' URL
1.177     lukem    1491:                 * (urltype is FTP_URL_T), then RFC3986 says we need to
1.53      lukem    1492:                 * send a separate CWD command for each unescaped "/"
                   1493:                 * in the path, and we have to interpret %hex escaping
                   1494:                 * *after* we find the slashes.  It's possible to get
                   1495:                 * empty components here, (from multiple adjacent
1.177     lukem    1496:                 * slashes in the path) and RFC3986 says that we should
1.53      lukem    1497:                 * still do `CWD ' (with a null argument) in such cases.
                   1498:                 *
1.63      lukem    1499:                 * Many ftp servers don't support `CWD ', so if there's an
                   1500:                 * error performing that command, bail out with a descriptive
                   1501:                 * message.
1.53      lukem    1502:                 *
                   1503:                 * Examples:
                   1504:                 *
1.63      lukem    1505:                 * host:                        dir="", urltype=CLASSIC_URL_T
                   1506:                 *              logged in (to default directory)
1.53      lukem    1507:                 * host:file                    dir=NULL, urltype=CLASSIC_URL_T
                   1508:                 *              "RETR file"
1.63      lukem    1509:                 * host:dir/                    dir="dir", urltype=CLASSIC_URL_T
                   1510:                 *              "CWD dir", logged in
                   1511:                 * ftp://host/                  dir="", urltype=FTP_URL_T
                   1512:                 *              logged in (to default directory)
                   1513:                 * ftp://host/dir/              dir="dir", urltype=FTP_URL_T
                   1514:                 *              "CWD dir", logged in
1.53      lukem    1515:                 * ftp://host/file              dir=NULL, urltype=FTP_URL_T
                   1516:                 *              "RETR file"
                   1517:                 * ftp://host//file             dir="", urltype=FTP_URL_T
1.63      lukem    1518:                 *              "CWD ", "RETR file"
1.53      lukem    1519:                 * host:/file                   dir="/", urltype=CLASSIC_URL_T
                   1520:                 *              "CWD /", "RETR file"
                   1521:                 * ftp://host///file            dir="/", urltype=FTP_URL_T
1.63      lukem    1522:                 *              "CWD ", "CWD ", "RETR file"
1.53      lukem    1523:                 * ftp://host/%2F/file          dir="%2F", urltype=FTP_URL_T
                   1524:                 *              "CWD /", "RETR file"
                   1525:                 * ftp://host/foo/file          dir="foo", urltype=FTP_URL_T
                   1526:                 *              "CWD foo", "RETR file"
                   1527:                 * ftp://host/foo/bar/file      dir="foo/bar"
                   1528:                 *              "CWD foo", "CWD bar", "RETR file"
                   1529:                 * ftp://host//foo/bar/file     dir="/foo/bar"
1.63      lukem    1530:                 *              "CWD ", "CWD foo", "CWD bar", "RETR file"
1.53      lukem    1531:                 * ftp://host/foo//bar/file     dir="foo//bar"
1.63      lukem    1532:                 *              "CWD foo", "CWD ", "CWD bar", "RETR file"
1.53      lukem    1533:                 * ftp://host/%2F/foo/bar/file  dir="%2F/foo/bar"
                   1534:                 *              "CWD /", "CWD foo", "CWD bar", "RETR file"
                   1535:                 * ftp://host/%2Ffoo/bar/file   dir="%2Ffoo/bar"
                   1536:                 *              "CWD /foo", "CWD bar", "RETR file"
                   1537:                 * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar"
                   1538:                 *              "CWD /foo/bar", "RETR file"
                   1539:                 * ftp://host/%2Ffoo%2Fbar%2Ffile       dir=NULL
                   1540:                 *              "RETR /foo/bar/file"
                   1541:                 *
                   1542:                 * Note that we don't need `dir' after this point.
                   1543:                 */
                   1544:                do {
                   1545:                        if (urltype == FTP_URL_T) {
                   1546:                                nextpart = strchr(dir, '/');
                   1547:                                if (nextpart) {
                   1548:                                        *nextpart = '\0';
                   1549:                                        nextpart++;
                   1550:                                }
                   1551:                                url_decode(dir);
                   1552:                        } else
                   1553:                                nextpart = NULL;
1.163     christos 1554:                        DPRINTF("dir `%s', nextpart `%s'\n",
                   1555:                            dir ? dir : "<null>",
                   1556:                            nextpart ? nextpart : "<null>");
1.63      lukem    1557:                        if (urltype == FTP_URL_T || *dir != '\0') {
1.53      lukem    1558:                                xargv[0] = "cd";
                   1559:                                xargv[1] = dir;
                   1560:                                xargv[2] = NULL;
                   1561:                                dirchange = 0;
                   1562:                                cd(2, xargv);
1.63      lukem    1563:                                if (! dirchange) {
                   1564:                                        if (*dir == '\0' && code == 500)
                   1565:                                                fprintf(stderr,
                   1566: "\n"
                   1567: "ftp: The `CWD ' command (without a directory), which is required by\n"
1.177     lukem    1568: "     RFC3986 to support the empty directory in the URL pathname (`//'),\n"
                   1569: "     conflicts with the server's conformance to RFC0959.\n"
1.63      lukem    1570: "     Try the same URL without the `//' in the URL pathname.\n"
                   1571: "\n");
1.53      lukem    1572:                                        goto cleanup_fetch_ftp;
1.63      lukem    1573:                                }
1.53      lukem    1574:                        }
                   1575:                        dir = nextpart;
                   1576:                } while (dir != NULL);
1.42      lukem    1577:        }
                   1578:
                   1579:        if (EMPTYSTRING(file)) {
                   1580:                rval = -1;
                   1581:                goto cleanup_fetch_ftp;
                   1582:        }
                   1583:
                   1584:        if (dirhasglob) {
1.78      lukem    1585:                (void)strlcpy(rempath, dir,     sizeof(rempath));
                   1586:                (void)strlcat(rempath, "/",     sizeof(rempath));
                   1587:                (void)strlcat(rempath, file,    sizeof(rempath));
1.42      lukem    1588:                file = rempath;
                   1589:        }
                   1590:
                   1591:                        /* Fetch the file(s). */
                   1592:        xargc = 2;
                   1593:        xargv[0] = "get";
                   1594:        xargv[1] = file;
                   1595:        xargv[2] = NULL;
                   1596:        if (dirhasglob || filehasglob) {
                   1597:                int ointeractive;
                   1598:
                   1599:                ointeractive = interactive;
                   1600:                interactive = 0;
1.155     lukem    1601:                if (restartautofetch)
                   1602:                        xargv[0] = "mreget";
                   1603:                else
                   1604:                        xargv[0] = "mget";
1.42      lukem    1605:                mget(xargc, xargv);
                   1606:                interactive = ointeractive;
                   1607:        } else {
1.53      lukem    1608:                if (outfile == NULL) {
                   1609:                        cp = strrchr(file, '/');        /* find savefile */
                   1610:                        if (cp != NULL)
                   1611:                                outfile = cp + 1;
                   1612:                        else
                   1613:                                outfile = file;
1.42      lukem    1614:                }
1.53      lukem    1615:                xargv[2] = (char *)outfile;
                   1616:                xargv[3] = NULL;
                   1617:                xargc++;
1.52      lukem    1618:                if (restartautofetch)
                   1619:                        reget(xargc, xargv);
                   1620:                else
                   1621:                        get(xargc, xargv);
1.42      lukem    1622:        }
                   1623:
                   1624:        if ((code / 100) == COMPLETE)
                   1625:                rval = 0;
                   1626:
1.118     lukem    1627:  cleanup_fetch_ftp:
1.169     christos 1628:        FREEPTR(port);
1.42      lukem    1629:        FREEPTR(host);
                   1630:        FREEPTR(path);
                   1631:        FREEPTR(user);
1.162     lukem    1632:        if (pass)
                   1633:                memset(pass, 0, strlen(pass));
1.42      lukem    1634:        FREEPTR(pass);
                   1635:        return (rval);
                   1636: }
                   1637:
                   1638: /*
                   1639:  * Retrieve the given file to outfile.
                   1640:  * Supports arguments of the form:
                   1641:  *     "host:path", "ftp://host/path"  if $ftpproxy, call fetch_url() else
                   1642:  *                                     call fetch_ftp()
1.50      lukem    1643:  *     "http://host/path"              call fetch_url() to use HTTP
1.42      lukem    1644:  *     "file:///path"                  call fetch_url() to copy
                   1645:  *     "about:..."                     print a message
                   1646:  *
                   1647:  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
                   1648:  * is still open (e.g, ftp xfer with trailing /)
                   1649:  */
                   1650: static int
1.111     lukem    1651: go_fetch(const char *url)
1.42      lukem    1652: {
1.91      lukem    1653:        char *proxy;
1.42      lukem    1654:
1.147     christos 1655: #ifndef NO_ABOUT
1.42      lukem    1656:        /*
                   1657:         * Check for about:*
                   1658:         */
1.150     lukem    1659:        if (STRNEQUAL(url, ABOUT_URL)) {
1.42      lukem    1660:                url += sizeof(ABOUT_URL) -1;
1.144     lukem    1661:                if (strcasecmp(url, "ftp") == 0 ||
                   1662:                    strcasecmp(url, "tnftp") == 0) {
1.87      lukem    1663:                        fputs(
1.143     salo     1664: "This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n"
1.87      lukem    1665: "for the NetBSD project.  Execute `man ftp' for more details.\n", ttyout);
                   1666:                } else if (strcasecmp(url, "lukem") == 0) {
                   1667:                        fputs(
                   1668: "Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
1.143     salo     1669: "Please email feedback to <lukem@NetBSD.org>.\n", ttyout);
1.42      lukem    1670:                } else if (strcasecmp(url, "netbsd") == 0) {
1.87      lukem    1671:                        fputs(
                   1672: "NetBSD is a freely available and redistributable UNIX-like operating system.\n"
1.144     lukem    1673: "For more information, see http://www.NetBSD.org/\n", ttyout);
1.92      lukem    1674:                } else if (strcasecmp(url, "version") == 0) {
1.115     lukem    1675:                        fprintf(ttyout, "Version: %s %s%s\n",
                   1676:                            FTP_PRODUCT, FTP_VERSION,
                   1677: #ifdef INET6
                   1678:                            ""
                   1679: #else
                   1680:                            " (-IPv6)"
                   1681: #endif
                   1682:                        );
1.42      lukem    1683:                } else {
                   1684:                        fprintf(ttyout, "`%s' is an interesting topic.\n", url);
                   1685:                }
1.92      lukem    1686:                fputs("\n", ttyout);
1.42      lukem    1687:                return (0);
                   1688:        }
1.147     christos 1689: #endif
1.42      lukem    1690:
                   1691:        /*
                   1692:         * Check for file:// and http:// URLs.
                   1693:         */
1.150     lukem    1694:        if (STRNEQUAL(url, HTTP_URL) || STRNEQUAL(url, FILE_URL))
1.52      lukem    1695:                return (fetch_url(url, NULL, NULL, NULL));
1.42      lukem    1696:
                   1697:        /*
                   1698:         * Try FTP URL-style and host:file arguments next.
                   1699:         * If ftpproxy is set with an FTP URL, use fetch_url()
                   1700:         * Othewise, use fetch_ftp().
                   1701:         */
1.91      lukem    1702:        proxy = getoptionvalue("ftp_proxy");
1.150     lukem    1703:        if (!EMPTYSTRING(proxy) && STRNEQUAL(url, FTP_URL))
1.52      lukem    1704:                return (fetch_url(url, NULL, NULL, NULL));
1.42      lukem    1705:
1.52      lukem    1706:        return (fetch_ftp(url));
1.42      lukem    1707: }
                   1708:
                   1709: /*
                   1710:  * Retrieve multiple files from the command line,
                   1711:  * calling go_fetch() for each file.
                   1712:  *
                   1713:  * If an ftp path has a trailing "/", the path will be cd-ed into and
1.41      lukem    1714:  * the connection remains open, and the function will return -1
                   1715:  * (to indicate the connection is alive).
1.1       lukem    1716:  * If an error occurs the return value will be the offset+1 in
                   1717:  * argv[] of the file that caused a problem (i.e, argv[x]
                   1718:  * returns x+1)
                   1719:  * Otherwise, 0 is returned if all files retrieved successfully.
                   1720:  */
                   1721: int
1.111     lukem    1722: auto_fetch(int argc, char *argv[])
1.1       lukem    1723: {
1.160     lukem    1724:        volatile int    argpos, rval;
1.22      lukem    1725:
1.160     lukem    1726:        argpos = rval = 0;
1.1       lukem    1727:
1.88      lukem    1728:        if (sigsetjmp(toplevel, 1)) {
1.2       lukem    1729:                if (connected)
                   1730:                        disconnect(0, NULL);
1.148     lukem    1731:                if (rval > 0)
                   1732:                        rval = argpos + 1;
                   1733:                return (rval);
1.2       lukem    1734:        }
1.88      lukem    1735:        (void)xsignal(SIGINT, intr);
1.91      lukem    1736:        (void)xsignal(SIGPIPE, lostpeer);
1.1       lukem    1737:
                   1738:        /*
                   1739:         * Loop through as long as there's files to fetch.
                   1740:         */
1.160     lukem    1741:        for (; (rval == 0) && (argpos < argc); argpos++) {
1.1       lukem    1742:                if (strchr(argv[argpos], ':') == NULL)
                   1743:                        break;
1.42      lukem    1744:                redirect_loop = 0;
1.90      lukem    1745:                if (!anonftp)
                   1746:                        anonftp = 2;    /* Handle "automatic" transfers. */
1.52      lukem    1747:                rval = go_fetch(argv[argpos]);
                   1748:                if (outfile != NULL && strcmp(outfile, "-") != 0
                   1749:                    && outfile[0] != '|')
                   1750:                        outfile = NULL;
1.42      lukem    1751:                if (rval > 0)
1.1       lukem    1752:                        rval = argpos + 1;
1.42      lukem    1753:        }
1.3       lukem    1754:
1.1       lukem    1755:        if (connected && rval != -1)
                   1756:                disconnect(0, NULL);
1.114     lukem    1757:        return (rval);
                   1758: }
                   1759:
                   1760:
1.170     lukem    1761: /*
                   1762:  * Upload multiple files from the command line.
                   1763:  *
                   1764:  * If an error occurs the return value will be the offset+1 in
                   1765:  * argv[] of the file that caused a problem (i.e, argv[x]
                   1766:  * returns x+1)
                   1767:  * Otherwise, 0 is returned if all files uploaded successfully.
                   1768:  */
1.114     lukem    1769: int
                   1770: auto_put(int argc, char **argv, const char *uploadserver)
                   1771: {
                   1772:        char    *uargv[4], *path, *pathsep;
1.170     lukem    1773:        int      uargc, rval, argpos;
1.159     lukem    1774:        size_t   len;
1.114     lukem    1775:
                   1776:        uargc = 0;
                   1777:        uargv[uargc++] = "mput";
                   1778:        uargv[uargc++] = argv[0];
                   1779:        uargv[2] = uargv[3] = NULL;
                   1780:        pathsep = NULL;
                   1781:        rval = 1;
                   1782:
1.163     christos 1783:        DPRINTF("auto_put: target `%s'\n", uploadserver);
1.114     lukem    1784:
1.166     christos 1785:        path = ftp_strdup(uploadserver);
1.114     lukem    1786:        len = strlen(path);
                   1787:        if (path[len - 1] != '/' && path[len - 1] != ':') {
                   1788:                        /*
                   1789:                         * make sure we always pass a directory to auto_fetch
                   1790:                         */
                   1791:                if (argc > 1) {         /* more than one file to upload */
                   1792:                        len = strlen(uploadserver) + 2; /* path + "/" + "\0" */
                   1793:                        free(path);
1.166     christos 1794:                        path = (char *)ftp_malloc(len);
1.114     lukem    1795:                        (void)strlcpy(path, uploadserver, len);
                   1796:                        (void)strlcat(path, "/", len);
                   1797:                } else {                /* single file to upload */
                   1798:                        uargv[0] = "put";
                   1799:                        pathsep = strrchr(path, '/');
                   1800:                        if (pathsep == NULL) {
                   1801:                                pathsep = strrchr(path, ':');
                   1802:                                if (pathsep == NULL) {
                   1803:                                        warnx("Invalid URL `%s'", path);
                   1804:                                        goto cleanup_auto_put;
                   1805:                                }
                   1806:                                pathsep++;
1.166     christos 1807:                                uargv[2] = ftp_strdup(pathsep);
1.114     lukem    1808:                                pathsep[0] = '/';
1.157     lukem    1809:                        } else
1.166     christos 1810:                                uargv[2] = ftp_strdup(pathsep + 1);
1.114     lukem    1811:                        pathsep[1] = '\0';
                   1812:                        uargc++;
                   1813:                }
                   1814:        }
1.163     christos 1815:        DPRINTF("auto_put: URL `%s' argv[2] `%s'\n",
                   1816:            path, uargv[2] ? uargv[2] : "<null>");
1.157     lukem    1817:
                   1818:                        /* connect and cwd */
1.114     lukem    1819:        rval = auto_fetch(1, &path);
                   1820:        if(rval >= 0)
                   1821:                goto cleanup_auto_put;
                   1822:
1.170     lukem    1823:        rval = 0;
                   1824:
                   1825:                        /* target filename provided; upload 1 file */
1.114     lukem    1826:                        /* XXX : is this the best way? */
                   1827:        if (uargc == 3) {
                   1828:                uargv[1] = argv[0];
                   1829:                put(uargc, uargv);
1.170     lukem    1830:                if ((code / 100) != COMPLETE)
                   1831:                        rval = 1;
                   1832:        } else {        /* otherwise a target dir: upload all files to it */
                   1833:                for(argpos = 0; argv[argpos] != NULL; argpos++) {
                   1834:                        uargv[1] = argv[argpos];
                   1835:                        mput(uargc, uargv);
                   1836:                        if ((code / 100) != COMPLETE) {
                   1837:                                rval = argpos + 1;
                   1838:                                break;
                   1839:                        }
                   1840:                }
1.114     lukem    1841:        }
                   1842:
1.118     lukem    1843:  cleanup_auto_put:
1.168     christos 1844:        free(path);
1.114     lukem    1845:        FREEPTR(uargv[2]);
1.1       lukem    1846:        return (rval);
                   1847: }

CVSweb <webmaster@jp.NetBSD.org>