1 /*	$NetBSD: lua-bozo.c,v 1.15 2017/05/28 22:37:36 alnsn Exp $	*/
2 
3 /*
4  * Copyright (c) 2013 Marc Balmer <marc@msys.ch>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer and
14  *    dedication in the documentation and/or other materials provided
15  *    with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  */
30 
31 /* this code implements dynamic content generation using Lua for bozohttpd */
32 
33 #ifndef NO_LUA_SUPPORT
34 
35 #include <sys/param.h>
36 
37 #include <lua.h>
38 #include <lauxlib.h>
39 #include <lualib.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 #include "bozohttpd.h"
45 
46 /* Lua binding for bozohttp */
47 
48 #if LUA_VERSION_NUM < 502
49 #define LUA_HTTPDLIBNAME "httpd"
50 #endif
51 
52 #define FORM	"application/x-www-form-urlencoded"
53 
54 static bozohttpd_t *
httpd_instance(lua_State * L)55 httpd_instance(lua_State *L)
56 {
57 	bozohttpd_t *httpd;
58 
59 	lua_pushstring(L, "bozohttpd");
60 	lua_gettable(L, LUA_REGISTRYINDEX);
61 	httpd = lua_touserdata(L, -1);
62 	lua_pop(L, 1);
63 
64 	return httpd;
65 }
66 
67 static int
lua_flush(lua_State * L)68 lua_flush(lua_State *L)
69 {
70 	bozohttpd_t *httpd = httpd_instance(L);
71 
72 	bozo_flush(httpd, stdout);
73 	return 0;
74 }
75 
76 static int
lua_print(lua_State * L)77 lua_print(lua_State *L)
78 {
79 	bozohttpd_t *httpd = httpd_instance(L);
80 
81 	bozo_printf(httpd, "%s\r\n", lua_tostring(L, 1));
82 	return 0;
83 }
84 
85 static int
lua_read(lua_State * L)86 lua_read(lua_State *L)
87 {
88 	bozohttpd_t *httpd = httpd_instance(L);
89 	luaL_Buffer lbuf;
90 	char *data;
91 	lua_Integer len;
92 	ssize_t n;
93 
94 	len = luaL_checkinteger(L, 1);
95 	data = luaL_buffinitsize(L, &lbuf, (size_t)len);
96 
97 	if ((n = bozo_read(httpd, STDIN_FILENO, data, len)) >= 0) {
98 		luaL_pushresultsize(&lbuf, n);
99 		return 1;
100 	} else {
101 		lua_pushnil(L);
102 		lua_pushstring(L, "bozo_read() call failed");
103 		return 2;
104 	}
105 }
106 
107 static int
lua_register_handler(lua_State * L)108 lua_register_handler(lua_State *L)
109 {
110 	bozohttpd_t *httpd = httpd_instance(L);
111 	lua_state_map_t *map;
112 	lua_handler_t *handler;
113 	const char *name;
114 	int ref;
115 
116 	lua_pushstring(L, "lua_state_map");
117 	lua_gettable(L, LUA_REGISTRYINDEX);
118 	map = lua_touserdata(L, -1);
119 	lua_pop(L, 1);
120 
121 	name = luaL_checkstring(L, 1);
122 
123 	luaL_checktype(L, 2, LUA_TFUNCTION);
124 	lua_pushvalue(L, 2);
125 	ref = luaL_ref(L, LUA_REGISTRYINDEX);
126 
127 	handler = bozomalloc(httpd, sizeof(lua_handler_t));
128 	handler->name = bozostrdup(httpd, NULL, name);
129 	handler->ref = ref;
130 	SIMPLEQ_INSERT_TAIL(&map->handlers, handler, h_next);
131 	httpd->process_lua = 1;
132 	return 0;
133 }
134 
135 static int
lua_write(lua_State * L)136 lua_write(lua_State *L)
137 {
138 	bozohttpd_t *httpd = httpd_instance(L);
139 	const char *data;
140 	size_t len;
141 	ssize_t n;
142 
143 	data = luaL_checklstring(L, 1, &len);
144 	if ((n = bozo_write(httpd, STDIN_FILENO, data, len)) >= 0) {
145 		lua_pushinteger(L, n);
146 		return 1;
147 	} else {
148 		lua_pushnil(L);
149 		lua_pushstring(L, "bozo_write() call failed");
150 		return 2;
151 	}
152 }
153 
154 static int
luaopen_httpd(lua_State * L)155 luaopen_httpd(lua_State *L)
156 {
157 	static struct luaL_Reg functions[] = {
158 		{ "flush",		lua_flush },
159 		{ "print",		lua_print },
160 		{ "read",		lua_read },
161 		{ "register_handler",	lua_register_handler },
162 		{ "write",		lua_write },
163 		{ NULL, NULL }
164 	};
165 #if LUA_VERSION_NUM >= 502
166 	luaL_newlib(L, functions);
167 #else
168 	luaL_register(L, LUA_HTTPDLIBNAME, functions);
169 #endif
170 	lua_pushstring(L, "httpd 1.0.0");
171 	lua_setfield(L, -2, "_VERSION");
172 	return 1;
173 }
174 
175 #if LUA_VERSION_NUM < 502
176 static void
lua_openlib(lua_State * L,const char * name,lua_CFunction fn)177 lua_openlib(lua_State *L, const char *name, lua_CFunction fn)
178 {
179 	lua_pushcfunction(L, fn);
180 	lua_pushstring(L, name);
181 	lua_call(L, 1, 0);
182 }
183 #endif
184 
185 /* bozohttpd integration */
186 void
bozo_add_lua_map(bozohttpd_t * httpd,const char * prefix,const char * script)187 bozo_add_lua_map(bozohttpd_t *httpd, const char *prefix, const char *script)
188 {
189 	lua_state_map_t *map;
190 
191 	map = bozomalloc(httpd, sizeof(lua_state_map_t));
192 	map->prefix = bozostrdup(httpd, NULL, prefix);
193 	if (*script == '/')
194 		map->script = bozostrdup(httpd, NULL, script);
195 	else {
196 		char cwd[MAXPATHLEN], *path;
197 
198 		getcwd(cwd, sizeof(cwd) - 1);
199 		bozoasprintf(httpd, &path, "%s/%s", cwd, script);
200 		map->script = path;
201 	}
202 	map->L = luaL_newstate();
203 	if (map->L == NULL)
204 		bozoerr(httpd, 1, "can't create Lua state");
205 	SIMPLEQ_INIT(&map->handlers);
206 
207 #if LUA_VERSION_NUM >= 502
208 	luaL_openlibs(map->L);
209 	lua_getglobal(map->L, "package");
210 	lua_getfield(map->L, -1, "preload");
211 	lua_pushcfunction(map->L, luaopen_httpd);
212 	lua_setfield(map->L, -2, "httpd");
213 	lua_pop(map->L, 2);
214 #else
215 	lua_openlib(map->L, "", luaopen_base);
216 	lua_openlib(map->L, LUA_LOADLIBNAME, luaopen_package);
217 	lua_openlib(map->L, LUA_TABLIBNAME, luaopen_table);
218 	lua_openlib(map->L, LUA_STRLIBNAME, luaopen_string);
219 	lua_openlib(map->L, LUA_MATHLIBNAME, luaopen_math);
220 	lua_openlib(map->L, LUA_OSLIBNAME, luaopen_os);
221 	lua_openlib(map->L, LUA_IOLIBNAME, luaopen_io);
222 	lua_openlib(map->L, LUA_HTTPDLIBNAME, luaopen_httpd);
223 #endif
224 	lua_pushstring(map->L, "lua_state_map");
225 	lua_pushlightuserdata(map->L, map);
226 	lua_settable(map->L, LUA_REGISTRYINDEX);
227 
228 	lua_pushstring(map->L, "bozohttpd");
229 	lua_pushlightuserdata(map->L, httpd);
230 	lua_settable(map->L, LUA_REGISTRYINDEX);
231 
232 	if (luaL_loadfile(map->L, script))
233 		bozoerr(httpd, 1, "failed to load script %s: %s", script,
234 		    lua_tostring(map->L, -1));
235 	if (lua_pcall(map->L, 0, 0, 0))
236 		bozoerr(httpd, 1, "failed to execute script %s: %s", script,
237 		    lua_tostring(map->L, -1));
238 	SIMPLEQ_INSERT_TAIL(&httpd->lua_states, map, s_next);
239 }
240 
241 static void
lua_env(lua_State * L,const char * name,const char * value)242 lua_env(lua_State *L, const char *name, const char *value)
243 {
244 	lua_pushstring(L, value);
245 	lua_setfield(L, -2, name);
246 }
247 
248 /* decode query string */
249 static void
lua_url_decode(lua_State * L,char * s)250 lua_url_decode(lua_State *L, char *s)
251 {
252 	char *v, *p, *val, *q;
253 	char buf[3];
254 	int c;
255 
256 	v = strchr(s, '=');
257 	if (v == NULL)
258 		return;
259 	*v++ = '\0';
260 	val = malloc(strlen(v) + 1);
261 	if (val == NULL)
262 		return;
263 
264 	for (p = v, q = val; *p; p++) {
265 		switch (*p) {
266 		case '%':
267 			if (*(p + 1) == '\0' || *(p + 2) == '\0') {
268 				free(val);
269 				return;
270 			}
271 			buf[0] = *++p;
272 			buf[1] = *++p;
273 			buf[2] = '\0';
274 			sscanf(buf, "%2x", &c);
275 			*q++ = (char)c;
276 			break;
277 		case '+':
278 			*q++ = ' ';
279 			break;
280 		default:
281 			*q++ = *p;
282 		}
283 	}
284 	*q = '\0';
285 	lua_pushstring(L, val);
286 	lua_setfield(L, -2, s);
287 	free(val);
288 }
289 
290 static void
lua_decode_query(lua_State * L,char * query)291 lua_decode_query(lua_State *L, char *query)
292 {
293 	char *s;
294 
295 	s = strtok(query, "&");
296 	while (s) {
297 		lua_url_decode(L, s);
298 		s = strtok(NULL, "&");
299 	}
300 }
301 
302 int
bozo_process_lua(bozo_httpreq_t * request)303 bozo_process_lua(bozo_httpreq_t *request)
304 {
305 	bozohttpd_t *httpd = request->hr_httpd;
306 	lua_state_map_t *map;
307 	lua_handler_t *hndlr;
308 	int n, ret, length;
309 	char date[40];
310 	bozoheaders_t *headp;
311 	char *s, *query, *uri, *file, *command, *info, *content;
312 	const char *type, *clen;
313 	char *prefix, *handler, *p;
314 	int rv = 0;
315 
316 	if (!httpd->process_lua)
317 		return 0;
318 
319 	info = NULL;
320 	query = NULL;
321 	prefix = NULL;
322 	uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file;
323 
324 	if (*uri == '/') {
325 		file = bozostrdup(httpd, request, uri);
326 		if (file == NULL)
327 			goto out;
328 		prefix = bozostrdup(httpd, request, &uri[1]);
329 	} else {
330 		if (asprintf(&file, "/%s", uri) < 0)
331 			goto out;
332 		prefix = bozostrdup(httpd, request, uri);
333 	}
334 	if (prefix == NULL)
335 		goto out;
336 
337 	if (request->hr_query && request->hr_query[0])
338 		query = bozostrdup(httpd, request, request->hr_query);
339 
340 	p = strchr(prefix, '/');
341 	if (p == NULL)
342 		goto out;
343 	*p++ = '\0';
344 	handler = p;
345 	if (!*handler)
346 		goto out;
347 	p = strchr(handler, '/');
348 	if (p != NULL)
349 		*p++ = '\0';
350 
351 	command = file + 1;
352 	if ((s = strchr(command, '/')) != NULL) {
353 		info = bozostrdup(httpd, request, s);
354 		*s = '\0';
355 	}
356 
357 	type = request->hr_content_type;
358 	clen = request->hr_content_length;
359 
360 	SIMPLEQ_FOREACH(map, &httpd->lua_states, s_next) {
361 		if (strcmp(map->prefix, prefix))
362 			continue;
363 
364 		SIMPLEQ_FOREACH(hndlr, &map->handlers, h_next) {
365 			if (strcmp(hndlr->name, handler))
366 				continue;
367 
368 			lua_rawgeti(map->L, LUA_REGISTRYINDEX, hndlr->ref);
369 
370 			/* Create the "environment" */
371 			lua_newtable(map->L);
372 			lua_env(map->L, "SERVER_NAME",
373 			    BOZOHOST(httpd, request));
374 			lua_env(map->L, "GATEWAY_INTERFACE", "Luigi/1.0");
375 			lua_env(map->L, "SERVER_PROTOCOL", request->hr_proto);
376 			lua_env(map->L, "REQUEST_METHOD",
377 			    request->hr_methodstr);
378 			lua_env(map->L, "SCRIPT_PREFIX", map->prefix);
379 			lua_env(map->L, "SCRIPT_NAME", file);
380 			lua_env(map->L, "HANDLER_NAME", hndlr->name);
381 			lua_env(map->L, "SCRIPT_FILENAME", map->script);
382 			lua_env(map->L, "SERVER_SOFTWARE",
383 			    httpd->server_software);
384 			lua_env(map->L, "REQUEST_URI", uri);
385 			lua_env(map->L, "DATE_GMT",
386 			    bozo_http_date(date, sizeof(date)));
387 			if (query && *query)
388 				lua_env(map->L, "QUERY_STRING", query);
389 			if (info && *info)
390 				lua_env(map->L, "PATH_INFO", info);
391 			if (type && *type)
392 				lua_env(map->L, "CONTENT_TYPE", type);
393 			if (clen && *clen)
394 				lua_env(map->L, "CONTENT_LENGTH", clen);
395 			if (request->hr_serverport && *request->hr_serverport)
396 				lua_env(map->L, "SERVER_PORT",
397 				    request->hr_serverport);
398 			if (request->hr_remotehost && *request->hr_remotehost)
399 				lua_env(map->L, "REMOTE_HOST",
400 				    request->hr_remotehost);
401 			if (request->hr_remoteaddr && *request->hr_remoteaddr)
402 				lua_env(map->L, "REMOTE_ADDR",
403 				    request->hr_remoteaddr);
404 
405 			/* Pass the headers in a separate table */
406 			lua_newtable(map->L);
407 			SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next)
408 				lua_env(map->L, headp->h_header,
409 				    headp->h_value);
410 
411 			/* Pass the query variables */
412 			if ((query && *query) ||
413 			    (type && *type && !strcmp(type, FORM))) {
414 				lua_newtable(map->L);
415 				if (query && *query)
416 					lua_decode_query(map->L, query);
417 				if (type && *type && !strcmp(type, FORM)) {
418 					if (clen && *clen && atol(clen) > 0) {
419 						length = atol(clen);
420 						content = bozomalloc(httpd,
421 						    length + 1);
422 						n = bozo_read(httpd,
423 						    STDIN_FILENO, content,
424 						    length);
425 						if (n >= 0) {
426 							content[n] = '\0';
427 							lua_decode_query(map->L,
428 							    content);
429 						} else {
430 							lua_pop(map->L, 1);
431 							lua_pushnil(map->L);
432 						}
433 						free(content);
434 					}
435 				}
436 			} else
437 				lua_pushnil(map->L);
438 
439 			ret = lua_pcall(map->L, 3, 0, 0);
440 			if (ret)
441 				printf("<br>Lua error: %s\n",
442 				    lua_tostring(map->L, -1));
443 			bozo_flush(httpd, stdout);
444 			rv = 1;
445 			goto out;
446 		}
447 	}
448 out:
449 	free(prefix);
450 	free(uri);
451 	free(info);
452 	free(query);
453 	free(file);
454 	return rv;
455 }
456 
457 #endif /* NO_LUA_SUPPORT */
458