Module Name: src Committed By: christos Date: Wed Dec 16 19:17:16 UTC 2015
Modified Files: src/usr.bin/ftp: fetch.c Log Message: more refactoring: - introduce authinfo and urlinfo structures - split negotiation code out. To generate a diff of this commit: cvs rdiff -u -r1.213 -r1.214 src/usr.bin/ftp/fetch.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/usr.bin/ftp/fetch.c diff -u src/usr.bin/ftp/fetch.c:1.213 src/usr.bin/ftp/fetch.c:1.214 --- src/usr.bin/ftp/fetch.c:1.213 Tue Dec 15 20:20:05 2015 +++ src/usr.bin/ftp/fetch.c Wed Dec 16 14:17:16 2015 @@ -1,4 +1,4 @@ -/* $NetBSD: fetch.c,v 1.213 2015/12/16 01:20:05 nonaka Exp $ */ +/* $NetBSD: fetch.c,v 1.214 2015/12/16 19:17:16 christos Exp $ */ /*- * Copyright (c) 1997-2015 The NetBSD Foundation, Inc. @@ -37,7 +37,7 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: fetch.c,v 1.213 2015/12/16 01:20:05 nonaka Exp $"); +__RCSID("$NetBSD: fetch.c,v 1.214 2015/12/16 19:17:16 christos Exp $"); #endif /* not lint */ /* @@ -80,19 +80,35 @@ typedef enum { CLASSIC_URL_T } url_t; +struct authinfo { + char *auth; + char *user; + char *pass; +}; + +struct urlinfo { + char *host; + char *port; + char *path; + url_t utype; + in_port_t portnum; +}; + __dead static void aborthttp(int); __dead static void timeouthttp(int); #ifndef NO_AUTH -static int auth_url(const char *, char **, const char *, const char *); +static int auth_url(const char *, char **, const struct authinfo *); static void base64_encode(const unsigned char *, size_t, unsigned char *); #endif static int go_fetch(const char *); static int fetch_ftp(const char *); static int fetch_url(const char *, const char *, char *, char *); static const char *match_token(const char **, const char *); -static int parse_url(const char *, const char *, url_t *, char **, - char **, char **, char **, in_port_t *, char **); +static int parse_url(const char *, const char *, struct urlinfo *, + struct authinfo *); static void url_decode(char *); +static void freeauthinfo(struct authinfo *); +static void freeurlinfo(struct urlinfo *); static int redirect_loop; @@ -144,6 +160,38 @@ match_token(const char **buf, const char return orig; } +static void +initauthinfo(struct authinfo *ai, char *auth) +{ + ai->auth = auth; + ai->user = ai->pass = 0; +} + +static void +freeauthinfo(struct authinfo *a) +{ + FREEPTR(a->user); + if (a->pass != NULL) + memset(a->pass, 0, strlen(a->pass)); + FREEPTR(a->pass); +} + +static void +initurlinfo(struct urlinfo *ui) +{ + ui->host = ui->port = ui->path = 0; + ui->utype = UNKNOWN_URL_T; + ui->portnum = 0; +} + +static void +freeurlinfo(struct urlinfo *ui) +{ + FREEPTR(ui->host); + FREEPTR(ui->port); + FREEPTR(ui->path); +} + #ifndef NO_AUTH /* * Generate authorization response based on given authentication challenge. @@ -151,8 +199,7 @@ match_token(const char **buf, const char * Sets response to a malloc(3)ed string; caller should free. */ static int -auth_url(const char *challenge, char **response, const char *guser, - const char *gpass) +auth_url(const char *challenge, char **response, const struct authinfo *auth) { const char *cp, *scheme, *errormsg; char *ep, *clear, *realm; @@ -196,8 +243,8 @@ auth_url(const char *challenge, char **r } fprintf(ttyout, "Username for `%s': ", realm); - if (guser != NULL) { - (void)strlcpy(uuser, guser, sizeof(uuser)); + if (auth->user != NULL) { + (void)strlcpy(uuser, auth->user, sizeof(uuser)); fprintf(ttyout, "%s\n", uuser); } else { (void)fflush(ttyout); @@ -206,8 +253,8 @@ auth_url(const char *challenge, char **r goto cleanup_auth_url; } } - if (gpass != NULL) - upass = gpass; + if (auth->pass != NULL) + upass = auth->pass; else { gotpass = getpass("Password: "); if (gotpass == NULL) { @@ -227,7 +274,7 @@ auth_url(const char *challenge, char **r /* scheme + " " + enc + "\0" */ rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1; - *response = (char *)ftp_malloc(rlen); + *response = ftp_malloc(rlen); (void)strlcpy(*response, scheme, rlen); len = strlcat(*response, " ", rlen); /* use `clen - 1' to not encode the trailing NUL */ @@ -326,57 +373,47 @@ url_decode(char *url) * "ftp://host/dir/file" "dir/file" * "ftp://host//dir/file" "/dir/file" */ + static int -parse_url(const char *url, const char *desc, url_t *utype, - char **uuser, char **pass, char **host, char **port, - in_port_t *portnum, char **path) +parse_url(const char *url, const char *desc, struct urlinfo *ui, + struct authinfo *auth) { const char *origurl, *tport; char *cp, *ep, *thost; size_t len; - if (url == NULL || desc == NULL || utype == NULL || uuser == NULL - || pass == NULL || host == NULL || port == NULL || portnum == NULL - || path == NULL) + if (url == NULL || desc == NULL || ui == NULL || auth == NULL) errx(1, "parse_url: invoked with NULL argument!"); DPRINTF("parse_url: %s `%s'\n", desc, url); origurl = url; - *utype = UNKNOWN_URL_T; - *uuser = *pass = *host = *port = *path = NULL; - *portnum = 0; tport = NULL; if (STRNEQUAL(url, HTTP_URL)) { url += sizeof(HTTP_URL) - 1; - *utype = HTTP_URL_T; - *portnum = HTTP_PORT; + ui->utype = HTTP_URL_T; + ui->portnum = HTTP_PORT; tport = httpport; } else if (STRNEQUAL(url, FTP_URL)) { url += sizeof(FTP_URL) - 1; - *utype = FTP_URL_T; - *portnum = FTP_PORT; + ui->utype = FTP_URL_T; + ui->portnum = FTP_PORT; tport = ftpport; } else if (STRNEQUAL(url, FILE_URL)) { url += sizeof(FILE_URL) - 1; - *utype = FILE_URL_T; + ui->utype = FILE_URL_T; #ifdef WITH_SSL } else if (STRNEQUAL(url, HTTPS_URL)) { url += sizeof(HTTPS_URL) - 1; - *utype = HTTPS_URL_T; - *portnum = HTTPS_PORT; + ui->utype = HTTPS_URL_T; + ui->portnum = HTTPS_PORT; tport = httpsport; #endif } else { warnx("Invalid %s `%s'", desc, url); cleanup_parse_url: - FREEPTR(*uuser); - if (*pass != NULL) - memset(*pass, 0, strlen(*pass)); - FREEPTR(*pass); - FREEPTR(*host); - FREEPTR(*port); - FREEPTR(*path); + freeauthinfo(auth); + freeurlinfo(ui); return (-1); } @@ -391,26 +428,26 @@ parse_url(const char *url, const char *d len = ep - url; thost = (char *)ftp_malloc(len + 1); (void)strlcpy(thost, url, len + 1); - if (*utype == FTP_URL_T) /* skip first / for ftp URLs */ + if (ui->utype == FTP_URL_T) /* skip first / for ftp URLs */ ep++; - *path = ftp_strdup(ep); + ui->path = ftp_strdup(ep); } cp = strchr(thost, '@'); /* look for user[:pass]@ in URLs */ if (cp != NULL) { - if (*utype == FTP_URL_T) + if (ui->utype == FTP_URL_T) anonftp = 0; /* disable anonftp */ - *uuser = thost; + auth->user = thost; *cp = '\0'; thost = ftp_strdup(cp + 1); - cp = strchr(*uuser, ':'); + cp = strchr(auth->user, ':'); if (cp != NULL) { *cp = '\0'; - *pass = ftp_strdup(cp + 1); + auth->pass = ftp_strdup(cp + 1); } - url_decode(*uuser); - if (*pass) - url_decode(*pass); + url_decode(auth->user); + if (auth->pass) + url_decode(auth->pass); } #ifdef INET6 @@ -444,7 +481,7 @@ parse_url(const char *url, const char *d #endif /* INET6 */ if ((cp = strchr(thost, ':')) != NULL) *cp++ = '\0'; - *host = thost; + ui->host = thost; /* look for [:port] */ if (cp != NULL) { @@ -457,24 +494,24 @@ parse_url(const char *url, const char *d cp, desc, origurl); goto cleanup_parse_url; } - *portnum = nport; + ui->portnum = nport; tport = cp; } if (tport != NULL) - *port = ftp_strdup(tport); - if (*path == NULL) { + ui->port = ftp_strdup(tport); + if (ui->path == NULL) { const char *emptypath = "/"; - if (*utype == FTP_URL_T) /* skip first / for ftp URLs */ + if (ui->utype == FTP_URL_T) /* skip first / for ftp URLs */ emptypath++; - *path = ftp_strdup(emptypath); + ui->path = ftp_strdup(emptypath); } DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) " "path `%s'\n", - STRorNULL(*uuser), STRorNULL(*pass), - STRorNULL(*host), STRorNULL(*port), - *portnum ? *portnum : -1, STRorNULL(*path)); + STRorNULL(auth->user), STRorNULL(auth->pass), + STRorNULL(ui->host), STRorNULL(ui->port), + ui->portnum ? ui->portnum : -1, STRorNULL(ui->path)); return (0); } @@ -482,11 +519,16 @@ parse_url(const char *url, const char *d sigjmp_buf httpabort; static int -ftp_socket(const char *host, const char *port, void **ssl) +ftp_socket(const struct urlinfo *ui, void **ssl) { struct addrinfo hints, *res, *res0 = NULL; int error; int s; + const char *host = ui->host; + const char *port = ui->port; + + if (ui->utype != HTTPS_URL_T) + ssl = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_flags = 0; @@ -604,45 +646,436 @@ handle_noproxy(const char *host, in_port } static int -handle_proxy(const char *penv, char **host, char **port, char **puser, - char **ppass, url_t *urltype) +handle_proxy(const char *url, const char *penv, struct urlinfo *ui, + struct authinfo *pauth) { - url_t purltype; - char *phost, *ppath; - char *pport; - in_port_t pportnum; + struct urlinfo pui; - if (isipv6addr(*host) && strchr(*host, '%') != NULL) { + if (isipv6addr(ui->host) && strchr(ui->host, '%') != NULL) { warnx("Scoped address notation `%s' disallowed via web proxy", - *host); + ui->host); return -1; } - if (parse_url(penv, "proxy URL", &purltype, puser, ppass, &phost, - &pport, &pportnum, &ppath) == -1) + initurlinfo(&pui); + if (parse_url(penv, "proxy URL", &pui, pauth) == -1) return -1; - if ((!IS_HTTP_TYPE(purltype) && purltype != FTP_URL_T) || - EMPTYSTRING(phost) || - (! EMPTYSTRING(ppath) && strcmp(ppath, "/") != 0)) { + if ((!IS_HTTP_TYPE(pui.utype) && pui.utype != FTP_URL_T) || + EMPTYSTRING(pui.host) || + (! EMPTYSTRING(pui.path) && strcmp(pui.path, "/") != 0)) { warnx("Malformed proxy URL `%s'", penv); - FREEPTR(phost); - FREEPTR(pport); - FREEPTR(ppath); + freeurlinfo(&pui); return -1; } - FREEPTR(ppath); - FREEPTR(*host); - *host = phost; - FREEPTR(*port); - *port = pport; + FREEPTR(pui.path); + pui.path = ftp_strdup(url); - *urltype = purltype; + freeurlinfo(ui); + *ui = pui; return 0; } +static void +print_host(FETCH *fin, const char *host) +{ + char *h, *p; + + if (strchr(host, ':') == NULL) { + fetch_printf(fin, "Host: %s", host); + return; + } + + /* + * strip off IPv6 scope identifier, since it is + * local to the node + */ + h = ftp_strdup(host); + if (isipv6addr(h) && (p = strchr(h, '%')) != NULL) + *p = '\0'; + + fetch_printf(fin, "Host: [%s]", h); + free(h); +} + +static void +print_agent(FETCH *fin) +{ + const char *useragent; + if ((useragent = getenv("FTPUSERAGENT")) != NULL) { + fetch_printf(fin, "User-Agent: %s\r\n", useragent); + } else { + fetch_printf(fin, "User-Agent: %s/%s\r\n", + FTP_PRODUCT, FTP_VERSION); + } +} + +static void +print_cache(FETCH *fin, int isproxy) +{ + fetch_printf(fin, isproxy ? + "Pragma: no-cache\r\n" : + "Cache-Control: no-cache\r\n"); +} + +static int +print_get(FETCH *fin, int isproxy, const struct urlinfo *ui) +{ + int hasleading = 0; + const char *leading = " ("; + + if (isproxy) { + if (verbose) { + fprintf(ttyout, "%svia %s:%u", leading, + ui->host, ui->portnum); + leading = ", "; + hasleading++; + } + fetch_printf(fin, "GET %s HTTP/1.0\r\n", ui->path); + return hasleading; + } + + fetch_printf(fin, "GET %s HTTP/1.1\r\n", ui->path); + print_host(fin, ui->host); + + if ((ui->utype == HTTP_URL_T && ui->portnum != HTTP_PORT) || + (ui->utype == HTTPS_URL_T && ui->portnum != HTTPS_PORT)) + fetch_printf(fin, ":%u", ui->portnum); + + fetch_printf(fin, "\r\n"); + fetch_printf(fin, "Accept: */*\r\n"); + fetch_printf(fin, "Connection: close\r\n"); + if (restart_point) { + fputs(leading, ttyout); + fetch_printf(fin, "Range: bytes=" LLF "-\r\n", + (LLT)restart_point); + fprintf(ttyout, "restarting at " LLF, (LLT)restart_point); + hasleading++; + } + return hasleading; +} + +static void +getmtime(const char *cp, time_t *mtime) +{ + struct tm parsed; + const char *t; + + memset(&parsed, 0, sizeof(parsed)); + t = parse_rfc2616time(&parsed, cp); + + if (t == NULL) + return; + + parsed.tm_isdst = -1; + if (*t == '\0') + *mtime = timegm(&parsed); + +#ifndef NO_DEBUG + if (ftp_debug && *mtime != -1) { + fprintf(ttyout, "parsed time as: %s", + rfc2822time(localtime(mtime))); + } +#endif +} + +static int +print_proxy(FETCH *fin, const char *leading, + const char *wwwauth, const char *proxyauth) +{ + int hasleading = 0; + + if (wwwauth) { + if (verbose) { + fprintf(ttyout, "%swith authorization", leading); + hasleading++; + } + fetch_printf(fin, "Authorization: %s\r\n", wwwauth); + } + if (proxyauth) { + if (verbose) { + fprintf(ttyout, "%swith proxy authorization", leading); + hasleading++; + } + fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth); + } + return hasleading; +} + +#define C_OK 0 +#define C_CLEANUP 1 +#define C_IMPROPER 2 + + +static int +negotiate_connection(FETCH *fin, const char *url, const char *penv, + off_t *rangestart, off_t *rangeend, off_t *entitylen, + time_t *mtime, struct authinfo *wauth, struct authinfo *pauth, + int *rval, int *ischunked) +{ + int len, hcode, rv; + char buf[FTPBUFLEN], *ep; + const char *errormsg, *cp, *token; + char *location, *message, *auth; + + auth = message = location = NULL; + + /* Read the response */ + alarmtimer(quit_time ? quit_time : 60); + len = fetch_getline(fin, buf, sizeof(buf), &errormsg); + alarmtimer(0); + if (len < 0) { + if (*errormsg == '\n') + errormsg++; + warnx("Receiving HTTP reply: %s", errormsg); + goto cleanup_fetch_url; + } + while (len > 0 && (ISLWS(buf[len-1]))) + buf[--len] = '\0'; + + DPRINTF("%s: received `%s'\n", __func__, buf); + + /* Determine HTTP response code */ + cp = strchr(buf, ' '); + if (cp == NULL) + goto improper; + else + cp++; + + hcode = strtol(cp, &ep, 10); + if (*ep != '\0' && !isspace((unsigned char)*ep)) + goto improper; + + message = ftp_strdup(cp); + + /* Read the rest of the header. */ + + for (;;) { + alarmtimer(quit_time ? quit_time : 60); + len = fetch_getline(fin, buf, sizeof(buf), &errormsg); + alarmtimer(0); + if (len < 0) { + if (*errormsg == '\n') + errormsg++; + warnx("Receiving HTTP reply: %s", errormsg); + goto cleanup_fetch_url; + } + while (len > 0 && (ISLWS(buf[len-1]))) + buf[--len] = '\0'; + if (len == 0) + break; + DPRINTF("%s: received `%s'\n", __func__, buf); + + /* + * Look for some headers + */ + + cp = buf; + + if (match_token(&cp, "Content-Length:")) { + filesize = STRTOLL(cp, &ep, 10); + if (filesize < 0 || *ep != '\0') + goto improper; + DPRINTF("%s: parsed len as: " LLF "\n", + __func__, (LLT)filesize); + + } else if (match_token(&cp, "Content-Range:")) { + if (! match_token(&cp, "bytes")) + goto improper; + + if (*cp == '*') + cp++; + else { + *rangestart = STRTOLL(cp, &ep, 10); + if (*rangestart < 0 || *ep != '-') + goto improper; + cp = ep + 1; + *rangeend = STRTOLL(cp, &ep, 10); + if (*rangeend < 0 || *rangeend < *rangestart) + goto improper; + cp = ep; + } + if (*cp != '/') + goto improper; + cp++; + if (*cp == '*') + cp++; + else { + *entitylen = STRTOLL(cp, &ep, 10); + if (*entitylen < 0) + goto improper; + cp = ep; + } + if (*cp != '\0') + goto improper; + +#ifndef NO_DEBUG + if (ftp_debug) { + fprintf(ttyout, "parsed range as: "); + if (*rangestart == -1) + fprintf(ttyout, "*"); + else + fprintf(ttyout, LLF "-" LLF, + (LLT)*rangestart, + (LLT)*rangeend); + fprintf(ttyout, "/" LLF "\n", (LLT)*entitylen); + } +#endif + if (! restart_point) { + warnx( + "Received unexpected Content-Range header"); + goto cleanup_fetch_url; + } + + } else if (match_token(&cp, "Last-Modified:")) { + getmtime(cp, mtime); + + } else if (match_token(&cp, "Location:")) { + location = ftp_strdup(cp); + DPRINTF("%s: parsed location as `%s'\n", + __func__, cp); + + } else if (match_token(&cp, "Transfer-Encoding:")) { + if (match_token(&cp, "binary")) { + warnx( + "Bogus transfer encoding `binary' (fetching anyway)"); + continue; + } + if (! (token = match_token(&cp, "chunked"))) { + warnx( + "Unsupported transfer encoding `%s'", + token); + goto cleanup_fetch_url; + } + (*ischunked)++; + DPRINTF("%s: using chunked encoding\n", + __func__); + + } else if (match_token(&cp, "Proxy-Authenticate:") + || match_token(&cp, "WWW-Authenticate:")) { + if (! (token = match_token(&cp, "Basic"))) { + DPRINTF("%s: skipping unknown auth " + "scheme `%s'\n", __func__, token); + continue; + } + FREEPTR(auth); + auth = ftp_strdup(token); + DPRINTF("%s: parsed auth as `%s'\n", + __func__, cp); + } + + } + /* finished parsing header */ + + switch (hcode) { + case 200: + break; + case 206: + if (! restart_point) { + warnx("Not expecting partial content header"); + goto cleanup_fetch_url; + } + break; + case 300: + case 301: + case 302: + case 303: + case 305: + case 307: + if (EMPTYSTRING(location)) { + warnx( + "No redirection Location provided by server"); + goto cleanup_fetch_url; + } + if (redirect_loop++ > 5) { + warnx("Too many redirections requested"); + goto cleanup_fetch_url; + } + if (hcode == 305) { + if (verbose) + fprintf(ttyout, "Redirected via %s\n", + location); + *rval = fetch_url(url, location, + pauth->auth, wauth->auth); + } else { + if (verbose) + fprintf(ttyout, "Redirected to %s\n", + location); + *rval = go_fetch(location); + } + goto cleanup_fetch_url; +#ifndef NO_AUTH + case 401: + case 407: + { + struct authinfo aauth; + char **authp; + + if (hcode == 401) + aauth = *wauth; + else + aauth = *pauth; + + if (verbose || aauth.auth == NULL || + aauth.user == NULL || aauth.pass == NULL) + fprintf(ttyout, "%s\n", message); + if (EMPTYSTRING(auth)) { + warnx( + "No authentication challenge provided by server"); + goto cleanup_fetch_url; + } + + if (aauth.auth != NULL) { + char reply[10]; + + fprintf(ttyout, + "Authorization failed. Retry (y/n)? "); + if (get_line(stdin, reply, sizeof(reply), NULL) + < 0) { + goto cleanup_fetch_url; + } + if (tolower((unsigned char)reply[0]) != 'y') + goto cleanup_fetch_url; + aauth.user = NULL; + aauth.pass = NULL; + } + + authp = &aauth.auth; + if (auth_url(auth, authp, &aauth) == 0) { + *rval = fetch_url(url, penv, + pauth->auth, wauth->auth); + memset(*authp, 0, strlen(*authp)); + FREEPTR(*authp); + } + goto cleanup_fetch_url; + } +#endif + default: + if (message) + warnx("Error retrieving file `%s'", message); + else + warnx("Unknown error retrieving file"); + goto cleanup_fetch_url; + } + rv = C_OK; + goto out; + +cleanup_fetch_url: + rv = C_CLEANUP; + goto out; +improper: + rv = C_IMPROPER; + goto out; +out: + FREEPTR(auth); + FREEPTR(message); + FREEPTR(location); + return rv; +} /* end of ftp:// or http:// specific setup */ + + /* * Retrieve URL, via a proxy if necessary, using HTTP. * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or @@ -660,33 +1093,26 @@ fetch_url(const char *url, const char *p sigfunc volatile oldquit; int volatile s; struct stat sb; - int volatile ischunked; int volatile isproxy; - int volatile rval; - int volatile hcode; - int len; + int rval, ischunked; size_t flen; static size_t bufsize; static char *xferbuf; - const char *cp, *token; + const char *cp; char *ep; - char buf[FTPBUFLEN]; - const char *errormsg; char *volatile savefile; char *volatile auth; char *volatile location; char *volatile message; - char *uuser, *pass, *host, *port, *path; char *volatile decodedpath; - char *puser, *ppass, *useragent; + struct authinfo wauth, pauth; off_t hashbytes, rangestart, rangeend, entitylen; int (*volatile closefunc)(FILE *); FETCH *volatile fin; FILE *volatile fout; const char *volatile penv = proxyenv; + struct urlinfo ui; time_t mtime; - url_t urltype; - in_port_t portnum; void *ssl = NULL; DPRINTF("%s: `%s' proxyenv `%s'\n", __func__, url, STRorNULL(penv)); @@ -698,35 +1124,39 @@ fetch_url(const char *url, const char *p s = -1; savefile = NULL; auth = location = message = NULL; - ischunked = isproxy = hcode = 0; + ischunked = isproxy = 0; rval = 1; - uuser = pass = host = path = decodedpath = puser = ppass = NULL; + + initurlinfo(&ui); + initauthinfo(&wauth, wwwauth); + initauthinfo(&pauth, proxyauth); + + decodedpath = NULL; if (sigsetjmp(httpabort, 1)) goto cleanup_fetch_url; - if (parse_url(url, "URL", &urltype, &uuser, &pass, &host, &port, - &portnum, &path) == -1) + if (parse_url(url, "URL", &ui, &wauth) == -1) goto cleanup_fetch_url; - if (urltype == FILE_URL_T && ! EMPTYSTRING(host) - && strcasecmp(host, "localhost") != 0) { + if (ui.utype == FILE_URL_T && ! EMPTYSTRING(ui.host) + && strcasecmp(ui.host, "localhost") != 0) { warnx("No support for non local file URL `%s'", url); goto cleanup_fetch_url; } - if (EMPTYSTRING(path)) { - if (urltype == FTP_URL_T) { + if (EMPTYSTRING(ui.path)) { + if (ui.utype == FTP_URL_T) { rval = fetch_ftp(url); goto cleanup_fetch_url; } - if (!IS_HTTP_TYPE(urltype) || outfile == NULL) { + if (!IS_HTTP_TYPE(ui.utype) || outfile == NULL) { warnx("Invalid URL (no file after host) `%s'", url); goto cleanup_fetch_url; } } - decodedpath = ftp_strdup(path); + decodedpath = ftp_strdup(ui.path); url_decode(decodedpath); if (outfile) @@ -740,7 +1170,7 @@ fetch_url(const char *url, const char *p } DPRINTF("%s: savefile `%s'\n", __func__, savefile); if (EMPTYSTRING(savefile)) { - if (urltype == FTP_URL_T) { + if (ui.utype == FTP_URL_T) { rval = fetch_ftp(url); goto cleanup_fetch_url; } @@ -757,7 +1187,7 @@ fetch_url(const char *url, const char *p if (stat(savefile, &sb) == 0) restart_point = sb.st_size; } - if (urltype == FILE_URL_T) { /* file:// URLs */ + if (ui.utype == FILE_URL_T) { /* file:// URLs */ direction = "copied"; fin = fetch_open(decodedpath, "r"); if (fin == NULL) { @@ -791,20 +1221,20 @@ fetch_url(const char *url, const char *p if (penv == NULL) { #ifdef WITH_SSL - if (urltype == HTTPS_URL_T) + if (ui.utype == HTTPS_URL_T) penv = getoptionvalue("https_proxy"); #endif - if (penv == NULL && IS_HTTP_TYPE(urltype)) + if (penv == NULL && IS_HTTP_TYPE(ui.utype)) penv = getoptionvalue("http_proxy"); - else if (urltype == FTP_URL_T) + else if (ui.utype == FTP_URL_T) penv = getoptionvalue("ftp_proxy"); } direction = "retrieved"; if (! EMPTYSTRING(penv)) { /* use proxy */ - isproxy = handle_noproxy(host, portnum); + isproxy = handle_noproxy(ui.host, ui.portnum); - if (isproxy == 0 && urltype == FTP_URL_T) { + if (isproxy == 0 && ui.utype == FTP_URL_T) { rval = fetch_ftp(url); goto cleanup_fetch_url; } @@ -816,20 +1246,14 @@ fetch_url(const char *url, const char *p penv); goto cleanup_fetch_url; } - if (handle_proxy(penv, &host, &port, - &puser, &ppass, &urltype) < 0) { + if (handle_proxy(url, penv, &ui, &pauth) < 0) goto cleanup_fetch_url; - } else { - FREEPTR(path); - path = ftp_strdup(url); - } } } /* ! EMPTYSTRING(penv) */ - s = ftp_socket(host, port, - urltype == HTTPS_URL_T ? &ssl : NULL); + s = ftp_socket(&ui, &ssl); if (s < 0) { - warnx("Can't connect to `%s:%s'", host, port); + warnx("Can't connect to `%s:%s'", ui.host, ui.port); goto cleanup_fetch_url; } @@ -845,84 +1269,24 @@ fetch_url(const char *url, const char *p */ if (verbose) fprintf(ttyout, "Requesting %s\n", url); - leading = " ("; - hasleading = 0; - if (isproxy) { - if (verbose) { - fprintf(ttyout, "%svia %s:%s", leading, - host, port); - leading = ", "; - hasleading++; - } - fetch_printf(fin, "GET %s HTTP/1.0\r\n", path); - if (flushcache) - fetch_printf(fin, "Pragma: no-cache\r\n"); - } else { - fetch_printf(fin, "GET %s HTTP/1.1\r\n", path); - if (strchr(host, ':')) { - char *h, *p; - /* - * strip off IPv6 scope identifier, since it is - * local to the node - */ - h = ftp_strdup(host); - if (isipv6addr(h) && - (p = strchr(h, '%')) != NULL) { - *p = '\0'; - } - fetch_printf(fin, "Host: [%s]", h); - free(h); - } else - fetch_printf(fin, "Host: %s", host); -#ifdef WITH_SSL - if ((urltype == HTTP_URL_T && portnum != HTTP_PORT) || - (urltype == HTTPS_URL_T && portnum != HTTPS_PORT)) -#else - if (portnum != HTTP_PORT) -#endif - fetch_printf(fin, ":%u", portnum); - fetch_printf(fin, "\r\n"); - fetch_printf(fin, "Accept: */*\r\n"); - fetch_printf(fin, "Connection: close\r\n"); - if (restart_point) { - fputs(leading, ttyout); - fetch_printf(fin, "Range: bytes=" LLF "-\r\n", - (LLT)restart_point); - fprintf(ttyout, "restarting at " LLF, - (LLT)restart_point); - leading = ", "; - hasleading++; - } - if (flushcache) - fetch_printf(fin, "Cache-Control: no-cache\r\n"); - } - if ((useragent=getenv("FTPUSERAGENT")) != NULL) { - fetch_printf(fin, "User-Agent: %s\r\n", useragent); - } else { - fetch_printf(fin, "User-Agent: %s/%s\r\n", - FTP_PRODUCT, FTP_VERSION); - } - if (wwwauth) { - if (verbose) { - fprintf(ttyout, "%swith authorization", - leading); - leading = ", "; - hasleading++; - } - fetch_printf(fin, "Authorization: %s\r\n", wwwauth); - } - if (proxyauth) { - if (verbose) { - fprintf(ttyout, - "%swith proxy authorization", leading); - leading = ", "; - hasleading++; - } - fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth); + hasleading = print_get(fin, isproxy, &ui); + if (hasleading) + leading = ", "; + else + leading = " ("; + + if (flushcache) + print_cache(fin, isproxy); + + print_agent(fin); + hasleading = print_proxy(fin, leading, wauth.auth, pauth.auth); + if (hasleading) { + leading = ", "; + if (verbose) + fputs(")\n", ttyout); } - if (verbose && hasleading) - fputs(")\n", ttyout); + fetch_printf(fin, "\r\n"); if (fetch_flush(fin) == EOF) { warn("Writing HTTP request"); @@ -931,257 +1295,19 @@ fetch_url(const char *url, const char *p } alarmtimer(0); - /* Read the response */ - alarmtimer(quit_time ? quit_time : 60); - len = fetch_getline(fin, buf, sizeof(buf), &errormsg); - alarmtimer(0); - if (len < 0) { - if (*errormsg == '\n') - errormsg++; - warnx("Receiving HTTP reply: %s", errormsg); - goto cleanup_fetch_url; - } - while (len > 0 && (ISLWS(buf[len-1]))) - buf[--len] = '\0'; - DPRINTF("%s: received `%s'\n", __func__, buf); - - /* Determine HTTP response code */ - cp = strchr(buf, ' '); - if (cp == NULL) - goto improper; - else - cp++; - hcode = strtol(cp, &ep, 10); - if (*ep != '\0' && !isspace((unsigned char)*ep)) - goto improper; - message = ftp_strdup(cp); - - /* Read the rest of the header. */ - while (1) { - alarmtimer(quit_time ? quit_time : 60); - len = fetch_getline(fin, buf, sizeof(buf), &errormsg); - alarmtimer(0); - if (len < 0) { - if (*errormsg == '\n') - errormsg++; - warnx("Receiving HTTP reply: %s", errormsg); - goto cleanup_fetch_url; - } - while (len > 0 && (ISLWS(buf[len-1]))) - buf[--len] = '\0'; - if (len == 0) - break; - DPRINTF("%s: received `%s'\n", __func__, buf); - - /* - * Look for some headers - */ - - cp = buf; - - if (match_token(&cp, "Content-Length:")) { - filesize = STRTOLL(cp, &ep, 10); - if (filesize < 0 || *ep != '\0') - goto improper; - DPRINTF("%s: parsed len as: " LLF "\n", - __func__, (LLT)filesize); - - } else if (match_token(&cp, "Content-Range:")) { - if (! match_token(&cp, "bytes")) - goto improper; - - if (*cp == '*') - cp++; - else { - rangestart = STRTOLL(cp, &ep, 10); - if (rangestart < 0 || *ep != '-') - goto improper; - cp = ep + 1; - rangeend = STRTOLL(cp, &ep, 10); - if (rangeend < 0 || rangeend < rangestart) - goto improper; - cp = ep; - } - if (*cp != '/') - goto improper; - cp++; - if (*cp == '*') - cp++; - else { - entitylen = STRTOLL(cp, &ep, 10); - if (entitylen < 0) - goto improper; - cp = ep; - } - if (*cp != '\0') - goto improper; - -#ifndef NO_DEBUG - if (ftp_debug) { - fprintf(ttyout, "parsed range as: "); - if (rangestart == -1) - fprintf(ttyout, "*"); - else - fprintf(ttyout, LLF "-" LLF, - (LLT)rangestart, - (LLT)rangeend); - fprintf(ttyout, "/" LLF "\n", (LLT)entitylen); - } -#endif - if (! restart_point) { - warnx( - "Received unexpected Content-Range header"); - goto cleanup_fetch_url; - } - - } else if (match_token(&cp, "Last-Modified:")) { - struct tm parsed; - const char *t; - - memset(&parsed, 0, sizeof(parsed)); - t = parse_rfc2616time(&parsed, cp); - if (t != NULL) { - parsed.tm_isdst = -1; - if (*t == '\0') - mtime = timegm(&parsed); -#ifndef NO_DEBUG - if (ftp_debug && mtime != -1) { - fprintf(ttyout, - "parsed time as: %s", - rfc2822time(localtime(&mtime))); - } -#endif - } - - } else if (match_token(&cp, "Location:")) { - location = ftp_strdup(cp); - DPRINTF("%s: parsed location as `%s'\n", - __func__, cp); - - } else if (match_token(&cp, "Transfer-Encoding:")) { - if (match_token(&cp, "binary")) { - warnx( - "Bogus transfer encoding `binary' (fetching anyway)"); - continue; - } - if (! (token = match_token(&cp, "chunked"))) { - warnx( - "Unsupported transfer encoding `%s'", - token); - goto cleanup_fetch_url; - } - ischunked++; - DPRINTF("%s: using chunked encoding\n", - __func__); - - } else if (match_token(&cp, "Proxy-Authenticate:") - || match_token(&cp, "WWW-Authenticate:")) { - if (! (token = match_token(&cp, "Basic"))) { - DPRINTF("%s: skipping unknown auth " - "scheme `%s'\n", __func__, token); - continue; - } - FREEPTR(auth); - auth = ftp_strdup(token); - DPRINTF("%s: parsed auth as `%s'\n", - __func__, cp); - } - - } - /* finished parsing header */ - - switch (hcode) { - case 200: - break; - case 206: - if (! restart_point) { - warnx("Not expecting partial content header"); - goto cleanup_fetch_url; - } + switch (negotiate_connection(fin, url, penv, + &rangestart, &rangeend, &entitylen, + &mtime, &wauth, &pauth, &rval, &ischunked)) { + case C_OK: break; - case 300: - case 301: - case 302: - case 303: - case 305: - case 307: - if (EMPTYSTRING(location)) { - warnx( - "No redirection Location provided by server"); - goto cleanup_fetch_url; - } - if (redirect_loop++ > 5) { - warnx("Too many redirections requested"); - goto cleanup_fetch_url; - } - if (hcode == 305) { - if (verbose) - fprintf(ttyout, "Redirected via %s\n", - location); - rval = fetch_url(url, location, - proxyauth, wwwauth); - } else { - if (verbose) - fprintf(ttyout, "Redirected to %s\n", - location); - rval = go_fetch(location); - } + case C_CLEANUP: goto cleanup_fetch_url; -#ifndef NO_AUTH - case 401: - case 407: - { - char **authp; - char *auser, *apass; - - if (hcode == 401) { - authp = &wwwauth; - auser = uuser; - apass = pass; - } else { - authp = &proxyauth; - auser = puser; - apass = ppass; - } - if (verbose || *authp == NULL || - auser == NULL || apass == NULL) - fprintf(ttyout, "%s\n", message); - if (EMPTYSTRING(auth)) { - warnx( - "No authentication challenge provided by server"); - goto cleanup_fetch_url; - } - if (*authp != NULL) { - char reply[10]; - - fprintf(ttyout, - "Authorization failed. Retry (y/n)? "); - if (get_line(stdin, reply, sizeof(reply), NULL) - < 0) { - goto cleanup_fetch_url; - } - if (tolower((unsigned char)reply[0]) != 'y') - goto cleanup_fetch_url; - auser = NULL; - apass = NULL; - } - if (auth_url(auth, authp, auser, apass) == 0) { - rval = fetch_url(url, penv, - proxyauth, wwwauth); - memset(*authp, 0, strlen(*authp)); - FREEPTR(*authp); - } - goto cleanup_fetch_url; - } -#endif + case C_IMPROPER: + goto improper; default: - if (message) - warnx("Error retrieving file `%s'", message); - else - warnx("Unknown error retrieving file"); - goto cleanup_fetch_url; + abort(); } - } /* end of ftp:// or http:// specific setup */ + } /* Open the output file. */ @@ -1401,7 +1527,7 @@ fetch_url(const char *url, const char *p goto cleanup_fetch_url; improper: - warnx("Improper response from `%s:%s'", host, port); + warnx("Improper response from `%s:%s'", ui.host, ui.port); cleanup_fetch_url: if (oldint) @@ -1420,18 +1546,10 @@ fetch_url(const char *url, const char *p (*closefunc)(fout); if (savefile != outfile) FREEPTR(savefile); - FREEPTR(uuser); - if (pass != NULL) - memset(pass, 0, strlen(pass)); - FREEPTR(pass); - FREEPTR(host); - FREEPTR(port); - FREEPTR(path); + freeurlinfo(&ui); + freeauthinfo(&wauth); + freeauthinfo(&pauth); FREEPTR(decodedpath); - FREEPTR(puser); - if (ppass != NULL) - memset(ppass, 0, strlen(ppass)); - FREEPTR(ppass); FREEPTR(auth); FREEPTR(location); FREEPTR(message); @@ -1483,26 +1601,26 @@ static int fetch_ftp(const char *url) { char *cp, *xargv[5], rempath[MAXPATHLEN]; - char *host, *path, *dir, *file, *uuser, *pass; - char *port; + char *dir, *file; char cmdbuf[MAXPATHLEN]; char dirbuf[4]; int dirhasglob, filehasglob, rval, transtype, xargc; int oanonftp, oautologin; - in_port_t portnum; - url_t urltype; + struct authinfo auth; + struct urlinfo ui; DPRINTF("fetch_ftp: `%s'\n", url); - host = path = dir = file = uuser = pass = NULL; - port = NULL; + dir = file = NULL; rval = 1; transtype = TYPE_I; + initurlinfo(&ui); + initauthinfo(&auth, NULL); + if (STRNEQUAL(url, FTP_URL)) { - if ((parse_url(url, "URL", &urltype, &uuser, &pass, - &host, &port, &portnum, &path) == -1) || - (uuser != NULL && *uuser == '\0') || - EMPTYSTRING(host)) { + if ((parse_url(url, "URL", &ui, &auth) == -1) || + (auth.user != NULL && *auth.user == '\0') || + EMPTYSTRING(ui.host)) { warnx("Invalid URL `%s'", url); goto cleanup_fetch_ftp; } @@ -1512,7 +1630,7 @@ fetch_ftp(const char *url) */ /* check for trailing ';type=[aid]' */ - if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) { + if (! EMPTYSTRING(ui.path) && (cp = strrchr(ui.path, ';')) != NULL) { if (strcasecmp(cp, ";type=a") == 0) transtype = TYPE_A; else if (strcasecmp(cp, ";type=i") == 0) @@ -1529,26 +1647,26 @@ fetch_ftp(const char *url) *cp = 0; } } else { /* classic style `[user@]host:[file]' */ - urltype = CLASSIC_URL_T; - host = ftp_strdup(url); - cp = strchr(host, '@'); + ui.utype = CLASSIC_URL_T; + ui.host = ftp_strdup(url); + cp = strchr(ui.host, '@'); if (cp != NULL) { *cp = '\0'; - uuser = host; + auth.user = ui.host; anonftp = 0; /* disable anonftp */ - host = ftp_strdup(cp + 1); + ui.host = ftp_strdup(cp + 1); } - cp = strchr(host, ':'); + cp = strchr(ui.host, ':'); if (cp != NULL) { *cp = '\0'; - path = ftp_strdup(cp + 1); + ui.path = ftp_strdup(cp + 1); } } - if (EMPTYSTRING(host)) + if (EMPTYSTRING(ui.host)) goto cleanup_fetch_ftp; /* Extract the file and (if present) directory name. */ - dir = path; + dir = ui.path; if (! EMPTYSTRING(dir)) { /* * If we are dealing with classic `[user@]host:[path]' syntax, @@ -1573,7 +1691,7 @@ fetch_ftp(const char *url) * resulting from an URL of the form `ftp://host//file'.) */ cp = strrchr(dir, '/'); - if (cp == dir && urltype == CLASSIC_URL_T) { + if (cp == dir && ui.utype == CLASSIC_URL_T) { file = cp + 1; (void)strlcpy(dirbuf, "/", sizeof(dirbuf)); dir = dirbuf; @@ -1586,18 +1704,18 @@ fetch_ftp(const char *url) } } else dir = NULL; - if (urltype == FTP_URL_T && file != NULL) { + if (ui.utype == FTP_URL_T && file != NULL) { url_decode(file); /* but still don't url_decode(dir) */ } DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s " "path `%s' dir `%s' file `%s'\n", - STRorNULL(uuser), STRorNULL(pass), - STRorNULL(host), STRorNULL(port), - STRorNULL(path), STRorNULL(dir), STRorNULL(file)); + STRorNULL(auth.user), STRorNULL(auth.pass), + STRorNULL(ui.host), STRorNULL(ui.port), + STRorNULL(ui.path), STRorNULL(dir), STRorNULL(file)); dirhasglob = filehasglob = 0; - if (doglob && urltype == CLASSIC_URL_T) { + if (doglob && ui.utype == CLASSIC_URL_T) { if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL) dirhasglob = 1; if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL) @@ -1611,11 +1729,11 @@ fetch_ftp(const char *url) anonftp = oanonftp; (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf)); xargv[0] = cmdbuf; - xargv[1] = host; + xargv[1] = ui.host; xargv[2] = NULL; xargc = 2; - if (port) { - xargv[2] = port; + if (ui.port) { + xargv[2] = ui.port; xargv[3] = NULL; xargc = 3; } @@ -1625,9 +1743,9 @@ fetch_ftp(const char *url) setpeer(xargc, xargv); autologin = oautologin; if ((connected == 0) || - (connected == 1 && !ftp_login(host, uuser, pass))) { + (connected == 1 && !ftp_login(ui.host, auth.user, auth.pass))) { warnx("Can't connect or login to host `%s:%s'", - host, port ? port : "?"); + ui.host, ui.port ? ui.port : "?"); goto cleanup_fetch_ftp; } @@ -1712,7 +1830,7 @@ fetch_ftp(const char *url) * Note that we don't need `dir' after this point. */ do { - if (urltype == FTP_URL_T) { + if (ui.utype == FTP_URL_T) { nextpart = strchr(dir, '/'); if (nextpart) { *nextpart = '\0'; @@ -1723,7 +1841,7 @@ fetch_ftp(const char *url) nextpart = NULL; DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n", STRorNULL(dir), STRorNULL(nextpart)); - if (urltype == FTP_URL_T || *dir != '\0') { + if (ui.utype == FTP_URL_T || *dir != '\0') { (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf)); xargv[0] = cmdbuf; xargv[1] = dir; @@ -1797,13 +1915,8 @@ fetch_ftp(const char *url) rval = 0; cleanup_fetch_ftp: - FREEPTR(port); - FREEPTR(host); - FREEPTR(path); - FREEPTR(uuser); - if (pass) - memset(pass, 0, strlen(pass)); - FREEPTR(pass); + freeurlinfo(&ui); + freeauthinfo(&auth); return (rval); }