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
load_file(lua_State * L,void * data,size_t * size)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
lua_to_c_get_string(lua_State * L,const char * varname,buffer * b)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
lua_to_c_is_table(lua_State * L,const char * varname)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
c_to_lua_push(lua_State * L,int tbl,const char * key,size_t key_len,const char * val,size_t val_len)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
cache_export_get_params(lua_State * L,int tbl,buffer * qrystr)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
cache_parse_lua(server * srv,connection * con,plugin_data * p,buffer * fn)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
cache_parse_lua(server * srv,connection * con,plugin_data * p,buffer * fn)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