1 #include "mod_cml.h" 2 #include "mod_cml_funcs.h" 3 #include "log.h" 4 #include "stream.h" 5 6 #include "stat_cache.h" 7 8 #include <assert.h> 9 #include <stdio.h> 10 #include <errno.h> 11 #include <time.h> 12 #include <string.h> 13 14 #define HASHLEN 16 15 typedef unsigned char HASH[HASHLEN]; 16 #define HASHHEXLEN 32 17 typedef char HASHHEX[HASHHEXLEN+1]; 18 #ifdef USE_OPENSSL 19 #define IN const 20 #else 21 #define IN 22 #endif 23 #define OUT 24 25 #ifdef HAVE_LUA_H 26 27 #include <lua.h> 28 #include <lualib.h> 29 #include <lauxlib.h> 30 31 typedef struct { 32 stream st; 33 int done; 34 } readme; 35 36 static const char * load_file(lua_State *L, void *data, size_t *size) { 37 readme *rm = data; 38 39 UNUSED(L); 40 41 if (rm->done) return 0; 42 43 *size = rm->st.size; 44 rm->done = 1; 45 return rm->st.start; 46 } 47 48 static int lua_to_c_get_string(lua_State *L, const char *varname, buffer *b) { 49 int curelem; 50 51 lua_pushstring(L, varname); 52 53 curelem = lua_gettop(L); 54 lua_gettable(L, LUA_GLOBALSINDEX); 55 56 /* it should be a table */ 57 if (!lua_isstring(L, curelem)) { 58 lua_settop(L, curelem - 1); 59 60 return -1; 61 } 62 63 buffer_copy_string(b, lua_tostring(L, curelem)); 64 65 lua_pop(L, 1); 66 67 assert(curelem - 1 == lua_gettop(L)); 68 69 return 0; 70 } 71 72 static int lua_to_c_is_table(lua_State *L, const char *varname) { 73 int curelem; 74 75 lua_pushstring(L, varname); 76 77 curelem = lua_gettop(L); 78 lua_gettable(L, LUA_GLOBALSINDEX); 79 80 /* it should be a table */ 81 if (!lua_istable(L, curelem)) { 82 lua_settop(L, curelem - 1); 83 84 return 0; 85 } 86 87 lua_settop(L, curelem - 1); 88 89 assert(curelem - 1 == lua_gettop(L)); 90 91 return 1; 92 } 93 94 static int c_to_lua_push(lua_State *L, int tbl, const char *key, size_t key_len, const char *val, size_t val_len) { 95 lua_pushlstring(L, key, key_len); 96 lua_pushlstring(L, val, val_len); 97 lua_settable(L, tbl); 98 99 return 0; 100 } 101 102 103 static int cache_export_get_params(lua_State *L, int tbl, buffer *qrystr) { 104 size_t is_key = 1; 105 size_t i; 106 char *key = NULL, *val = NULL; 107 108 key = qrystr->ptr; 109 110 /* we need the \0 */ 111 for (i = 0; i < qrystr->used; i++) { 112 switch(qrystr->ptr[i]) { 113 case '=': 114 if (is_key) { 115 val = qrystr->ptr + i + 1; 116 117 qrystr->ptr[i] = '\0'; 118 119 is_key = 0; 120 } 121 122 break; 123 case '&': 124 case '\0': /* fin symbol */ 125 if (!is_key) { 126 /* we need at least a = since the last & */ 127 128 /* terminate the value */ 129 qrystr->ptr[i] = '\0'; 130 131 c_to_lua_push(L, tbl, 132 key, strlen(key), 133 val, strlen(val)); 134 } 135 136 key = qrystr->ptr + i + 1; 137 val = NULL; 138 is_key = 1; 139 break; 140 } 141 } 142 143 return 0; 144 } 145 #if 0 146 int cache_export_cookie_params(server *srv, connection *con, plugin_data *p) { 147 data_unset *d; 148 149 UNUSED(srv); 150 151 if (NULL != (d = array_get_element(con->request.headers, "Cookie"))) { 152 data_string *ds = (data_string *)d; 153 size_t key = 0, value = 0; 154 size_t is_key = 1, is_sid = 0; 155 size_t i; 156 157 /* found COOKIE */ 158 if (!DATA_IS_STRING(d)) return -1; 159 if (ds->value->used == 0) return -1; 160 161 if (ds->value->ptr[0] == '\0' || 162 ds->value->ptr[0] == '=' || 163 ds->value->ptr[0] == ';') return -1; 164 165 buffer_reset(p->session_id); 166 for (i = 0; i < ds->value->used; i++) { 167 switch(ds->value->ptr[i]) { 168 case '=': 169 if (is_key) { 170 if (0 == strncmp(ds->value->ptr + key, "PHPSESSID", i - key)) { 171 /* found PHP-session-id-key */ 172 is_sid = 1; 173 } 174 value = i + 1; 175 176 is_key = 0; 177 } 178 179 break; 180 case ';': 181 if (is_sid) { 182 buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value); 183 } 184 185 is_sid = 0; 186 key = i + 1; 187 value = 0; 188 is_key = 1; 189 break; 190 case ' ': 191 if (is_key == 1 && key == i) key = i + 1; 192 if (is_key == 0 && value == i) value = i + 1; 193 break; 194 case '\0': 195 if (is_sid) { 196 buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value); 197 } 198 /* fin */ 199 break; 200 } 201 } 202 } 203 204 return 0; 205 } 206 #endif 207 208 int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { 209 lua_State *L; 210 readme rm; 211 int ret = -1; 212 buffer *b = buffer_init(); 213 int header_tbl = 0; 214 215 rm.done = 0; 216 stream_open(&rm.st, fn); 217 218 /* push the lua file to the interpreter and see what happends */ 219 L = luaL_newstate(); 220 luaL_openlibs(L); 221 222 /* register functions */ 223 lua_register(L, "md5", f_crypto_md5); 224 lua_register(L, "file_mtime", f_file_mtime); 225 lua_register(L, "file_isreg", f_file_isreg); 226 lua_register(L, "file_isdir", f_file_isreg); 227 lua_register(L, "dir_files", f_dir_files); 228 229 #ifdef HAVE_MEMCACHE_H 230 lua_pushliteral(L, "memcache_get_long"); 231 lua_pushlightuserdata(L, p->conf.mc); 232 lua_pushcclosure(L, f_memcache_get_long, 1); 233 lua_settable(L, LUA_GLOBALSINDEX); 234 235 lua_pushliteral(L, "memcache_get_string"); 236 lua_pushlightuserdata(L, p->conf.mc); 237 lua_pushcclosure(L, f_memcache_get_string, 1); 238 lua_settable(L, LUA_GLOBALSINDEX); 239 240 lua_pushliteral(L, "memcache_exists"); 241 lua_pushlightuserdata(L, p->conf.mc); 242 lua_pushcclosure(L, f_memcache_exists, 1); 243 lua_settable(L, LUA_GLOBALSINDEX); 244 #endif 245 /* register CGI environment */ 246 lua_pushliteral(L, "request"); 247 lua_newtable(L); 248 lua_settable(L, LUA_GLOBALSINDEX); 249 250 lua_pushliteral(L, "request"); 251 header_tbl = lua_gettop(L); 252 lua_gettable(L, LUA_GLOBALSINDEX); 253 254 c_to_lua_push(L, header_tbl, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); 255 c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); 256 c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); 257 c_to_lua_push(L, header_tbl, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); 258 if (!buffer_is_empty(con->request.pathinfo)) { 259 c_to_lua_push(L, header_tbl, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); 260 } 261 262 c_to_lua_push(L, header_tbl, CONST_STR_LEN("CWD"), CONST_BUF_LEN(p->basedir)); 263 c_to_lua_push(L, header_tbl, CONST_STR_LEN("BASEURL"), CONST_BUF_LEN(p->baseurl)); 264 265 /* register GET parameter */ 266 lua_pushliteral(L, "get"); 267 lua_newtable(L); 268 lua_settable(L, LUA_GLOBALSINDEX); 269 270 lua_pushliteral(L, "get"); 271 header_tbl = lua_gettop(L); 272 lua_gettable(L, LUA_GLOBALSINDEX); 273 274 buffer_copy_string_buffer(b, con->uri.query); 275 cache_export_get_params(L, header_tbl, b); 276 buffer_reset(b); 277 278 /* 2 default constants */ 279 lua_pushliteral(L, "CACHE_HIT"); 280 lua_pushnumber(L, 0); 281 lua_settable(L, LUA_GLOBALSINDEX); 282 283 lua_pushliteral(L, "CACHE_MISS"); 284 lua_pushnumber(L, 1); 285 lua_settable(L, LUA_GLOBALSINDEX); 286 287 /* load lua program */ 288 if (lua_load(L, load_file, &rm, fn->ptr) || lua_pcall(L,0,1,0)) { 289 log_error_write(srv, __FILE__, __LINE__, "s", 290 lua_tostring(L,-1)); 291 292 goto error; 293 } 294 295 /* get return value */ 296 ret = (int)lua_tonumber(L, -1); 297 lua_pop(L, 1); 298 299 /* fetch the data from lua */ 300 lua_to_c_get_string(L, "trigger_handler", p->trigger_handler); 301 302 if (0 == lua_to_c_get_string(L, "output_contenttype", b)) { 303 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(b)); 304 } 305 306 if (ret == 0) { 307 /* up to now it is a cache-hit, check if all files exist */ 308 309 int curelem; 310 time_t mtime = 0; 311 312 if (!lua_to_c_is_table(L, "output_include")) { 313 log_error_write(srv, __FILE__, __LINE__, "s", 314 "output_include is missing or not a table"); 315 ret = -1; 316 317 goto error; 318 } 319 320 lua_pushstring(L, "output_include"); 321 322 curelem = lua_gettop(L); 323 lua_gettable(L, LUA_GLOBALSINDEX); 324 325 /* HOW-TO build a etag ? 326 * as we don't just have one file we have to take the stat() 327 * from all base files, merge them and build the etag from 328 * it later. 329 * 330 * The mtime of the content is the mtime of the freshest base file 331 * 332 * */ 333 334 lua_pushnil(L); /* first key */ 335 while (lua_next(L, curelem) != 0) { 336 stat_cache_entry *sce = NULL; 337 /* key' is at index -2 and value' at index -1 */ 338 339 if (lua_isstring(L, -1)) { 340 const char *s = lua_tostring(L, -1); 341 342 /* the file is relative, make it absolute */ 343 if (s[0] != '/') { 344 buffer_copy_string_buffer(b, p->basedir); 345 buffer_append_string(b, lua_tostring(L, -1)); 346 } else { 347 buffer_copy_string(b, lua_tostring(L, -1)); 348 } 349 350 if (HANDLER_ERROR == stat_cache_get_entry(srv, con, b, &sce)) { 351 /* stat failed */ 352 353 switch(errno) { 354 case ENOENT: 355 /* a file is missing, call the handler to generate it */ 356 if (!buffer_is_empty(p->trigger_handler)) { 357 ret = 1; /* cache-miss */ 358 359 log_error_write(srv, __FILE__, __LINE__, "s", 360 "a file is missing, calling handler"); 361 362 break; 363 } else { 364 /* handler not set -> 500 */ 365 ret = -1; 366 367 log_error_write(srv, __FILE__, __LINE__, "s", 368 "a file missing and no handler set"); 369 370 break; 371 } 372 break; 373 default: 374 break; 375 } 376 } else { 377 chunkqueue_append_file(con->write_queue, b, 0, sce->st.st_size); 378 if (sce->st.st_mtime > mtime) mtime = sce->st.st_mtime; 379 } 380 } else { 381 /* not a string */ 382 ret = -1; 383 log_error_write(srv, __FILE__, __LINE__, "s", 384 "not a string"); 385 break; 386 } 387 388 lua_pop(L, 1); /* removes value'; keeps key' for next iteration */ 389 } 390 391 lua_settop(L, curelem - 1); 392 393 if (ret == 0) { 394 data_string *ds; 395 char timebuf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")]; 396 buffer tbuf; 397 398 con->file_finished = 1; 399 400 ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"); 401 402 /* no Last-Modified specified */ 403 if ((mtime) && (NULL == ds)) { 404 405 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime)); 406 407 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), timebuf, sizeof(timebuf) - 1); 408 409 410 tbuf.ptr = timebuf; 411 tbuf.used = sizeof(timebuf); 412 tbuf.size = sizeof(timebuf); 413 } else if (ds) { 414 tbuf.ptr = ds->value->ptr; 415 tbuf.used = ds->value->used; 416 tbuf.size = ds->value->size; 417 } else { 418 tbuf.size = 0; 419 tbuf.used = 0; 420 tbuf.ptr = NULL; 421 } 422 423 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, &tbuf)) { 424 /* ok, the client already has our content, 425 * no need to send it again */ 426 427 chunkqueue_reset(con->write_queue); 428 ret = 0; /* cache-hit */ 429 } 430 } else { 431 chunkqueue_reset(con->write_queue); 432 } 433 } 434 435 if (ret == 1 && !buffer_is_empty(p->trigger_handler)) { 436 /* cache-miss */ 437 buffer_copy_string_buffer(con->uri.path, p->baseurl); 438 buffer_append_string_buffer(con->uri.path, p->trigger_handler); 439 440 buffer_copy_string_buffer(con->physical.path, p->basedir); 441 buffer_append_string_buffer(con->physical.path, p->trigger_handler); 442 443 chunkqueue_reset(con->write_queue); 444 } 445 446 error: 447 lua_close(L); 448 449 stream_close(&rm.st); 450 buffer_free(b); 451 452 return ret /* cache-error */; 453 } 454 #else 455 int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { 456 UNUSED(srv); 457 UNUSED(con); 458 UNUSED(p); 459 UNUSED(fn); 460 /* error */ 461 return -1; 462 } 463 #endif 464