server_file.c revision 1.10
1/*	$OpenBSD: server_file.c,v 1.10 2014/07/23 22:20:37 reyk Exp $	*/
2
3/*
4 * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/queue.h>
21#include <sys/time.h>
22#include <sys/stat.h>
23#include <sys/socket.h>
24#include <sys/un.h>
25#include <sys/tree.h>
26#include <sys/hash.h>
27
28#include <net/if.h>
29#include <netinet/in_systm.h>
30#include <netinet/in.h>
31#include <netinet/ip.h>
32#include <netinet/tcp.h>
33#include <arpa/inet.h>
34
35#include <errno.h>
36#include <fcntl.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40#include <stdio.h>
41#include <err.h>
42#include <event.h>
43
44#include <openssl/ssl.h>
45
46#include "httpd.h"
47#include "http.h"
48
49int	 server_file_access(struct http_descriptor *, char *, size_t,
50	    struct stat *);
51void	 server_file_error(struct bufferevent *, short, void *);
52
53int
54server_file_access(struct http_descriptor *desc, char *path, size_t len,
55    struct stat *st)
56{
57	errno = 0;
58	if (access(path, R_OK) == -1) {
59		goto fail;
60	} else if (stat(path, st) == -1) {
61		goto fail;
62	} else if (S_ISDIR(st->st_mode)) {
63		/* XXX Should we support directory listing? */
64
65		if (!len) {
66			/* Recursion - the index "file" is a directory? */
67			errno = EINVAL;
68			goto fail;
69		}
70
71		/* Redirect to path with trailing "/" */
72		if (path[strlen(path) - 1] != '/') {
73			/* Remove the document root to get the relative URL */
74			if (canonicalize_path(NULL,
75			    desc->http_path, path, len) == NULL ||
76			    strlcat(path, "/", len) >= len) {
77				errno = EINVAL;
78				goto fail;
79			}
80
81			/* Indicate that the file has been moved */
82			return (301);
83		}
84
85		/* Otherwise append the default index file */
86		if (strlcat(path, HTTPD_INDEX, len) >= len) {
87			errno = EACCES;
88			goto fail;
89		}
90
91		/* Check again but set len to 0 to avoid recursion */
92		return (server_file_access(desc, path, 0, st));
93	} else if (!S_ISREG(st->st_mode)) {
94		/* Don't follow symlinks and ignore special files */
95		errno = EACCES;
96		goto fail;
97	}
98
99	return (0);
100
101 fail:
102	/* Remove the document root */
103	if (len && canonicalize_path(NULL, desc->http_path, path, len) == NULL)
104		return (500);
105
106	switch (errno) {
107	case ENOENT:
108		return (404);
109	case EACCES:
110		return (403);
111	default:
112		return (500);
113	}
114
115	/* NOTREACHED */
116}
117
118int
119server_file(struct httpd *env, struct client *clt)
120{
121	struct http_descriptor	*desc = clt->clt_desc;
122	struct server		*srv = clt->clt_server;
123	struct media_type	*media;
124	const char		*errstr = NULL;
125	int			 fd = -1, ret;
126	char			 path[MAXPATHLEN];
127	struct stat		 st;
128
129	if (canonicalize_path(HTTPD_DOCROOT,
130	    desc->http_path, path, sizeof(path)) == NULL) {
131		/* Do not echo the uncanonicalized path */
132		server_abort_http(clt, 500, "invalid request path");
133		return (-1);
134	}
135
136	/* Returns HTTP status code on error */
137	if ((ret = server_file_access(desc, path, sizeof(path), &st)) != 0) {
138		server_abort_http(clt, ret, path);
139		return (-1);
140	}
141
142	/* Now open the file, should be readable or we have another problem */
143	if ((fd = open(path, O_RDONLY)) == -1)
144		goto fail;
145
146	/* File descriptor is opened, decrement inflight counter */
147	server_inflight_dec(clt, __func__);
148
149	media = media_find(env->sc_mediatypes, path);
150	ret = server_response_http(clt, 200, media, st.st_size);
151	switch (ret) {
152	case -1:
153		goto fail;
154	case 0:
155		/* Connection is already finished */
156		close(fd);
157		return (0);
158	default:
159		break;
160	}
161
162	clt->clt_fd = fd;
163	if (clt->clt_file != NULL)
164		bufferevent_free(clt->clt_file);
165
166	clt->clt_file = bufferevent_new(clt->clt_fd, server_read,
167	    server_write, server_file_error, clt);
168	if (clt->clt_file == NULL) {
169		errstr = "failed to allocate file buffer event";
170		goto fail;
171	}
172
173	bufferevent_settimeout(clt->clt_file,
174	    srv->srv_conf.timeout.tv_sec, srv->srv_conf.timeout.tv_sec);
175	bufferevent_enable(clt->clt_file, EV_READ);
176	bufferevent_disable(clt->clt_bev, EV_READ);
177
178	return (0);
179 fail:
180	if (errstr == NULL)
181		errstr = strerror(errno);
182	server_abort_http(clt, 500, errstr);
183	return (-1);
184}
185
186void
187server_file_error(struct bufferevent *bev, short error, void *arg)
188{
189	struct client		*clt = arg;
190	struct evbuffer		*dst;
191
192	if (error & EVBUFFER_TIMEOUT) {
193		server_close(clt, "buffer event timeout");
194		return;
195	}
196	if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
197		bufferevent_disable(bev, EV_READ);
198
199		clt->clt_done = 1;
200
201		dst = EVBUFFER_OUTPUT(clt->clt_bev);
202		if (EVBUFFER_LENGTH(dst)) {
203			/* Finish writing all data first */
204			bufferevent_enable(clt->clt_bev, EV_WRITE);
205			return;
206		}
207
208		if (clt->clt_persist) {
209			/* Close input file and wait for next HTTP request */
210			if (clt->clt_fd != -1)
211				close(clt->clt_fd);
212			clt->clt_fd = -1;
213			clt->clt_toread = TOREAD_HTTP_HEADER;
214			server_reset_http(clt);
215			bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
216			return;
217		}
218		server_close(clt, "done");
219		return;
220	}
221	if (error & EVBUFFER_ERROR && errno == EFBIG) {
222		bufferevent_enable(bev, EV_READ);
223		return;
224	}
225	server_close(clt, "buffer event error");
226	return;
227}
228