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