[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.186

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

CVSweb <webmaster@jp.NetBSD.org>