a sandboxed Lua backend for FastCGI
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

366 lines
7.7 KiB

  1. #include <stdlib.h>
  2. #include <stdint.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <ctype.h>
  6. #include <fcntl.h>
  7. #include <errno.h>
  8. #include <sys/time.h>
  9. #include <sys/resource.h>
  10. #include <fcgiapp.h>
  11. #include <lua5.1/lua.h>
  12. #include <lua5.1/lauxlib.h>
  13. #include <lua5.1/lualib.h>
  14. #include "lua.h"
  15. #include "lfuncs.h"
  16. #define LF_BUFFERSIZE 4096
  17. typedef struct {
  18. int fd;
  19. char buffer[LF_BUFFERSIZE];
  20. size_t total;
  21. } LF_loaderdata;
  22. #ifdef DEBUG
  23. void LF_printstack(lua_State *l)
  24. {
  25. int ss = lua_gettop(l);
  26. printf("Lua Stack:\n");
  27. for(int i=1; i <= ss; i++){
  28. printf("\t%d) %s\n", i, lua_typename(l, lua_type(l, i)));
  29. }
  30. }
  31. #endif
  32. // Gets current thread usage
  33. static int LF_threadusage(struct timeval *tv)
  34. {
  35. struct rusage usage;
  36. if(getrusage(RUSAGE_SELF, &usage) == -1){
  37. return 1;
  38. }
  39. // Add user to sys to get actual usage
  40. tv->tv_usec = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
  41. tv->tv_sec = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec;
  42. if(tv->tv_usec > 1000000){
  43. tv->tv_usec -= 1000000;
  44. tv->tv_sec++;
  45. }
  46. return 0;
  47. }
  48. // limits cpu usage
  49. static void LF_limit_hook(lua_State *l, lua_Debug *d)
  50. {
  51. lua_pushstring(l, "CPU_LIMIT");
  52. lua_rawget(l, LUA_REGISTRYINDEX);
  53. struct timeval *limit = lua_touserdata(l, -1);
  54. lua_pop(l, 1);
  55. struct timeval tv;
  56. if(LF_threadusage(&tv)){ luaL_error(l, "CPU usage sample error"); }
  57. if(timercmp(&tv, limit, >)){ luaL_error(l, "CPU limit exceeded"); }
  58. }
  59. // removes global variables
  60. static void LF_nilglobal(lua_State *l, char *var)
  61. {
  62. lua_pushnil(l);
  63. lua_setglobal(l, var);
  64. }
  65. // Limited memory allocator
  66. static void *LF_limit_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
  67. {
  68. size_t *limit = ud;
  69. *limit += osize;
  70. if(nsize == 0){
  71. free(ptr);
  72. return NULL;
  73. } else {
  74. if(*limit < nsize){ return NULL; }
  75. *limit -= nsize;
  76. return realloc(ptr, nsize);
  77. }
  78. }
  79. // Allocates and initializes a new set of limits for a lua state
  80. LF_limits *LF_newlimits()
  81. {
  82. LF_limits *limits = malloc(sizeof(LF_limits));
  83. if(limits == NULL){ return NULL; }
  84. memset(limits, 0, sizeof(LF_limits));
  85. return limits;
  86. }
  87. void LF_setlimits(LF_limits *limits, size_t memory, size_t output, uint32_t cpu_sec, uint32_t cpu_usec)
  88. {
  89. limits->memory = memory;
  90. limits->output = output;
  91. limits->cpu.tv_usec = cpu_usec;
  92. limits->cpu.tv_sec = cpu_sec;
  93. }
  94. void LF_enablelimits(lua_State *l, LF_limits *limits)
  95. {
  96. if(limits->cpu.tv_usec > 0 || limits->cpu.tv_sec > 0){
  97. struct timeval curr;
  98. if(LF_threadusage(&curr)){
  99. printf("CPU usage sample error\n"); // FIX ME
  100. } else {
  101. timeradd(&limits->cpu, &curr, &limits->cpu);
  102. lua_sethook(l, &LF_limit_hook, LUA_MASKCOUNT, 1000);
  103. }
  104. lua_pushstring(l, "CPU_LIMIT");
  105. lua_pushlightuserdata(l, &limits->cpu);
  106. lua_rawset(l, LUA_REGISTRYINDEX);
  107. }
  108. if(limits->output){
  109. lua_pushstring(l, "RESPONSE_LIMIT");
  110. lua_pushlightuserdata(l, &limits->output);
  111. lua_rawset(l, LUA_REGISTRYINDEX);
  112. }
  113. if(limits->memory){ lua_setallocf(l, &LF_limit_alloc, &limits->memory); }
  114. }
  115. // Initialize a new lua state using specific parameters
  116. lua_State *LF_newstate(int sandbox, char *content_type)
  117. {
  118. lua_State *l = luaL_newstate();
  119. if(l == NULL){ return NULL; }
  120. // Load base
  121. lua_pushcfunction(l, luaopen_base);
  122. lua_pushliteral(l, "");
  123. lua_call(l, 1, 0);
  124. if(sandbox){
  125. // Load table
  126. lua_pushcfunction(l, luaopen_table);
  127. lua_pushliteral(l, LUA_TABLIBNAME);
  128. lua_call(l, 1, 0);
  129. // Load string
  130. lua_pushcfunction(l, luaopen_string);
  131. lua_pushliteral(l, LUA_STRLIBNAME);
  132. lua_call(l, 1, 0);
  133. // Load math
  134. lua_pushcfunction(l, luaopen_math);
  135. lua_pushliteral(l, LUA_MATHLIBNAME);
  136. lua_call(l, 1, 0);
  137. // Nil out unsafe functions/objects
  138. LF_nilglobal(l, "dofile");
  139. LF_nilglobal(l, "load");
  140. LF_nilglobal(l, "loadfile");
  141. LF_nilglobal(l, "xpcall");
  142. LF_nilglobal(l, "pcall");
  143. LF_nilglobal(l, "module");
  144. LF_nilglobal(l, "require");
  145. }
  146. // Register the print function
  147. lua_register(l, "print", &LF_print);
  148. // Register the write function
  149. lua_register(l, "write", &LF_write);
  150. // Setup the "HEADER" value
  151. lua_newtable(l);
  152. lua_pushstring(l, "Content-Type");
  153. lua_pushstring(l, content_type);
  154. lua_rawset(l, 1);
  155. lua_setglobal(l, "HEADER");
  156. return l;
  157. }
  158. // Set GET variables
  159. static void LF_parsequerystring(lua_State *l, char *query_string)
  160. {
  161. lua_newtable(l);
  162. int stack = lua_gettop(l);
  163. char *sptr, *optr, *nptr;
  164. for(nptr = optr = sptr = &query_string[0]; 1; optr++){
  165. switch(*optr){
  166. case '+':
  167. *nptr++ = ' ';
  168. break;
  169. case '=':
  170. // Push a key, if it's valid and there's not already one
  171. if(lua_gettop(l) == stack){
  172. if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
  173. sptr = nptr;
  174. } else {
  175. *nptr++ = '=';
  176. }
  177. break;
  178. case '&':
  179. // Push key or value if valid
  180. if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
  181. // Push value, if there is already a key
  182. if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); }
  183. // Set key/value if they exist
  184. if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); }
  185. sptr = nptr;
  186. break;
  187. case '%': {
  188. // Decode hex percent encoded sets, if valid
  189. char c1 = *(optr+1), c2 = *(optr+2);
  190. if(isxdigit(c1) && isxdigit(c2)){
  191. char digit = 16 * (c1 >= 'A' ? (c1 & 0xdf) - '7' : (c1 - '0'));
  192. digit += (c2 >= 'A' ? (c2 & 0xdf) - '7' : (c2 - '0'));
  193. *nptr++ = digit;
  194. optr += 2;
  195. } else {
  196. *nptr++ = '%';
  197. }
  198. } break;
  199. case '\0':
  200. // Push key or value if valid
  201. if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
  202. // Push value, if needed
  203. if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); }
  204. // Set key/value if valid
  205. if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); }
  206. // Finally, set the table
  207. lua_setglobal(l, "GET");
  208. return;
  209. break;
  210. default:
  211. *nptr++ = *optr;
  212. break;
  213. }
  214. }
  215. }
  216. // Parses fastcgi request
  217. void LF_parserequest(lua_State *l, FCGX_Request *request, LF_response *response)
  218. {
  219. lua_newtable(l);
  220. for(char **p = request->envp; *p; ++p){
  221. char *vptr = strchr(*p, '=');
  222. int keylen = (vptr - *p);
  223. lua_pushlstring(l, *p, keylen); // Push Key
  224. lua_pushstring(l, (vptr+1)); // Push Value
  225. lua_rawset(l, 1); // Set key/value into table
  226. if(keylen == 12 && memcmp(*p, "QUERY_STRING", 12) == 0){
  227. LF_parsequerystring(l, (vptr+1));
  228. }
  229. }
  230. lua_setglobal(l, "REQUEST");
  231. response->committed = 0;
  232. response->out = request->out;
  233. lua_pushstring(l, "RESPONSE");
  234. lua_pushlightuserdata(l, response);
  235. lua_rawset(l, LUA_REGISTRYINDEX);
  236. }
  237. static const char *LF_filereader(lua_State *l, void *data, size_t *size)
  238. {
  239. LF_loaderdata *ld = data;
  240. *size = read(ld->fd, ld->buffer, LF_BUFFERSIZE);
  241. if(ld->total == 0 && *size > 3){
  242. if(memcmp(ld->buffer, LUA_SIGNATURE, 4) == 0){
  243. luaL_error(l, "Compiled bytecode not supported.");
  244. }
  245. }
  246. switch(*size){
  247. case 0: return NULL;
  248. case -1: luaL_error(l, strerror(errno));
  249. default:
  250. ld->total += *size;
  251. return ld->buffer;
  252. }
  253. }
  254. // Loads a lua file into a state
  255. int LF_loadfile(lua_State *l)
  256. {
  257. lua_getglobal(l, "REQUEST");
  258. int stack = lua_gettop(l);
  259. lua_pushstring(l, "SCRIPT_FILENAME");
  260. lua_rawget(l, stack);
  261. const char *path = lua_tostring(l, stack+1);
  262. lua_pushstring(l, "SCRIPT_NAME");
  263. lua_rawget(l, stack);
  264. const char *name = lua_tostring(l, stack+2);
  265. LF_loaderdata ld;
  266. ld.total = 0;
  267. ld.fd = open(path, O_RDONLY);
  268. if(ld.fd == -1){
  269. lua_pushstring(l, strerror(errno));
  270. return errno;
  271. }
  272. // Generate a string with an '=' followed by the script name
  273. // this ensures lua will generation a reasonable error
  274. size_t len = strlen(name) + 1;
  275. char scriptname[len + 1];
  276. scriptname[0] = '=';
  277. memcpy(&scriptname[1], name, len);
  278. int r = lua_load(l, &LF_filereader, &ld, scriptname);
  279. close(ld.fd);
  280. return (r == 0 ? 0 : ENOMSG);
  281. }
  282. // Closes a state
  283. void LF_closestate(lua_State *l)
  284. {
  285. lua_close(l);
  286. }