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

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

CVSweb <webmaster@jp.NetBSD.org>