File: [local] / src / usr.bin / ftp / Attic / http.c (download)
Revision 1.6, Sun May 12 22:48:03 2019 UTC (5 years ago) by tb
Branch: MAIN
Changes since 1.5: +6 -1 lines
Fix double free by nulling out pointers after free.
from florian, ok jca
|
/* $OpenBSD: http.c,v 1.6 2019/05/12 22:48:03 tb Exp $ */
/*
* Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
* Copyright (c) 2012 - 2015 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <err.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#ifndef NOSSL
#include <tls.h>
#endif
#include "ftp.h"
#include "xmalloc.h"
#define MAX_REDIRECTS 10
#ifndef NOSSL
#define MINBUF 128
static struct tls_config *tls_config;
static struct tls *ctx;
static int tls_session_fd = -1;
static char * const tls_verify_opts[] = {
#define HTTP_TLS_CAFILE 0
"cafile",
#define HTTP_TLS_CAPATH 1
"capath",
#define HTTP_TLS_CIPHERS 2
"ciphers",
#define HTTP_TLS_DONTVERIFY 3
"dont",
#define HTTP_TLS_VERIFYDEPTH 4
"depth",
#define HTTP_TLS_MUSTSTAPLE 5
"muststaple",
#define HTTP_TLS_NOVERIFYTIME 6
"noverifytime",
#define HTTP_TLS_SESSION 7
"session",
#define HTTP_TLS_DOVERIFY 8
"do",
NULL
};
#endif /* NOSSL */
/*
* HTTP status codes based on IANA assignments (2014-06-11 version):
* https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
* plus legacy (306) and non-standard (420).
*/
static struct http_status {
int code;
const char *name;
} http_status[] = {
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
/* 103-199 unassigned */
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
/* 209-225 unassigned */
{ 226, "IM Used" },
/* 227-299 unassigned */
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "Switch Proxy" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
/* 309-399 unassigned */
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 418, "I'm a teapot" },
/* 419-421 unassigned */
{ 420, "Enhance Your Calm" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
/* 425 unassigned */
{ 426, "Upgrade Required" },
/* 427 unassigned */
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
/* 430 unassigned */
{ 431, "Request Header Fields Too Large" },
/* 432-450 unassigned */
{ 451, "Unavailable For Legal Reasons" },
/* 452-499 unassigned */
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
/* 509 unassigned */
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
/* 512-599 unassigned */
{ 0, NULL },
};
struct http_headers {
char *location;
off_t content_length;
int chunked;
};
static void decode_chunk(int, uint, FILE *);
static char *header_lookup(const char *, const char *);
static const char *http_error(int);
static void http_headers_free(struct http_headers *);
static ssize_t http_getline(int, char **, size_t *);
static size_t http_read(int, char *, size_t);
static struct url *http_redirect(struct url *, char *);
static void http_save_chunks(struct url *, FILE *, off_t *);
static int http_status_cmp(const void *, const void *);
static int http_request(int, const char *,
struct http_headers **);
static char *relative_path_resolve(const char *, const char *);
#ifndef NOSSL
static void tls_copy_file(struct url *, FILE *, off_t *);
static ssize_t tls_getline(char **, size_t *, struct tls *);
#endif
static FILE *fp;
void
http_connect(struct url *url, struct url *proxy, int timeout)
{
const char *host, *port;
int sock;
host = proxy ? proxy->host : url->host;
port = proxy ? proxy->port : url->port;
if ((sock = tcp_connect(host, port, timeout)) == -1)
exit(1);
if ((fp = fdopen(sock, "r+")) == NULL)
err(1, "%s: fdopen", __func__);
#ifndef NOSSL
struct http_headers *headers;
char *auth = NULL, *req;
int authlen = 0, code;
if (url->scheme != S_HTTPS)
return;
if (proxy) {
if (url->basic_auth)
authlen = xasprintf(&auth,
"Proxy-Authorization: Basic %s\r\n",
url->basic_auth);
xasprintf(&req,
"CONNECT %s:%s HTTP/1.0\r\n"
"User-Agent: %s\r\n"
"%s"
"\r\n",
url->host, url->port,
useragent,
url->basic_auth ? auth : "");
freezero(auth, authlen);
if ((code = http_request(S_HTTP, req, &headers)) != 200)
errx(1, "%s: failed to CONNECT to %s:%s: %s",
__func__, url->host, url->port, http_error(code));
free(req);
http_headers_free(headers);
}
if ((ctx = tls_client()) == NULL)
errx(1, "failed to create tls client");
if (tls_configure(ctx, tls_config) != 0)
errx(1, "%s: %s", __func__, tls_error(ctx));
if (tls_connect_socket(ctx, sock, url->host) != 0)
errx(1, "%s: %s", __func__, tls_error(ctx));
#endif /* NOSSL */
}
struct url *
http_get(struct url *url, struct url *proxy, off_t *offset, off_t *sz)
{
struct http_headers *headers;
char *auth = NULL, *path = NULL, *range = NULL, *req;
int authlen = 0, code, redirects = 0;
redirected:
log_request("Requesting", url, proxy);
if (*offset)
xasprintf(&range, "Range: bytes=%lld-\r\n", *offset);
if (url->basic_auth)
authlen = xasprintf(&auth, "Authorization: Basic %s\r\n",
url->basic_auth);
if (proxy && url->scheme != S_HTTPS)
path = url_str(url);
else if (url->path)
path = url_encode(url->path);
xasprintf(&req,
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"%s"
"%s"
"Connection: close\r\n"
"User-Agent: %s\r\n"
"\r\n",
path ? path : "/",
url->host,
*offset ? range : "",
url->basic_auth ? auth : "",
useragent);
code = http_request(url->scheme, req, &headers);
freezero(auth, authlen);
auth = NULL;
authlen = 0;
free(range);
range = NULL;
free(path);
path = NULL;
free(req);
req = NULL;
switch (code) {
case 200:
if (*offset) {
warnx("Server does not support resume.");
*offset = 0;
}
break;
case 206:
break;
case 301:
case 302:
case 303:
case 307:
http_close(url);
if (++redirects > MAX_REDIRECTS)
errx(1, "Too many redirections requested");
if (headers->location == NULL)
errx(1, "%s: Location header missing", __func__);
url = http_redirect(url, headers->location);
http_headers_free(headers);
log_request("Redirected to", url, proxy);
http_connect(url, proxy, 0);
goto redirected;
case 416:
errx(1, "File is already fully retrieved.");
break;
default:
errx(1, "Error retrieving file: %d %s", code, http_error(code));
}
*sz = headers->content_length + *offset;
url->chunked = headers->chunked;
http_headers_free(headers);
return url;
}
void
http_save(struct url *url, FILE *dst_fp, off_t *offset)
{
if (url->chunked)
http_save_chunks(url, dst_fp, offset);
#ifndef NOSSL
else if (url->scheme == S_HTTPS)
tls_copy_file(url, dst_fp, offset);
#endif
else
copy_file(dst_fp, fp, offset);
}
static struct url *
http_redirect(struct url *old_url, char *location)
{
struct url *new_url;
/* absolute uri reference */
if (strncasecmp(location, "http", 4) == 0 ||
strncasecmp(location, "https", 5) == 0) {
if ((new_url = url_parse(location)) == NULL)
exit(1);
goto done;
}
/* relative uri reference */
new_url = xcalloc(1, sizeof *new_url);
new_url->scheme = old_url->scheme;
new_url->host = xstrdup(old_url->host);
new_url->port = xstrdup(old_url->port);
/* absolute-path reference */
if (location[0] == '/')
new_url->path = xstrdup(location);
else
new_url->path = relative_path_resolve(old_url->path, location);
done:
new_url->fname = xstrdup(old_url->fname);
url_free(old_url);
return new_url;
}
static char *
relative_path_resolve(const char *base_path, const char *location)
{
char *new_path, *p;
/* trim fragment component from both uri */
if ((p = strchr(location, '#')) != NULL)
*p = '\0';
if (base_path && (p = strchr(base_path, '#')) != NULL)
*p = '\0';
if (base_path == NULL)
xasprintf(&new_path, "/%s", location);
else if (base_path[strlen(base_path) - 1] == '/')
xasprintf(&new_path, "%s%s", base_path, location);
else {
p = dirname(base_path);
xasprintf(&new_path, "%s/%s",
strcmp(p, ".") == 0 ? "" : p, location);
}
return new_path;
}
static void
http_save_chunks(struct url *url, FILE *dst_fp, off_t *offset)
{
char *buf = NULL;
size_t n = 0;
uint chunk_sz;
http_getline(url->scheme, &buf, &n);
if (sscanf(buf, "%x", &chunk_sz) != 1)
errx(1, "%s: Failed to get chunk size", __func__);
while (chunk_sz > 0) {
decode_chunk(url->scheme, chunk_sz, dst_fp);
*offset += chunk_sz;
http_getline(url->scheme, &buf, &n);
if (sscanf(buf, "%x", &chunk_sz) != 1)
errx(1, "%s: Failed to get chunk size", __func__);
}
free(buf);
}
static void
decode_chunk(int scheme, uint sz, FILE *dst_fp)
{
size_t bufsz;
size_t r;
char buf[BUFSIZ], crlf[2];
bufsz = sizeof(buf);
while (sz > 0) {
if (sz < bufsz)
bufsz = sz;
r = http_read(scheme, buf, bufsz);
if (fwrite(buf, 1, r, dst_fp) != r)
errx(1, "%s: fwrite", __func__);
sz -= r;
}
/* CRLF terminating the chunk */
if (http_read(scheme, crlf, sizeof(crlf)) != sizeof(crlf))
errx(1, "%s: Failed to read terminal crlf", __func__);
if (crlf[0] != '\r' || crlf[1] != '\n')
errx(1, "%s: Invalid chunked encoding", __func__);
}
void
http_close(struct url *url)
{
#ifndef NOSSL
ssize_t r;
if (url->scheme == S_HTTPS) {
if (tls_session_fd != -1)
dprintf(STDERR_FILENO, "tls session resumed: %s\n",
tls_conn_session_resumed(ctx) ? "yes" : "no");
do {
r = tls_close(ctx);
} while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT);
tls_free(ctx);
}
#endif
fclose(fp);
}
static int
http_request(int scheme, const char *req, struct http_headers **hdrs)
{
struct http_headers *headers;
const char *e;
char *buf = NULL, *p;
size_t n = 0;
ssize_t buflen;
uint code;
#ifndef NOSSL
ssize_t nw;
#endif
if (io_debug)
fprintf(stderr, "<<< %s", req);
switch (scheme) {
#ifndef NOSSL
case S_HTTPS:
do {
nw = tls_write(ctx, req, strlen(req));
} while (nw == TLS_WANT_POLLIN || nw == TLS_WANT_POLLOUT);
if (nw == -1)
errx(1, "%s: tls_write: %s", __func__, tls_error(ctx));
break;
#endif
case S_FTP:
case S_HTTP:
if (fprintf(fp, "%s", req) < 0)
errx(1, "%s: fprintf", __func__);
(void)fflush(fp);
break;
}
http_getline(scheme, &buf, &n);
if (io_debug)
fprintf(stderr, ">>> %s", buf);
if (sscanf(buf, "%*s %u %*s", &code) != 1)
errx(1, "%s: failed to extract status code", __func__);
if (code < 100 || code > 511)
errx(1, "%s: invalid status code %d", __func__, code);
headers = xcalloc(1, sizeof *headers);
for (;;) {
buflen = http_getline(scheme, &buf, &n);
buflen -= 1;
if (buflen > 0 && buf[buflen - 1] == '\r')
buflen -= 1;
buf[buflen] = '\0';
if (io_debug)
fprintf(stderr, ">>> %s\n", buf);
if (buflen == 0)
break; /* end of headers */
if ((p = header_lookup(buf, "Content-Length:")) != NULL) {
headers->content_length = strtonum(p, 0, INT64_MAX, &e);
if (e)
err(1, "%s: Content-Length is %s: %lld",
__func__, e, headers->content_length);
}
if ((p = header_lookup(buf, "Location:")) != NULL)
headers->location = xstrdup(p);
if ((p = header_lookup(buf, "Transfer-Encoding:")) != NULL)
if (strcasestr(p, "chunked") != NULL)
headers->chunked = 1;
}
*hdrs = headers;
free(buf);
return code;
}
static void
http_headers_free(struct http_headers *headers)
{
if (headers == NULL)
return;
free(headers->location);
free(headers);
}
static char *
header_lookup(const char *buf, const char *key)
{
char *p;
if (strncasecmp(buf, key, strlen(key)) == 0) {
if ((p = strchr(buf, ' ')) == NULL)
errx(1, "Failed to parse %s", key);
return ++p;
}
return NULL;
}
static const char *
http_error(int code)
{
struct http_status error, *res;
/* Set up key */
error.code = code;
if ((res = bsearch(&error, http_status,
sizeof(http_status) / sizeof(http_status[0]) - 1,
sizeof(http_status[0]), http_status_cmp)) != NULL)
return (res->name);
return (NULL);
}
static int
http_status_cmp(const void *a, const void *b)
{
const struct http_status *ea = a;
const struct http_status *eb = b;
return (ea->code - eb->code);
}
static ssize_t
http_getline(int scheme, char **buf, size_t *n)
{
ssize_t buflen;
switch (scheme) {
#ifndef NOSSL
case S_HTTPS:
if ((buflen = tls_getline(buf, n, ctx)) == -1)
errx(1, "%s: tls_getline", __func__);
break;
#endif
case S_FTP:
case S_HTTP:
if ((buflen = getline(buf, n, fp)) == -1)
err(1, "%s: getline", __func__);
break;
default:
errx(1, "%s: invalid scheme", __func__);
}
return buflen;
}
static size_t
http_read(int scheme, char *buf, size_t size)
{
size_t r;
#ifndef NOSSL
ssize_t rs;
#endif
switch (scheme) {
#ifndef NOSSL
case S_HTTPS:
do {
rs = tls_read(ctx, buf, size);
} while (rs == TLS_WANT_POLLIN || rs == TLS_WANT_POLLOUT);
if (rs == -1)
errx(1, "%s: tls_read: %s", __func__, tls_error(ctx));
r = rs;
break;
#endif
case S_HTTP:
if ((r = fread(buf, 1, size, fp)) < size)
if (!feof(fp))
errx(1, "%s: fread", __func__);
break;
default:
errx(1, "%s: invalid scheme", __func__);
}
return r;
}
#ifndef NOSSL
void
https_init(char *tls_options)
{
char *str;
int depth;
const char *ca_file, *errstr;
if (tls_init() != 0)
errx(1, "tls_init failed");
if ((tls_config = tls_config_new()) == NULL)
errx(1, "tls_config_new failed");
if (tls_config_set_ciphers(tls_config, "legacy") != 0)
errx(1, "tls set ciphers failed: %s",
tls_config_error(tls_config));
ca_file = tls_default_ca_cert_file();
while (tls_options && *tls_options) {
switch (getsubopt(&tls_options, tls_verify_opts, &str)) {
case HTTP_TLS_CAFILE:
if (str == NULL)
errx(1, "missing CA file");
ca_file = str;
break;
case HTTP_TLS_CAPATH:
if (str == NULL)
errx(1, "missing ca path");
if (tls_config_set_ca_path(tls_config, str) != 0)
errx(1, "tls ca path failed");
break;
case HTTP_TLS_CIPHERS:
if (str == NULL)
errx(1, "missing cipher list");
if (tls_config_set_ciphers(tls_config, str) != 0)
errx(1, "tls set ciphers failed");
break;
case HTTP_TLS_DONTVERIFY:
tls_config_insecure_noverifycert(tls_config);
tls_config_insecure_noverifyname(tls_config);
break;
case HTTP_TLS_VERIFYDEPTH:
if (str == NULL)
errx(1, "missing depth");
depth = strtonum(str, 0, INT_MAX, &errstr);
if (errstr)
errx(1, "Cert validation depth is %s", errstr);
tls_config_set_verify_depth(tls_config, depth);
break;
case HTTP_TLS_MUSTSTAPLE:
tls_config_ocsp_require_stapling(tls_config);
break;
case HTTP_TLS_NOVERIFYTIME:
tls_config_insecure_noverifytime(tls_config);
break;
case HTTP_TLS_SESSION:
if (str == NULL)
errx(1, "missing session file");
tls_session_fd = open(str, O_RDWR|O_CREAT, 0600);
if (tls_session_fd == -1)
err(1, "failed to open or create session file "
"'%s'", str);
if (tls_config_set_session_fd(tls_config,
tls_session_fd) == -1)
errx(1, "failed to set session: %s",
tls_config_error(tls_config));
break;
case HTTP_TLS_DOVERIFY:
/* For compatibility, we do verify by default */
break;
default:
errx(1, "Unknown -S suboption `%s'",
suboptarg ? suboptarg : "");
}
}
if (tls_config_set_ca_file(tls_config, ca_file) == -1)
errx(1, "tls_config_set_ca_file failed");
}
static ssize_t
tls_getline(char **buf, size_t *buflen, struct tls *tls)
{
char *newb;
size_t newlen, off;
int ret;
unsigned char c;
if (buf == NULL || buflen == NULL)
return -1;
/* If buf is NULL, we have to assume a size of zero */
if (*buf == NULL)
*buflen = 0;
off = 0;
do {
do {
ret = tls_read(tls, &c, 1);
} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
if (ret == -1)
return -1;
/* Ensure we can handle it */
if (off + 2 > SSIZE_MAX)
return -1;
newlen = off + 2; /* reserve space for NUL terminator */
if (newlen > *buflen) {
newlen = newlen < MINBUF ? MINBUF : *buflen * 2;
newb = recallocarray(*buf, *buflen, newlen, 1);
if (newb == NULL)
return -1;
*buf = newb;
*buflen = newlen;
}
*(*buf + off) = c;
off += 1;
} while (c != '\n');
*(*buf + off) = '\0';
return off;
}
static void
tls_copy_file(struct url *url, FILE *dst_fp, off_t *offset)
{
char *tmp_buf;
ssize_t r;
tmp_buf = xmalloc(TMPBUF_LEN);
for (;;) {
do {
r = tls_read(ctx, tmp_buf, TMPBUF_LEN);
} while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT);
if (r == -1)
errx(1, "%s: tls_read: %s", __func__, tls_error(ctx));
else if (r == 0)
break;
*offset += r;
if (fwrite(tmp_buf, 1, r, dst_fp) != (size_t)r)
err(1, "%s: fwrite", __func__);
}
free(tmp_buf);
}
#endif /* NOSSL */