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