14 changed files with 981 additions and 0 deletions
-
3.gitignore
-
24LICENCE
-
17Makefile
-
38README.md
-
17TODO
-
37lua-fastcgi.lua
-
126src/config.c
-
16src/config.h
-
137src/lfuncs.c
-
4src/lfuncs.h
-
171src/lua-fastcgi.c
-
5src/lua-fastcgi.h
-
366src/lua.c
-
20src/lua.h
@ -0,0 +1,3 @@ |
|||
*.swp |
|||
*.o |
|||
lua-fastcgi |
@ -0,0 +1,24 @@ |
|||
Copyright (c) 2012, Christopher Ramey |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions |
|||
are met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright notice, |
|||
this list of conditions and the following disclaimer. |
|||
2. Redistributions in binary form must reproduce the above copyright notice, |
|||
this list of conditions and the following disclaimer in the documentation |
|||
and/or other materials provided with the distribution. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,17 @@ |
|||
CFLAGS=-c -std=gnu99 -Wall |
|||
LDFLAGS=-O2 -Wl,-Bstatic -lfcgi -llua5.1 -Wl,-Bdynamic -lm -lpthread |
|||
|
|||
|
|||
.c.o: |
|||
$(CC) $(CFLAGS) $< -o $@ |
|||
|
|||
all: lua-fastcgi |
|||
|
|||
debug: CFLAGS+=-g -DDEBUG |
|||
debug: lua-fastcgi |
|||
|
|||
lua-fastcgi: src/lua-fastcgi.o src/lfuncs.o src/lua.o src/config.o |
|||
$(CC) $^ $(LDFLAGS) -o $@ |
|||
|
|||
clean: |
|||
rm -f src/*.o lua-fastcgi |
@ -0,0 +1,38 @@ |
|||
lua-fastcgi |
|||
=========== |
|||
|
|||
lua-fastcgi is a sandboxed Lua backend for FastCGI. That is, you can write |
|||
Lua scripts that serve up web pages. Options exist in lua-fastcgi.lua |
|||
to configure a fixed amount of memory, cpu usage, and output bytes |
|||
for each request. While sandboxed, lua-fastcgi supports a limit set |
|||
of functions. If sandboxing is disabled, lua-fastcgi loads the standard |
|||
libraries and users may load modules as needed. |
|||
|
|||
|
|||
compiling |
|||
--------- |
|||
|
|||
lua-fastcgi requires libfcgi and liblua to successfully compile. lua-fastcgi |
|||
has been tested under Linux, specifically Ubuntu 10.10. Other versions of |
|||
Ubuntu will likely work without any changes. Other flavors of Linux should |
|||
work with little to no effort. Other Unix-like operating systems are untested |
|||
and unsupported, for now. |
|||
|
|||
|
|||
running |
|||
------- |
|||
|
|||
lua-fastcgi reads lua-fastcgi.lua in the current working directory. If it |
|||
fails to read this file, it will assume certain defaults and continue anyway. |
|||
Configuration defaults are documented in the included lua-fastcgi.lua file. |
|||
|
|||
|
|||
lua-fastcgi has been tested with nginx, but will likely work with other |
|||
FastCGI compatible web servers with little effort. lua-fastcgi relies only |
|||
on SCRIPT_NAME and SCRIPT_FILENAME FastCGI variables passed to it. Lua scripts |
|||
can be configured inside of an nginx server directive as follows: |
|||
|
|||
location ~* \.lua$ { |
|||
include /etc/nginx/fastcgi_params; |
|||
fastcgi_pass 127.0.0.1:9222; |
|||
} |
@ -0,0 +1,17 @@ |
|||
High Priority |
|||
------------- |
|||
POST variable parsing |
|||
Basic sandboxed database support (e.g. GET/PUT) |
|||
Sandboxed dofile() / loadfile() / loadstring() |
|||
|
|||
|
|||
Medium Priority |
|||
--------------- |
|||
File Upload Support |
|||
Database error logging |
|||
Consider switching to mmap() for file reads |
|||
|
|||
|
|||
Low Priority |
|||
------------ |
|||
Database file adapter |
@ -0,0 +1,37 @@ |
|||
return { |
|||
-- IP/Port to listen on |
|||
-- Default: "127.0.0.1:9222" |
|||
listen = "127.0.0.1:9222", |
|||
|
|||
-- How many connections should be back-logged by each thread |
|||
-- Default: 100 |
|||
backlog = 100, |
|||
|
|||
-- Number of threads to spin off. Usually one per CPU |
|||
-- (plus hardware threads) is a good idea |
|||
-- Default: 4 |
|||
threads = 1, |
|||
|
|||
-- Indicates if states should be sandboxed (i.e. Denied access to |
|||
-- file system resources or system-level functions) |
|||
-- Default: true |
|||
sandbox = true, |
|||
|
|||
-- Maximum amount of memory a state may consume or 0 for no limit |
|||
-- Default: 65536 |
|||
mem_max = 65536, |
|||
|
|||
-- Maximum amount of CPU time, in both seconds and picoseconds for each |
|||
-- execution. If cpu_sec and cpu_usec are 0, there is no limit |
|||
-- Default: 5000000 |
|||
cpu_usec = 500000, |
|||
-- Default: 0 |
|||
cpu_sec = 0, |
|||
|
|||
-- Limit page output to x bytes or 0 for unlimited |
|||
-- Default: 65536 |
|||
output_max = 0, |
|||
|
|||
-- Default content type returned in header |
|||
content_type = "text/html; charset=iso-8859-1" |
|||
} |
@ -0,0 +1,126 @@ |
|||
#include <stdlib.h> |
|||
#include <unistd.h> |
|||
#include <string.h> |
|||
|
|||
#include <lua5.1/lua.h> |
|||
#include <lua5.1/lauxlib.h> |
|||
#include <lua5.1/lualib.h> |
|||
|
|||
#include "config.h" |
|||
|
|||
|
|||
// Checks if a file exists |
|||
static int LF_fileexists(char *path) |
|||
{ |
|||
if(access(path, F_OK) == 0){ return 1; } |
|||
else { return 0; } |
|||
} |
|||
|
|||
|
|||
// Create configuration with default settings |
|||
LF_config *LF_createconfig() |
|||
{ |
|||
LF_config *c = malloc(sizeof(LF_config)); |
|||
if(c == NULL){ return NULL; } |
|||
|
|||
// Default settings |
|||
c->listen = "127.0.0.1:9222"; |
|||
c->backlog = 100; |
|||
c->threads = 1; |
|||
c->sandbox = 1; |
|||
c->mem_max = 65536; |
|||
c->output_max = 65536; |
|||
c->cpu_usec = 500000; |
|||
c->cpu_sec = 0; |
|||
|
|||
return c; |
|||
} |
|||
|
|||
|
|||
// Load configuration |
|||
int LF_loadconfig(LF_config *cfg, char *path) |
|||
{ |
|||
if(!LF_fileexists(path)){ return 1; } |
|||
|
|||
lua_State *l = luaL_newstate(); |
|||
if(luaL_loadfile(l, path) || lua_pcall(l,0,1,0)){ |
|||
printf("%s\n", lua_tostring(l, -1)); |
|||
lua_close(l); |
|||
return 1; |
|||
} |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
if(lua_istable(l, 1)){ |
|||
lua_pushstring(l, "listen"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isstring(l, 2)){ |
|||
size_t len = 0; |
|||
const char *str = lua_tolstring(l, 2, &len); |
|||
|
|||
if(len > 0){ |
|||
cfg->listen = malloc(len+1); |
|||
memcpy(cfg->listen, str, len+1); |
|||
} |
|||
} |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "backlog"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isnumber(l, 2)){ cfg->backlog = lua_tonumber(l, 2); } |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "threads"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isnumber(l, 2)){ cfg->threads = lua_tonumber(l, 2); } |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "sandbox"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isboolean(l, 2)){ cfg->sandbox = lua_toboolean(l, 2); } |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "mem_max"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isnumber(l, 2)){ cfg->mem_max = lua_tonumber(l, 2); } |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "cpu_usec"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isnumber(l, 2)){ cfg->cpu_usec = lua_tonumber(l, 2); } |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "cpu_sec"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isnumber(l, 2)){ cfg->cpu_sec = lua_tonumber(l, 2); } |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "output_max"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isnumber(l, 2)){ cfg->output_max = lua_tonumber(l, 2); } |
|||
|
|||
lua_settop(l, 1); |
|||
|
|||
lua_pushstring(l, "content_type"); |
|||
lua_rawget(l, 1); |
|||
if(lua_isstring(l, 2)){ |
|||
size_t len = 0; |
|||
const char *str = lua_tolstring(l, 2, &len); |
|||
|
|||
if(len > 0){ |
|||
cfg->content_type = malloc(len+1); |
|||
memcpy(cfg->content_type, str, len+1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
lua_close(l); |
|||
return 0; |
|||
} |
@ -0,0 +1,16 @@ |
|||
typedef struct { |
|||
char *listen; |
|||
int backlog; |
|||
int threads; |
|||
|
|||
int sandbox; |
|||
size_t mem_max; |
|||
size_t output_max; |
|||
unsigned long cpu_usec; |
|||
unsigned long cpu_sec; |
|||
|
|||
char *content_type; |
|||
} LF_config; |
|||
|
|||
LF_config *LF_createconfig(); |
|||
int LF_loadconfig(LF_config *, char *); |
@ -0,0 +1,137 @@ |
|||
#include <stdlib.h> |
|||
#include <stdint.h> |
|||
#include <string.h> |
|||
|
|||
#include <fcgiapp.h> |
|||
|
|||
#include <lua5.1/lua.h> |
|||
#include <lua5.1/lauxlib.h> |
|||
|
|||
#include "lua.h" |
|||
#include "lfuncs.h" |
|||
|
|||
|
|||
// replacement print function, outputs to FCGI stream |
|||
static int LF_pprint(lua_State *l, int cr) |
|||
{ |
|||
int args = lua_gettop(l); |
|||
|
|||
// Fetch the response |
|||
lua_pushstring(l, "RESPONSE"); |
|||
lua_rawget(l, LUA_REGISTRYINDEX); |
|||
LF_response *response = lua_touserdata(l, args+1); |
|||
lua_pop(l, 1); |
|||
|
|||
// fetch limits |
|||
lua_pushstring(l, "RESPONSE_LIMIT"); |
|||
lua_rawget(l, LUA_REGISTRYINDEX); |
|||
size_t *limit = lua_touserdata(l, args+1); |
|||
lua_pop(l, 1); |
|||
|
|||
|
|||
// If the response isn't committed, send the header |
|||
if(!response->committed){ |
|||
lua_getglobal(l, "HEADER"); |
|||
if(!lua_istable(l, args+1)){ luaL_error(l, "Invalid HEADER (Not table)."); } |
|||
|
|||
lua_pushstring(l, "Status"); |
|||
lua_rawget(l, args+1); |
|||
|
|||
// If the status has been explicitly set, send that |
|||
if(!lua_isnil(l, args+2)){ |
|||
if(!lua_isstring(l, args+2)){ |
|||
luaL_error(l, "Invalid HEADER (Invalid Status)."); |
|||
} |
|||
|
|||
size_t len; |
|||
const char *str = lua_tolstring(l, args+2, &len); |
|||
|
|||
if(limit){ |
|||
if((len+10) > *limit){ luaL_error(l, "Output limit exceeded."); } |
|||
*limit -= (len+10); |
|||
} |
|||
|
|||
FCGX_PutStr("Status: ", 8, response->out); |
|||
FCGX_PutStr(str, len, response->out); |
|||
FCGX_PutStr("\r\n", 2, response->out); |
|||
response->committed = 1; |
|||
} |
|||
lua_pop(l, 1); // Pop the status |
|||
|
|||
// Loop over the header, ignoring status, but sending everything else |
|||
lua_pushnil(l); |
|||
while(lua_next(l, args+1)){ |
|||
// If the key or the value isn't a string (or number) throw an error |
|||
if(!lua_isstring(l, args+2) || !lua_isstring(l, args+3)){ |
|||
luaL_error(l, "Invalid HEADER (Invalid key and/or value)."); |
|||
} |
|||
|
|||
size_t keylen = 0; |
|||
const char *key = lua_tolstring(l, args+2, &keylen); |
|||
if(keylen == 6 && memcmp(key, "Status", 6) == 0){ |
|||
// Clear the last value out |
|||
lua_pop(l, 1); |
|||
continue; |
|||
} |
|||
|
|||
size_t vallen = 0; |
|||
const char *val = lua_tolstring(l, args+3, &vallen); |
|||
|
|||
if(limit){ |
|||
if((vallen+keylen+4) > *limit){ luaL_error(l, "Output limit exceeded."); } |
|||
*limit -= (vallen+keylen+4); |
|||
} |
|||
|
|||
FCGX_PutStr(key, keylen, response->out); |
|||
FCGX_PutStr(": ", 2, response->out); |
|||
FCGX_PutStr(val, vallen, response->out); |
|||
FCGX_PutStr("\r\n", 2, response->out); |
|||
|
|||
response->committed = 1; |
|||
lua_pop(l, 1); // Clear the last value out |
|||
} |
|||
lua_pop(l, 1); // Clear the table out |
|||
|
|||
if(limit){ |
|||
if(2 >= *limit){ luaL_error(l, "Output limit exceeded."); } |
|||
*limit -= 2; |
|||
} |
|||
|
|||
FCGX_PutS("\r\n", response->out); |
|||
response->committed = 1; |
|||
} |
|||
|
|||
size_t strlen; |
|||
const char *str; |
|||
for(int i=1; i <= args; i++){ |
|||
switch(lua_type(l, i)){ |
|||
case LUA_TSTRING: |
|||
case LUA_TNUMBER: |
|||
case LUA_TBOOLEAN: |
|||
str = lua_tolstring(l, i, &strlen); |
|||
if(limit){ |
|||
if(strlen > *limit){ luaL_error(l, "Output limit exceeded."); } |
|||
*limit -= strlen; |
|||
} |
|||
|
|||
FCGX_PutStr(str, strlen, response->out); |
|||
break; |
|||
|
|||
default: /* Ignore other types */ break; |
|||
} |
|||
} |
|||
|
|||
if(cr){ |
|||
if(limit){ |
|||
if(*limit == 0){ luaL_error(l, "Output limit exceeded."); } |
|||
(*limit)--; |
|||
} |
|||
|
|||
FCGX_PutChar('\n', response->out); |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
int LF_print(lua_State *l){ return LF_pprint(l, 1); } |
|||
int LF_write(lua_State *l){ return LF_pprint(l, 0); } |
@ -0,0 +1,4 @@ |
|||
// Writes FCGI output followed by a carriage return |
|||
int LF_print(lua_State *); |
|||
// Writes FCGI output without a carriage return |
|||
int LF_write(lua_State *); |
@ -0,0 +1,171 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <stdint.h> |
|||
#include <string.h> |
|||
#include <errno.h> |
|||
#include <time.h> |
|||
|
|||
#include <fcgi_config.h> |
|||
#include <fcgiapp.h> |
|||
|
|||
#include <lua5.1/lua.h> |
|||
#include <pthread.h> |
|||
|
|||
#include "lua.h" |
|||
#include "config.h" |
|||
#include "lua-fastcgi.h" |
|||
|
|||
|
|||
static char *http_status_strings[] = { |
|||
[200] = "OK", |
|||
[403] = "Forbidden", |
|||
[404] = "Not Found", |
|||
[500] = "Internal Server Error" |
|||
}; |
|||
|
|||
|
|||
#define senderror(status_code,error_string) \ |
|||
if(!response.committed){ \ |
|||
FCGX_FPrintF(request.out, "Status: %d %s\r\n", status_code, http_status_strings[status_code]); \ |
|||
FCGX_FPrintF(request.out, "Content-Type: %s\r\n\r\n", config->content_type); \ |
|||
response.committed = 1; \ |
|||
} \ |
|||
FCGX_PutS(error_string, response.out); |
|||
|
|||
|
|||
#ifdef DEBUG |
|||
static void printcfg(LF_config *cfg) |
|||
{ |
|||
printf("Listen: %s\n", cfg->listen); |
|||
printf("Backlog: %d\n", cfg->backlog); |
|||
printf("Threads: %d\n", cfg->threads); |
|||
printf("Sandbox: %d\n", cfg->sandbox); |
|||
printf("Max Memory: %zu\n", cfg->mem_max); |
|||
printf("Max Output: %zu\n", cfg->output_max); |
|||
printf("CPU usec: %lu\n", cfg->cpu_usec); |
|||
printf("CPU sec: %lu\n", cfg->cpu_sec); |
|||
printf("Default Content Type: %s\n", cfg->content_type); |
|||
printf("\n"); |
|||
} |
|||
|
|||
|
|||
static void printvars(FCGX_Request *request) |
|||
{ |
|||
for(int i=0; request->envp[i] != NULL; i++){ |
|||
printf("%s\n", request->envp[i]); |
|||
} |
|||
printf("\n"); |
|||
} |
|||
#endif |
|||
|
|||
|
|||
void *thread_run(void *arg) |
|||
{ |
|||
LF_params *params = arg; |
|||
LF_config *config = params->config; |
|||
LF_limits *limits = LF_newlimits(); |
|||
LF_response response; |
|||
lua_State *l; |
|||
|
|||
FCGX_Request request; |
|||
FCGX_InitRequest(&request, params->socket, 0); |
|||
|
|||
for(;;){ |
|||
LF_setlimits( |
|||
limits, config->mem_max, config->output_max, |
|||
config->cpu_sec, config->cpu_usec |
|||
); |
|||
|
|||
l = LF_newstate(config->sandbox, config->content_type); |
|||
|
|||
if(FCGX_Accept_r(&request)){ |
|||
printf("FCGX_Accept_r() failure\n"); |
|||
LF_closestate(l); |
|||
continue; |
|||
} |
|||
|
|||
#ifdef DEBUG |
|||
printvars(&request); |
|||
#endif |
|||
|
|||
LF_parserequest(l, &request, &response); |
|||
LF_enablelimits(l, limits); |
|||
|
|||
switch(LF_loadfile(l)){ |
|||
case 0: |
|||
if(lua_pcall(l, 0, 0, 0)){ |
|||
senderror(500, lua_tostring(l, -1)); |
|||
} else if(!response.committed){ |
|||
senderror(200, ""); |
|||
} |
|||
break; |
|||
|
|||
case EACCES: |
|||
senderror(403, lua_tostring(l, -1)); |
|||
break; |
|||
|
|||
case ENOENT: |
|||
senderror(404, lua_tostring(l, -1)); |
|||
break; |
|||
|
|||
default: |
|||
senderror(500, lua_tostring(l, -1)); |
|||
break; |
|||
} |
|||
|
|||
FCGX_Finish_r(&request); |
|||
LF_closestate(l); |
|||
} |
|||
} |
|||
|
|||
|
|||
int main() |
|||
{ |
|||
if(FCGX_Init() != 0){ |
|||
printf("FCGX_Init() failure\n"); |
|||
exit(EXIT_FAILURE); |
|||
} |
|||
|
|||
LF_config *config = LF_createconfig(); |
|||
if(config == NULL){ |
|||
printf("LF_createconfig(): memory allocation error\n"); |
|||
exit(EXIT_FAILURE); |
|||
} |
|||
|
|||
if(LF_loadconfig(config, "./lua-fastcgi.lua")){ |
|||
printf("Error loading lua-fastcgi.lua\n"); |
|||
} |
|||
|
|||
#ifdef DEBUG |
|||
printcfg(config); |
|||
#endif |
|||
|
|||
int socket = FCGX_OpenSocket(config->listen, config->backlog); |
|||
if(socket < 0){ |
|||
printf("FCGX_OpenSocket() failure: could not open %s\n", config->listen); |
|||
exit(EXIT_FAILURE); |
|||
} |
|||
|
|||
LF_params *params = malloc(sizeof(LF_params)); |
|||
params->socket = socket; |
|||
params->config = config; |
|||
|
|||
if(config->threads == 1){ |
|||
thread_run(params); |
|||
} else { |
|||
pthread_attr_t attr; |
|||
pthread_attr_init(&attr); |
|||
pthread_t threads[config->threads]; |
|||
for(int i=0; i < config->threads; i++){ |
|||
int r = pthread_create(&threads[i], &attr, &thread_run, params); |
|||
if(r){ |
|||
printf("Thread creation error: %d\n", r); |
|||
exit(EXIT_FAILURE); |
|||
} |
|||
} |
|||
|
|||
pthread_join(threads[0], NULL); |
|||
} |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,5 @@ |
|||
typedef struct { |
|||
LF_config *config; |
|||
int socket; |
|||
} LF_params; |
|||
|
@ -0,0 +1,366 @@ |
|||
#include <stdlib.h> |
|||
#include <stdint.h> |
|||
#include <string.h> |
|||
#include <unistd.h> |
|||
#include <ctype.h> |
|||
#include <fcntl.h> |
|||
#include <errno.h> |
|||
#include <sys/time.h> |
|||
#include <sys/resource.h> |
|||
|
|||
#include <fcgiapp.h> |
|||
|
|||
#include <lua5.1/lua.h> |
|||
#include <lua5.1/lauxlib.h> |
|||
#include <lua5.1/lualib.h> |
|||
|
|||
#include "lua.h" |
|||
#include "lfuncs.h" |
|||
|
|||
#define LF_BUFFERSIZE 4096 |
|||
|
|||
typedef struct { |
|||
int fd; |
|||
char buffer[LF_BUFFERSIZE]; |
|||
size_t total; |
|||
} LF_loaderdata; |
|||
|
|||
|
|||
#ifdef DEBUG |
|||
void LF_printstack(lua_State *l) |
|||
{ |
|||
int ss = lua_gettop(l); |
|||
printf("Lua Stack:\n"); |
|||
for(int i=1; i <= ss; i++){ |
|||
printf("\t%d) %s\n", i, lua_typename(l, lua_type(l, i))); |
|||
} |
|||
} |
|||
#endif |
|||
|
|||
|
|||
// Gets current thread usage |
|||
static int LF_threadusage(struct timeval *tv) |
|||
{ |
|||
struct rusage usage; |
|||
if(getrusage(RUSAGE_SELF, &usage) == -1){ |
|||
return 1; |
|||
} |
|||
|
|||
// Add user to sys to get actual usage |
|||
tv->tv_usec = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; |
|||
tv->tv_sec = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec; |
|||
|
|||
if(tv->tv_usec > 1000000){ |
|||
tv->tv_usec -= 1000000; |
|||
tv->tv_sec++; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
// limits cpu usage |
|||
static void LF_limit_hook(lua_State *l, lua_Debug *d) |
|||
{ |
|||
lua_pushstring(l, "CPU_LIMIT"); |
|||
lua_rawget(l, LUA_REGISTRYINDEX); |
|||
struct timeval *limit = lua_touserdata(l, -1); |
|||
lua_pop(l, 1); |
|||
|
|||
struct timeval tv; |
|||
if(LF_threadusage(&tv)){ luaL_error(l, "CPU usage sample error"); } |
|||
if(timercmp(&tv, limit, >)){ luaL_error(l, "CPU limit exceeded"); } |
|||
} |
|||
|
|||
|
|||
// removes global variables |
|||
static void LF_nilglobal(lua_State *l, char *var) |
|||
{ |
|||
lua_pushnil(l); |
|||
lua_setglobal(l, var); |
|||
} |
|||
|
|||
|
|||
// Limited memory allocator |
|||
static void *LF_limit_alloc(void *ud, void *ptr, size_t osize, size_t nsize) |
|||
{ |
|||
size_t *limit = ud; |
|||
|
|||
*limit += osize; |
|||
|
|||
if(nsize == 0){ |
|||
free(ptr); |
|||
return NULL; |
|||
} else { |
|||
if(*limit < nsize){ return NULL; } |
|||
*limit -= nsize; |
|||
|
|||
return realloc(ptr, nsize); |
|||
} |
|||
} |
|||
|
|||
|
|||
// Allocates and initializes a new set of limits for a lua state |
|||
LF_limits *LF_newlimits() |
|||
{ |
|||
LF_limits *limits = malloc(sizeof(LF_limits)); |
|||
if(limits == NULL){ return NULL; } |
|||
|
|||
memset(limits, 0, sizeof(LF_limits)); |
|||
return limits; |
|||
} |
|||
|
|||
|
|||
void LF_setlimits(LF_limits *limits, size_t memory, size_t output, uint32_t cpu_sec, uint32_t cpu_usec) |
|||
{ |
|||
limits->memory = memory; |
|||
limits->output = output; |
|||
limits->cpu.tv_usec = cpu_usec; |
|||
limits->cpu.tv_sec = cpu_sec; |
|||
} |
|||
|
|||
|
|||
|
|||
void LF_enablelimits(lua_State *l, LF_limits *limits) |
|||
{ |
|||
if(limits->cpu.tv_usec > 0 || limits->cpu.tv_sec > 0){ |
|||
struct timeval curr; |
|||
if(LF_threadusage(&curr)){ |
|||
printf("CPU usage sample error\n"); // FIX ME |
|||
} else { |
|||
timeradd(&limits->cpu, &curr, &limits->cpu); |
|||
lua_sethook(l, &LF_limit_hook, LUA_MASKCOUNT, 1000); |
|||
} |
|||
|
|||
lua_pushstring(l, "CPU_LIMIT"); |
|||
lua_pushlightuserdata(l, &limits->cpu); |
|||
lua_rawset(l, LUA_REGISTRYINDEX); |
|||
} |
|||
|
|||
if(limits->output){ |
|||
lua_pushstring(l, "RESPONSE_LIMIT"); |
|||
lua_pushlightuserdata(l, &limits->output); |
|||
lua_rawset(l, LUA_REGISTRYINDEX); |
|||
} |
|||
|
|||
if(limits->memory){ lua_setallocf(l, &LF_limit_alloc, &limits->memory); } |
|||
} |
|||
|
|||
|
|||
// Initialize a new lua state using specific parameters |
|||
lua_State *LF_newstate(int sandbox, char *content_type) |
|||
{ |
|||
lua_State *l = luaL_newstate(); |
|||
if(l == NULL){ return NULL; } |
|||
|
|||
// Load base |
|||
lua_pushcfunction(l, luaopen_base); |
|||
lua_pushliteral(l, ""); |
|||
lua_call(l, 1, 0); |
|||
|
|||
if(sandbox){ |
|||
// Load table |
|||
lua_pushcfunction(l, luaopen_table); |
|||
lua_pushliteral(l, LUA_TABLIBNAME); |
|||
lua_call(l, 1, 0); |
|||
|
|||
// Load string |
|||
lua_pushcfunction(l, luaopen_string); |
|||
lua_pushliteral(l, LUA_STRLIBNAME); |
|||
lua_call(l, 1, 0); |
|||
|
|||
// Load math |
|||
lua_pushcfunction(l, luaopen_math); |
|||
lua_pushliteral(l, LUA_MATHLIBNAME); |
|||
lua_call(l, 1, 0); |
|||
|
|||
// Nil out unsafe functions/objects |
|||
LF_nilglobal(l, "dofile"); |
|||
LF_nilglobal(l, "load"); |
|||
LF_nilglobal(l, "loadfile"); |
|||
LF_nilglobal(l, "xpcall"); |
|||
LF_nilglobal(l, "pcall"); |
|||
LF_nilglobal(l, "module"); |
|||
LF_nilglobal(l, "require"); |
|||
} |
|||
|
|||
// Register the print function |
|||
lua_register(l, "print", &LF_print); |
|||
// Register the write function |
|||
lua_register(l, "write", &LF_write); |
|||
|
|||
// Setup the "HEADER" value |
|||
lua_newtable(l); |
|||
|
|||
lua_pushstring(l, "Content-Type"); |
|||
lua_pushstring(l, content_type); |
|||
lua_rawset(l, 1); |
|||
|
|||
lua_setglobal(l, "HEADER"); |
|||
|
|||
return l; |
|||
} |
|||
|
|||
|
|||
// Set GET variables |
|||
static void LF_parsequerystring(lua_State *l, char *query_string) |
|||
{ |
|||
lua_newtable(l); |
|||
|
|||
int stack = lua_gettop(l); |
|||
|
|||
char *sptr, *optr, *nptr; |
|||
for(nptr = optr = sptr = &query_string[0]; 1; optr++){ |
|||
switch(*optr){ |
|||
case '+': |
|||
*nptr++ = ' '; |
|||
break; |
|||
|
|||
case '=': |
|||
// Push a key, if it's valid and there's not already one |
|||
if(lua_gettop(l) == stack){ |
|||
if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); } |
|||
sptr = nptr; |
|||
} else { |
|||
*nptr++ = '='; |
|||
} |
|||
break; |
|||
|
|||
case '&': |
|||
// Push key or value if valid |
|||
if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); } |
|||
|
|||
// Push value, if there is already a key |
|||
if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); } |
|||
|
|||
// Set key/value if they exist |
|||
if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); } |
|||
|
|||
sptr = nptr; |
|||
break; |
|||
|
|||
case '%': { |
|||
// Decode hex percent encoded sets, if valid |
|||
char c1 = *(optr+1), c2 = *(optr+2); |
|||
if(isxdigit(c1) && isxdigit(c2)){ |
|||
char digit = 16 * (c1 >= 'A' ? (c1 & 0xdf) - '7' : (c1 - '0')); |
|||
digit += (c2 >= 'A' ? (c2 & 0xdf) - '7' : (c2 - '0')); |
|||
*nptr++ = digit; |
|||
optr += 2; |
|||
} else { |
|||
*nptr++ = '%'; |
|||
} |
|||
} break; |
|||
|
|||
case '\0': |
|||
// Push key or value if valid |
|||
if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); } |
|||
|
|||
// Push value, if needed |
|||
if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); } |
|||
|
|||
// Set key/value if valid |
|||
if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); } |
|||
|
|||
// Finally, set the table |
|||
lua_setglobal(l, "GET"); |
|||
return; |
|||
break; |
|||
|
|||
default: |
|||
*nptr++ = *optr; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
// Parses fastcgi request |
|||
void LF_parserequest(lua_State *l, FCGX_Request *request, LF_response *response) |
|||
{ |
|||
lua_newtable(l); |
|||
for(char **p = request->envp; *p; ++p){ |
|||
char *vptr = strchr(*p, '='); |
|||
int keylen = (vptr - *p); |
|||
|
|||
lua_pushlstring(l, *p, keylen); // Push Key |
|||
lua_pushstring(l, (vptr+1)); // Push Value |
|||
lua_rawset(l, 1); // Set key/value into table |
|||
|
|||
if(keylen == 12 && memcmp(*p, "QUERY_STRING", 12) == 0){ |
|||
LF_parsequerystring(l, (vptr+1)); |
|||
} |
|||
} |
|||
lua_setglobal(l, "REQUEST"); |
|||
|
|||
response->committed = 0; |
|||
response->out = request->out; |
|||
lua_pushstring(l, "RESPONSE"); |
|||
lua_pushlightuserdata(l, response); |
|||
lua_rawset(l, LUA_REGISTRYINDEX); |
|||
} |
|||
|
|||
|
|||
static const char *LF_filereader(lua_State *l, void *data, size_t *size) |
|||
{ |
|||
LF_loaderdata *ld = data; |
|||
|
|||
*size = read(ld->fd, ld->buffer, LF_BUFFERSIZE); |
|||
|
|||
if(ld->total == 0 && *size > 3){ |
|||
if(memcmp(ld->buffer, LUA_SIGNATURE, 4) == 0){ |
|||
luaL_error(l, "Compiled bytecode not supported."); |
|||
} |
|||
} |
|||
|
|||
switch(*size){ |
|||
case 0: return NULL; |
|||
case -1: luaL_error(l, strerror(errno)); |
|||
default: |
|||
ld->total += *size; |
|||
return ld->buffer; |
|||
} |
|||
} |
|||
|
|||
|
|||
// Loads a lua file into a state |
|||
int LF_loadfile(lua_State *l) |
|||
{ |
|||
lua_getglobal(l, "REQUEST"); |
|||
|
|||
int stack = lua_gettop(l); |
|||
|
|||
lua_pushstring(l, "SCRIPT_FILENAME"); |
|||
lua_rawget(l, stack); |
|||
const char *path = lua_tostring(l, stack+1); |
|||
|
|||
lua_pushstring(l, "SCRIPT_NAME"); |
|||
lua_rawget(l, stack); |
|||
const char *name = lua_tostring(l, stack+2); |
|||
|
|||
LF_loaderdata ld; |
|||
ld.total = 0; |
|||
ld.fd = open(path, O_RDONLY); |
|||
if(ld.fd == -1){ |
|||
lua_pushstring(l, strerror(errno)); |
|||
return errno; |
|||
} |
|||
|
|||
// Generate a string with an '=' followed by the script name |
|||
// this ensures lua will generation a reasonable error |
|||
size_t len = strlen(name) + 1; |
|||
char scriptname[len + 1]; |
|||
scriptname[0] = '='; |
|||
memcpy(&scriptname[1], name, len); |
|||
|
|||
int r = lua_load(l, &LF_filereader, &ld, scriptname); |
|||
|
|||
close(ld.fd); |
|||
return (r == 0 ? 0 : ENOMSG); |
|||
} |
|||
|
|||
|
|||
// Closes a state |
|||
void LF_closestate(lua_State *l) |
|||
{ |
|||
lua_close(l); |
|||
} |
@ -0,0 +1,20 @@ |
|||
typedef struct { |
|||
int committed; |
|||
FCGX_Stream *out; |
|||
} LF_response; |
|||
|
|||
typedef struct { |
|||
size_t memory; |
|||
struct timeval cpu; |
|||
size_t output; |
|||
} LF_limits; |
|||
|
|||
|
|||
lua_State *LF_newstate(int, char *); |
|||
LF_limits *LF_newlimits(); |
|||
void LF_setlimits(LF_limits *, size_t, size_t, uint32_t, uint32_t); |
|||
void LF_enablelimits(lua_State *, LF_limits *); |
|||
void LF_parserequest(lua_State *l, FCGX_Request *, LF_response *); |
|||
void LF_emptystack(lua_State *); |
|||
int LF_loadfile(lua_State *); |
|||
void LF_closestate(lua_State *); |
Reference in new issue