[BACK]Return to http.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ftp

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 */