Index: head/lib/libfetch/common.c =================================================================== --- head/lib/libfetch/common.c (revision 367999) +++ head/lib/libfetch/common.c (revision 368000) @@ -1,1805 +1,1803 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1998-2016 Dag-Erling Smørgrav * Copyright (c) 2013 Michael Gmelin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_SSL #include #endif #include "fetch.h" #include "common.h" /*** Local data **************************************************************/ /* * Error messages for resolver errors */ static struct fetcherr netdb_errlist[] = { #ifdef EAI_NODATA { EAI_NODATA, FETCH_RESOLV, "Host not found" }, #endif { EAI_AGAIN, FETCH_TEMP, "Transient resolver failure" }, { EAI_FAIL, FETCH_RESOLV, "Non-recoverable resolver failure" }, { EAI_NONAME, FETCH_RESOLV, "No address record" }, { -1, FETCH_UNKNOWN, "Unknown resolver error" } }; /* * SOCKS5 error enumerations */ enum SOCKS5_ERR { /* Protocol errors */ SOCKS5_ERR_SELECTION, SOCKS5_ERR_READ_METHOD, SOCKS5_ERR_VER5_ONLY, SOCKS5_ERR_NOMETHODS, SOCKS5_ERR_NOTIMPLEMENTED, SOCKS5_ERR_HOSTNAME_SIZE, SOCKS5_ERR_REQUEST, SOCKS5_ERR_REPLY, SOCKS5_ERR_NON_VER5_RESP, SOCKS5_ERR_GENERAL, SOCKS5_ERR_NOT_ALLOWED, SOCKS5_ERR_NET_UNREACHABLE, SOCKS5_ERR_HOST_UNREACHABLE, SOCKS5_ERR_CONN_REFUSED, SOCKS5_ERR_TTL_EXPIRED, SOCKS5_ERR_COM_UNSUPPORTED, SOCKS5_ERR_ADDR_UNSUPPORTED, SOCKS5_ERR_UNSPECIFIED, /* Configuration errors */ SOCKS5_ERR_BAD_HOST, SOCKS5_ERR_BAD_PROXY_FORMAT, SOCKS5_ERR_BAD_PORT }; /* * Error messages for SOCKS5 errors */ static struct fetcherr socks5_errlist[] = { /* SOCKS5 protocol errors */ { SOCKS5_ERR_SELECTION, FETCH_ABORT, "SOCKS5: Failed to send selection method" }, { SOCKS5_ERR_READ_METHOD, FETCH_ABORT, "SOCKS5: Failed to read method" }, { SOCKS5_ERR_VER5_ONLY, FETCH_PROTO, "SOCKS5: Only version 5 is implemented" }, { SOCKS5_ERR_NOMETHODS, FETCH_PROTO, "SOCKS5: No acceptable methods" }, { SOCKS5_ERR_NOTIMPLEMENTED, FETCH_PROTO, "SOCKS5: Method currently not implemented" }, { SOCKS5_ERR_HOSTNAME_SIZE, FETCH_PROTO, "SOCKS5: Hostname size is above 256 bytes" }, { SOCKS5_ERR_REQUEST, FETCH_PROTO, "SOCKS5: Failed to request" }, { SOCKS5_ERR_REPLY, FETCH_PROTO, "SOCKS5: Failed to receive reply" }, { SOCKS5_ERR_NON_VER5_RESP, FETCH_PROTO, "SOCKS5: Server responded with a non-version 5 response" }, { SOCKS5_ERR_GENERAL, FETCH_ABORT, "SOCKS5: General server failure" }, { SOCKS5_ERR_NOT_ALLOWED, FETCH_AUTH, "SOCKS5: Connection not allowed by ruleset" }, { SOCKS5_ERR_NET_UNREACHABLE, FETCH_NETWORK, "SOCKS5: Network unreachable" }, { SOCKS5_ERR_HOST_UNREACHABLE, FETCH_ABORT, "SOCKS5: Host unreachable" }, { SOCKS5_ERR_CONN_REFUSED, FETCH_ABORT, "SOCKS5: Connection refused" }, { SOCKS5_ERR_TTL_EXPIRED, FETCH_TIMEOUT, "SOCKS5: TTL expired" }, { SOCKS5_ERR_COM_UNSUPPORTED, FETCH_PROTO, "SOCKS5: Command not supported" }, { SOCKS5_ERR_ADDR_UNSUPPORTED, FETCH_ABORT, "SOCKS5: Address type not supported" }, { SOCKS5_ERR_UNSPECIFIED, FETCH_UNKNOWN, "SOCKS5: Unspecified error" }, /* Configuration error */ { SOCKS5_ERR_BAD_HOST, FETCH_ABORT, "SOCKS5: Bad proxy host" }, { SOCKS5_ERR_BAD_PROXY_FORMAT, FETCH_ABORT, "SOCKS5: Bad proxy format" }, { SOCKS5_ERR_BAD_PORT, FETCH_ABORT, "SOCKS5: Bad port" } }; /* End-of-Line */ static const char ENDL[2] = "\r\n"; /*** Error-reporting functions ***********************************************/ /* * Map error code to string */ static struct fetcherr * fetch_finderr(struct fetcherr *p, int e) { while (p->num != -1 && p->num != e) p++; return (p); } /* * Set error code */ void fetch_seterr(struct fetcherr *p, int e) { p = fetch_finderr(p, e); fetchLastErrCode = p->cat; snprintf(fetchLastErrString, MAXERRSTRING, "%s", p->string); } /* * Set error code according to errno */ void fetch_syserr(void) { switch (errno) { case 0: fetchLastErrCode = FETCH_OK; break; case EPERM: case EACCES: case EROFS: case EAUTH: case ENEEDAUTH: fetchLastErrCode = FETCH_AUTH; break; case ENOENT: case EISDIR: /* XXX */ fetchLastErrCode = FETCH_UNAVAIL; break; case ENOMEM: fetchLastErrCode = FETCH_MEMORY; break; case EBUSY: case EAGAIN: fetchLastErrCode = FETCH_TEMP; break; case EEXIST: fetchLastErrCode = FETCH_EXISTS; break; case ENOSPC: fetchLastErrCode = FETCH_FULL; break; case EADDRINUSE: case EADDRNOTAVAIL: case ENETDOWN: case ENETUNREACH: case ENETRESET: case EHOSTUNREACH: fetchLastErrCode = FETCH_NETWORK; break; case ECONNABORTED: case ECONNRESET: fetchLastErrCode = FETCH_ABORT; break; case ETIMEDOUT: fetchLastErrCode = FETCH_TIMEOUT; break; case ECONNREFUSED: case EHOSTDOWN: fetchLastErrCode = FETCH_DOWN; break; default: fetchLastErrCode = FETCH_UNKNOWN; } snprintf(fetchLastErrString, MAXERRSTRING, "%s", strerror(errno)); } /* * Emit status message */ void fetch_info(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); } /*** Network-related utility functions ***************************************/ /* * Return the default port for a scheme */ int fetch_default_port(const char *scheme) { struct servent *se; if ((se = getservbyname(scheme, "tcp")) != NULL) return (ntohs(se->s_port)); if (strcmp(scheme, SCHEME_FTP) == 0) return (FTP_DEFAULT_PORT); if (strcmp(scheme, SCHEME_HTTP) == 0) return (HTTP_DEFAULT_PORT); return (0); } /* * Return the default proxy port for a scheme */ int fetch_default_proxy_port(const char *scheme) { if (strcmp(scheme, SCHEME_FTP) == 0) return (FTP_DEFAULT_PROXY_PORT); if (strcmp(scheme, SCHEME_HTTP) == 0) return (HTTP_DEFAULT_PROXY_PORT); return (0); } /* * Create a connection for an existing descriptor. */ conn_t * fetch_reopen(int sd) { conn_t *conn; int opt = 1; /* allocate and fill connection structure */ if ((conn = calloc(1, sizeof(*conn))) == NULL) return (NULL); fcntl(sd, F_SETFD, FD_CLOEXEC); setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof opt); conn->sd = sd; ++conn->ref; return (conn); } /* * Bump a connection's reference count. */ conn_t * fetch_ref(conn_t *conn) { ++conn->ref; return (conn); } /* * Resolve an address */ struct addrinfo * fetch_resolve(const char *addr, int port, int af) { char hbuf[256], sbuf[8]; struct addrinfo hints, *res; const char *hb, *he, *sep; const char *host, *service; int err, len; /* first, check for a bracketed IPv6 address */ if (*addr == '[') { hb = addr + 1; if ((sep = strchr(hb, ']')) == NULL) { errno = EINVAL; goto syserr; } he = sep++; } else { hb = addr; sep = strchrnul(hb, ':'); he = sep; } /* see if we need to copy the host name */ if (*he != '\0') { len = snprintf(hbuf, sizeof(hbuf), "%.*s", (int)(he - hb), hb); if (len < 0) goto syserr; if (len >= (int)sizeof(hbuf)) { errno = ENAMETOOLONG; goto syserr; } host = hbuf; } else { host = hb; } /* was it followed by a service name? */ if (*sep == '\0' && port != 0) { if (port < 1 || port > 65535) { errno = EINVAL; goto syserr; } if (snprintf(sbuf, sizeof(sbuf), "%d", port) < 0) goto syserr; service = sbuf; } else if (*sep != '\0') { service = sep + 1; } else { service = NULL; } /* resolve */ memset(&hints, 0, sizeof(hints)); hints.ai_family = af; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_ADDRCONFIG; if ((err = getaddrinfo(host, service, &hints, &res)) != 0) { netdb_seterr(err); return (NULL); } return (res); syserr: fetch_syserr(); return (NULL); } /* * Bind a socket to a specific local address */ int fetch_bind(int sd, int af, const char *addr) { struct addrinfo *cliai, *ai; int err; if ((cliai = fetch_resolve(addr, 0, af)) == NULL) return (-1); for (ai = cliai; ai != NULL; ai = ai->ai_next) if ((err = bind(sd, ai->ai_addr, ai->ai_addrlen)) == 0) break; if (err != 0) fetch_syserr(); freeaddrinfo(cliai); return (err == 0 ? 0 : -1); } /* * SOCKS5 connection initiation, based on RFC 1928 * Default DNS resolution over SOCKS5 */ int fetch_socks5_init(conn_t *conn, const char *host, int port, int verbose) { /* * Size is based on largest packet prefix (4 bytes) + * Largest FQDN (256) + one byte size (1) + * Port (2) */ unsigned char buf[BUFF_SIZE]; unsigned char *ptr; int ret = 1; if (verbose) fetch_info("Initializing SOCKS5 connection: %s:%d", host, port); /* Connection initialization */ ptr = buf; *ptr++ = SOCKS_VERSION_5; *ptr++ = SOCKS_CONNECTION; *ptr++ = SOCKS_RSV; if (fetch_write(conn, buf, 3) != 3) { ret = SOCKS5_ERR_SELECTION; goto fail; } /* Verify response from SOCKS5 server */ if (fetch_read(conn, buf, 2) != 2) { ret = SOCKS5_ERR_READ_METHOD; goto fail; } ptr = buf; if (ptr[0] != SOCKS_VERSION_5) { ret = SOCKS5_ERR_VER5_ONLY; goto fail; } if (ptr[1] == SOCKS_NOMETHODS) { ret = SOCKS5_ERR_NOMETHODS; goto fail; } else if (ptr[1] != SOCKS5_NOTIMPLEMENTED) { ret = SOCKS5_ERR_NOTIMPLEMENTED; goto fail; } /* Send Request */ *ptr++ = SOCKS_VERSION_5; *ptr++ = SOCKS_CONNECTION; *ptr++ = SOCKS_RSV; /* Encode all targets as a hostname to avoid DNS leaks */ *ptr++ = SOCKS_ATYP_DOMAINNAME; if (strlen(host) > FQDN_SIZE) { ret = SOCKS5_ERR_HOSTNAME_SIZE; goto fail; } *ptr++ = strlen(host); strncpy(ptr, host, strlen(host)); ptr = ptr + strlen(host); port = htons(port); *ptr++ = port & 0x00ff; *ptr++ = (port & 0xff00) >> 8; if (fetch_write(conn, buf, ptr - buf) != ptr - buf) { ret = SOCKS5_ERR_REQUEST; goto fail; } /* BND.ADDR is variable length, read the largest on non-blocking socket */ if (!fetch_read(conn, buf, BUFF_SIZE)) { ret = SOCKS5_ERR_REPLY; goto fail; } ptr = buf; if (*ptr++ != SOCKS_VERSION_5) { ret = SOCKS5_ERR_NON_VER5_RESP; goto fail; } switch(*ptr++) { case SOCKS_SUCCESS: break; case SOCKS_GENERAL_FAILURE: ret = SOCKS5_ERR_GENERAL; goto fail; case SOCKS_CONNECTION_NOT_ALLOWED: ret = SOCKS5_ERR_NOT_ALLOWED; goto fail; case SOCKS_NETWORK_UNREACHABLE: ret = SOCKS5_ERR_NET_UNREACHABLE; goto fail; case SOCKS_HOST_UNREACHABLE: ret = SOCKS5_ERR_HOST_UNREACHABLE; goto fail; case SOCKS_CONNECTION_REFUSED: ret = SOCKS5_ERR_CONN_REFUSED; goto fail; case SOCKS_TTL_EXPIRED: ret = SOCKS5_ERR_TTL_EXPIRED; goto fail; case SOCKS_COMMAND_NOT_SUPPORTED: ret = SOCKS5_ERR_COM_UNSUPPORTED; goto fail; case SOCKS_ADDRESS_NOT_SUPPORTED: ret = SOCKS5_ERR_ADDR_UNSUPPORTED; goto fail; default: ret = SOCKS5_ERR_UNSPECIFIED; goto fail; } return (ret); fail: socks5_seterr(ret); return (0); } /* * Perform SOCKS5 initialization */ int fetch_socks5_getenv(char **host, int *port) { char *socks5env, *endptr, *ext; const char *portDelim; size_t slen; portDelim = ":"; if ((socks5env = getenv("SOCKS5_PROXY")) == NULL || *socks5env == '\0') { *host = NULL; *port = -1; return (-1); } /* * IPv6 addresses begin and end in brackets. Set the port delimiter * accordingly and search for it so we can do appropriate validation. */ if (socks5env[0] == '[') portDelim = "]:"; slen = strlen(socks5env); ext = strstr(socks5env, portDelim); if (socks5env[0] == '[') { if (socks5env[slen - 1] == ']') { *host = strndup(socks5env, slen); } else if (ext != NULL) { *host = strndup(socks5env, ext - socks5env + 1); } else { socks5_seterr(SOCKS5_ERR_BAD_PROXY_FORMAT); return (0); } } else { *host = strndup(socks5env, ext - socks5env); } if (*host == NULL) { fprintf(stderr, "Failure to allocate memory, exiting.\n"); return (-1); } if (ext == NULL) { *port = 1080; /* Default port as defined in RFC1928 */ } else { ext += strlen(portDelim); errno = 0; *port = strtoimax(ext, (char **)&endptr, 10); if (*endptr != '\0' || errno != 0 || *port < 0 || *port > 65535) { free(*host); *host = NULL; socks5_seterr(SOCKS5_ERR_BAD_PORT); return (0); } } return (2); } /* * Establish a TCP connection to the specified port on the specified host. */ conn_t * fetch_connect(const char *host, int port, int af, int verbose) { struct addrinfo *cais = NULL, *sais = NULL, *cai, *sai; const char *bindaddr; conn_t *conn = NULL; int err = 0, sd = -1; char *sockshost; int socksport; DEBUGF("---> %s:%d\n", host, port); /* * Check if SOCKS5_PROXY env variable is set. fetch_socks5_getenv * will either set sockshost = NULL or allocate memory in all cases. */ sockshost = NULL; if (!fetch_socks5_getenv(&sockshost, &socksport)) goto fail; /* Not using SOCKS5 proxy */ if (sockshost == NULL) { /* resolve server address */ if (verbose) fetch_info("resolving server address: %s:%d", host, port); if ((sais = fetch_resolve(host, port, af)) == NULL) goto fail; /* resolve client address */ bindaddr = getenv("FETCH_BIND_ADDRESS"); if (bindaddr != NULL && *bindaddr != '\0') { if (verbose) fetch_info("resolving client address: %s", bindaddr); if ((cais = fetch_resolve(bindaddr, 0, af)) == NULL) goto fail; } } else { /* resolve socks5 proxy address */ if (verbose) fetch_info("resolving SOCKS5 server address: %s:%d", sockshost, socksport); if ((sais = fetch_resolve(sockshost, socksport, af)) == NULL) { socks5_seterr(SOCKS5_ERR_BAD_HOST); goto fail; } } /* try each server address in turn */ for (err = 0, sai = sais; sai != NULL; sai = sai->ai_next) { /* open socket */ if ((sd = socket(sai->ai_family, SOCK_STREAM, 0)) < 0) goto syserr; /* attempt to bind to client address */ for (err = 0, cai = cais; cai != NULL; cai = cai->ai_next) { if (cai->ai_family != sai->ai_family) continue; if ((err = bind(sd, cai->ai_addr, cai->ai_addrlen)) == 0) break; } if (err != 0) { if (verbose) fetch_info("failed to bind to %s", bindaddr); goto syserr; } /* attempt to connect to server address */ if ((err = connect(sd, sai->ai_addr, sai->ai_addrlen)) == 0) break; /* clean up before next attempt */ close(sd); sd = -1; } if (err != 0) { if (verbose && sockshost == NULL) { fetch_info("failed to connect to %s:%d", host, port); goto syserr; } else if (sockshost != NULL) { if (verbose) fetch_info( "failed to connect to SOCKS5 server %s:%d", sockshost, socksport); socks5_seterr(SOCKS5_ERR_CONN_REFUSED); goto fail; } goto syserr; } if ((conn = fetch_reopen(sd)) == NULL) goto syserr; if (sockshost) if (!fetch_socks5_init(conn, host, port, verbose)) goto fail; free(sockshost); if (cais != NULL) freeaddrinfo(cais); if (sais != NULL) freeaddrinfo(sais); return (conn); syserr: fetch_syserr(); fail: free(sockshost); /* Fully close if it was opened; otherwise just don't leak the fd. */ if (conn != NULL) fetch_close(conn); else if (sd >= 0) close(sd); if (cais != NULL) freeaddrinfo(cais); if (sais != NULL) freeaddrinfo(sais); return (NULL); } #ifdef WITH_SSL /* * Convert characters A-Z to lowercase (intentionally avoid any locale * specific conversions). */ static char fetch_ssl_tolower(char in) { if (in >= 'A' && in <= 'Z') return (in + 32); else return (in); } /* * isalpha implementation that intentionally avoids any locale specific * conversions. */ static int fetch_ssl_isalpha(char in) { return ((in >= 'A' && in <= 'Z') || (in >= 'a' && in <= 'z')); } /* * Check if passed hostnames a and b are equal. */ static int fetch_ssl_hname_equal(const char *a, size_t alen, const char *b, size_t blen) { size_t i; if (alen != blen) return (0); for (i = 0; i < alen; ++i) { if (fetch_ssl_tolower(a[i]) != fetch_ssl_tolower(b[i])) return (0); } return (1); } /* * Check if domain label is traditional, meaning that only A-Z, a-z, 0-9 * and '-' (hyphen) are allowed. Hyphens have to be surrounded by alpha- * numeric characters. Double hyphens (like they're found in IDN a-labels * 'xn--') are not allowed. Empty labels are invalid. */ static int fetch_ssl_is_trad_domain_label(const char *l, size_t len, int wcok) { size_t i; if (!len || l[0] == '-' || l[len-1] == '-') return (0); for (i = 0; i < len; ++i) { if (!isdigit(l[i]) && !fetch_ssl_isalpha(l[i]) && !(l[i] == '*' && wcok) && !(l[i] == '-' && l[i - 1] != '-')) return (0); } return (1); } /* * Check if host name consists only of numbers. This might indicate an IP * address, which is not a good idea for CN wildcard comparison. */ static int fetch_ssl_hname_is_only_numbers(const char *hostname, size_t len) { size_t i; for (i = 0; i < len; ++i) { if (!((hostname[i] >= '0' && hostname[i] <= '9') || hostname[i] == '.')) return (0); } return (1); } /* * Check if the host name h passed matches the pattern passed in m which * is usually part of subjectAltName or CN of a certificate presented to * the client. This includes wildcard matching. The algorithm is based on * RFC6125, sections 6.4.3 and 7.2, which clarifies RFC2818 and RFC3280. */ static int fetch_ssl_hname_match(const char *h, size_t hlen, const char *m, size_t mlen) { int delta, hdotidx, mdot1idx, wcidx; const char *hdot, *mdot1, *mdot2; const char *wc; /* wildcard */ if (!(h && *h && m && *m)) return (0); if ((wc = strnstr(m, "*", mlen)) == NULL) return (fetch_ssl_hname_equal(h, hlen, m, mlen)); wcidx = wc - m; /* hostname should not be just dots and numbers */ if (fetch_ssl_hname_is_only_numbers(h, hlen)) return (0); /* only one wildcard allowed in pattern */ if (strnstr(wc + 1, "*", mlen - wcidx - 1) != NULL) return (0); /* * there must be at least two more domain labels and * wildcard has to be in the leftmost label (RFC6125) */ mdot1 = strnstr(m, ".", mlen); if (mdot1 == NULL || mdot1 < wc || (mlen - (mdot1 - m)) < 4) return (0); mdot1idx = mdot1 - m; mdot2 = strnstr(mdot1 + 1, ".", mlen - mdot1idx - 1); if (mdot2 == NULL || (mlen - (mdot2 - m)) < 2) return (0); /* hostname must contain a dot and not be the 1st char */ hdot = strnstr(h, ".", hlen); if (hdot == NULL || hdot == h) return (0); hdotidx = hdot - h; /* * host part of hostname must be at least as long as * pattern it's supposed to match */ if (hdotidx < mdot1idx) return (0); /* * don't allow wildcards in non-traditional domain names * (IDN, A-label, U-label...) */ if (!fetch_ssl_is_trad_domain_label(h, hdotidx, 0) || !fetch_ssl_is_trad_domain_label(m, mdot1idx, 1)) return (0); /* match domain part (part after first dot) */ if (!fetch_ssl_hname_equal(hdot, hlen - hdotidx, mdot1, mlen - mdot1idx)) return (0); /* match part left of wildcard */ if (!fetch_ssl_hname_equal(h, wcidx, m, wcidx)) return (0); /* match part right of wildcard */ delta = mdot1idx - wcidx - 1; if (!fetch_ssl_hname_equal(hdot - delta, delta, mdot1 - delta, delta)) return (0); /* all tests succeeded, it's a match */ return (1); } /* * Get numeric host address info - returns NULL if host was not an IP * address. The caller is responsible for deallocation using * freeaddrinfo(3). */ static struct addrinfo * fetch_ssl_get_numeric_addrinfo(const char *hostname, size_t len) { struct addrinfo hints, *res; char *host; host = (char *)malloc(len + 1); memcpy(host, hostname, len); host[len] = '\0'; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_flags = AI_NUMERICHOST; /* port is not relevant for this purpose */ if (getaddrinfo(host, "443", &hints, &res) != 0) res = NULL; free(host); return res; } /* * Compare ip address in addrinfo with address passes. */ static int fetch_ssl_ipaddr_match_bin(const struct addrinfo *lhost, const char *rhost, size_t rhostlen) { const void *left; if (lhost->ai_family == AF_INET && rhostlen == 4) { left = (void *)&((struct sockaddr_in*)(void *) lhost->ai_addr)->sin_addr.s_addr; #ifdef INET6 } else if (lhost->ai_family == AF_INET6 && rhostlen == 16) { left = (void *)&((struct sockaddr_in6 *)(void *) lhost->ai_addr)->sin6_addr; #endif } else return (0); return (!memcmp(left, (const void *)rhost, rhostlen) ? 1 : 0); } /* * Compare ip address in addrinfo with host passed. If host is not an IP * address, comparison will fail. */ static int fetch_ssl_ipaddr_match(const struct addrinfo *laddr, const char *r, size_t rlen) { struct addrinfo *raddr; int ret; char *rip; ret = 0; if ((raddr = fetch_ssl_get_numeric_addrinfo(r, rlen)) == NULL) return 0; /* not a numeric host */ if (laddr->ai_family == raddr->ai_family) { if (laddr->ai_family == AF_INET) { rip = (char *)&((struct sockaddr_in *)(void *) raddr->ai_addr)->sin_addr.s_addr; ret = fetch_ssl_ipaddr_match_bin(laddr, rip, 4); #ifdef INET6 } else if (laddr->ai_family == AF_INET6) { rip = (char *)&((struct sockaddr_in6 *)(void *) raddr->ai_addr)->sin6_addr; ret = fetch_ssl_ipaddr_match_bin(laddr, rip, 16); #endif } } freeaddrinfo(raddr); return (ret); } /* * Verify server certificate by subjectAltName. */ static int fetch_ssl_verify_altname(STACK_OF(GENERAL_NAME) *altnames, const char *host, struct addrinfo *ip) { const GENERAL_NAME *name; size_t nslen; int i; const char *ns; for (i = 0; i < sk_GENERAL_NAME_num(altnames); ++i) { #if OPENSSL_VERSION_NUMBER < 0x10000000L /* * This is a workaround, since the following line causes * alignment issues in clang: * name = sk_GENERAL_NAME_value(altnames, i); * OpenSSL explicitly warns not to use those macros * directly, but there isn't much choice (and there * shouldn't be any ill side effects) */ name = (GENERAL_NAME *)SKM_sk_value(void, altnames, i); #else name = sk_GENERAL_NAME_value(altnames, i); #endif #if OPENSSL_VERSION_NUMBER < 0x10100000L ns = (const char *)ASN1_STRING_data(name->d.ia5); #else ns = (const char *)ASN1_STRING_get0_data(name->d.ia5); #endif nslen = (size_t)ASN1_STRING_length(name->d.ia5); if (name->type == GEN_DNS && ip == NULL && fetch_ssl_hname_match(host, strlen(host), ns, nslen)) return (1); else if (name->type == GEN_IPADD && ip != NULL && fetch_ssl_ipaddr_match_bin(ip, ns, nslen)) return (1); } return (0); } /* * Verify server certificate by CN. */ static int fetch_ssl_verify_cn(X509_NAME *subject, const char *host, struct addrinfo *ip) { ASN1_STRING *namedata; X509_NAME_ENTRY *nameentry; int cnlen, lastpos, loc, ret; unsigned char *cn; ret = 0; lastpos = -1; loc = -1; cn = NULL; /* get most specific CN (last entry in list) and compare */ while ((lastpos = X509_NAME_get_index_by_NID(subject, NID_commonName, lastpos)) != -1) loc = lastpos; if (loc > -1) { nameentry = X509_NAME_get_entry(subject, loc); namedata = X509_NAME_ENTRY_get_data(nameentry); cnlen = ASN1_STRING_to_UTF8(&cn, namedata); if (ip == NULL && fetch_ssl_hname_match(host, strlen(host), cn, cnlen)) ret = 1; else if (ip != NULL && fetch_ssl_ipaddr_match(ip, cn, cnlen)) ret = 1; OPENSSL_free(cn); } return (ret); } /* * Verify that server certificate subjectAltName/CN matches * hostname. First check, if there are alternative subject names. If yes, * those have to match. Only if those don't exist it falls back to * checking the subject's CN. */ static int fetch_ssl_verify_hname(X509 *cert, const char *host) { struct addrinfo *ip; STACK_OF(GENERAL_NAME) *altnames; X509_NAME *subject; int ret; ret = 0; ip = fetch_ssl_get_numeric_addrinfo(host, strlen(host)); altnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (altnames != NULL) { ret = fetch_ssl_verify_altname(altnames, host, ip); } else { subject = X509_get_subject_name(cert); if (subject != NULL) ret = fetch_ssl_verify_cn(subject, host, ip); } if (ip != NULL) freeaddrinfo(ip); if (altnames != NULL) GENERAL_NAMES_free(altnames); return (ret); } /* * Configure transport security layer based on environment. */ static void fetch_ssl_setup_transport_layer(SSL_CTX *ctx, int verbose) { long ssl_ctx_options; - ssl_ctx_options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_TICKET; - if (getenv("SSL_ALLOW_SSL3") == NULL) - ssl_ctx_options |= SSL_OP_NO_SSLv3; + ssl_ctx_options = SSL_OP_ALL | SSL_OP_NO_SSLv3 | SSL_OP_NO_TICKET; if (getenv("SSL_NO_TLS1") != NULL) ssl_ctx_options |= SSL_OP_NO_TLSv1; if (getenv("SSL_NO_TLS1_1") != NULL) ssl_ctx_options |= SSL_OP_NO_TLSv1_1; if (getenv("SSL_NO_TLS1_2") != NULL) ssl_ctx_options |= SSL_OP_NO_TLSv1_2; if (verbose) fetch_info("SSL options: %lx", ssl_ctx_options); SSL_CTX_set_options(ctx, ssl_ctx_options); } /* * Configure peer verification based on environment. */ #define LOCAL_CERT_FILE _PATH_LOCALBASE "/etc/ssl/cert.pem" #define BASE_CERT_FILE "/etc/ssl/cert.pem" static int fetch_ssl_setup_peer_verification(SSL_CTX *ctx, int verbose) { X509_LOOKUP *crl_lookup; X509_STORE *crl_store; const char *ca_cert_file, *ca_cert_path, *crl_file; if (getenv("SSL_NO_VERIFY_PEER") == NULL) { ca_cert_file = getenv("SSL_CA_CERT_FILE"); if (ca_cert_file == NULL && access(LOCAL_CERT_FILE, R_OK) == 0) ca_cert_file = LOCAL_CERT_FILE; if (ca_cert_file == NULL && access(BASE_CERT_FILE, R_OK) == 0) ca_cert_file = BASE_CERT_FILE; ca_cert_path = getenv("SSL_CA_CERT_PATH"); if (verbose) { fetch_info("Peer verification enabled"); if (ca_cert_file != NULL) fetch_info("Using CA cert file: %s", ca_cert_file); if (ca_cert_path != NULL) fetch_info("Using CA cert path: %s", ca_cert_path); if (ca_cert_file == NULL && ca_cert_path == NULL) fetch_info("Using OpenSSL default " "CA cert file and path"); } SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, fetch_ssl_cb_verify_crt); if (ca_cert_file != NULL || ca_cert_path != NULL) SSL_CTX_load_verify_locations(ctx, ca_cert_file, ca_cert_path); else SSL_CTX_set_default_verify_paths(ctx); if ((crl_file = getenv("SSL_CRL_FILE")) != NULL) { if (verbose) fetch_info("Using CRL file: %s", crl_file); crl_store = SSL_CTX_get_cert_store(ctx); crl_lookup = X509_STORE_add_lookup(crl_store, X509_LOOKUP_file()); if (crl_lookup == NULL || !X509_load_crl_file(crl_lookup, crl_file, X509_FILETYPE_PEM)) { fprintf(stderr, "Could not load CRL file %s\n", crl_file); return (0); } X509_STORE_set_flags(crl_store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); } } return (1); } /* * Configure client certificate based on environment. */ static int fetch_ssl_setup_client_certificate(SSL_CTX *ctx, int verbose) { const char *client_cert_file, *client_key_file; if ((client_cert_file = getenv("SSL_CLIENT_CERT_FILE")) != NULL) { client_key_file = getenv("SSL_CLIENT_KEY_FILE") != NULL ? getenv("SSL_CLIENT_KEY_FILE") : client_cert_file; if (verbose) { fetch_info("Using client cert file: %s", client_cert_file); fetch_info("Using client key file: %s", client_key_file); } if (SSL_CTX_use_certificate_chain_file(ctx, client_cert_file) != 1) { fprintf(stderr, "Could not load client certificate %s\n", client_cert_file); return (0); } if (SSL_CTX_use_PrivateKey_file(ctx, client_key_file, SSL_FILETYPE_PEM) != 1) { fprintf(stderr, "Could not load client key %s\n", client_key_file); return (0); } } return (1); } /* * Callback for SSL certificate verification, this is called on server * cert verification. It takes no decision, but informs the user in case * verification failed. */ int fetch_ssl_cb_verify_crt(int verified, X509_STORE_CTX *ctx) { X509 *crt; X509_NAME *name; char *str; str = NULL; if (!verified) { if ((crt = X509_STORE_CTX_get_current_cert(ctx)) != NULL && (name = X509_get_subject_name(crt)) != NULL) str = X509_NAME_oneline(name, 0, 0); fprintf(stderr, "Certificate verification failed for %s\n", str != NULL ? str : "no relevant certificate"); OPENSSL_free(str); } return (verified); } #endif /* * Enable SSL on a connection. */ int fetch_ssl(conn_t *conn, const struct url *URL, int verbose) { #ifdef WITH_SSL int ret, ssl_err; X509_NAME *name; char *str; /* Init the SSL library and context */ if (!SSL_library_init()){ fprintf(stderr, "SSL library init failed\n"); return (-1); } SSL_load_error_strings(); conn->ssl_meth = SSLv23_client_method(); conn->ssl_ctx = SSL_CTX_new(conn->ssl_meth); SSL_CTX_set_mode(conn->ssl_ctx, SSL_MODE_AUTO_RETRY); fetch_ssl_setup_transport_layer(conn->ssl_ctx, verbose); if (!fetch_ssl_setup_peer_verification(conn->ssl_ctx, verbose)) return (-1); if (!fetch_ssl_setup_client_certificate(conn->ssl_ctx, verbose)) return (-1); conn->ssl = SSL_new(conn->ssl_ctx); if (conn->ssl == NULL) { fprintf(stderr, "SSL context creation failed\n"); return (-1); } SSL_set_fd(conn->ssl, conn->sd); #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) if (!SSL_set_tlsext_host_name(conn->ssl, __DECONST(struct url *, URL)->host)) { fprintf(stderr, "TLS server name indication extension failed for host %s\n", URL->host); return (-1); } #endif while ((ret = SSL_connect(conn->ssl)) == -1) { ssl_err = SSL_get_error(conn->ssl, ret); if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE) { ERR_print_errors_fp(stderr); return (-1); } } conn->ssl_cert = SSL_get_peer_certificate(conn->ssl); if (conn->ssl_cert == NULL) { fprintf(stderr, "No server SSL certificate\n"); return (-1); } if (getenv("SSL_NO_VERIFY_HOSTNAME") == NULL) { if (verbose) fetch_info("Verify hostname"); if (!fetch_ssl_verify_hname(conn->ssl_cert, URL->host)) { fprintf(stderr, "SSL certificate subject doesn't match host %s\n", URL->host); return (-1); } } if (verbose) { fetch_info("%s connection established using %s", SSL_get_version(conn->ssl), SSL_get_cipher(conn->ssl)); name = X509_get_subject_name(conn->ssl_cert); str = X509_NAME_oneline(name, 0, 0); fetch_info("Certificate subject: %s", str); OPENSSL_free(str); name = X509_get_issuer_name(conn->ssl_cert); str = X509_NAME_oneline(name, 0, 0); fetch_info("Certificate issuer: %s", str); OPENSSL_free(str); } return (0); #else (void)conn; (void)verbose; (void)URL; fprintf(stderr, "SSL support disabled\n"); return (-1); #endif } #define FETCH_READ_WAIT -2 #define FETCH_READ_ERROR -1 #define FETCH_READ_DONE 0 #ifdef WITH_SSL static ssize_t fetch_ssl_read(SSL *ssl, char *buf, size_t len) { ssize_t rlen; int ssl_err; rlen = SSL_read(ssl, buf, len); if (rlen < 0) { ssl_err = SSL_get_error(ssl, rlen); if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { return (FETCH_READ_WAIT); } else { ERR_print_errors_fp(stderr); return (FETCH_READ_ERROR); } } return (rlen); } #endif static ssize_t fetch_socket_read(int sd, char *buf, size_t len) { ssize_t rlen; rlen = read(sd, buf, len); if (rlen < 0) { if (errno == EAGAIN || (errno == EINTR && fetchRestartCalls)) return (FETCH_READ_WAIT); else return (FETCH_READ_ERROR); } return (rlen); } /* * Read a character from a connection w/ timeout */ ssize_t fetch_read(conn_t *conn, char *buf, size_t len) { struct timeval now, timeout, delta; struct pollfd pfd; ssize_t rlen; int deltams; if (fetchTimeout > 0) { gettimeofday(&timeout, NULL); timeout.tv_sec += fetchTimeout; } deltams = INFTIM; memset(&pfd, 0, sizeof pfd); pfd.fd = conn->sd; pfd.events = POLLIN | POLLERR; for (;;) { /* * The socket is non-blocking. Instead of the canonical * poll() -> read(), we do the following: * * 1) call read() or SSL_read(). * 2) if we received some data, return it. * 3) if an error occurred, return -1. * 4) if read() or SSL_read() signaled EOF, return. * 5) if we did not receive any data but we're not at EOF, * call poll(). * * In the SSL case, this is necessary because if we * receive a close notification, we have to call * SSL_read() one additional time after we've read * everything we received. * * In the non-SSL case, it may improve performance (very * slightly) when reading small amounts of data. */ #ifdef WITH_SSL if (conn->ssl != NULL) rlen = fetch_ssl_read(conn->ssl, buf, len); else #endif rlen = fetch_socket_read(conn->sd, buf, len); if (rlen >= 0) { break; } else if (rlen == FETCH_READ_ERROR) { fetch_syserr(); return (-1); } // assert(rlen == FETCH_READ_WAIT); if (fetchTimeout > 0) { gettimeofday(&now, NULL); if (!timercmp(&timeout, &now, >)) { errno = ETIMEDOUT; fetch_syserr(); return (-1); } timersub(&timeout, &now, &delta); deltams = delta.tv_sec * 1000 + delta.tv_usec / 1000;; } errno = 0; pfd.revents = 0; if (poll(&pfd, 1, deltams) < 0) { if (errno == EINTR && fetchRestartCalls) continue; fetch_syserr(); return (-1); } } return (rlen); } /* * Read a line of text from a connection w/ timeout */ #define MIN_BUF_SIZE 1024 int fetch_getln(conn_t *conn) { char *tmp; size_t tmpsize; ssize_t len; char c; if (conn->buf == NULL) { if ((conn->buf = malloc(MIN_BUF_SIZE)) == NULL) { errno = ENOMEM; return (-1); } conn->bufsize = MIN_BUF_SIZE; } conn->buf[0] = '\0'; conn->buflen = 0; do { len = fetch_read(conn, &c, 1); if (len == -1) return (-1); if (len == 0) break; conn->buf[conn->buflen++] = c; if (conn->buflen == conn->bufsize) { tmp = conn->buf; tmpsize = conn->bufsize * 2 + 1; if ((tmp = realloc(tmp, tmpsize)) == NULL) { errno = ENOMEM; return (-1); } conn->buf = tmp; conn->bufsize = tmpsize; } } while (c != '\n'); conn->buf[conn->buflen] = '\0'; DEBUGF("<<< %s", conn->buf); return (0); } /* * Write to a connection w/ timeout */ ssize_t fetch_write(conn_t *conn, const char *buf, size_t len) { struct iovec iov; iov.iov_base = __DECONST(char *, buf); iov.iov_len = len; return fetch_writev(conn, &iov, 1); } /* * Write a vector to a connection w/ timeout * Note: can modify the iovec. */ ssize_t fetch_writev(conn_t *conn, struct iovec *iov, int iovcnt) { struct timeval now, timeout, delta; struct pollfd pfd; ssize_t wlen, total; int deltams; memset(&pfd, 0, sizeof pfd); if (fetchTimeout) { pfd.fd = conn->sd; pfd.events = POLLOUT | POLLERR; gettimeofday(&timeout, NULL); timeout.tv_sec += fetchTimeout; } total = 0; while (iovcnt > 0) { while (fetchTimeout && pfd.revents == 0) { gettimeofday(&now, NULL); if (!timercmp(&timeout, &now, >)) { errno = ETIMEDOUT; fetch_syserr(); return (-1); } timersub(&timeout, &now, &delta); deltams = delta.tv_sec * 1000 + delta.tv_usec / 1000; errno = 0; pfd.revents = 0; if (poll(&pfd, 1, deltams) < 0) { /* POSIX compliance */ if (errno == EAGAIN) continue; if (errno == EINTR && fetchRestartCalls) continue; return (-1); } } errno = 0; #ifdef WITH_SSL if (conn->ssl != NULL) wlen = SSL_write(conn->ssl, iov->iov_base, iov->iov_len); else #endif wlen = writev(conn->sd, iov, iovcnt); if (wlen == 0) { /* we consider a short write a failure */ /* XXX perhaps we shouldn't in the SSL case */ errno = EPIPE; fetch_syserr(); return (-1); } if (wlen < 0) { if (errno == EINTR && fetchRestartCalls) continue; return (-1); } total += wlen; while (iovcnt > 0 && wlen >= (ssize_t)iov->iov_len) { wlen -= iov->iov_len; iov++; iovcnt--; } if (iovcnt > 0) { iov->iov_len -= wlen; iov->iov_base = __DECONST(char *, iov->iov_base) + wlen; } } return (total); } /* * Write a line of text to a connection w/ timeout */ int fetch_putln(conn_t *conn, const char *str, size_t len) { struct iovec iov[2]; int ret; DEBUGF(">>> %s\n", str); iov[0].iov_base = __DECONST(char *, str); iov[0].iov_len = len; iov[1].iov_base = __DECONST(char *, ENDL); iov[1].iov_len = sizeof(ENDL); if (len == 0) ret = fetch_writev(conn, &iov[1], 1); else ret = fetch_writev(conn, iov, 2); if (ret == -1) return (-1); return (0); } /* * Close connection */ int fetch_close(conn_t *conn) { int ret; if (--conn->ref > 0) return (0); #ifdef WITH_SSL if (conn->ssl) { SSL_shutdown(conn->ssl); SSL_set_connect_state(conn->ssl); SSL_free(conn->ssl); conn->ssl = NULL; } if (conn->ssl_ctx) { SSL_CTX_free(conn->ssl_ctx); conn->ssl_ctx = NULL; } if (conn->ssl_cert) { X509_free(conn->ssl_cert); conn->ssl_cert = NULL; } #endif ret = close(conn->sd); free(conn->buf); free(conn); return (ret); } /*** Directory-related utility functions *************************************/ int fetch_add_entry(struct url_ent **p, int *size, int *len, const char *name, struct url_stat *us) { struct url_ent *tmp; if (*p == NULL) { *size = 0; *len = 0; } if (*len >= *size - 1) { tmp = reallocarray(*p, *size * 2 + 1, sizeof(**p)); if (tmp == NULL) { errno = ENOMEM; fetch_syserr(); return (-1); } *size = (*size * 2 + 1); *p = tmp; } tmp = *p + *len; snprintf(tmp->name, PATH_MAX, "%s", name); memcpy(&tmp->stat, us, sizeof(*us)); (*len)++; (++tmp)->name[0] = 0; return (0); } /*** Authentication-related utility functions ********************************/ static const char * fetch_read_word(FILE *f) { static char word[1024]; if (fscanf(f, " %1023s ", word) != 1) return (NULL); return (word); } static int fetch_netrc_open(void) { struct passwd *pwd; char fn[PATH_MAX]; const char *p; int fd, serrno; if ((p = getenv("NETRC")) != NULL) { DEBUGF("NETRC=%s\n", p); if (snprintf(fn, sizeof(fn), "%s", p) >= (int)sizeof(fn)) { fetch_info("$NETRC specifies a file name " "longer than PATH_MAX"); return (-1); } } else { if ((p = getenv("HOME")) == NULL) { if ((pwd = getpwuid(getuid())) == NULL || (p = pwd->pw_dir) == NULL) return (-1); } if (snprintf(fn, sizeof(fn), "%s/.netrc", p) >= (int)sizeof(fn)) return (-1); } if ((fd = open(fn, O_RDONLY)) < 0) { serrno = errno; DEBUGF("%s: %s\n", fn, strerror(serrno)); errno = serrno; } return (fd); } /* * Get authentication data for a URL from .netrc */ int fetch_netrc_auth(struct url *url) { const char *word; int serrno; FILE *f; if (url->netrcfd < 0) url->netrcfd = fetch_netrc_open(); if (url->netrcfd < 0) return (-1); if ((f = fdopen(url->netrcfd, "r")) == NULL) { serrno = errno; DEBUGF("fdopen(netrcfd): %s", strerror(errno)); close(url->netrcfd); url->netrcfd = -1; errno = serrno; return (-1); } rewind(f); DEBUGF("searching netrc for %s\n", url->host); while ((word = fetch_read_word(f)) != NULL) { if (strcmp(word, "default") == 0) { DEBUGF("using default netrc settings\n"); break; } if (strcmp(word, "machine") == 0 && (word = fetch_read_word(f)) != NULL && strcasecmp(word, url->host) == 0) { DEBUGF("using netrc settings for %s\n", word); break; } } if (word == NULL) goto ferr; while ((word = fetch_read_word(f)) != NULL) { if (strcmp(word, "login") == 0) { if ((word = fetch_read_word(f)) == NULL) goto ferr; if (snprintf(url->user, sizeof(url->user), "%s", word) > (int)sizeof(url->user)) { fetch_info("login name in .netrc is too long"); url->user[0] = '\0'; } } else if (strcmp(word, "password") == 0) { if ((word = fetch_read_word(f)) == NULL) goto ferr; if (snprintf(url->pwd, sizeof(url->pwd), "%s", word) > (int)sizeof(url->pwd)) { fetch_info("password in .netrc is too long"); url->pwd[0] = '\0'; } } else if (strcmp(word, "account") == 0) { if ((word = fetch_read_word(f)) == NULL) goto ferr; /* XXX not supported! */ } else { break; } } fclose(f); url->netrcfd = -1; return (0); ferr: serrno = errno; fclose(f); url->netrcfd = -1; errno = serrno; return (-1); } /* * The no_proxy environment variable specifies a set of domains for * which the proxy should not be consulted; the contents is a comma-, * or space-separated list of domain names. A single asterisk will * override all proxy variables and no transactions will be proxied * (for compatibility with lynx and curl, see the discussion at * ). */ int fetch_no_proxy_match(const char *host) { const char *no_proxy, *p, *q; size_t h_len, d_len; if ((no_proxy = getenv("NO_PROXY")) == NULL && (no_proxy = getenv("no_proxy")) == NULL) return (0); /* asterisk matches any hostname */ if (strcmp(no_proxy, "*") == 0) return (1); h_len = strlen(host); p = no_proxy; do { /* position p at the beginning of a domain suffix */ while (*p == ',' || isspace((unsigned char)*p)) p++; /* position q at the first separator character */ for (q = p; *q; ++q) if (*q == ',' || isspace((unsigned char)*q)) break; d_len = q - p; if (d_len > 0 && h_len >= d_len && strncasecmp(host + h_len - d_len, p, d_len) == 0) { /* domain name matches */ return (1); } p = q + 1; } while (*q); return (0); } Index: head/lib/libfetch/fetch.3 =================================================================== --- head/lib/libfetch/fetch.3 (revision 367999) +++ head/lib/libfetch/fetch.3 (revision 368000) @@ -1,881 +1,877 @@ .\"- .\" Copyright (c) 1998-2013 Dag-Erling Smørgrav .\" Copyright (c) 2013-2016 Michael Gmelin .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd August 28, 2019 +.Dd November 24, 2020 .Dt FETCH 3 .Os .Sh NAME .Nm fetchMakeURL , .Nm fetchParseURL , .Nm fetchFreeURL , .Nm fetchXGetURL , .Nm fetchGetURL , .Nm fetchPutURL , .Nm fetchStatURL , .Nm fetchListURL , .Nm fetchXGet , .Nm fetchGet , .Nm fetchPut , .Nm fetchStat , .Nm fetchList , .Nm fetchXGetFile , .Nm fetchGetFile , .Nm fetchPutFile , .Nm fetchStatFile , .Nm fetchListFile , .Nm fetchXGetHTTP , .Nm fetchGetHTTP , .Nm fetchPutHTTP , .Nm fetchStatHTTP , .Nm fetchListHTTP , .Nm fetchReqHTTP , .Nm fetchXGetFTP , .Nm fetchGetFTP , .Nm fetchPutFTP , .Nm fetchStatFTP , .Nm fetchListFTP .Nd file transfer functions .Sh LIBRARY .Lb libfetch .Sh SYNOPSIS .In sys/param.h .In stdio.h .In fetch.h .Ft struct url * .Fn fetchMakeURL "const char *scheme" "const char *host" "int port" "const char *doc" "const char *user" "const char *pwd" .Ft struct url * .Fn fetchParseURL "const char *URL" .Ft void .Fn fetchFreeURL "struct url *u" .Ft FILE * .Fn fetchXGetURL "const char *URL" "struct url_stat *us" "const char *flags" .Ft FILE * .Fn fetchGetURL "const char *URL" "const char *flags" .Ft FILE * .Fn fetchPutURL "const char *URL" "const char *flags" .Ft int .Fn fetchStatURL "const char *URL" "struct url_stat *us" "const char *flags" .Ft struct url_ent * .Fn fetchListURL "const char *URL" "const char *flags" .Ft FILE * .Fn fetchXGet "struct url *u" "struct url_stat *us" "const char *flags" .Ft FILE * .Fn fetchGet "struct url *u" "const char *flags" .Ft FILE * .Fn fetchPut "struct url *u" "const char *flags" .Ft int .Fn fetchStat "struct url *u" "struct url_stat *us" "const char *flags" .Ft struct url_ent * .Fn fetchList "struct url *u" "const char *flags" .Ft FILE * .Fn fetchXGetFile "struct url *u" "struct url_stat *us" "const char *flags" .Ft FILE * .Fn fetchGetFile "struct url *u" "const char *flags" .Ft FILE * .Fn fetchPutFile "struct url *u" "const char *flags" .Ft int .Fn fetchStatFile "struct url *u" "struct url_stat *us" "const char *flags" .Ft struct url_ent * .Fn fetchListFile "struct url *u" "const char *flags" .Ft FILE * .Fn fetchXGetHTTP "struct url *u" "struct url_stat *us" "const char *flags" .Ft FILE * .Fn fetchGetHTTP "struct url *u" "const char *flags" .Ft FILE * .Fn fetchPutHTTP "struct url *u" "const char *flags" .Ft int .Fn fetchStatHTTP "struct url *u" "struct url_stat *us" "const char *flags" .Ft struct url_ent * .Fn fetchListHTTP "struct url *u" "const char *flags" .Ft FILE * .Fn fetchReqHTTP "struct url *u" "const char *method" "const char *flags" "const char *content_type" "const char *body" .Ft FILE * .Fn fetchXGetFTP "struct url *u" "struct url_stat *us" "const char *flags" .Ft FILE * .Fn fetchGetFTP "struct url *u" "const char *flags" .Ft FILE * .Fn fetchPutFTP "struct url *u" "const char *flags" .Ft int .Fn fetchStatFTP "struct url *u" "struct url_stat *us" "const char *flags" .Ft struct url_ent * .Fn fetchListFTP "struct url *u" "const char *flags" .Sh DESCRIPTION These functions implement a high-level library for retrieving and uploading files using Uniform Resource Locators (URLs). .Pp .Fn fetchParseURL takes a URL in the form of a null-terminated string and splits it into its components function according to the Common Internet Scheme Syntax detailed in RFC1738. A regular expression which produces this syntax is: .Bd -literal :(//((:)?@)?(:)?)?/()? .Ed .Pp If the URL does not seem to begin with a scheme name, the following syntax is assumed: .Bd -literal (((:)?@)?(:)?)?/()? .Ed .Pp Note that some components of the URL are not necessarily relevant to all URL schemes. For instance, the file scheme only needs the and components. .Pp .Fn fetchMakeURL and .Fn fetchParseURL return a pointer to a .Vt url structure, which is defined as follows in .In fetch.h : .Bd -literal #define URL_SCHEMELEN 16 #define URL_USERLEN 256 #define URL_PWDLEN 256 struct url { char scheme[URL_SCHEMELEN+1]; char user[URL_USERLEN+1]; char pwd[URL_PWDLEN+1]; char host[MAXHOSTNAMELEN+1]; int port; char *doc; off_t offset; size_t length; time_t ims_time; }; .Ed .Pp The .Va ims_time field stores the time value for .Li If-Modified-Since HTTP requests. .Pp The pointer returned by .Fn fetchMakeURL or .Fn fetchParseURL should be freed using .Fn fetchFreeURL . .Pp .Fn fetchXGetURL , .Fn fetchGetURL , and .Fn fetchPutURL constitute the recommended interface to the .Nm fetch library. They examine the URL passed to them to determine the transfer method, and call the appropriate lower-level functions to perform the actual transfer. .Fn fetchXGetURL also returns the remote document's metadata in the .Vt url_stat structure pointed to by the .Fa us argument. .Pp The .Fa flags argument is a string of characters which specify transfer options. The meaning of the individual flags is scheme-dependent, and is detailed in the appropriate section below. .Pp .Fn fetchStatURL attempts to obtain the requested document's metadata and fill in the structure pointed to by its second argument. The .Vt url_stat structure is defined as follows in .In fetch.h : .Bd -literal struct url_stat { off_t size; time_t atime; time_t mtime; }; .Ed .Pp If the size could not be obtained from the server, the .Fa size field is set to -1. If the modification time could not be obtained from the server, the .Fa mtime field is set to the epoch. If the access time could not be obtained from the server, the .Fa atime field is set to the modification time. .Pp .Fn fetchListURL attempts to list the contents of the directory pointed to by the URL provided. If successful, it returns a malloced array of .Vt url_ent structures. The .Vt url_ent structure is defined as follows in .In fetch.h : .Bd -literal struct url_ent { char name[PATH_MAX]; struct url_stat stat; }; .Ed .Pp The list is terminated by an entry with an empty name. .Pp The pointer returned by .Fn fetchListURL should be freed using .Fn free . .Pp .Fn fetchXGet , .Fn fetchGet , .Fn fetchPut and .Fn fetchStat are similar to .Fn fetchXGetURL , .Fn fetchGetURL , .Fn fetchPutURL and .Fn fetchStatURL , except that they expect a pre-parsed URL in the form of a pointer to a .Vt struct url rather than a string. .Pp All of the .Fn fetchXGetXXX , .Fn fetchGetXXX and .Fn fetchPutXXX functions return a pointer to a stream which can be used to read or write data from or to the requested document, respectively. Note that although the implementation details of the individual access methods vary, it can generally be assumed that a stream returned by one of the .Fn fetchXGetXXX or .Fn fetchGetXXX functions is read-only, and that a stream returned by one of the .Fn fetchPutXXX functions is write-only. .Sh FILE SCHEME .Fn fetchXGetFile , .Fn fetchGetFile and .Fn fetchPutFile provide access to documents which are files in a locally mounted file system. Only the component of the URL is used. .Pp .Fn fetchXGetFile and .Fn fetchGetFile do not accept any flags. .Pp .Fn fetchPutFile accepts the .Ql a (append to file) flag. If that flag is specified, the data written to the stream returned by .Fn fetchPutFile will be appended to the previous contents of the file, instead of replacing them. .Sh FTP SCHEME .Fn fetchXGetFTP , .Fn fetchGetFTP and .Fn fetchPutFTP implement the FTP protocol as described in RFC959. .Pp If the .Ql P (not passive) flag is specified, an active (rather than passive) connection will be attempted. .Pp The .Ql p flag is supported for compatibility with earlier versions where active connections were the default. It has precedence over the .Ql P flag, so if both are specified, .Nm will use a passive connection. .Pp If the .Ql l (low) flag is specified, data sockets will be allocated in the low (or default) port range instead of the high port range (see .Xr ip 4 ) . .Pp If the .Ql d (direct) flag is specified, .Fn fetchXGetFTP , .Fn fetchGetFTP and .Fn fetchPutFTP will use a direct connection even if a proxy server is defined. .Pp If no user name or password is given, the .Nm fetch library will attempt an anonymous login, with user name "anonymous" and password "anonymous@". .Sh HTTP SCHEME The .Fn fetchXGetHTTP , .Fn fetchGetHTTP , .Fn fetchPutHTTP and .Fn fetchReqHTTP functions implement the HTTP/1.1 protocol. With a little luck, there is even a chance that they comply with RFC2616 and RFC2617. .Pp If the .Ql d (direct) flag is specified, .Fn fetchXGetHTTP , .Fn fetchGetHTTP and .Fn fetchPutHTTP will use a direct connection even if a proxy server is defined. .Pp If the .Ql i (if-modified-since) flag is specified, and the .Va ims_time field is set in .Vt "struct url" , then .Fn fetchXGetHTTP and .Fn fetchGetHTTP will send a conditional .Li If-Modified-Since HTTP header to only fetch the content if it is newer than .Va ims_time . .Pp The function .Fn fetchReqHTTP can be used to make requests with an arbitrary HTTP verb, including POST, DELETE, CONNECT, OPTIONS, TRACE or PATCH. This can be done by setting the argument .Fa method to the intended verb, such as .Ql POST , and .Fa body to the content. .Pp Since there seems to be no good way of implementing the HTTP PUT method in a manner consistent with the rest of the .Nm fetch library, .Fn fetchPutHTTP is currently unimplemented. .Sh HTTPS SCHEME Based on HTTP SCHEME. By default the peer is verified using the CA bundle located in .Pa /usr/local/etc/ssl/cert.pem . If this file does not exist, .Pa /etc/ssl/cert.pem is used instead. If neither file exists, and .Ev SSL_CA_CERT_PATH has not been set, OpenSSL's default CA cert and path settings apply. The certificate bundle can contain multiple CA certificates. A common source of a current CA bundle is .Pa \%security/ca_root_nss . .Pp The CA bundle used for peer verification can be changed by setting the environment variables .Ev SSL_CA_CERT_FILE to point to a concatenated bundle of trusted certificates and .Ev SSL_CA_CERT_PATH to point to a directory containing hashes of trusted CAs (see .Xr verify 1 ) . .Pp A certificate revocation list (CRL) can be used by setting the environment variable .Ev SSL_CRL_FILE (see .Xr crl 1 ) . .Pp Peer verification can be disabled by setting the environment variable .Ev SSL_NO_VERIFY_PEER . Note that this also disables CRL checking. .Pp By default the service identity is verified according to the rules detailed in RFC6125 (also known as hostname verification). This feature can be disabled by setting the environment variable .Ev SSL_NO_VERIFY_HOSTNAME . .Pp Client certificate based authentication is supported. The environment variable .Ev SSL_CLIENT_CERT_FILE should be set to point to a file containing key and client certificate to be used in PEM format. When a PEM-format key is in a separate file from the client certificate, the environment variable .Ev SSL_CLIENT_KEY_FILE can be set to point to the key file. In case the key uses a password, the user will be prompted on standard input (see .Xr PEM 3 ) . .Pp By default .Nm libfetch allows TLSv1 and newer when negotiating the connecting with the remote peer. You can change this behavior by setting the -.Ev SSL_ALLOW_SSL3 -environment variable to allow SSLv3 and .Ev SSL_NO_TLS1 , .Ev SSL_NO_TLS1_1 and .Ev SSL_NO_TLS1_2 -to disable TLS 1.0, 1.1 and 1.2 respectively. +environment variables to disable TLS 1.0, 1.1 and 1.2 respectively. .Sh AUTHENTICATION Apart from setting the appropriate environment variables and specifying the user name and password in the URL or the .Vt struct url , the calling program has the option of defining an authentication function with the following prototype: .Pp .Ft int .Fn myAuthMethod "struct url *u" .Pp The callback function should fill in the .Fa user and .Fa pwd fields in the provided .Vt struct url and return 0 on success, or any other value to indicate failure. .Pp To register the authentication callback, simply set .Va fetchAuthMethod to point at it. The callback will be used whenever a site requires authentication and the appropriate environment variables are not set. .Pp This interface is experimental and may be subject to change. .Sh RETURN VALUES .Fn fetchParseURL returns a pointer to a .Vt struct url containing the individual components of the URL. If it is unable to allocate memory, or the URL is syntactically incorrect, .Fn fetchParseURL returns a NULL pointer. .Pp The .Fn fetchStat functions return 0 on success and -1 on failure. .Pp All other functions return a stream pointer which may be used to access the requested document, or NULL if an error occurred. .Pp The following error codes are defined in .In fetch.h : .Bl -tag -width 18n .It Bq Er FETCH_ABORT Operation aborted .It Bq Er FETCH_AUTH Authentication failed .It Bq Er FETCH_DOWN Service unavailable .It Bq Er FETCH_EXISTS File exists .It Bq Er FETCH_FULL File system full .It Bq Er FETCH_INFO Informational response .It Bq Er FETCH_MEMORY Insufficient memory .It Bq Er FETCH_MOVED File has moved .It Bq Er FETCH_NETWORK Network error .It Bq Er FETCH_OK No error .It Bq Er FETCH_PROTO Protocol error .It Bq Er FETCH_RESOLV Resolver error .It Bq Er FETCH_SERVER Server error .It Bq Er FETCH_TEMP Temporary error .It Bq Er FETCH_TIMEOUT Operation timed out .It Bq Er FETCH_UNAVAIL File is not available .It Bq Er FETCH_UNKNOWN Unknown error .It Bq Er FETCH_URL Invalid URL .El .Pp The accompanying error message includes a protocol-specific error code and message, like "File is not available (404 Not Found)" .Sh ENVIRONMENT .Bl -tag -width ".Ev FETCH_BIND_ADDRESS" .It Ev FETCH_BIND_ADDRESS Specifies a hostname or IP address to which sockets used for outgoing connections will be bound. .It Ev FTP_LOGIN Default FTP login if none was provided in the URL. .It Ev FTP_PASSIVE_MODE If set to .Ql no , forces the FTP code to use active mode. If set to any other value, forces passive mode even if the application requested active mode. .It Ev FTP_PASSWORD Default FTP password if the remote server requests one and none was provided in the URL. .It Ev FTP_PROXY URL of the proxy to use for FTP requests. The document part is ignored. FTP and HTTP proxies are supported; if no scheme is specified, FTP is assumed. If the proxy is an FTP proxy, .Nm libfetch will send .Ql user@host as user name to the proxy, where .Ql user is the real user name, and .Ql host is the name of the FTP server. .Pp If this variable is set to an empty string, no proxy will be used for FTP requests, even if the .Ev HTTP_PROXY variable is set. .It Ev ftp_proxy Same as .Ev FTP_PROXY , for compatibility. .It Ev HTTP_ACCEPT Specifies the value of the .Va Accept header for HTTP requests. If empty, no .Va Accept header is sent. The default is .Dq */* . .It Ev HTTP_AUTH Specifies HTTP authorization parameters as a colon-separated list of items. The first and second item are the authorization scheme and realm respectively; further items are scheme-dependent. Currently, the .Dq basic and .Dq digest authorization methods are supported. .Pp Both methods require two parameters: the user name and password, in that order. .Pp This variable is only used if the server requires authorization and no user name or password was specified in the URL. .It Ev HTTP_PROXY URL of the proxy to use for HTTP requests. The document part is ignored. Only HTTP proxies are supported for HTTP requests. If no port number is specified, the default is 3128. .Pp Note that this proxy will also be used for FTP documents, unless the .Ev FTP_PROXY variable is set. .It Ev http_proxy Same as .Ev HTTP_PROXY , for compatibility. .It Ev HTTP_PROXY_AUTH Specifies authorization parameters for the HTTP proxy in the same format as the .Ev HTTP_AUTH variable. .Pp This variable is used if and only if connected to an HTTP proxy, and is ignored if a user and/or a password were specified in the proxy URL. .It Ev HTTP_REFERER Specifies the referrer URL to use for HTTP requests. If set to .Dq auto , the document URL will be used as referrer URL. .It Ev HTTP_USER_AGENT Specifies the User-Agent string to use for HTTP requests. This can be useful when working with HTTP origin or proxy servers that differentiate between user agents. If defined but empty, no User-Agent header is sent. .It Ev NETRC Specifies a file to use instead of .Pa ~/.netrc to look up login names and passwords for FTP and HTTP sites as well as HTTP proxies. See .Xr ftp 1 for a description of the file format. .It Ev NO_PROXY Either a single asterisk, which disables the use of proxies altogether, or a comma- or whitespace-separated list of hosts for which proxies should not be used. .It Ev no_proxy Same as .Ev NO_PROXY , for compatibility. .It Ev SOCKS5_PROXY Uses SOCKS version 5 to make connection. The format must be the IP or hostname followed by a colon for the port. IPv6 addresses must enclose the address in brackets. If no port is specified, the default is 1080. This setting will supercede a connection to an .Ev HTTP_PROXY . -.It Ev SSL_ALLOW_SSL3 -Allow SSL version 3 when negotiating the connection (not recommended). .It Ev SSL_CA_CERT_FILE CA certificate bundle containing trusted CA certificates. Default value: See HTTPS SCHEME above. .It Ev SSL_CA_CERT_PATH Path containing trusted CA hashes. .It Ev SSL_CLIENT_CERT_FILE PEM encoded client certificate/key which will be used in client certificate authentication. .It Ev SSL_CLIENT_KEY_FILE PEM encoded client key in case key and client certificate are stored separately. .It Ev SSL_CRL_FILE File containing certificate revocation list. .It Ev SSL_NO_TLS1 Do not allow TLS version 1.0 when negotiating the connection. .It Ev SSL_NO_TLS1_1 Do not allow TLS version 1.1 when negotiating the connection. .It Ev SSL_NO_TLS1_2 Do not allow TLS version 1.2 when negotiating the connection. .It Ev SSL_NO_VERIFY_HOSTNAME If set, do not verify that the hostname matches the subject of the certificate presented by the server. .It Ev SSL_NO_VERIFY_PEER If set, do not verify the peer certificate against trusted CAs. .El .Sh EXAMPLES To access a proxy server on .Pa proxy.example.com port 8080, set the .Ev HTTP_PROXY environment variable in a manner similar to this: .Pp .Dl HTTP_PROXY=http://proxy.example.com:8080 .Pp If the proxy server requires authentication, there are two options available for passing the authentication data. The first method is by using the proxy URL: .Pp .Dl HTTP_PROXY=http://:@proxy.example.com:8080 .Pp The second method is by using the .Ev HTTP_PROXY_AUTH environment variable: .Bd -literal -offset indent HTTP_PROXY=http://proxy.example.com:8080 HTTP_PROXY_AUTH=basic:*:: .Ed .Pp To disable the use of a proxy for an HTTP server running on the local host, define .Ev NO_PROXY as follows: .Bd -literal -offset indent NO_PROXY=localhost,127.0.0.1 .Ed .Pp To use a SOCKS5 proxy, set the .Ev SOCKS5_PROXY environment variable to a valid host or IP followed by an optional colon and the port. IPv6 addresses must be enclosed in brackets. The following are examples of valid settings: .Bd -literal -offset indent SOCKS5_PROXY=proxy.example.com SOCKS5_PROXY=proxy.example.com:1080 SOCKS5_PROXY=192.0.2.0 SOCKS5_PROXY=198.51.100.0:1080 SOCKS5_PROXY=[2001:db8::1] SOCKS5_PROXY=[2001:db8::2]:1080 .Ed .Pp Access HTTPS website without any certificate verification whatsoever: .Bd -literal -offset indent SSL_NO_VERIFY_PEER=1 SSL_NO_VERIFY_HOSTNAME=1 .Ed .Pp Access HTTPS website using client certificate based authentication and a private CA: .Bd -literal -offset indent SSL_CLIENT_CERT_FILE=/path/to/client.pem SSL_CA_CERT_FILE=/path/to/myca.pem .Ed .Sh SEE ALSO .Xr fetch 1 , .Xr ip 4 .Rs .%A J. Postel .%A J. K. Reynolds .%D October 1985 .%B File Transfer Protocol .%O RFC959 .Re .Rs .%A P. Deutsch .%A A. Emtage .%A A. Marine. .%D May 1994 .%T How to Use Anonymous FTP .%O RFC1635 .Re .Rs .%A T. Berners-Lee .%A L. Masinter .%A M. McCahill .%D December 1994 .%T Uniform Resource Locators (URL) .%O RFC1738 .Re .Rs .%A R. Fielding .%A J. Gettys .%A J. Mogul .%A H. Frystyk .%A L. Masinter .%A P. Leach .%A T. Berners-Lee .%D January 1999 .%B Hypertext Transfer Protocol -- HTTP/1.1 .%O RFC2616 .Re .Rs .%A J. Franks .%A P. Hallam-Baker .%A J. Hostetler .%A S. Lawrence .%A P. Leach .%A A. Luotonen .%A L. Stewart .%D June 1999 .%B HTTP Authentication: Basic and Digest Access Authentication .%O RFC2617 .Re .Sh HISTORY The .Nm fetch library first appeared in .Fx 3.0 . .Sh AUTHORS .An -nosplit The .Nm fetch library was mostly written by .An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org with numerous suggestions and contributions from .An Jordan K. Hubbard Aq Mt jkh@FreeBSD.org , .An Eugene Skepner Aq Mt eu@qub.com , .An Hajimu Umemoto Aq Mt ume@FreeBSD.org , .An Henry Whincup Aq Mt henry@techiebod.com , .An Jukka A. Ukkonen Aq Mt jau@iki.fi , .An Jean-Fran\(,cois Dockes Aq Mt jf@dockes.org , .An Michael Gmelin Aq Mt freebsd@grem.de and others. It replaces the older .Nm ftpio library written by .An Poul-Henning Kamp Aq Mt phk@FreeBSD.org and .An Jordan K. Hubbard Aq Mt jkh@FreeBSD.org . .Pp This manual page was written by .An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org and .An Michael Gmelin Aq Mt freebsd@grem.de . .Sh BUGS Some parts of the library are not yet implemented. The most notable examples of this are .Fn fetchPutHTTP , .Fn fetchListHTTP , .Fn fetchListFTP and FTP proxy support. .Pp There is no way to select a proxy at run-time other than setting the .Ev HTTP_PROXY or .Ev FTP_PROXY environment variables as appropriate. .Pp .Nm libfetch does not understand or obey 305 (Use Proxy) replies. .Pp Error numbers are unique only within a certain context; the error codes used for FTP and HTTP overlap, as do those used for resolver and system errors. For instance, error code 202 means "Command not implemented, superfluous at this site" in an FTP context and "Accepted" in an HTTP context. .Pp .Fn fetchStatFTP does not check that the result of an MDTM command is a valid date. .Pp In case password protected keys are used for client certificate based authentication the user is prompted for the password on each and every fetch operation. .Pp The man page is incomplete, poorly written and produces badly formatted text. .Pp The error reporting mechanism is unsatisfactory. .Pp Some parts of the code are not fully reentrant.