Module Name: src Committed By: bouyer Date: Sat Aug 27 13:57:01 UTC 2016
Modified Files: src/usr.bin/ftp [netbsd-6]: cmds.c fetch.c ftp.1 ftp.c ftp_var.h main.c ssl.c ssl.h version.h Log Message: Apply patch, requested by nonaka in ticket #1375: src/usr.bin/ftp/cmds.c: patch src/usr.bin/ftp/fetch.c: patch src/usr.bin/ftp/ftp.1: patch src/usr.bin/ftp/ftp.c: patch src/usr.bin/ftp/ftp_var.h: patch src/usr.bin/ftp/main.c: patch src/usr.bin/ftp/ssl.c: patch src/usr.bin/ftp/ssl.h: patch src/usr.bin/ftp/version.h: patch Update ftp(1) to version 20150912, adding https via proxy support. To generate a diff of this commit: cvs rdiff -u -r1.134.2.1 -r1.134.2.2 src/usr.bin/ftp/cmds.c cvs rdiff -u -r1.195.2.2 -r1.195.2.3 src/usr.bin/ftp/fetch.c cvs rdiff -u -r1.131.8.1 -r1.131.8.2 src/usr.bin/ftp/ftp.1 cvs rdiff -u -r1.163.2.1 -r1.163.2.2 src/usr.bin/ftp/ftp.c cvs rdiff -u -r1.81.8.1 -r1.81.8.2 src/usr.bin/ftp/ftp_var.h cvs rdiff -u -r1.120.2.1 -r1.120.2.2 src/usr.bin/ftp/main.c cvs rdiff -u -r1.2.10.2 -r1.2.10.3 src/usr.bin/ftp/ssl.c cvs rdiff -u -r1.1.10.2 -r1.1.10.3 src/usr.bin/ftp/ssl.h cvs rdiff -u -r1.82.8.2 -r1.82.8.3 src/usr.bin/ftp/version.h 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/cmds.c diff -u src/usr.bin/ftp/cmds.c:1.134.2.1 src/usr.bin/ftp/cmds.c:1.134.2.2 --- src/usr.bin/ftp/cmds.c:1.134.2.1 Tue Dec 17 21:07:59 2013 +++ src/usr.bin/ftp/cmds.c Sat Aug 27 13:57:01 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: cmds.c,v 1.134.2.1 2013/12/17 21:07:59 bouyer Exp $ */ +/* $NetBSD: cmds.c,v 1.134.2.2 2016/08/27 13:57:01 bouyer Exp $ */ /*- * Copyright (c) 1996-2009 The NetBSD Foundation, Inc. @@ -96,7 +96,7 @@ #if 0 static char sccsid[] = "@(#)cmds.c 8.6 (Berkeley) 10/9/94"; #else -__RCSID("$NetBSD: cmds.c,v 1.134.2.1 2013/12/17 21:07:59 bouyer Exp $"); +__RCSID("$NetBSD: cmds.c,v 1.134.2.2 2016/08/27 13:57:01 bouyer Exp $"); #endif #endif /* not lint */ @@ -1967,15 +1967,15 @@ dotrans(char *dst, size_t dlen, const ch char *cp2 = dst; size_t i, ostop; - for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++) + for (ostop = 0; ntout[ostop] && ostop < sizeof(ntout); ostop++) continue; for (cp1 = src; *cp1; cp1++) { int found = 0; - for (i = 0; *(ntin + i) && i < 16; i++) { - if (*cp1 == *(ntin + i)) { + for (i = 0; i < sizeof(ntin) && ntin[i]; i++) { + if (*cp1 == ntin[i]) { found++; if (i < ostop) { - *cp2++ = *(ntout + i); + *cp2++ = ntout[i]; if (cp2 - dst >= (ptrdiff_t)(dlen - 1)) goto out; } Index: src/usr.bin/ftp/fetch.c diff -u src/usr.bin/ftp/fetch.c:1.195.2.2 src/usr.bin/ftp/fetch.c:1.195.2.3 --- src/usr.bin/ftp/fetch.c:1.195.2.2 Mon Oct 27 05:53:04 2014 +++ src/usr.bin/ftp/fetch.c Sat Aug 27 13:57:01 2016 @@ -1,7 +1,7 @@ -/* $NetBSD: fetch.c,v 1.195.2.2 2014/10/27 05:53:04 snj Exp $ */ +/* $NetBSD: fetch.c,v 1.195.2.3 2016/08/27 13:57:01 bouyer Exp $ */ /*- - * Copyright (c) 1997-2009 The NetBSD Foundation, Inc. + * Copyright (c) 1997-2015 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -10,6 +10,9 @@ * This code is derived from software contributed to The NetBSD Foundation * by Scott Aaron Bamford. * + * This code is derived from software contributed to The NetBSD Foundation + * by Thomas Klausner. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -34,7 +37,7 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: fetch.c,v 1.195.2.2 2014/10/27 05:53:04 snj Exp $"); +__RCSID("$NetBSD: fetch.c,v 1.195.2.3 2016/08/27 13:57:01 bouyer Exp $"); #endif /* not lint */ /* @@ -71,27 +74,47 @@ __RCSID("$NetBSD: fetch.c,v 1.195.2.2 20 typedef enum { UNKNOWN_URL_T=-1, HTTP_URL_T, -#ifdef WITH_SSL HTTPS_URL_T, -#endif FTP_URL_T, FILE_URL_T, 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; +}; + +struct posinfo { + off_t rangestart; + off_t rangeend; + off_t entitylen; +}; + __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; @@ -143,6 +166,54 @@ match_token(const char **buf, const char return orig; } +static void +initposinfo(struct posinfo *pi) +{ + pi->rangestart = pi->rangeend = pi->entitylen = -1; +} + +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 +copyurlinfo(struct urlinfo *dui, struct urlinfo *sui) +{ + dui->host = ftp_strdup(sui->host); + dui->port = ftp_strdup(sui->port); + dui->path = ftp_strdup(sui->path); + dui->utype = sui->utype; + dui->portnum = sui->portnum; +} + +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. @@ -150,8 +221,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; @@ -195,8 +265,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); @@ -205,8 +275,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) { @@ -226,7 +296,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 */ @@ -325,57 +395,48 @@ 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; + tport = ""; #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); } @@ -390,26 +451,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 @@ -443,7 +504,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) { @@ -456,30 +517,732 @@ 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); } sigjmp_buf httpabort; +static int +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; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + error = getaddrinfo(host, port, &hints, &res0); + if (error) { + warnx("Can't LOOKUP `%s:%s': %s", host, port, + (error == EAI_SYSTEM) ? strerror(errno) + : gai_strerror(error)); + return -1; + } + + if (res0->ai_canonname) + host = res0->ai_canonname; + + s = -1; + if (ssl) + *ssl = NULL; + for (res = res0; res; res = res->ai_next) { + char hname[NI_MAXHOST], sname[NI_MAXSERV]; + + ai_unmapped(res); + if (getnameinfo(res->ai_addr, res->ai_addrlen, + hname, sizeof(hname), sname, sizeof(sname), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) { + strlcpy(hname, "?", sizeof(hname)); + strlcpy(sname, "?", sizeof(sname)); + } + + if (verbose && res0->ai_next) { +#ifdef INET6 + if(res->ai_family == AF_INET6) { + fprintf(ttyout, "Trying [%s]:%s ...\n", + hname, sname); + } else { +#endif + fprintf(ttyout, "Trying %s:%s ...\n", + hname, sname); +#ifdef INET6 + } +#endif + } + + s = socket(res->ai_family, SOCK_STREAM, res->ai_protocol); + if (s < 0) { + warn( + "Can't create socket for connection to " + "`%s:%s'", hname, sname); + continue; + } + + if (ftp_connect(s, res->ai_addr, res->ai_addrlen, + verbose || !res->ai_next) < 0) { + close(s); + s = -1; + continue; + } + +#ifdef WITH_SSL + if (ssl) { + if ((*ssl = fetch_start_ssl(s, host)) == NULL) { + close(s); + s = -1; + continue; + } + } +#endif + break; + } + if (res0) + freeaddrinfo(res0); + return s; +} + +static int +handle_noproxy(const char *host, in_port_t portnum) +{ + + char *cp, *ep, *np, *np_copy, *np_iter, *no_proxy; + unsigned long np_port; + size_t hlen, plen; + int isproxy = 1; + + /* check URL against list of no_proxied sites */ + no_proxy = getoptionvalue("no_proxy"); + if (EMPTYSTRING(no_proxy)) + return isproxy; + + np_iter = np_copy = ftp_strdup(no_proxy); + hlen = strlen(host); + while ((cp = strsep(&np_iter, " ,")) != NULL) { + if (*cp == '\0') + continue; + if ((np = strrchr(cp, ':')) != NULL) { + *np++ = '\0'; + np_port = strtoul(np, &ep, 10); + if (*np == '\0' || *ep != '\0') + continue; + if (np_port != portnum) + continue; + } + plen = strlen(cp); + if (hlen < plen) + continue; + if (strncasecmp(host + hlen - plen, cp, plen) == 0) { + isproxy = 0; + break; + } + } + FREEPTR(np_copy); + return isproxy; +} + +static int +handle_proxy(const char *url, const char *penv, struct urlinfo *ui, + struct authinfo *pauth) +{ + struct urlinfo pui; + + if (isipv6addr(ui->host) && strchr(ui->host, '%') != NULL) { + warnx("Scoped address notation `%s' disallowed via web proxy", + ui->host); + return -1; + } + + initurlinfo(&pui); + if (parse_url(penv, "proxy URL", &pui, pauth) == -1) + return -1; + + 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); + freeurlinfo(&pui); + return -1; + } + + FREEPTR(pui.path); + pui.path = ftp_strdup(url); + + freeurlinfo(ui); + *ui = pui; + + return 0; +} + +static void +print_host(FETCH *fin, const struct urlinfo *ui) +{ + char *h, *p; + + if (strchr(ui->host, ':') == NULL) { + fetch_printf(fin, "Host: %s", ui->host); + } else { + /* + * strip off IPv6 scope identifier, since it is + * local to the node + */ + h = ftp_strdup(ui->host); + if (isipv6addr(h) && (p = strchr(h, '%')) != NULL) + *p = '\0'; + + fetch_printf(fin, "Host: [%s]", h); + free(h); + } + + 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"); +} + +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 hasleading, int isproxy, const struct urlinfo *oui, + const struct urlinfo *ui) +{ + const char *leading = hasleading ? ", " : " ("; + + 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); + print_host(fin, oui); + return hasleading; + } + + fetch_printf(fin, "GET %s HTTP/1.1\r\n", ui->path); + print_host(fin, ui); + 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, int hasleading, const char *wwwauth, + const char *proxyauth) +{ + const char *leading = hasleading ? ", " : " ("; + + 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; +} + +#ifdef WITH_SSL +static void +print_connect(FETCH *fin, const struct urlinfo *ui) +{ + char hname[NI_MAXHOST], *p; + const char *h; + + if (isipv6addr(ui->host)) { + /* + * strip off IPv6 scope identifier, + * since it is local to the node + */ + if ((p = strchr(ui->host, '%')) == NULL) + snprintf(hname, sizeof(hname), "[%s]", ui->host); + else + snprintf(hname, sizeof(hname), "[%.*s]", + (int)(p - ui->host), ui->host); + h = hname; + } else + h = ui->host; + + fetch_printf(fin, "CONNECT %s:%s HTTP/1.1\r\n", h, ui->port); + fetch_printf(fin, "Host: %s:%s\r\n", h, ui->port); +} +#endif + +#define C_OK 0 +#define C_CLEANUP 1 +#define C_IMPROPER 2 +#define C_PROXY 3 +#define C_NOPROXY 4 + +static int +getresponseline(FETCH *fin, char *buf, size_t buflen, int *len) +{ + const char *errormsg; + + alarmtimer(quit_time ? quit_time : 60); + *len = fetch_getline(fin, buf, buflen, &errormsg); + alarmtimer(0); + if (*len < 0) { + if (*errormsg == '\n') + errormsg++; + warnx("Receiving HTTP reply: %s", errormsg); + return C_CLEANUP; + } + while (*len > 0 && (ISLWS(buf[*len-1]))) + buf[--*len] = '\0'; + + if (*len) + DPRINTF("%s: received `%s'\n", __func__, buf); + return C_OK; +} + +static int +getresponse(FETCH *fin, char **cp, size_t buflen, int *hcode) +{ + int len, rv; + char *ep, *buf = *cp; + + *hcode = 0; + if ((rv = getresponseline(fin, buf, buflen, &len)) != C_OK) + return rv; + + /* Determine HTTP response code */ + *cp = strchr(buf, ' '); + if (*cp == NULL) + return C_IMPROPER; + + (*cp)++; + + *hcode = strtol(*cp, &ep, 10); + if (*ep != '\0' && !isspace((unsigned char)*ep)) + return C_IMPROPER; + + return C_OK; +} + +static int +parse_posinfo(const char **cp, struct posinfo *pi) +{ + char *ep; + if (!match_token(cp, "bytes")) + return -1; + + if (**cp == '*') + (*cp)++; + else { + pi->rangestart = STRTOLL(*cp, &ep, 10); + if (pi->rangestart < 0 || *ep != '-') + return -1; + *cp = ep + 1; + pi->rangeend = STRTOLL(*cp, &ep, 10); + if (pi->rangeend < 0 || pi->rangeend < pi->rangestart) + return -1; + *cp = ep; + } + if (**cp != '/') + return -1; + (*cp)++; + if (**cp == '*') + (*cp)++; + else { + pi->entitylen = STRTOLL(*cp, &ep, 10); + if (pi->entitylen < 0) + return -1; + *cp = ep; + } + if (**cp != '\0') + return -1; + +#ifndef NO_DEBUG + if (ftp_debug) { + fprintf(ttyout, "parsed range as: "); + if (pi->rangestart == -1) + fprintf(ttyout, "*"); + else + fprintf(ttyout, LLF "-" LLF, (LLT)pi->rangestart, + (LLT)pi->rangeend); + fprintf(ttyout, "/" LLF "\n", (LLT)pi->entitylen); + } +#endif + return 0; +} + +static int +negotiate_connection(FETCH *fin, const char *url, const char *penv, + struct posinfo *pi, time_t *mtime, struct authinfo *wauth, + struct authinfo *pauth, volatile int *rval, volatile int *ischunked, + char * volatile *auth) +{ + int len, hcode, rv; + char buf[FTPBUFLEN], *ep; + const char *cp, *token; + char *location, *message; + + *auth = message = location = NULL; + + /* Read the response */ + ep = buf; + switch (getresponse(fin, &ep, sizeof(buf), &hcode)) { + case C_CLEANUP: + goto cleanup_fetch_url; + case C_IMPROPER: + goto improper; + case C_OK: + message = ftp_strdup(ep); + break; + } + + /* Read the rest of the header. */ + + for (;;) { + if ((rv = getresponseline(fin, buf, sizeof(buf), &len)) != C_OK) + goto cleanup_fetch_url; + if (len == 0) + break; + + /* + * 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 (parse_posinfo(&cp, pi) == -1) + goto improper; + 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(message); + FREEPTR(location); + return rv; +} /* end of ftp:// or http:// specific setup */ + +#ifdef WITH_SSL +static int +connectmethod(int s, FETCH *fin, struct urlinfo *oui, struct urlinfo *ui, + struct authinfo *pauth, char * volatile *auth, int *hasleading) +{ + void *ssl; + int hcode, rv; + const char *cp; + char buf[FTPBUFLEN], *ep; + char *message = NULL; + + print_connect(fin, oui); + + print_agent(fin); + *hasleading = print_proxy(fin, *hasleading, NULL, pauth->auth); + + if (verbose && *hasleading) + fputs(")\n", ttyout); + *hasleading = 0; + + fetch_printf(fin, "\r\n"); + if (fetch_flush(fin) == EOF) { + warn("Writing HTTP request"); + alarmtimer(0); + goto cleanup_fetch_url; + } + alarmtimer(0); + + /* Read the response */ + ep = buf; + switch (getresponse(fin, &ep, sizeof(buf), &hcode)) { + case C_CLEANUP: + goto cleanup_fetch_url; + case C_IMPROPER: + goto improper; + case C_OK: + message = ftp_strdup(ep); + break; + } + + for (;;) { + int len; + if (getresponseline(fin, buf, sizeof(buf), &len) != C_OK) + goto cleanup_fetch_url; + if (len == 0) + break; + + cp = buf; + if (match_token(&cp, "Proxy-Authenticate:")) { + const char *token; + 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; + default: + if (message) + warnx("Error proxy connect " "`%s'", message); + else + warnx("Unknown error proxy " "connect"); + goto cleanup_fetch_url; + } + + if ((ssl = fetch_start_ssl(s, oui->host)) == NULL) + goto cleanup_fetch_url; + fetch_set_ssl(fin, ssl); + + rv = C_OK; + goto out; +improper: + rv = C_IMPROPER; + goto out; +cleanup_fetch_url: + rv = C_CLEANUP; + goto out; +out: + FREEPTR(message); + return rv; +} +#endif + /* * Retrieve URL, via a proxy if necessary, using HTTP. * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or @@ -491,44 +1254,34 @@ sigjmp_buf httpabort; static int fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth) { - struct addrinfo hints, *res, *res0 = NULL; - int error; sigfunc volatile oldint; sigfunc volatile oldpipe; sigfunc volatile oldalrm; sigfunc volatile oldquit; int volatile s; struct stat sb; - int volatile ischunked; int volatile isproxy; - int volatile rval; - int volatile hcode; - int len; + int volatile 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 savefile; char *volatile location; char *volatile message; - char *uuser, *pass, *host, *port, *path; char *volatile decodedpath; - char *puser, *ppass, *useragent; - off_t hashbytes, rangestart, rangeend, entitylen; + struct authinfo wauth, pauth; + struct posinfo pi; + off_t hashbytes; int (*volatile closefunc)(FILE *); FETCH *volatile fin; FILE *volatile fout; const char *volatile penv = proxyenv; + struct urlinfo ui, oui; time_t mtime; - url_t urltype; - in_port_t portnum; -#ifdef WITH_SSL - void *ssl; -#endif + void *ssl = NULL; DPRINTF("%s: `%s' proxyenv `%s'\n", __func__, url, STRorNULL(penv)); @@ -539,35 +1292,41 @@ 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) { + copyurlinfo(&oui, &ui); + + 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) @@ -581,7 +1340,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; } @@ -592,14 +1351,13 @@ fetch_url(const char *url, const char *p restart_point = 0; filesize = -1; - rangestart = rangeend = entitylen = -1; + initposinfo(&pi); mtime = -1; if (restartautofetch) { - if (strcmp(savefile, "-") != 0 && *savefile != '|' && - stat(savefile, &sb) == 0) + 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) { @@ -628,175 +1386,43 @@ fetch_url(const char *url, const char *p rcvbuf_size = 8 * 1024; /* XXX */ } } else { /* ftp:// or http:// URLs */ - const char *leading; int hasleading; 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 */ - url_t purltype; - char *phost, *ppath; - char *pport, *no_proxy; - in_port_t pportnum; - - isproxy = 1; - - /* check URL against list of no_proxied sites */ - no_proxy = getoptionvalue("no_proxy"); - if (! EMPTYSTRING(no_proxy)) { - char *np, *np_copy, *np_iter; - unsigned long np_port; - size_t hlen, plen; - - np_iter = np_copy = ftp_strdup(no_proxy); - hlen = strlen(host); - while ((cp = strsep(&np_iter, " ,")) != NULL) { - if (*cp == '\0') - continue; - if ((np = strrchr(cp, ':')) != NULL) { - *np++ = '\0'; - np_port = strtoul(np, &ep, 10); - if (*np == '\0' || *ep != '\0') - continue; - if (np_port != portnum) - continue; - } - plen = strlen(cp); - if (hlen < plen) - continue; - if (strncasecmp(host + hlen - plen, - cp, plen) == 0) { - isproxy = 0; - break; - } - } - FREEPTR(np_copy); - if (isproxy == 0 && urltype == FTP_URL_T) { - rval = fetch_ftp(url); - goto cleanup_fetch_url; - } + + isproxy = handle_noproxy(ui.host, ui.portnum); + + if (isproxy == 0 && ui.utype == FTP_URL_T) { + rval = fetch_ftp(url); + goto cleanup_fetch_url; } if (isproxy) { if (restart_point) { - warnx("Can't restart via proxy URL `%s'", + warnx( + "Can't restart via proxy URL `%s'", penv); goto cleanup_fetch_url; } - if (parse_url(penv, "proxy URL", &purltype, - &puser, &ppass, &phost, &pport, &pportnum, - &ppath) == -1) - goto cleanup_fetch_url; - - if ((!IS_HTTP_TYPE(purltype) - && purltype != FTP_URL_T) || - EMPTYSTRING(phost) || - (! EMPTYSTRING(ppath) - && strcmp(ppath, "/") != 0)) { - warnx("Malformed proxy URL `%s'", penv); - FREEPTR(phost); - FREEPTR(pport); - FREEPTR(ppath); + if (handle_proxy(url, penv, &ui, &pauth) < 0) goto cleanup_fetch_url; - } - if (isipv6addr(host) && - strchr(host, '%') != NULL) { - warnx( -"Scoped address notation `%s' disallowed via web proxy", - host); - FREEPTR(phost); - FREEPTR(pport); - FREEPTR(ppath); - goto cleanup_fetch_url; - } - - FREEPTR(host); - host = phost; - FREEPTR(port); - port = pport; - FREEPTR(path); - path = ftp_strdup(url); - FREEPTR(ppath); - urltype = purltype; } } /* ! EMPTYSTRING(penv) */ - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = 0; - hints.ai_family = family; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - error = getaddrinfo(host, port, &hints, &res0); - if (error) { - warnx("Can't LOOKUP `%s:%s': %s", host, port, - (error == EAI_SYSTEM) ? strerror(errno) - : gai_strerror(error)); - goto cleanup_fetch_url; - } - if (res0->ai_canonname) - host = res0->ai_canonname; - - s = -1; -#ifdef WITH_SSL - ssl = NULL; -#endif - for (res = res0; res; res = res->ai_next) { - char hname[NI_MAXHOST], sname[NI_MAXSERV]; - - ai_unmapped(res); - if (getnameinfo(res->ai_addr, res->ai_addrlen, - hname, sizeof(hname), sname, sizeof(sname), - NI_NUMERICHOST | NI_NUMERICSERV) != 0) { - strlcpy(hname, "?", sizeof(hname)); - strlcpy(sname, "?", sizeof(sname)); - } - - if (verbose && res0->ai_next) { - fprintf(ttyout, "Trying %s:%s ...\n", - hname, sname); - } - - s = socket(res->ai_family, SOCK_STREAM, - res->ai_protocol); - if (s < 0) { - warn( - "Can't create socket for connection to " - "`%s:%s'", hname, sname); - continue; - } - - if (ftp_connect(s, res->ai_addr, res->ai_addrlen, - verbose || !res->ai_next) < 0) { - close(s); - s = -1; - continue; - } - -#ifdef WITH_SSL - if (urltype == HTTPS_URL_T) { - if ((ssl = fetch_start_ssl(s)) == NULL) { - close(s); - s = -1; - continue; - } - } -#endif - - /* success */ - break; - } - + 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; } @@ -812,84 +1438,38 @@ 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); + hasleading = 0; #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 (isproxy && oui.utype == HTTPS_URL_T) { + switch (connectmethod(s, fin, &oui, &ui, &pauth, &auth, + &hasleading)) { + case C_CLEANUP: + goto cleanup_fetch_url; + case C_IMPROPER: + goto improper; + case C_OK: + break; + default: + abort(); } - 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); +#endif + + hasleading = print_get(fin, hasleading, isproxy, &oui, &ui); + + if (flushcache) + print_cache(fin, isproxy); + + print_agent(fin); + hasleading = print_proxy(fin, hasleading, wauth.auth, + auth ? NULL : pauth.auth); + if (hasleading) { + hasleading = 0; + 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"); @@ -898,265 +1478,25 @@ 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: + switch (negotiate_connection(fin, url, penv, &pi, + &mtime, &wauth, &pauth, &rval, &ischunked, &auth)) { + case C_OK: 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, - proxyauth, wwwauth); - } 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: - { - 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); - } + case C_CLEANUP: 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. */ - - /* - * Only trust filenames with special meaning if they came from + + /* + * Only trust filenames with special meaning if they came from * the command line */ - if (outfile == savefile) { if (strcmp(savefile, "-") == 0) { fout = stdout; @@ -1171,18 +1511,19 @@ fetch_url(const char *url, const char *p } } if (fout == NULL) { - if ((rangeend != -1 && rangeend <= restart_point) || - (rangestart == -1 && filesize != -1 && filesize <= restart_point)) { + if ((pi.rangeend != -1 && pi.rangeend <= restart_point) || + (pi.rangestart == -1 && + filesize != -1 && filesize <= restart_point)) { /* already done */ if (verbose) fprintf(ttyout, "already done\n"); rval = 0; goto cleanup_fetch_url; } - if (restart_point && rangestart != -1) { - if (entitylen != -1) - filesize = entitylen; - if (rangestart != restart_point) { + if (restart_point && pi.rangestart != -1) { + if (pi.entitylen != -1) + filesize = pi.entitylen; + if (pi.rangestart != restart_point) { warnx( "Size of `%s' differs from save file `%s'", url, savefile); @@ -1369,7 +1710,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) @@ -1386,22 +1727,13 @@ fetch_url(const char *url, const char *p close(s); if (closefunc != NULL && fout != NULL) (*closefunc)(fout); - if (res0) - freeaddrinfo(res0); 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); + freeurlinfo(&oui); + freeauthinfo(&wauth); + freeauthinfo(&pauth); FREEPTR(decodedpath); - FREEPTR(puser); - if (ppass != NULL) - memset(ppass, 0, strlen(ppass)); - FREEPTR(ppass); FREEPTR(auth); FREEPTR(location); FREEPTR(message); @@ -1453,26 +1785,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; } @@ -1482,7 +1814,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) @@ -1499,26 +1831,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, @@ -1543,7 +1875,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; @@ -1556,18 +1888,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) @@ -1581,11 +1913,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; } @@ -1595,9 +1927,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; } @@ -1682,7 +2014,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'; @@ -1693,7 +2025,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; @@ -1767,13 +2099,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); } Index: src/usr.bin/ftp/ftp.1 diff -u src/usr.bin/ftp/ftp.1:1.131.8.1 src/usr.bin/ftp/ftp.1:1.131.8.2 --- src/usr.bin/ftp/ftp.1:1.131.8.1 Tue Dec 17 21:07:59 2013 +++ src/usr.bin/ftp/ftp.1 Sat Aug 27 13:57:01 2016 @@ -1,6 +1,6 @@ -.\" $NetBSD: ftp.1,v 1.131.8.1 2013/12/17 21:07:59 bouyer Exp $ +.\" $NetBSD: ftp.1,v 1.131.8.2 2016/08/27 13:57:01 bouyer Exp $ .\" -.\" Copyright (c) 1996-2010 The NetBSD Foundation, Inc. +.\" Copyright (c) 1996-2015 The NetBSD Foundation, Inc. .\" All rights reserved. .\" .\" This code is derived from software contributed to The NetBSD Foundation @@ -57,7 +57,7 @@ .\" .\" @(#)ftp.1 8.3 (Berkeley) 10/9/94 .\" -.Dd December 22, 2012 +.Dd April 24, 2015 .Dt FTP 1 .Os .Sh NAME @@ -84,6 +84,7 @@ .Xc .Oc .Ek +.Op Fl x Ar xferbufsize .Bk -words .\" [[user@]host [port]] .Oo @@ -311,6 +312,12 @@ Forces .Nm to show all responses from the remote server, as well as report on data transfer statistics. +.It Fl x Ar xferbufsize +Set the size of the socket send and receive buffers to +.Ar xferbufsize . +Refer to +.Ic xferbuf +for more information. .El .Pp The client host with which Index: src/usr.bin/ftp/ftp.c diff -u src/usr.bin/ftp/ftp.c:1.163.2.1 src/usr.bin/ftp/ftp.c:1.163.2.2 --- src/usr.bin/ftp/ftp.c:1.163.2.1 Tue Dec 17 21:07:59 2013 +++ src/usr.bin/ftp/ftp.c Sat Aug 27 13:57:01 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: ftp.c,v 1.163.2.1 2013/12/17 21:07:59 bouyer Exp $ */ +/* $NetBSD: ftp.c,v 1.163.2.2 2016/08/27 13:57:01 bouyer Exp $ */ /*- * Copyright (c) 1996-2009 The NetBSD Foundation, Inc. @@ -92,7 +92,7 @@ #if 0 static char sccsid[] = "@(#)ftp.c 8.6 (Berkeley) 10/27/94"; #else -__RCSID("$NetBSD: ftp.c,v 1.163.2.1 2013/12/17 21:07:59 bouyer Exp $"); +__RCSID("$NetBSD: ftp.c,v 1.163.2.2 2016/08/27 13:57:01 bouyer Exp $"); #endif #endif /* not lint */ @@ -200,7 +200,17 @@ hookup(const char *host, const char *por } if (verbose && res0->ai_next) { /* if we have multiple possibilities */ - fprintf(ttyout, "Trying %s:%s ...\n", hname, sname); +#ifdef INET6 + if(res->ai_family == AF_INET6) { + fprintf(ttyout, "Trying [%s]:%s ...\n", hname, + sname); + } else { +#endif + fprintf(ttyout, "Trying %s:%s ...\n", hname, + sname); +#ifdef INET6 + } +#endif } s = socket(res->ai_family, SOCK_STREAM, res->ai_protocol); if (s < 0) { Index: src/usr.bin/ftp/ftp_var.h diff -u src/usr.bin/ftp/ftp_var.h:1.81.8.1 src/usr.bin/ftp/ftp_var.h:1.81.8.2 --- src/usr.bin/ftp/ftp_var.h:1.81.8.1 Tue Dec 17 21:07:59 2013 +++ src/usr.bin/ftp/ftp_var.h Sat Aug 27 13:57:01 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: ftp_var.h,v 1.81.8.1 2013/12/17 21:07:59 bouyer Exp $ */ +/* $NetBSD: ftp_var.h,v 1.81.8.2 2016/08/27 13:57:01 bouyer Exp $ */ /*- * Copyright (c) 1996-2009 The NetBSD Foundation, Inc. @@ -169,7 +169,7 @@ enum { /* * Global defines */ -#define FTPBUFLEN MAXPATHLEN + 200 +#define FTPBUFLEN (4 * MAXPATHLEN) #define MAX_IN_PORT_T 0xffffU #define HASHBYTES 1024 /* default mark for `hash' command */ @@ -337,11 +337,12 @@ extern struct option optiontab[]; #endif #ifdef NO_DEBUG -#define DPRINTF(...) -#define DWARN(...) +#define DPRINTF(...) (void)0 +#define DWARN(...) (void)0 #else -#define DPRINTF(...) if (ftp_debug) (void)fprintf(ttyout, __VA_ARGS__) -#define DWARN(...) if (ftp_debug) warn(__VA_ARGS__) +#define DWFTP(a) do a; while (/*CONSTCOND*/0) +#define DPRINTF(...) DWFTP(if (ftp_debug) (void)fprintf(ttyout, __VA_ARGS__)) +#define DWARN(...) DWFTP(if (ftp_debug) warn(__VA_ARGS__)) #endif #define STRorNULL(s) ((s) ? (s) : "<null>") Index: src/usr.bin/ftp/main.c diff -u src/usr.bin/ftp/main.c:1.120.2.1 src/usr.bin/ftp/main.c:1.120.2.2 --- src/usr.bin/ftp/main.c:1.120.2.1 Tue Dec 17 21:07:59 2013 +++ src/usr.bin/ftp/main.c Sat Aug 27 13:57:01 2016 @@ -1,7 +1,7 @@ -/* $NetBSD: main.c,v 1.120.2.1 2013/12/17 21:07:59 bouyer Exp $ */ +/* $NetBSD: main.c,v 1.120.2.2 2016/08/27 13:57:01 bouyer Exp $ */ /*- - * Copyright (c) 1996-2009 The NetBSD Foundation, Inc. + * Copyright (c) 1996-2015 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -91,14 +91,14 @@ #ifndef lint __COPYRIGHT("@(#) Copyright (c) 1985, 1989, 1993, 1994\ The Regents of the University of California. All rights reserved.\ - Copyright 1996-2008 The NetBSD Foundation, Inc. All rights reserved"); + Copyright 1996-2015 The NetBSD Foundation, Inc. All rights reserved"); #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 10/9/94"; #else -__RCSID("$NetBSD: main.c,v 1.120.2.1 2013/12/17 21:07:59 bouyer Exp $"); +__RCSID("$NetBSD: main.c,v 1.120.2.2 2016/08/27 13:57:01 bouyer Exp $"); #endif #endif /* not lint */ @@ -266,7 +266,7 @@ main(int volatile argc, char **volatile } } - while ((ch = getopt(argc, argv, "46AadefginN:o:pP:q:r:Rs:tT:u:vV")) != -1) { + while ((ch = getopt(argc, argv, "46AadefginN:o:pP:q:r:Rs:tT:u:vVx:")) != -1) { switch (ch) { case '4': family = AF_INET; @@ -408,6 +408,13 @@ main(int volatile argc, char **volatile progress = verbose = 0; break; + case 'x': + sndbuf_size = strsuftoi(optarg); + if (sndbuf_size < 1) + errx(1, "Bad xferbuf value: %s", optarg); + rcvbuf_size = sndbuf_size; + break; + default: usage(); } @@ -1045,7 +1052,7 @@ usage(void) (void)fprintf(stderr, "usage: %s [-46AadefginpRtVv] [-N netrc] [-o outfile] [-P port] [-q quittime]\n" -" [-r retry] [-s srcaddr] [-T dir,max[,inc]]\n" +" [-r retry] [-s srcaddr] [-T dir,max[,inc]] [-x xferbufsize]\n" " [[user@]host [port]] [host:path[/]] [file:///file]\n" " [ftp://[user[:pass]@]host[:port]/path[/]]\n" " [http://[user[:pass]@]host[:port]/path] [...]\n" Index: src/usr.bin/ftp/ssl.c diff -u src/usr.bin/ftp/ssl.c:1.2.10.2 src/usr.bin/ftp/ssl.c:1.2.10.3 --- src/usr.bin/ftp/ssl.c:1.2.10.2 Tue Dec 17 21:07:59 2013 +++ src/usr.bin/ftp/ssl.c Sat Aug 27 13:57:01 2016 @@ -1,8 +1,9 @@ -/* $NetBSD: ssl.c,v 1.2.10.2 2013/12/17 21:07:59 bouyer Exp $ */ +/* $NetBSD: ssl.c,v 1.2.10.3 2016/08/27 13:57:01 bouyer Exp $ */ /*- - * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav + * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav * Copyright (c) 2008, 2010 Joerg Sonnenberger <jo...@netbsd.org> + * Copyright (c) 2015 Thomas Klausner <w...@netbsd.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +34,7 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: ssl.c,v 1.2.10.2 2013/12/17 21:07:59 bouyer Exp $"); +__RCSID("$NetBSD: ssl.c,v 1.2.10.3 2016/08/27 13:57:01 bouyer Exp $"); #endif #include <time.h> @@ -545,7 +546,7 @@ fetch_getline(struct fetch_connect *conn } void * -fetch_start_ssl(int sock) +fetch_start_ssl(int sock, const char *servername) { SSL *ssl; SSL_CTX *ctx; @@ -569,6 +570,11 @@ fetch_start_ssl(int sock) return NULL; } SSL_set_fd(ssl, sock); + if (!SSL_set_tlsext_host_name(ssl, __UNCONST(servername))) { + fprintf(ttyout, "SSL hostname setting failed\n"); + SSL_CTX_free(ctx); + return NULL; + } while ((ret = SSL_connect(ssl)) == -1) { ssl_err = SSL_get_error(ssl, ret); if (ssl_err != SSL_ERROR_WANT_READ && Index: src/usr.bin/ftp/ssl.h diff -u src/usr.bin/ftp/ssl.h:1.1.10.2 src/usr.bin/ftp/ssl.h:1.1.10.3 --- src/usr.bin/ftp/ssl.h:1.1.10.2 Tue Dec 17 21:07:59 2013 +++ src/usr.bin/ftp/ssl.h Sat Aug 27 13:57:01 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: ssl.h,v 1.1.10.2 2013/12/17 21:07:59 bouyer Exp $ */ +/* $NetBSD: ssl.h,v 1.1.10.3 2016/08/27 13:57:01 bouyer Exp $ */ /*- * Copyright (c) 2012 The NetBSD Foundation, Inc. @@ -30,7 +30,8 @@ #define FETCH struct fetch_connect struct fetch_connect; -int fetch_printf(struct fetch_connect *, const char *fmt, ...); +int fetch_printf(struct fetch_connect *, const char *fmt, ...) + __printflike(2, 3); int fetch_fileno(struct fetch_connect *); int fetch_error(struct fetch_connect *); int fetch_flush(struct fetch_connect *); @@ -41,7 +42,7 @@ ssize_t fetch_read(void *, size_t, size_ char *fetch_getln(char *, int, struct fetch_connect *); int fetch_getline(struct fetch_connect *, char *, size_t, const char **); void fetch_set_ssl(struct fetch_connect *, void *); -void *fetch_start_ssl(int); +void *fetch_start_ssl(int, const char *); #else /* !WITH_SSL */ Index: src/usr.bin/ftp/version.h diff -u src/usr.bin/ftp/version.h:1.82.8.2 src/usr.bin/ftp/version.h:1.82.8.3 --- src/usr.bin/ftp/version.h:1.82.8.2 Mon Nov 3 12:57:42 2014 +++ src/usr.bin/ftp/version.h Sat Aug 27 13:57:01 2016 @@ -1,7 +1,7 @@ -/* $NetBSD: version.h,v 1.82.8.2 2014/11/03 12:57:42 msaitoh Exp $ */ +/* $NetBSD: version.h,v 1.82.8.3 2016/08/27 13:57:01 bouyer Exp $ */ /*- - * Copyright (c) 1999-2009 The NetBSD Foundation, Inc. + * Copyright (c) 1999-2015 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -34,5 +34,5 @@ #endif #ifndef FTP_VERSION -#define FTP_VERSION "20141026" +#define FTP_VERSION "20150912" #endif