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