1/*-
2 * Copyright (c) 2005-2006 Robert N. M. Watson
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29#include <sys/types.h>
30#include <sys/mman.h>
31#include <sys/socket.h>
32#include <sys/uio.h>
33#include <sys/utsname.h>
34#include <sys/wait.h>
35
36#include <netinet/in.h>
37
38#include <arpa/inet.h>
39
40#include <err.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <limits.h>
44#include <pthread.h>
45#include <signal.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <sysexits.h>
50#include <unistd.h>
51
52static int	threaded;		/* 1 for threaded, 0 for forked. */
53
54/*
55 * Simple, multi-threaded/multi-process HTTP server.  Very dumb.
56 *
57 * If a path is specified as an argument, only that file is served.  If no
58 * path is specified, httpd will create one file to send per server thread.
59 */
60#define	THREADS		128
61#define	BUFFER		1024
62#define	FILESIZE	1024
63
64#define	HTTP_OK		"HTTP/1.1 200 OK\n"
65#define	HTTP_SERVER1	"Server rwatson_httpd/1.0 ("
66#define	HTTP_SERVER2	")\n"
67#define	HTTP_CONNECTION	"Connection: close\n"
68#define	HTTP_CONTENT	"Content-Type: text/html\n\n"
69
70/*
71 * In order to support both multi-threaded and multi-process operation but
72 * use a single shared memory statistics model, we create a page-aligned
73 * statistics buffer.  For threaded operation, it's just shared memory due to
74 * threading; for multi-process operation, we mark it as INHERIT_SHARE, so we
75 * must put it in page-aligned memory that isn't shared with other memory, or
76 * risk accidental sharing of other statep.
77 */
78static struct state {
79	struct httpd_thread_statep {
80		pthread_t	hts_thread;	/* Multi-thread. */
81		pid_t		hts_pid;	/* Multi-process. */
82		int		hts_fd;
83	} hts[THREADS];
84
85	const char	*path;
86	int		 data_file;
87	int		 listen_sock;
88	struct utsname	 utsname;
89} *statep;
90
91/*
92 * Borrowed from sys/param.h.
93 */
94#define	roundup(x, y)	((((x)+((y)-1))/(y))*(y))	/* to any y */
95
96/*
97 * Given an open client socket, process its request.  No notion of timeout.
98 */
99static int
100http_serve(int sock, int fd)
101{
102	struct iovec header_iovec[6];
103	struct sf_hdtr sf_hdtr;
104	char buffer[BUFFER];
105	ssize_t len;
106	int i, ncount;
107
108	/* Read until \n\n.  Not very smart. */
109	ncount = 0;
110	while (1) {
111		len = recv(sock, buffer, BUFFER, 0);
112		if (len < 0) {
113			warn("recv");
114			return (-1);
115		}
116		if (len == 0)
117			return (-1);
118		for (i = 0; i < len; i++) {
119			switch (buffer[i]) {
120			case '\n':
121				ncount++;
122				break;
123
124			case '\r':
125				break;
126
127			default:
128				ncount = 0;
129			}
130		}
131		if (ncount == 2)
132			break;
133	}
134
135	bzero(&sf_hdtr, sizeof(sf_hdtr));
136	bzero(&header_iovec, sizeof(header_iovec));
137	header_iovec[0].iov_base = HTTP_OK;
138	header_iovec[0].iov_len = strlen(HTTP_OK);
139	header_iovec[1].iov_base = HTTP_SERVER1;
140	header_iovec[1].iov_len = strlen(HTTP_SERVER1);
141	header_iovec[2].iov_base = statep->utsname.sysname;
142	header_iovec[2].iov_len = strlen(statep->utsname.sysname);
143	header_iovec[3].iov_base = HTTP_SERVER2;
144	header_iovec[3].iov_len = strlen(HTTP_SERVER2);
145	header_iovec[4].iov_base = HTTP_CONNECTION;
146	header_iovec[4].iov_len = strlen(HTTP_CONNECTION);
147	header_iovec[5].iov_base = HTTP_CONTENT;
148	header_iovec[5].iov_len = strlen(HTTP_CONTENT);
149	sf_hdtr.headers = header_iovec;
150	sf_hdtr.hdr_cnt = 6;
151	sf_hdtr.trailers = NULL;
152	sf_hdtr.trl_cnt = 0;
153
154	if (sendfile(fd, sock, 0, 0, &sf_hdtr, NULL, 0) < 0)
155		warn("sendfile");
156
157	return (0);
158}
159
160static void *
161httpd_worker(void *arg)
162{
163	struct httpd_thread_statep *htsp;
164	int sock;
165
166	htsp = arg;
167
168	while (1) {
169		sock = accept(statep->listen_sock, NULL, NULL);
170		if (sock < 0)
171			continue;
172		(void)http_serve(sock, htsp->hts_fd);
173		close(sock);
174	}
175}
176
177static void
178killall(void)
179{
180	int i;
181
182	for (i = 0; i < THREADS; i++) {
183		if (statep->hts[i].hts_pid != 0)
184			(void)kill(statep->hts[i].hts_pid, SIGTERM);
185	}
186}
187
188static void
189usage(void)
190{
191
192	fprintf(stderr, "httpd [-t] port [path]\n");
193	exit(EX_USAGE);
194}
195
196int
197main(int argc, char *argv[])
198{
199	u_char filebuffer[FILESIZE];
200	char temppath[PATH_MAX];
201	struct sockaddr_in sin;
202	int ch, error, i;
203	char *pagebuffer;
204	ssize_t len;
205	pid_t pid;
206
207
208	while ((ch = getopt(argc, argv, "t")) != -1) {
209		switch (ch) {
210		case 't':
211			threaded = 1;
212			break;
213
214		default:
215			usage();
216		}
217	}
218	argc -= optind;
219	argv += optind;
220
221	if (argc != 1 && argc != 2)
222		usage();
223
224	len = roundup(sizeof(struct state), getpagesize());
225	pagebuffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
226	if (pagebuffer == MAP_FAILED)
227		err(-1, "mmap");
228	if (minherit(pagebuffer, len, INHERIT_SHARE) < 0)
229		err(-1, "minherit");
230	statep = (struct state *)pagebuffer;
231
232	if (uname(&statep->utsname) < 0)
233		err(-1, "utsname");
234
235	statep->listen_sock = socket(PF_INET, SOCK_STREAM, 0);
236	if (statep->listen_sock < 0)
237		err(-1, "socket(PF_INET, SOCK_STREAM)");
238
239	bzero(&sin, sizeof(sin));
240	sin.sin_len = sizeof(sin);
241	sin.sin_family = AF_INET;
242	sin.sin_port = htons(atoi(argv[0]));
243
244	/*
245	 * If a path is specified, use it.  Otherwise, create temporary files
246	 * with some data for each thread.
247	 */
248	statep->path = argv[1];
249	if (statep->path != NULL) {
250		statep->data_file = open(statep->path, O_RDONLY);
251		if (statep->data_file < 0)
252			err(-1, "open: %s", statep->path);
253		for (i = 0; i < THREADS; i++)
254			statep->hts[i].hts_fd = statep->data_file;
255	} else {
256		memset(filebuffer, 'A', FILESIZE - 1);
257		filebuffer[FILESIZE - 1] = '\n';
258		for (i = 0; i < THREADS; i++) {
259			snprintf(temppath, PATH_MAX, "/tmp/httpd.XXXXXXXXXXX");
260			statep->hts[i].hts_fd = mkstemp(temppath);
261			if (statep->hts[i].hts_fd < 0)
262				err(-1, "mkstemp");
263			(void)unlink(temppath);
264			len = write(statep->hts[i].hts_fd, filebuffer,
265			    FILESIZE);
266			if (len < 0)
267				err(-1, "write");
268			if (len < FILESIZE)
269				errx(-1, "write: short");
270		}
271	}
272
273	if (bind(statep->listen_sock, (struct sockaddr *)&sin,
274	    sizeof(sin)) < 0)
275		err(-1, "bind");
276
277	if (listen(statep->listen_sock, -1) < 0)
278		err(-1, "listen");
279
280	for (i = 0; i < THREADS; i++) {
281		if (threaded) {
282			if (pthread_create(&statep->hts[i].hts_thread, NULL,
283			    httpd_worker, &statep->hts[i]) != 0)
284				err(-1, "pthread_create");
285		} else {
286			pid = fork();
287			if (pid < 0) {
288				error = errno;
289				killall();
290				errno = error;
291				err(-1, "fork");
292			}
293			if (pid == 0)
294				httpd_worker(&statep->hts[i]);
295			statep->hts[i].hts_pid = pid;
296		}
297	}
298
299	for (i = 0; i < THREADS; i++) {
300		if (threaded) {
301			if (pthread_join(statep->hts[i].hts_thread, NULL)
302			    != 0)
303				err(-1, "pthread_join");
304		} else {
305			pid = waitpid(statep->hts[i].hts_pid, NULL, 0);
306			if (pid == statep->hts[i].hts_pid)
307				statep->hts[i].hts_pid = 0;
308		}
309	}
310	if (!threaded)
311		killall();
312	return (0);
313}
314