1 /**
2  * Copyright 2010 Christian Liesch
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /**
18  * @file
19  *
20  * @Author christian liesch <liesch@gmx.ch>
21  *
22  * Implementation of the HTTP Test Tool Lua Extention
23  */
24 
25 /************************************************************************
26  * Includes
27  ***********************************************************************/
28 #define LUA_COMPAT_MODULE
29 #include <lua.h>
30 #include <lualib.h>
31 #include <lauxlib.h>
32 
33 #include <apr_sha1.h>
34 
35 #include "lua_crypto.h"
36 #include "module.h"
37 
38 /************************************************************************
39  * Definitions
40  ***********************************************************************/
41 const char * lua_module = "lua_module";
42 
43 typedef struct lua_wconf_s {
44   int starting_line_nr;
45   apr_table_t *params;
46   apr_table_t *retvars;
47   lua_State *L;
48 } lua_wconf_t;
49 
50 typedef struct lua_gconf_s {
51 	int do_read_line;
52 } lua_gconf_t;
53 
54 typedef struct lua_reader_s {
55   apr_pool_t *pool;
56   apr_table_t *lines;
57   int i;
58   int newline;
59   int starting_line_nr;
60 } lua_reader_t;
61 
62 /************************************************************************
63  * Private
64  ***********************************************************************/
65 /**
66  * Get lua config from worker
67  *
68  * @param worker IN worker
69  * @return lua worker config
70  */
lua_get_worker_config(worker_t * worker)71 static lua_wconf_t *lua_get_worker_config(worker_t *worker) {
72   lua_wconf_t *wconf = module_get_config(worker->config, lua_module);
73   if (wconf == NULL) {
74     wconf = apr_pcalloc(worker->pbody, sizeof(*wconf));
75     wconf->params = apr_table_make(worker->pbody, 5);
76     wconf->retvars = apr_table_make(worker->pbody, 5);
77     module_set_config(worker->config, apr_pstrdup(worker->pbody, lua_module), wconf);
78   }
79   return wconf;
80 }
81 
82 /**
83  * Get lua config from global
84  *
85  * @param global IN
86  * @return lua config
87  */
lua_get_global_config(global_t * global)88 static lua_gconf_t *lua_get_global_config(global_t *global) {
89   lua_gconf_t *gconf = module_get_config(global->config, lua_module);
90   if (gconf == NULL) {
91     gconf = apr_pcalloc(global->pool, sizeof(*gconf));
92     module_set_config(global->config, apr_pstrdup(global->pool, lua_module), gconf);
93   }
94   return gconf;
95 }
96 
97 /**
98  * Get a new lua reader instance
99  * @param worker IN callee
100  * @param pool IN
101  * @return lua reader instance
102  */
lua_new_lua_reader(worker_t * worker,apr_pool_t * pool)103 static lua_reader_t *lua_new_lua_reader(worker_t *worker, apr_pool_t *pool) {
104   lua_wconf_t *wconf = lua_get_worker_config(worker->block);
105   lua_reader_t *reader = apr_pcalloc(pool, sizeof(*reader));
106   reader->pool = pool;
107   reader->lines = worker->lines;
108   reader->starting_line_nr = wconf->starting_line_nr;
109 	return reader;
110 }
111 
112 /**
113  * A simple lua line reader
114  * @param L in lua state
115  * @param ud IN user data
116  * @param size OUT len of string
117  * @return line
118  */
lua_get_line(lua_State * L,void * ud,size_t * size)119 static const char *lua_get_line(lua_State *L, void *ud, size_t *size) {
120   lua_reader_t *reader = ud;
121   apr_table_entry_t * e;
122 
123   e = (apr_table_entry_t *) apr_table_elts(reader->lines)->elts;
124   if (reader->starting_line_nr) {
125     --reader->starting_line_nr;
126     *size = 1;
127     return apr_pstrdup(reader->pool, "\n");
128   }
129   if (reader->i < apr_table_elts(reader->lines)->nelts) {
130     if (reader->newline) {
131       reader->newline = 0;
132       *size = 1;
133       return apr_pstrdup(reader->pool, "\n");
134     }
135     else {
136       const char *line = e[reader->i].val;
137       *size = strlen(line);
138       ++reader->i;
139       reader->newline = 1;
140       return line;
141     }
142   }
143   else {
144     return NULL;
145   }
146 }
147 
148 /**
149  * Do push the httest version on the stack
150  * @lua_return version as a string
151  * @param L IN lua state
152  * @return 1
153  */
luam_version(lua_State * L)154 static int luam_version(lua_State *L) {
155   lua_pushstring(L, PACKAGE_VERSION);
156   return 1;
157 }
158 
159 /**
160  * Execute httest script.
161  * @param L IN lua state
162  * @return 0
163  */
luam_interpret(lua_State * L)164 static int luam_interpret(lua_State *L) {
165   apr_status_t status;
166   apr_pool_t *ptmp;
167   worker_t *worker;
168   worker_t *parent;
169   worker_t *call;
170   const char *string;
171   apr_table_t *lines;
172   char *buffer;
173   char *last;
174   char *line;
175   apr_size_t len;
176 
177   if (!lua_isstring(L, -1)) {
178     luaL_error(L, "Expect a string to interpret");
179     return 1;
180   }
181 
182   string = lua_tolstring(L, -1, &len);
183   lua_pop(L, 1);
184 
185   lua_getfield(L, LUA_REGISTRYINDEX, "htt_worker");
186   worker = lua_touserdata(L, 1);
187   lua_pop(L, 1);
188 
189   lua_getfield(L, LUA_REGISTRYINDEX, "htt_parent");
190   parent = lua_touserdata(L, 1);
191   lua_pop(L, 1);
192 
193   HT_POOL_CREATE(&ptmp);
194 
195   lines = apr_table_make(ptmp, 5);
196 
197   call = apr_pcalloc(ptmp, sizeof(*call));
198   memcpy(call, worker, sizeof(*call));
199 
200   buffer = apr_pcalloc(ptmp, len+1);
201   memcpy(buffer, string, len);
202 
203   line = apr_strtok(buffer, "\n", &last);
204   while (line) {
205     while (*line == ' ') { ++line ; }
206     if (*line != '\0') {
207       apr_table_add(lines, "lua inline", line);
208     }
209     line = apr_strtok(NULL, "\n", &last);
210   }
211 
212   call->lines = lines;
213   call->interpret = parent->interpret;
214 
215   if ((status = call->interpret(call, worker, ptmp)) != APR_SUCCESS) {
216     luaL_error(L, "Error: %s(%d)", my_status_str(ptmp, status), status);
217     return 1;
218   }
219   apr_pool_destroy(ptmp);
220 
221   return 0;
222 }
223 
224 /**
225  * Get variable from httest
226  * @param L IN lua state
227  * @return 0
228  */
luam_getvar(lua_State * L)229 static int luam_getvar(lua_State *L) {
230   worker_t *worker;
231   const char *val;
232 
233   const char *var = lua_tostring(L, -1);
234 
235   lua_pop(L, 1);
236 
237   lua_getfield(L, LUA_REGISTRYINDEX, "htt_worker");
238   worker = lua_touserdata(L, 1);
239 
240   if ((val = worker_var_get(worker, var))) {
241     lua_pushstring(L, val);
242     return 1;
243   }
244 
245   return 0;
246 }
247 
248 /**
249  * Get transport object.
250  * @param L IN lua state
251  * @return 0
252  */
luam_transport_get(lua_State * L)253 static int luam_transport_get(lua_State *L) {
254   worker_t *worker;
255 
256   lua_getfield(L, LUA_REGISTRYINDEX, "htt_worker");
257   worker = lua_touserdata(L, 1);
258 
259   if (!worker->socket || !worker->socket->transport) {
260     lua_pushnil(L);
261     return 1;
262   }
263 
264   lua_pushlightuserdata(L, worker->socket->transport);
265 
266   luaL_getmetatable(L, "htt.transport");
267   lua_setmetatable(L, -2);
268 
269   return 1;
270 }
271 
272 /**
273  * Get transport object.
274  * @param L IN lua state
275  * @return 0
276  */
lua_checktransport(lua_State * L)277 static transport_t *lua_checktransport (lua_State *L) {
278   void *ud = luaL_checkudata(L, 1, "htt.transport");
279   luaL_argcheck(L, ud != NULL, 1, "`transport' expected");
280   return (transport_t *)ud;
281 }
282 
283 /**
284  * Transport read method
285  * @param L IN lua state
286  * @return 0
287  */
luam_transport_read(lua_State * L)288 static int luam_transport_read(lua_State *L) {
289   if (lua_isnumber(L, -1)) {
290     apr_status_t status;
291     apr_pool_t *pool;
292     apr_size_t bytes;
293     transport_t *transport;
294     char *buffer;
295 
296     bytes = lua_tointeger(L, -1);
297     transport = lua_checktransport(L);
298     HT_POOL_CREATE(&pool);
299     buffer = apr_pcalloc(pool, bytes);
300     if ((status = transport_read(transport, buffer, &bytes)) != APR_SUCCESS) {
301       lua_pushnil(L);
302       apr_pool_destroy(pool);
303       return 1;
304     }
305     lua_pushlstring(L, buffer, bytes);
306     apr_pool_destroy(pool);
307     return 1;
308   }
309   else {
310     luaL_error(L, "Expect number of bytes");
311     return 1;
312   }
313 }
314 
315 /**
316  * Transport write method
317  * @param L IN lua state
318  * @return 0
319  */
luam_transport_write(lua_State * L)320 static int luam_transport_write(lua_State *L) {
321   if (lua_isstring(L, -1)) {
322     apr_status_t status;
323     apr_size_t bytes;
324     const char *buffer = lua_tolstring(L, -1, &bytes);
325     transport_t *transport = lua_checktransport(L);
326     if ((status = transport_write(transport, buffer, bytes)) != APR_SUCCESS) {
327       luaL_error(L, "Could not write %d bytes", bytes);
328       return 1;
329     }
330   }
331   return 0;
332 }
333 
334 /**
335  * Transport set timeout method
336  * @param L IN lua state
337  * @return 0
338  */
luam_transport_set_timeout(lua_State * L)339 static int luam_transport_set_timeout(lua_State *L) {
340   if (lua_isnumber(L, -1)) {
341     apr_status_t status;
342     apr_interval_time_t tmo = lua_tointeger(L, -1);
343     transport_t *transport = lua_checktransport(L);
344     if ((status = transport_set_timeout(transport, tmo * 1000)) != APR_SUCCESS) {
345       luaL_error(L, "Could not timeout %d ms", tmo);
346       return 1;
347     }
348   }
349   return 0;
350 }
351 
352 /**
353  * Transport get timeout method
354  * @param L IN lua state
355  * @return 1
356  */
luam_transport_get_timeout(lua_State * L)357 static int luam_transport_get_timeout(lua_State *L) {
358   apr_status_t status;
359   apr_interval_time_t tmo;
360   transport_t *transport = lua_checktransport(L);
361   if ((status = transport_get_timeout(transport, &tmo)) != APR_SUCCESS) {
362     luaL_error(L, "Could not get timeout");
363     return 1;
364   }
365   else {
366     lua_pushinteger(L, tmo/1000);
367     return 1;
368   }
369 }
370 
371 /**
372  * Set of htt commands for lua
373  */
374 static const struct luaL_Reg htt_lib_f[] = {
375   {"version", luam_version},
376   {"interpret", luam_interpret},
377   {"getVar", luam_getvar},
378   {"getTransport", luam_transport_get},
379   {NULL, NULL}
380 };
381 
382 static const struct luaL_Reg htt_transport_m[] = {
383   {"read", luam_transport_read},
384   {"write", luam_transport_write},
385   {"setTimeout", luam_transport_set_timeout},
386   {"getTimeout", luam_transport_get_timeout},
387   {NULL, NULL}
388 };
389 
390 /**
391  * Simple lua interpreter for lua block
392  * @param worker IN callee
393  * @param parent IN caller
394  * @param ptmp IN temp pool for this function
395  * @return apr status
396  */
block_lua_interpreter(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)397 static apr_status_t block_lua_interpreter(worker_t *worker, worker_t *parent,
398                                           apr_pool_t *ptmp) {
399   int failed;
400   int i;
401   apr_table_entry_t *e;
402   lua_reader_t *reader;
403 
404   lua_wconf_t *wconf = lua_get_worker_config(worker->block);
405   lua_State *L = luaL_newstate();
406 
407   luaL_openlibs(L);
408   e = (apr_table_entry_t *) apr_table_elts(wconf->params)->elts;
409   for (i = 1; i < apr_table_elts(wconf->params)->nelts; i++) {
410     const char *val = NULL;
411     char *param = store_get_copy(worker->params, ptmp, e[i].key);
412     val = worker_get_value_from_param(worker, param, ptmp);
413     lua_pushstring(L, val);
414     lua_setglobal(L, e[i].key);
415   }
416   luaopen_crypto(L);
417   lua_pushlightuserdata(L, parent);
418   lua_setfield(L, LUA_REGISTRYINDEX, "htt_parent");
419   lua_pushlightuserdata(L, worker);
420   lua_setfield(L, LUA_REGISTRYINDEX, "htt_worker");
421   luaL_newmetatable(L, "htt.transport");
422   lua_pushvalue(L, -1);  /* pushes the metatable */
423   lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
424   luaL_register(L, NULL, htt_transport_m);
425   luaL_register(L, "htt", htt_lib_f);
426   lua_pop(L, -1);
427   lua_pop(L, -1);
428   reader = lua_new_lua_reader(worker, ptmp);
429 #if ( LUA_VERSION_NUM == 501 )
430   failed = (lua_load(L, lua_get_line, reader, "@client") != 0 ||
431             lua_pcall(L, 0, LUA_MULTRET, 0) != 0);
432 #elif ( LUA_VERSION_NUM  == 502 )
433   failed = (lua_load(L, lua_get_line, reader, "@client", NULL) != 0 ||
434             lua_pcall(L, 0, LUA_MULTRET, 0) != 0);
435 #else
436   #error this lua version is not supported
437 #endif
438   if (failed) {
439     const char *msg = lua_tostring(L, -1);
440     if (msg == NULL) {
441       msg = "(error object is not a string)";
442     }
443     worker_log(worker, LOG_ERR, "Lua error: %s", msg);
444     lua_pop(L, 1);
445     return APR_EGENERAL;
446   }
447   e = (apr_table_entry_t *) apr_table_elts(wconf->retvars)->elts;
448   for (i = 0; i < apr_table_elts(wconf->retvars)->nelts; i++) {
449     logger_log(worker->logger, LOG_DEBUG, "param: %s; val: %s", e[i].key, e[i].val);
450     if (lua_isstring(L, i + 1)) {
451       store_set(worker->vars, store_get(worker->retvars, e[i].key), lua_tostring(L, i + 1));
452     }
453   }
454 
455   lua_close(L);
456 
457   return APR_SUCCESS;
458 }
459 
460 /**
461  * Get variable names for in/out for mapping it to/from lua
462  * @param worker IN callee
463  * @param line IN command line
464  */
lua_set_variable_names(worker_t * worker,char * line)465 static void lua_set_variable_names(worker_t *worker, char *line) {
466   char *token;
467   char *last;
468 
469   int input = 1;
470   lua_wconf_t *wconf = lua_get_worker_config(worker);
471   char *data = apr_pstrdup(worker->pbody, line);
472 
473   /* Get params and returns variable names for later mapping from/to lua */
474   token = apr_strtok(data, " ", &last);
475   while (token) {
476     if (strcmp(token, ":") == 0) {
477       /* : is separator between input and output vars */
478       input = 0;
479     }
480     else {
481       if (input) {
482         apr_table_setn(wconf->params, token, token);
483       }
484       else {
485         apr_table_setn(wconf->retvars, token, token);
486       }
487     }
488     token = apr_strtok(NULL, " ", &last);
489   }
490 }
491 
492 /************************************************************************
493  * Hooks
494  ***********************************************************************/
495 
496 /**
497  * Start load a lua block
498  * @param global IN
499  * @param line INOUT line
500  * @return APR_SUCCESS
501  */
lua_block_start(global_t * global,char ** line)502 static apr_status_t lua_block_start(global_t *global, char **line) {
503   if (strncmp(*line, ":LUA ", 5) == 0) {
504     lua_wconf_t *wconf;
505     lua_gconf_t *gconf = lua_get_global_config(global);
506     gconf->do_read_line = 1;
507     *line += 5;
508     worker_new(&global->cur_worker, "", global, block_lua_interpreter);
509     wconf = lua_get_worker_config(global->cur_worker);
510     wconf->starting_line_nr = global->line_nr;
511     lua_set_variable_names(global->cur_worker, *line);
512     return APR_SUCCESS;
513   }
514   return APR_ENOTIMPL;
515 }
516 
517 /**
518  * Read line of lua block
519  * @param global IN
520  * @param line INOUT line
521  * @return APR_SUCCESS
522  */
lua_read_line(global_t * global,char ** line)523 static apr_status_t lua_read_line(global_t *global, char **line) {
524   lua_gconf_t *gconf = lua_get_global_config(global);
525   if (gconf->do_read_line) {
526     if (*line[0] == 0) {
527       *line = apr_pstrdup(global->pool, " ");
528     }
529   }
530   return APR_SUCCESS;
531 }
532 
533 /**
534  * End load a lua block
535  * @param global IN
536  * @return APR_SUCCESS
537  */
lua_block_end(global_t * global)538 static apr_status_t lua_block_end(global_t *global) {
539   lua_gconf_t *gconf = lua_get_global_config(global);
540   gconf->do_read_line = 0;
541   return APR_SUCCESS;
542 }
543 
544 /************************************************************************
545  * Commands
546  ***********************************************************************/
547 
548 /************************************************************************
549  * Module
550  ***********************************************************************/
lua_module_init(global_t * global)551 apr_status_t lua_module_init(global_t *global) {
552   module_command_new(global, "LUA", "_MODULE", "", "", NULL);
553   htt_hook_block_start(lua_block_start, NULL, NULL, 0);
554   htt_hook_read_line(lua_read_line, NULL, NULL, 0);
555   htt_hook_block_end(lua_block_end, NULL, NULL, 0);
556 
557   return APR_SUCCESS;
558 }
559 
560