CVE-2017-5850: Allocation of Resources Without Limits or Throttling (CWE-770)
value: httpd in OpenBSD allows remote attackers to cause a denial of service (memory consumption) via a series of requests for a large file using an HTTP Range header.
CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVSS v3 base score: 7.5
Published: 2017-03-27T15:59Z
Repository: https://github.com/openbsd/src
Language: C
Files changed: usr.sbin/httpd/httpd.h, usr.sbin/httpd/server_file.c, usr.sbin/httpd/server_http.c
Commit date: 2017-01-31 14:39:47 +0000
Commit message: Reimplement httpd's support for byte ranges.

The previous implementation loaded all the output into a single output
buffer and used its size to determine the Content-Length of the body.

The new implementation calculates the body length first and writes the
individual ranges in an async way using the bufferevent mechanism.

This prevents httpd from using too much memory and applies the
watermark and throttling mechanisms to range requests.

Problem reported by Pierre Kim (pierre.kim.sec at gmail.com)

OK benno@ sunil@

Diff: diff --git a/usr.sbin/httpd/httpd.h b/usr.sbin/httpd/httpd.h
index 94fe76534af7..5490b4919ccc 100644
--- a/usr.sbin/httpd/httpd.h
+++ b/usr.sbin/httpd/httpd.h
@@ -1,6 +1,6 @@
-/*	$OpenBSD: httpd.h,v 1.126 2017/01/31 12:21:27 reyk Exp $	*/
+/*	$OpenBSD: httpd.h,v 1.127 2017/01/31 14:39:47 reyk Exp $	*/
 
 /*
  * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
  * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -71,10 +71,11 @@
 #define SERVER_BACKLOG		10
 #define SERVER_OUTOF_FD_RETRIES	5
 #define SERVER_MAX_PREFETCH	256
 #define SERVER_MIN_PREFETCHED	32
 #define SERVER_HSTS_DEFAULT_AGE	31536000
+#define SERVER_MAX_RANGES	4
 
 #define MEDIATYPE_NAMEMAX	128	/* file name extension */
 #define MEDIATYPE_TYPEMAX	64	/* length of type/subtype */
 
 #define CONFIG_RELOAD		0x00
@@ -91,11 +92,12 @@
 enum httpchunk {
 	TOREAD_UNLIMITED		= -1,
 	TOREAD_HTTP_HEADER		= -2,
 	TOREAD_HTTP_CHUNK_LENGTH	= -3,
 	TOREAD_HTTP_CHUNK_TRAILER	= -4,
-	TOREAD_HTTP_NONE		= -5
+	TOREAD_HTTP_NONE		= -5,
+	TOREAD_HTTP_RANGE		= TOREAD_HTTP_CHUNK_LENGTH
 };
 
 #if DEBUG
 #define DPRINTF		log_debug
 #else
@@ -293,10 +295,26 @@ struct fcgi_data {
 	int			 end;
 	int			 status;
 	int			 headersdone;
 };
 
+struct range {
+	off_t	start;
+	off_t	end;
+};
+
+struct range_data {
+	struct range		 range[SERVER_MAX_RANGES];
+	int			 range_count;
+	int			 range_index;
+	off_t			 range_toread;
+
+	/* For the Content headers in each part */
+	struct media_type	*range_media;
+	size_t			 range_total;
+};
+
 struct client {
 	uint32_t		 clt_id;
 	pid_t			 clt_pid;
 	void			*clt_srv;
 	void			*clt_srv_conf;
@@ -311,10 +329,11 @@ struct client {
 	struct evbuffer		*clt_output;
 	struct event		 clt_ev;
 	void			*clt_descreq;
 	void			*clt_descresp;
 	int			 clt_sndbufsiz;
+	uint64_t		 clt_boundary;
 
 	int			 clt_fd;
 	struct tls		*clt_tls_ctx;
 	struct bufferevent	*clt_srvbev;
 	int			 clt_srvbev_throttled;
@@ -325,10 +344,11 @@ struct client {
 	unsigned int		 clt_persist;
 	int			 clt_line;
 	int			 clt_done;
 	int			 clt_chunk;
 	int			 clt_inflight;
+	struct range_data	 clt_ranges;
 	struct fcgi_data	 clt_fcgi;
 	char			*clt_remote_user;
 	struct evbuffer		*clt_srvevb;
 
 	struct evbuffer		*clt_log;
@@ -599,10 +619,11 @@ const char
 	*server_httpmethod_byid(unsigned int);
 const char
 	*server_httperror_byid(unsigned int);
 void	 server_read_httpcontent(struct bufferevent *, void *);
 void	 server_read_httpchunks(struct bufferevent *, void *);
+void	 server_read_httprange(struct bufferevent *, void *);
 int	 server_writeheader_http(struct client *clt, struct kv *, void *);
 int	 server_headers(struct client *, void *,
 	    int (*)(struct client *, struct kv *, void *), void *);
 int	 server_writeresponse_http(struct client *);
 int	 server_response_http(struct client *, unsigned int, struct media_type *,
diff --git a/usr.sbin/httpd/server_file.c b/usr.sbin/httpd/server_file.c
index 500742ec9278..62c6fad306e8 100644
--- a/usr.sbin/httpd/server_file.c
+++ b/usr.sbin/httpd/server_file.c
@@ -1,9 +1,9 @@
-/*	$OpenBSD: server_file.c,v 1.63 2017/01/30 09:54:41 reyk Exp $	*/
+/*	$OpenBSD: server_file.c,v 1.64 2017/01/31 14:39:47 reyk Exp $	*/
 
 /*
- * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2006 - 2017 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.
  *
@@ -34,16 +34,10 @@
 #include "httpd.h"
 #include "http.h"
 
 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
 #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
-#define MAX_RANGES	4
-
-struct range {
-	off_t	start;
-	off_t	end;
-};
 
 int		 server_file_access(struct httpd *, struct client *,
 		    char *, size_t);
 int		 server_file_request(struct httpd *, struct client *,
 		    char *, struct stat *);
@@ -53,12 +47,11 @@ int		 server_file_index(struct httpd *, struct client *,
 		    struct stat *);
 int		 server_file_modified_since(struct http_descriptor *,
 		    struct stat *);
 int		 server_file_method(struct client *);
 int		 parse_range_spec(char *, size_t, struct range *);
-struct range	*parse_range(char *, size_t, int *);
-int		 buffer_add_range(int, struct evbuffer *, struct range *);
+int		 parse_ranges(struct client *, char *, size_t);
 
 int
 server_file_access(struct httpd *env, struct client *clt,
     char *path, size_t len)
 {
@@ -301,23 +294,22 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path,
 {
 	struct server_config	*srv_conf = clt->clt_srv_conf;
 	struct http_descriptor	*resp = clt->clt_descresp;
 	struct http_descriptor	*desc = clt->clt_descreq;
 	struct media_type	*media, multipart_media;
+	struct range_data	*r = &clt->clt_ranges;
 	struct range		*range;
-	struct evbuffer		*evb = NULL;
-	size_t			 content_length;
+	size_t			 content_length = 0;
 	int			 code = 500, fd = -1, i, nranges, ret;
-	uint32_t		 boundary;
 	char			 content_range[64];
 	const char		*errstr = NULL;
 
 	/* Ignore range request for methods other than GET */
 	if (desc->http_method != HTTP_METHOD_GET)
 		return server_file_request(env, clt, path, st);
 
-	if ((range = parse_range(range_str, st->st_size, &nranges)) == NULL) {
+	if ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) {
 		code = 416;
 		(void)snprintf(content_range, sizeof(content_range),
 		    "bytes */%lld", st->st_size);
 		errstr = content_range;
 		goto abort;
@@ -326,107 +318,104 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path,
 	/* Now open the file, should be readable or we have another problem */
 	if ((fd = open(path, O_RDONLY)) == -1)
 		goto abort;
 
 	media = media_find_config(env, srv_conf, path);
-	if ((evb = evbuffer_new()) == NULL) {
-		errstr = "failed to allocate file buffer";
-		goto abort;
-	}
+	r->range_media = media;
 
 	if (nranges == 1) {
+		range = &r->range[0];
 		(void)snprintf(content_range, sizeof(content_range),
 		    "bytes %lld-%lld/%lld", range->start, range->end,
 		    st->st_size);
 		if (kv_add(&resp->http_headers, "Content-Range",
 		    content_range) == NULL)
 			goto abort;
 
-		content_length = range->end - range->start + 1;
-		if (buffer_add_range(fd, evb, range) == 0)
-			goto abort;
-
+		range = &r->range[0];
+		content_length += range->end - range->start + 1;
 	} else {
-		content_length = 0;
-		boundary = arc4random();
-		/* Generate a multipart payload of byteranges */
-		while (nranges--) {
-			if ((i = evbuffer_add_printf(evb, "\r\n--%ud\r\n",
-			    boundary)) == -1)
-				goto abort;
+		/* Add boundary, all parts will be handled by the callback */
+		arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary));
 
-			content_length += i;
-			if ((i = evbuffer_add_printf(evb,
-			    "Content-Type: %s/%s\r\n",
-			    media->media_type, media->media_subtype)) == -1)
-				goto abort;
+		/* Calculate Content-Length of the complete multipart body */
+		for (i = 0; i < nranges; i++) {
+			range = &r->range[i];
 
-			content_length += i;
-			if ((i = evbuffer_add_printf(evb,
+			/* calculate Content-Length of the complete body */
+			if ((ret = snprintf(NULL, 0,
+			    "\r\n--%llu\r\n"
+			    "Content-Type: %s/%s\r\n"
 			    "Content-Range: bytes %lld-%lld/%lld\r\n\r\n",
-			    range->start, range->end, st->st_size)) == -1)
+			    clt->clt_boundary,
+			    media->media_type, media->media_subtype,
+			    range->start, range->end, st->st_size)) < 0)
 				goto abort;
 
-			content_length += i;
-			if (buffer_add_range(fd, evb, range) == 0)
-				goto abort;
+			/* Add data length */
+			content_length += ret + range->end - range->start + 1;
 
-			content_length += range->end - range->start + 1;
-			range++;
 		}
-
-		if ((i = evbuffer_add_printf(evb, "\r\n--%ud--\r\n",
-		    boundary)) == -1)
+		if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n",
+		    clt->clt_boundary)) < 0)
 			goto abort;
-
-		content_length += i;
+		content_length += ret;
 
 		/* prepare multipart/byteranges media type */
 		(void)strlcpy(multipart_media.media_type, "multipart",
 		    sizeof(multipart_media.media_type));
 		(void)snprintf(multipart_media.media_subtype,
 		    sizeof(multipart_media.media_subtype),
-		    "byteranges; boundary=%ud", boundary);
+		    "byteranges; boundary=%llu", clt->clt_boundary);
 		media = &multipart_media;
 	}
 
-	close(fd);
-	fd = -1;
+	/* Start with first range */
+	r->range_toread = TOREAD_HTTP_RANGE;
 
 	ret = server_response_http(clt, 206, media, content_length,
 	    MINIMUM(time(NULL), st->st_mtim.tv_sec));
 	switch (ret) {
 	case -1:
 		goto fail;
 	case 0:
 		/* Connection is already finished */
+		close(fd);
 		goto done;
 	default:
 		break;
 	}
 
-	if (server_bufferevent_write_buffer(clt, evb) == -1)
+	clt->clt_fd = fd;
+	if (clt->clt_srvbev != NULL)
+		bufferevent_free(clt->clt_srvbev);
+
+	clt->clt_srvbev_throttled = 0;
+	clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange,
+	    server_write, server_file_error, clt);
+	if (clt->clt_srvbev == NULL) {
+		errstr = "failed to allocate file buffer event";
 		goto fail;
+	}
 
-	bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
-	if (clt->clt_persist)
-		clt->clt_toread = TOREAD_HTTP_HEADER;
-	else
-		clt->clt_toread = TOREAD_HTTP_NONE;
-	clt->clt_done = 0;
+	/* Adjust read watermark to the socket output buffer size */
+	bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
+	    clt->clt_sndbufsiz);
+
+	bufferevent_settimeout(clt->clt_srvbev,
+	    srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
+	bufferevent_enable(clt->clt_srvbev, EV_READ);
+	bufferevent_disable(clt->clt_bev, EV_READ);
 
  done:
-	evbuffer_free(evb);
 	server_reset_http(clt);
 	return (0);
  fail:
 	bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
 	bufferevent_free(clt->clt_bev);
 	clt->clt_bev = NULL;
  abort:
-	if (evb != NULL)
-		evbuffer_free(evb);
 	if (fd != -1)
 		close(fd);
 	if (errstr == NULL)
 		errstr = strerror(errno);
 	server_abort_http(clt, code, errstr);
@@ -666,45 +655,48 @@ server_file_modified_since(struct http_descriptor *desc, struct stat *st)
 	}
 
 	return (-1);
 }
 
-struct range *
-parse_range(char *str, size_t file_sz, int *nranges)
+int
+parse_ranges(struct client *clt, char *str, size_t file_sz)
 {
-	static struct range	 ranges[MAX_RANGES];
 	int			 i = 0;
 	char			*p, *q;
+	struct range_data	*r = &clt->clt_ranges;
+
+	memset(r, 0, sizeof(*r));
 
 	/* Extract range unit */
 	if ((p = strchr(str, '=')) == NULL)
-		return (NULL);
+		return (-1);
 
 	*p++ = '\0';
 	/* Check if it's a bytes range spec */
 	if (strcmp(str, "bytes") != 0)
-		return (NULL);
+		return (-1);
 
 	while ((q = strchr(p, ',')) != NULL) {
 		*q++ = '\0';
 
 		/* Extract start and end positions */
-		if (parse_range_spec(p, file_sz, &ranges[i]) == 0)
+		if (parse_range_spec(p, file_sz, &r->range[i]) == 0)
 			continue;
 
 		i++;
-		if (i == MAX_RANGES)
-			return (NULL);
+		if (i == SERVER_MAX_RANGES)
+			return (-1);
 
 		p = q;
 	}
 
-	if (parse_range_spec(p, file_sz, &ranges[i]) != 0)
+	if (parse_range_spec(p, file_sz, &r->range[i]) != 0)
 		i++;
 
-	*nranges = i;
-	return (i ? ranges : NULL);
+	r->range_total = file_sz;
+	r->range_count = i;
+	return (i);
 }
 
 int
 parse_range_spec(char *str, size_t size, struct range *r)
 {
@@ -750,28 +742,5 @@ parse_range_spec(char *str, size_t size, struct range *r)
 	if (r->end < r->start)
 		return (0);
 
 	return (1);
 }
-
-int
-buffer_add_range(int fd, struct evbuffer *evb, struct range *range)
-{
-	char	buf[BUFSIZ];
-	size_t	n, range_sz;
-	ssize_t	nread;
-
-	if (lseek(fd, range->start, SEEK_SET) == -1)
-		return (0);
-
-	range_sz = range->end - range->start + 1;
-	while (range_sz) {
-		n = MINIMUM(range_sz, sizeof(buf));
-		if ((nread = read(fd, buf, n)) == -1)
-			return (0);
-
-		evbuffer_add(evb, buf, nread);
-		range_sz -= nread;
-	}
-
-	return (1);
-}
diff --git a/usr.sbin/httpd/server_http.c b/usr.sbin/httpd/server_http.c
index 9b4457d6b5d7..b5ea1a8e27ef 100644
--- a/usr.sbin/httpd/server_http.c
+++ b/usr.sbin/httpd/server_http.c
@@ -1,9 +1,9 @@
-/*	$OpenBSD: server_http.c,v 1.111 2017/01/31 12:21:27 reyk Exp $	*/
+/*	$OpenBSD: server_http.c,v 1.112 2017/01/31 14:39:47 reyk Exp $	*/
 
 /*
- * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2006 - 2017 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.
  *
@@ -607,10 +607,105 @@ server_read_httpchunks(struct bufferevent *bev, void *arg)
 	return;
  fail:
 	server_close(clt, strerror(errno));
 }
 
+void
+server_read_httprange(struct bufferevent *bev, void *arg)
+{
+	struct client		*clt = arg;
+	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	size_t			 size;
+	struct media_type	*media;
+	struct range_data	*r = &clt->clt_ranges;
+	struct range		*range;
+
+	getmonotime(&clt->clt_tv_last);
+
+	if (r->range_toread > 0) {
+		size = EVBUFFER_LENGTH(src);
+		if (!size)
+			return;
+
+		/* Read chunk data */
+		if ((off_t)size > r->range_toread) {
+			size = r->range_toread;
+			if (server_bufferevent_write_chunk(clt, src, size)
+			    == -1)
+				goto fail;
+			r->range_toread = 0;
+		} else {
+			if (server_bufferevent_write_buffer(clt, src) == -1)
+				goto fail;
+			r->range_toread -= size;
+		}
+		if (r->range_toread < 1)
+			r->range_toread = TOREAD_HTTP_RANGE;
+		DPRINTF("%s: done, size %lu, to read %lld", __func__,
+		    size, r->range_toread);
+	}
+
+	switch (r->range_toread) {
+	case TOREAD_HTTP_RANGE:
+		if (r->range_index >= r->range_count) {
+			if (r->range_count > 1) {
+				/* Add end marker */
+				if (server_bufferevent_printf(clt,
+				    "\r\n--%llu--\r\n",
+				    clt->clt_boundary) == -1)
+					goto fail;
+			}
+			r->range_toread = TOREAD_HTTP_NONE;
+			break;
+		}
+
+		range = &r->range[r->range_index];
+
+		if (r->range_count > 1) {
+			media = r->range_media;
+			if (server_bufferevent_printf(clt,
+			    "\r\n--%llu\r\n"
+			    "Content-Type: %s/%s\r\n"
+			    "Content-Range: bytes %lld-%lld/%zu\r\n\r\n",
+			    clt->clt_boundary,
+			    media->media_type, media->media_subtype,
+			    range->start, range->end, r->range_total) == -1)
+				goto fail;
+		}
+		r->range_toread = range->end - range->start + 1;
+
+		if (lseek(clt->clt_fd, range->start, SEEK_SET) == -1)
+			goto fail;
+
+		/* Throw away bytes that are already in the input buffer */
+		evbuffer_drain(src, EVBUFFER_LENGTH(src));
+
+		/* Increment for the next part */
+		r->range_index++;
+		break;
+	case TOREAD_HTTP_NONE:
+	case 0:
+		break;
+	}
+
+	if (clt->clt_done)
+		goto done;
+
+	if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(clt->clt_bev)) > (size_t)
+	    SERVER_MAX_PREFETCH * clt->clt_sndbufsiz) {
+		bufferevent_disable(clt->clt_srvbev, EV_READ);
+		clt->clt_srvbev_throttled = 1;
+	}
+
+	return;
+ done:
+	(*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg);
+	return;
+ fail:
+	server_close(clt, strerror(errno));
+}
+
 void
 server_reset_http(struct client *clt)
 {
 	struct server		*srv = clt->clt_srv;
 

Vulnerable code: /*	$OpenBSD: httpd.h,v 1.126 2017/01/31 12:21:27 reyk Exp $	*/
	TOREAD_HTTP_NONE		= -5
/*	$OpenBSD: server_file.c,v 1.63 2017/01/30 09:54:41 reyk Exp $	*/
 * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
#define MAX_RANGES	4

struct range {
	off_t	start;
	off_t	end;
};
struct range	*parse_range(char *, size_t, int *);
int		 buffer_add_range(int, struct evbuffer *, struct range *);
	struct evbuffer		*evb = NULL;
	size_t			 content_length;
	uint32_t		 boundary;
	if ((range = parse_range(range_str, st->st_size, &nranges)) == NULL) {
	if ((evb = evbuffer_new()) == NULL) {
		errstr = "failed to allocate file buffer";
		goto abort;
	}
		content_length = range->end - range->start + 1;
		if (buffer_add_range(fd, evb, range) == 0)
			goto abort;

		content_length = 0;
		boundary = arc4random();
		/* Generate a multipart payload of byteranges */
		while (nranges--) {
			if ((i = evbuffer_add_printf(evb, "\r\n--%ud\r\n",
			    boundary)) == -1)
				goto abort;
			content_length += i;
			if ((i = evbuffer_add_printf(evb,
			    "Content-Type: %s/%s\r\n",
			    media->media_type, media->media_subtype)) == -1)
				goto abort;
			content_length += i;
			if ((i = evbuffer_add_printf(evb,
			    range->start, range->end, st->st_size)) == -1)
			content_length += i;
			if (buffer_add_range(fd, evb, range) == 0)
				goto abort;
			content_length += range->end - range->start + 1;
			range++;

		if ((i = evbuffer_add_printf(evb, "\r\n--%ud--\r\n",
		    boundary)) == -1)

		content_length += i;
		    "byteranges; boundary=%ud", boundary);
	close(fd);
	fd = -1;
	if (server_bufferevent_write_buffer(clt, evb) == -1)
	bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
	if (clt->clt_persist)
		clt->clt_toread = TOREAD_HTTP_HEADER;
	else
		clt->clt_toread = TOREAD_HTTP_NONE;
	clt->clt_done = 0;
	evbuffer_free(evb);
	if (evb != NULL)
		evbuffer_free(evb);
struct range *
parse_range(char *str, size_t file_sz, int *nranges)
	static struct range	 ranges[MAX_RANGES];
		return (NULL);
		return (NULL);
		if (parse_range_spec(p, file_sz, &ranges[i]) == 0)
		if (i == MAX_RANGES)
			return (NULL);
	if (parse_range_spec(p, file_sz, &ranges[i]) != 0)
	*nranges = i;
	return (i ? ranges : NULL);

int
buffer_add_range(int fd, struct evbuffer *evb, struct range *range)
{
	char	buf[BUFSIZ];
	size_t	n, range_sz;
	ssize_t	nread;

	if (lseek(fd, range->start, SEEK_SET) == -1)
		return (0);

	range_sz = range->end - range->start + 1;
	while (range_sz) {
		n = MINIMUM(range_sz, sizeof(buf));
		if ((nread = read(fd, buf, n)) == -1)
			return (0);

		evbuffer_add(evb, buf, nread);
		range_sz -= nread;
	}

	return (1);
}
/*	$OpenBSD: server_http.c,v 1.111 2017/01/31 12:21:27 reyk Exp $	*/
 * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
Fixed code: /*	$OpenBSD: httpd.h,v 1.127 2017/01/31 14:39:47 reyk Exp $	*/
#define SERVER_MAX_RANGES	4
	TOREAD_HTTP_NONE		= -5,
	TOREAD_HTTP_RANGE		= TOREAD_HTTP_CHUNK_LENGTH
struct range {
	off_t	start;
	off_t	end;
};

struct range_data {
	struct range		 range[SERVER_MAX_RANGES];
	int			 range_count;
	int			 range_index;
	off_t			 range_toread;

	/* For the Content headers in each part */
	struct media_type	*range_media;
	size_t			 range_total;
};

	uint64_t		 clt_boundary;
	struct range_data	 clt_ranges;
void	 server_read_httprange(struct bufferevent *, void *);
/*	$OpenBSD: server_file.c,v 1.64 2017/01/31 14:39:47 reyk Exp $	*/
 * Copyright (c) 2006 - 2017 Reyk Floeter <reyk@openbsd.org>
int		 parse_ranges(struct client *, char *, size_t);
	struct range_data	*r = &clt->clt_ranges;
	size_t			 content_length = 0;
	if ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) {
	r->range_media = media;
		range = &r->range[0];
		range = &r->range[0];
		content_length += range->end - range->start + 1;
		/* Add boundary, all parts will be handled by the callback */
		arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary));
		/* Calculate Content-Length of the complete multipart body */
		for (i = 0; i < nranges; i++) {
			range = &r->range[i];
			/* calculate Content-Length of the complete body */
			if ((ret = snprintf(NULL, 0,
			    "\r\n--%llu\r\n"
			    "Content-Type: %s/%s\r\n"
			    clt->clt_boundary,
			    media->media_type, media->media_subtype,
			    range->start, range->end, st->st_size)) < 0)
			/* Add data length */
			content_length += ret + range->end - range->start + 1;
		if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n",
		    clt->clt_boundary)) < 0)
		content_length += ret;
		    "byteranges; boundary=%llu", clt->clt_boundary);
	/* Start with first range */
	r->range_toread = TOREAD_HTTP_RANGE;
		close(fd);
	clt->clt_fd = fd;
	if (clt->clt_srvbev != NULL)
		bufferevent_free(clt->clt_srvbev);

	clt->clt_srvbev_throttled = 0;
	clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange,
	    server_write, server_file_error, clt);
	if (clt->clt_srvbev == NULL) {
		errstr = "failed to allocate file buffer event";
	}
	/* Adjust read watermark to the socket output buffer size */
	bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
	    clt->clt_sndbufsiz);

	bufferevent_settimeout(clt->clt_srvbev,
	    srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
	bufferevent_enable(clt->clt_srvbev, EV_READ);
	bufferevent_disable(clt->clt_bev, EV_READ);
int
parse_ranges(struct client *clt, char *str, size_t file_sz)
	struct range_data	*r = &clt->clt_ranges;

	memset(r, 0, sizeof(*r));
		return (-1);
		return (-1);
		if (parse_range_spec(p, file_sz, &r->range[i]) == 0)
		if (i == SERVER_MAX_RANGES)
			return (-1);
	if (parse_range_spec(p, file_sz, &r->range[i]) != 0)
	r->range_total = file_sz;
	r->range_count = i;
	return (i);
/*	$OpenBSD: server_http.c,v 1.112 2017/01/31 14:39:47 reyk Exp $	*/
 * Copyright (c) 2006 - 2017 Reyk Floeter <reyk@openbsd.org>
void
server_read_httprange(struct bufferevent *bev, void *arg)
{
	struct client		*clt = arg;
	struct evbuffer		*src = EVBUFFER_INPUT(bev);
	size_t			 size;
	struct media_type	*media;
	struct range_data	*r = &clt->clt_ranges;
	struct range		*range;

	getmonotime(&clt->clt_tv_last);

	if (r->range_toread > 0) {
		size = EVBUFFER_LENGTH(src);
		if (!size)
			return;

		/* Read chunk data */
		if ((off_t)size > r->range_toread) {
			size = r->range_toread;
			if (server_bufferevent_write_chunk(clt, src, size)
			    == -1)
				goto fail;
			r->range_toread = 0;
		} else {
			if (server_bufferevent_write_buffer(clt, src) == -1)
				goto fail;
			r->range_toread -= size;
		}
		if (r->range_toread < 1)
			r->range_toread = TOREAD_HTTP_RANGE;
		DPRINTF("%s: done, size %lu, to read %lld", __func__,
		    size, r->range_toread);
	}

	switch (r->range_toread) {
	case TOREAD_HTTP_RANGE:
		if (r->range_index >= r->range_count) {
			if (r->range_count > 1) {
				/* Add end marker */
				if (server_bufferevent_printf(clt,
				    "\r\n--%llu--\r\n",
				    clt->clt_boundary) == -1)
					goto fail;
			}
			r->range_toread = TOREAD_HTTP_NONE;
			break;
		}

		range = &r->range[r->range_index];

		if (r->range_count > 1) {
			media = r->range_media;
			if (server_bufferevent_printf(clt,
			    "\r\n--%llu\r\n"
			    "Content-Type: %s/%s\r\n"
			    "Content-Range: bytes %lld-%lld/%zu\r\n\r\n",
			    clt->clt_boundary,
			    media->media_type, media->media_subtype,
			    range->start, range->end, r->range_total) == -1)
				goto fail;
		}
		r->range_toread = range->end - range->start + 1;

		if (lseek(clt->clt_fd, range->start, SEEK_SET) == -1)
			goto fail;

		/* Throw away bytes that are already in the input buffer */
		evbuffer_drain(src, EVBUFFER_LENGTH(src));

		/* Increment for the next part */
		r->range_index++;
		break;
	case TOREAD_HTTP_NONE:
	case 0:
		break;
	}

	if (clt->clt_done)
		goto done;

	if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(clt->clt_bev)) > (size_t)
	    SERVER_MAX_PREFETCH * clt->clt_sndbufsiz) {
		bufferevent_disable(clt->clt_srvbev, EV_READ);
		clt->clt_srvbev_throttled = 1;
	}

	return;
 done:
	(*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg);
	return;
 fail:
	server_close(clt, strerror(errno));
}