/* * Lua unsafe core engine * * Copyright 2015-2016 Thierry Fournier * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #define _GNU_SOURCE #include #include #include #include #include #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503 #error "Requires Lua 5.3 or later." #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Global LUA flags */ enum hlua_log_opt { /* tune.lua.log.loggers */ HLUA_LOG_LOGGERS_ON = 0x00000001, /* forward logs to current loggers */ /* tune.lua.log.stderr */ HLUA_LOG_STDERR_ON = 0x00000010, /* forward logs to stderr */ HLUA_LOG_STDERR_AUTO = 0x00000020, /* forward logs to stderr if no loggers */ HLUA_LOG_STDERR_MASK = 0x00000030, }; /* default log options, made of flags in hlua_log_opt */ static uint hlua_log_opts = HLUA_LOG_LOGGERS_ON | HLUA_LOG_STDERR_AUTO; /* Lua uses longjmp to perform yield or throwing errors. This * macro is used only for identifying the function that can * not return because a longjmp is executed. * __LJMP marks a prototype of hlua file that can use longjmp. * WILL_LJMP() marks an lua function that will use longjmp. * MAY_LJMP() marks an lua function that may use longjmp. */ #define __LJMP #define WILL_LJMP(func) do { func; my_unreachable(); } while(0) #define MAY_LJMP(func) func /* This couple of function executes securely some Lua calls outside of * the lua runtime environment. Each Lua call can return a longjmp * if it encounter a memory error. * * Lua documentation extract: * * If an error happens outside any protected environment, Lua calls * a panic function (see lua_atpanic) and then calls abort, thus * exiting the host application. Your panic function can avoid this * exit by never returning (e.g., doing a long jump to your own * recovery point outside Lua). * * The panic function runs as if it were a message handler (see * #2.3); in particular, the error message is at the top of the * stack. However, there is no guarantee about stack space. To push * anything on the stack, the panic function must first check the * available space (see #4.2). * * We must check all the Lua entry point. This includes: * - The include/proto/hlua.h exported functions * - the task wrapper function * - The action wrapper function * - The converters wrapper function * - The sample-fetch wrapper functions * * It is tolerated that the initialisation function returns an abort. * Before each Lua abort, an error message is written on stderr. * * The macro SET_SAFE_LJMP initialise the longjmp. The Macro * RESET_SAFE_LJMP reset the longjmp. These function must be macro * because they must be exists in the program stack when the longjmp * is called. * * Note that the Lua processing is not really thread safe. It provides * heavy system which consists to add our own lock function in the Lua * code and recompile the library. This system will probably not accepted * by maintainers of various distribs. * * Our main execution point of the Lua is the function lua_resume(). A * quick looking on the Lua sources displays a lua_lock() a the start * of function and a lua_unlock() at the end of the function. So I * conclude that the Lua thread safe mode just perform a mutex around * all execution. So I prefer to do this in the HAProxy code, it will be * easier for distro maintainers. * * Note that the HAProxy lua functions rounded by the macro SET_SAFE_LJMP * and RESET_SAFE_LJMP manipulates the Lua stack, so it will be careful * to set mutex around these functions. */ __decl_spinlock(hlua_global_lock); THREAD_LOCAL jmp_buf safe_ljmp_env; static int hlua_panic_safe(lua_State *L) { return 0; } static int hlua_panic_ljmp(lua_State *L) { WILL_LJMP(longjmp(safe_ljmp_env, 1)); return 0; } /* This is the chained list of struct hlua_function referenced * for haproxy action, sample-fetches, converters, cli and * applet bindings. It is used for a post-initialisation control. */ static struct list referenced_functions = LIST_HEAD_INIT(referenced_functions); /* This variable is used only during initialization to identify the Lua state * currently being initialized. 0 is the common lua state, 1 to n are the Lua * states dedicated to each thread (in this case hlua_state_id==tid+1). */ static int hlua_state_id; /* This is a NULL-terminated list of lua file which are referenced to load per thread */ static char ***per_thread_load = NULL; lua_State *hlua_init_state(int thread_id); /* This function takes the Lua global lock. Keep this function's visibility * global so that it can appear in stack dumps and performance profiles! */ static inline void lua_take_global_lock() { HA_SPIN_LOCK(LUA_LOCK, &hlua_global_lock); } static inline void lua_drop_global_lock() { HA_SPIN_UNLOCK(LUA_LOCK, &hlua_global_lock); } /* lua lock helpers: only lock when required * * state_id == 0: we're operating on the main lua stack (shared between * os threads), so we need to acquire the main lock * * If the thread already owns the lock (_hlua_locked != 0), skip the lock * attempt. This could happen if we run under protected lua environment. * Not doing this could result in deadlocks because of nested locking * attempts from the same thread */ static THREAD_LOCAL int _hlua_locked = 0; static inline void hlua_lock(struct hlua *hlua) { if (hlua->state_id != 0) return; if (!_hlua_locked) lua_take_global_lock(); _hlua_locked += 1; } static inline void hlua_unlock(struct hlua *hlua) { if (hlua->state_id != 0) return; BUG_ON(_hlua_locked <= 0); _hlua_locked--; /* drop the lock once the lock count reaches 0 */ if (!_hlua_locked) lua_drop_global_lock(); } /* below is an helper function to retrieve string on on Lua stack at * in a safe way (function may not LJMP). It can be useful to retrieve errors * at the top of the stack from an unprotected environment. * * The returned string will is only valid as long as the value at is * not removed from the stack. * * It is assumed that the calling function is allowed to manipulate */ __LJMP static int _hlua_tostring_safe(lua_State *L) { const char **str = lua_touserdata(L, 1); const char *cur_str = MAY_LJMP(lua_tostring(L, 2)); if (cur_str) *str = cur_str; return 0; } static const char *hlua_tostring_safe(lua_State *L, int index) { const char *str = NULL; if (!lua_checkstack(L, 4)) return NULL; /* before any stack modification, save the targeted value on the top of * the stack: this will allow us to use relative index to target it. */ lua_pushvalue(L, index); /* push our custom _hlua_tostring_safe() function on the stack, then push * our own string pointer and targeted value (at ) as argument */ lua_pushcfunction(L, _hlua_tostring_safe); lua_pushlightuserdata(L, &str); // 1st func argument = string pointer lua_pushvalue(L, -3); // 2nd func argument = targeted value lua_remove(L, -4); // remove copy as we're done using it /* call our custom function with proper arguments using pcall() to catch * exceptions (if any) */ switch (lua_pcall(L, 2, 0, 0)) { case LUA_OK: break; default: /* error was caught */ return NULL; } return str; } #define SET_SAFE_LJMP_L(__L, __HLUA) \ ({ \ int ret; \ hlua_lock(__HLUA); \ if (setjmp(safe_ljmp_env) != 0) { \ lua_atpanic(__L, hlua_panic_safe); \ ret = 0; \ hlua_unlock(__HLUA); \ } else { \ lua_atpanic(__L, hlua_panic_ljmp); \ ret = 1; \ } \ ret; \ }) /* If we are the last function catching Lua errors, we * must reset the panic function. */ #define RESET_SAFE_LJMP_L(__L, __HLUA) \ do { \ lua_atpanic(__L, hlua_panic_safe); \ hlua_unlock(__HLUA); \ } while(0) #define SET_SAFE_LJMP(__HLUA) \ SET_SAFE_LJMP_L((__HLUA)->T, __HLUA) #define RESET_SAFE_LJMP(__HLUA) \ RESET_SAFE_LJMP_L((__HLUA)->T, __HLUA) #define SET_SAFE_LJMP_PARENT(__HLUA) \ SET_SAFE_LJMP_L(hlua_states[(__HLUA)->state_id], __HLUA) #define RESET_SAFE_LJMP_PARENT(__HLUA) \ RESET_SAFE_LJMP_L(hlua_states[(__HLUA)->state_id], __HLUA) /* Applet status flags */ #define APPLET_DONE 0x01 /* applet processing is done. */ /* unused: 0x02 */ #define APPLET_HDR_SENT 0x04 /* Response header sent. */ /* unused: 0x08, 0x10 */ #define APPLET_HTTP11 0x20 /* Last chunk sent. */ #define APPLET_RSP_SENT 0x40 /* The response was fully sent */ /* The main Lua execution context. The 0 index is the * common state shared by all threads. */ static lua_State *hlua_states[MAX_THREADS + 1]; #define HLUA_FLT_CB_FINAL 0x00000001 #define HLUA_FLT_CB_RETVAL 0x00000002 #define HLUA_FLT_CB_ARG_CHN 0x00000004 #define HLUA_FLT_CB_ARG_HTTP_MSG 0x00000008 #define HLUA_FLT_CTX_FL_PAYLOAD 0x00000001 struct hlua_reg_filter { char *name; int flt_ref[MAX_THREADS + 1]; int fun_ref[MAX_THREADS + 1]; struct list l; }; struct hlua_flt_config { struct hlua_reg_filter *reg; int ref[MAX_THREADS + 1]; char **args; }; struct hlua_flt_ctx { struct hlua *_hlua; /* main hlua context */ int ref; /* ref to the filter lua object (in main hlua context) */ struct hlua *hlua[2]; /* lua runtime context (0: request, 1: response) */ unsigned int cur_off[2]; /* current offset (0: request, 1: response) */ unsigned int cur_len[2]; /* current forwardable length (0: request, 1: response) */ unsigned int flags; /* HLUA_FLT_CTX_FL_* */ }; /* appctx context used by the cosockets */ struct hlua_csk_ctx { int connected; struct xref xref; /* cross reference with the Lua object owner. */ struct list wake_on_read; struct list wake_on_write; struct appctx *appctx; struct server *srv; int timeout; int die; }; /* appctx context used by TCP services */ struct hlua_tcp_ctx { struct hlua *hlua; int flags; struct task *task; }; /* appctx context used by HTTP services */ struct hlua_http_ctx { struct hlua *hlua; int left_bytes; /* The max amount of bytes that we can read. */ int flags; int status; const char *reason; struct task *task; }; /* used by registered CLI keywords */ struct hlua_cli_ctx { struct hlua *hlua; struct task *task; struct hlua_function *fcn; }; DECLARE_STATIC_POOL(pool_head_hlua_flt_ctx, "hlua_flt_ctx", sizeof(struct hlua_flt_ctx)); static int hlua_filter_from_payload(struct filter *filter); /* This is the chained list of struct hlua_flt referenced * for haproxy filters. It is used for a post-initialisation control. */ static struct list referenced_filters = LIST_HEAD_INIT(referenced_filters); /* This is the memory pool containing struct lua for applets * (including cli). */ DECLARE_STATIC_POOL(pool_head_hlua, "hlua", sizeof(struct hlua)); /* Used for Socket connection. */ static struct proxy *socket_proxy; static struct server *socket_tcp; #ifdef USE_OPENSSL static struct server *socket_ssl; #endif /* List head of the function called at the initialisation time. */ struct list hlua_init_functions[MAX_THREADS + 1]; /* The following variables contains the reference of the different * Lua classes. These references are useful for identify metadata * associated with an object. */ static int class_txn_ref; static int class_socket_ref; static int class_channel_ref; static int class_fetches_ref; static int class_converters_ref; static int class_http_ref; static int class_http_msg_ref; static int class_httpclient_ref; static int class_map_ref; static int class_applet_tcp_ref; static int class_applet_http_ref; static int class_txn_reply_ref; /* Lua max execution timeouts. By default, stream-related * lua coroutines (e.g.: actions) have a short timeout. * On the other hand tasks coroutines don't have a timeout because * a task may remain alive during all the haproxy execution. * * Timeouts are expressed in milliseconds, they are meant to be used * with hlua timer's API exclusively. * 0 means no timeout */ static uint32_t hlua_timeout_burst = 1000; /* burst timeout. */ static uint32_t hlua_timeout_session = 4000; /* session timeout. */ static uint32_t hlua_timeout_task = 0; /* task timeout. */ static uint32_t hlua_timeout_applet = 4000; /* applet timeout. */ /* hlua multipurpose timer: * used to compute burst lua time (within a single hlua_ctx_resume()) * and cumulative lua time for a given coroutine, and to check * the lua coroutine against the configured timeouts */ /* fetch per-thread cpu_time with ms precision (may wrap) */ static inline uint32_t _hlua_time_ms() { /* We're interested in the current cpu time in ms, which will be returned * as a uint32_t to save some space. * We must take the following into account: * * - now_cpu_time_fast() which returns the time in nanoseconds as a uint64_t * will wrap every 585 years. * - uint32_t may only contain 4294967295ms (~=49.7 days), so _hlua_time_ms() * itself will also wrap every 49.7 days. * * While we can safely ignore the now_cpu_time_fast() wrap, we must * take care of the uint32_t wrap by making sure to exclusively * manipulate the time using uint32_t everywhere _hlua_time_ms() * is involved. */ return (uint32_t)(now_cpu_time_fast() / 1000000ULL); } /* computes time spent in a single lua execution (in ms) */ static inline uint32_t _hlua_time_burst(const struct hlua_timer *timer) { uint32_t burst_ms; /* wrapping is expected and properly * handled thanks to _hlua_time_ms() and burst_ms * being of the same type */ burst_ms = _hlua_time_ms() - timer->start; return burst_ms; } static inline void hlua_timer_init(struct hlua_timer *timer, unsigned int max) { timer->cumulative = 0; timer->burst = 0; timer->max = max; } /* reset the timer ctx between 2 yields */ static inline void hlua_timer_reset(struct hlua_timer *timer) { timer->cumulative += timer->burst; timer->burst = 0; } /* start the timer right before a new execution */ static inline void hlua_timer_start(struct hlua_timer *timer) { timer->start = _hlua_time_ms(); } /* update hlua timer when finishing an execution */ static inline void hlua_timer_stop(struct hlua_timer *timer) { timer->burst += _hlua_time_burst(timer); } /* check the timers for current hlua context: * - first check for burst timeout (max execution time for the current hlua resume, ie: time between effective yields) * - then check for yield cumulative timeout * * Returns 1 if the check succeeded and 0 if it failed * (ie: timeout exceeded) */ static inline int hlua_timer_check(const struct hlua_timer *timer) { uint32_t pburst = _hlua_time_burst(timer); /* pending burst time in ms */ if (hlua_timeout_burst && (timer->burst + pburst) > hlua_timeout_burst) return 0; /* burst timeout exceeded */ if (timer->max && (timer->cumulative + timer->burst + pburst) > timer->max) return 0; /* cumulative timeout exceeded */ return 1; /* ok */ } /* Interrupts the Lua processing each "hlua_nb_instruction" instructions. * it is used for preventing infinite loops. * * I test the scheer with an infinite loop containing one incrementation * and one test. I run this loop between 10 seconds, I raise a ceil of * 710M loops from one interrupt each 9000 instructions, so I fix the value * to one interrupt each 10 000 instructions. * * configured | Number of * instructions | loops executed * between two | in milions * forced yields | * ---------------+--------------- * 10 | 160 * 500 | 670 * 1000 | 680 * 5000 | 700 * 7000 | 700 * 8000 | 700 * 9000 | 710 <- ceil * 10000 | 710 * 100000 | 710 * 1000000 | 710 * */ static unsigned int hlua_nb_instruction = 10000; /* Descriptor for the memory allocation state. The limit is pre-initialised to * 0 until it is replaced by "tune.lua.maxmem" during the config parsing, or it * is replaced with ~0 during post_init after everything was loaded. This way * it is guaranteed that if limit is ~0 the boot is complete and that if it's * zero it's not yet limited and proper accounting is required. */ struct hlua_mem_allocator { size_t allocated; size_t limit; }; static struct hlua_mem_allocator hlua_global_allocator THREAD_ALIGNED(64); /* hlua event subscription */ struct hlua_event_sub { int fcn_ref; int state_id; struct hlua *hlua; struct task *task; event_hdl_async_equeue equeue; struct event_hdl_sub *sub; uint8_t paused; }; /* This is the memory pool containing struct hlua_event_sub * for event subscriptions from lua */ DECLARE_STATIC_POOL(pool_head_hlua_event_sub, "hlua_esub", sizeof(struct hlua_event_sub)); /* These functions converts types between HAProxy internal args or * sample and LUA types. Another function permits to check if the * LUA stack contains arguments according with an required ARG_T * format. */ __LJMP static int hlua_arg2lua(lua_State *L, const struct arg *arg); static int hlua_lua2arg(lua_State *L, int ud, struct arg *arg); __LJMP static int hlua_lua2arg_check(lua_State *L, int first, struct arg *argp, uint64_t mask, struct proxy *p); __LJMP static int hlua_smp2lua(lua_State *L, struct sample *smp); __LJMP static int hlua_smp2lua_str(lua_State *L, struct sample *smp); static int hlua_lua2smp(lua_State *L, int ud, struct sample *smp); __LJMP static int hlua_http_get_headers(lua_State *L, struct http_msg *msg); struct prepend_path { struct list l; char *type; char *path; }; static struct list prepend_path_list = LIST_HEAD_INIT(prepend_path_list); #define SEND_ERR(__be, __fmt, __args...) \ do { \ send_log(__be, LOG_ERR, __fmt, ## __args); \ if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) \ ha_alert(__fmt, ## __args); \ } while (0) static inline struct hlua_function *new_hlua_function() { struct hlua_function *fcn; int i; fcn = calloc(1, sizeof(*fcn)); if (!fcn) return NULL; LIST_APPEND(&referenced_functions, &fcn->l); for (i = 0; i < MAX_THREADS + 1; i++) fcn->function_ref[i] = -1; return fcn; } static inline void release_hlua_function(struct hlua_function *fcn) { if (!fcn) return; if (fcn->name) ha_free(&fcn->name); LIST_DELETE(&fcn->l); ha_free(&fcn); } /* If the common state is set, the stack id is 0, otherwise it is the tid + 1 */ static inline int fcn_ref_to_stack_id(struct hlua_function *fcn) { if (fcn->function_ref[0] == -1) return tid + 1; return 0; } /* Create a new registered filter. Only its name is filled */ static inline struct hlua_reg_filter *new_hlua_reg_filter(const char *name) { struct hlua_reg_filter *reg_flt; int i; reg_flt = calloc(1, sizeof(*reg_flt)); if (!reg_flt) return NULL; reg_flt->name = strdup(name); if (!reg_flt->name) { free(reg_flt); return NULL; } LIST_APPEND(&referenced_filters, ®_flt->l); for (i = 0; i < MAX_THREADS + 1; i++) { reg_flt->flt_ref[i] = -1; reg_flt->fun_ref[i] = -1; } return reg_flt; } /* Release a registered filter */ static inline void release_hlua_reg_filter(struct hlua_reg_filter *reg_flt) { if (!reg_flt) return; if (reg_flt->name) ha_free(®_flt->name); LIST_DELETE(®_flt->l); ha_free(®_flt); } /* If the common state is set, the stack id is 0, otherwise it is the tid + 1 */ static inline int reg_flt_to_stack_id(struct hlua_reg_filter *reg_flt) { if (reg_flt->fun_ref[0] == -1) return tid + 1; return 0; } /* Used to check an Lua function type in the stack. It creates and * returns a reference of the function. This function throws an * error if the argument is not a "function". * When no longer used, the ref must be released with hlua_unref() */ __LJMP int hlua_checkfunction(lua_State *L, int argno) { if (!lua_isfunction(L, argno)) { const char *msg = lua_pushfstring(L, "function expected, got %s", luaL_typename(L, argno)); WILL_LJMP(luaL_argerror(L, argno, msg)); } lua_pushvalue(L, argno); return luaL_ref(L, LUA_REGISTRYINDEX); } /* Used to check an Lua table type in the stack. It creates and * returns a reference of the table. This function throws an * error if the argument is not a "table". * When no longer used, the ref must be released with hlua_unref() */ __LJMP int hlua_checktable(lua_State *L, int argno) { if (!lua_istable(L, argno)) { const char *msg = lua_pushfstring(L, "table expected, got %s", luaL_typename(L, argno)); WILL_LJMP(luaL_argerror(L, argno, msg)); } lua_pushvalue(L, argno); return luaL_ref(L, LUA_REGISTRYINDEX); } /* Get a reference to the object that is at the top of the stack * The referenced object will be popped from the stack * * The function returns the reference to the object which must * be cleared using hlua_unref() when no longer used */ __LJMP int hlua_ref(lua_State *L) { return MAY_LJMP(luaL_ref(L, LUA_REGISTRYINDEX)); } /* Pushes a reference previously created using luaL_ref(L, LUA_REGISTRYINDEX) * on stack * (ie: hlua_checkfunction(), hlua_checktable() or hlua_ref()) * * When the reference is no longer used, it should be released by calling * hlua_unref() * * can be from any co-routine as long as it belongs to the same lua * parent state that the one used to get the reference. */ void hlua_pushref(lua_State *L, int ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, ref); } /* Releases a reference previously created using luaL_ref(L, LUA_REGISTRYINDEX) * (ie: hlua_checkfunction(), hlua_checktable() or hlua_ref()) * * This will allow the reference to be reused and the referred object * to be garbage collected. * * can be from any co-routine as long as it belongs to the same lua * parent state that the one used to get the reference. */ void hlua_unref(lua_State *L, int ref) { luaL_unref(L, LUA_REGISTRYINDEX, ref); } __LJMP const char *hlua_traceback(lua_State *L, const char* sep) { lua_Debug ar; int level = 0; struct buffer *msg = get_trash_chunk(); while (lua_getstack(L, level++, &ar)) { /* Fill fields: * 'S': fills in the fields source, short_src, linedefined, lastlinedefined, and what; * 'l': fills in the field currentline; * 'n': fills in the field name and namewhat; * 't': fills in the field istailcall; */ lua_getinfo(L, "Slnt", &ar); /* skip these empty entries, usually they come from deep C functions */ if (ar.currentline < 0 && *ar.what == 'C' && !*ar.namewhat && !ar.name) continue; /* Add separator */ if (b_data(msg)) chunk_appendf(msg, "%s", sep); /* Append code localisation */ if (ar.currentline > 0) chunk_appendf(msg, "%s:%d: ", ar.short_src, ar.currentline); else chunk_appendf(msg, "%s: ", ar.short_src); /* * Get function name * * if namewhat is no empty, name is defined. * what contains "Lua" for Lua function, "C" for C function, * or "main" for main code. */ if (*ar.namewhat != '\0' && ar.name != NULL) /* is there a name from code? */ chunk_appendf(msg, "in %s '%s'", ar.namewhat, ar.name); /* use it */ else if (*ar.what == 'm') /* "main", the code is not executed in a function */ chunk_appendf(msg, "in main chunk"); else if (*ar.what != 'C') /* for Lua functions, use */ chunk_appendf(msg, "in function line %d", ar.linedefined); else /* nothing left... */ chunk_appendf(msg, "?"); /* Display tailed call */ if (ar.istailcall) chunk_appendf(msg, " ..."); } return msg->area; } /* This function check the number of arguments available in the * stack. If the number of arguments available is not the same * then an error is thrown. */ __LJMP static inline void check_args(lua_State *L, int nb, char *fcn) { if (lua_gettop(L) == nb) return; WILL_LJMP(luaL_error(L, "'%s' needs %d arguments", fcn, nb)); } /* This function pushes an error string prefixed by the file name * and the line number where the error is encountered. */ static int hlua_pusherror(lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); luaL_where(L, 1); lua_pushvfstring(L, fmt, argp); va_end(argp); lua_concat(L, 2); return 1; } /* This functions is used with sample fetch and converters. It * converts the HAProxy configuration argument in a lua stack * values. * * It takes an array of "arg", and each entry of the array is * converted and pushed in the LUA stack. */ __LJMP static int hlua_arg2lua(lua_State *L, const struct arg *arg) { switch (arg->type) { case ARGT_SINT: case ARGT_TIME: case ARGT_SIZE: lua_pushinteger(L, arg->data.sint); break; case ARGT_STR: lua_pushlstring(L, arg->data.str.area, arg->data.str.data); break; case ARGT_IPV4: case ARGT_IPV6: case ARGT_MSK4: case ARGT_MSK6: case ARGT_FE: case ARGT_BE: case ARGT_TAB: case ARGT_SRV: case ARGT_USR: case ARGT_MAP: default: lua_pushnil(L); break; } return 1; } /* This function take one entry in an LUA stack at the index "ud", * and try to convert it in an HAProxy argument entry. This is useful * with sample fetch wrappers. The input arguments are given to the * lua wrapper and converted as arg list by the function. * * Note: although lua_tolstring() may raise a memory error according to * lua documentation, in practise this could only happen when using to * use lua_tolstring() on a number (lua will try to push the number as a * string on the stack, and this may result in memory failure), so here we * assume that hlua_lua2arg() will never raise an exception since it is * exclusively used with lua string inputs. * * Note2: You should be extra careful when using argument, since * string arguments rely on lua_tolstring() which returns a pointer to lua * object that may be garbage collected at any time when removed from lua * stack, thus you should make sure that is only used from a local * scope within lua context (and not exported or stored in a lua-independent * ctx) and that related lua object still exists when accessing arg data. * See: https://www.lua.org/manual/5.4/manual.html#4.1.3 */ static int hlua_lua2arg(lua_State *L, int ud, struct arg *arg) { switch (lua_type(L, ud)) { case LUA_TNUMBER: case LUA_TBOOLEAN: arg->type = ARGT_SINT; arg->data.sint = lua_tointeger(L, ud); break; case LUA_TSTRING: arg->type = ARGT_STR; arg->data.str.area = (char *)lua_tolstring(L, ud, &arg->data.str.data); /* We don't know the actual size of the underlying allocation, so be conservative. */ arg->data.str.size = arg->data.str.data+1; /* count the terminating null byte */ arg->data.str.head = 0; break; case LUA_TUSERDATA: case LUA_TNIL: case LUA_TTABLE: case LUA_TFUNCTION: case LUA_TTHREAD: case LUA_TLIGHTUSERDATA: arg->type = ARGT_SINT; arg->data.sint = 0; break; } return 1; } /* the following functions are used to convert a struct sample * in Lua type. This useful to convert the return of the * fetches or converters. */ __LJMP static int hlua_smp2lua(lua_State *L, struct sample *smp) { switch (smp->data.type) { case SMP_T_SINT: case SMP_T_BOOL: lua_pushinteger(L, smp->data.u.sint); break; case SMP_T_BIN: case SMP_T_STR: lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); break; case SMP_T_METH: switch (smp->data.u.meth.meth) { case HTTP_METH_OPTIONS: lua_pushstring(L, "OPTIONS"); break; case HTTP_METH_GET: lua_pushstring(L, "GET"); break; case HTTP_METH_HEAD: lua_pushstring(L, "HEAD"); break; case HTTP_METH_POST: lua_pushstring(L, "POST"); break; case HTTP_METH_PUT: lua_pushstring(L, "PUT"); break; case HTTP_METH_DELETE: lua_pushstring(L, "DELETE"); break; case HTTP_METH_TRACE: lua_pushstring(L, "TRACE"); break; case HTTP_METH_CONNECT: lua_pushstring(L, "CONNECT"); break; case HTTP_METH_OTHER: lua_pushlstring(L, smp->data.u.meth.str.area, smp->data.u.meth.str.data); break; default: lua_pushnil(L); break; } break; case SMP_T_IPV4: case SMP_T_IPV6: case SMP_T_ADDR: /* This type is never used to qualify a sample. */ if (sample_casts[smp->data.type][SMP_T_STR] && sample_casts[smp->data.type][SMP_T_STR](smp)) lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); else lua_pushnil(L); break; default: lua_pushnil(L); break; } return 1; } /* the following functions are used to convert a struct sample * in Lua strings. This is useful to convert the return of the * fetches or converters. */ __LJMP static int hlua_smp2lua_str(lua_State *L, struct sample *smp) { switch (smp->data.type) { case SMP_T_BIN: case SMP_T_STR: lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); break; case SMP_T_METH: switch (smp->data.u.meth.meth) { case HTTP_METH_OPTIONS: lua_pushstring(L, "OPTIONS"); break; case HTTP_METH_GET: lua_pushstring(L, "GET"); break; case HTTP_METH_HEAD: lua_pushstring(L, "HEAD"); break; case HTTP_METH_POST: lua_pushstring(L, "POST"); break; case HTTP_METH_PUT: lua_pushstring(L, "PUT"); break; case HTTP_METH_DELETE: lua_pushstring(L, "DELETE"); break; case HTTP_METH_TRACE: lua_pushstring(L, "TRACE"); break; case HTTP_METH_CONNECT: lua_pushstring(L, "CONNECT"); break; case HTTP_METH_OTHER: lua_pushlstring(L, smp->data.u.meth.str.area, smp->data.u.meth.str.data); break; default: lua_pushstring(L, ""); break; } break; case SMP_T_SINT: case SMP_T_BOOL: case SMP_T_IPV4: case SMP_T_IPV6: case SMP_T_ADDR: /* This type is never used to qualify a sample. */ if (sample_casts[smp->data.type][SMP_T_STR] && sample_casts[smp->data.type][SMP_T_STR](smp)) lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); else lua_pushstring(L, ""); break; default: lua_pushstring(L, ""); break; } return 1; } /* The following function is used to convert a Lua type to a * struct sample. This is useful to provide data from LUA code to * a converter. * * Note: although lua_tolstring() may raise a memory error according to * lua documentation, in practise this could only happen when using to * use lua_tolstring() on a number (lua will try to push the number as a * string on the stack, and this may result in memory failure), so here we * assume that hlua_lua2arg() will never raise an exception since it is * exclusively used with lua string inputs. * * Note2: You should be extra careful when using argument, since * string arguments rely on lua_tolstring() which returns a pointer to lua * object that may be garbage collected at any time when removed from lua * stack, thus you should make sure that is only used from a local * scope within lua context (not exported or stored in a lua-independent * ctx) and that related lua object still exists when accessing arg data. * See: https://www.lua.org/manual/5.4/manual.html#4.1.3 * * If you don't comply with this usage restriction, then you should consider * duplicating the smp using smp_dup() to make it portable (little overhead), * as this will ensure that the smp always points to valid memory block. */ static int hlua_lua2smp(lua_State *L, int ud, struct sample *smp) { switch (lua_type(L, ud)) { case LUA_TNUMBER: smp->data.type = SMP_T_SINT; smp->data.u.sint = lua_tointeger(L, ud); break; case LUA_TBOOLEAN: smp->data.type = SMP_T_BOOL; smp->data.u.sint = lua_toboolean(L, ud); break; case LUA_TSTRING: smp->data.type = SMP_T_STR; smp->flags |= SMP_F_CONST; smp->data.u.str.area = (char *)lua_tolstring(L, ud, &smp->data.u.str.data); /* We don't know the actual size of the underlying allocation, so be conservative. */ smp->data.u.str.size = smp->data.u.str.data+1; /* count the terminating null byte */ smp->data.u.str.head = 0; break; case LUA_TUSERDATA: case LUA_TNIL: case LUA_TTABLE: case LUA_TFUNCTION: case LUA_TTHREAD: case LUA_TLIGHTUSERDATA: case LUA_TNONE: default: smp->data.type = SMP_T_BOOL; smp->data.u.sint = 0; break; } return 1; } /* This function check the "argp" built by another conversion function * is in accord with the expected argp defined by the "mask". The function * returns true or false. It can be adjust the types if there compatibles. * * This function assumes that the argp argument contains ARGM_NBARGS + 1 * entries and that there is at least one stop at the last position. */ __LJMP int hlua_lua2arg_check(lua_State *L, int first, struct arg *argp, uint64_t mask, struct proxy *p) { int min_arg; int idx; struct proxy *px; struct userlist *ul; struct my_regex *reg; const char *msg = NULL; char *sname, *pname, *err = NULL; idx = 0; min_arg = ARGM(mask); mask >>= ARGM_BITS; while (1) { struct buffer tmp = BUF_NULL; /* Check for mandatory arguments. */ if (argp[idx].type == ARGT_STOP) { if (idx < min_arg) { /* If miss other argument than the first one, we return an error. */ if (idx > 0) { msg = "Mandatory argument expected"; goto error; } /* If first argument have a certain type, some default values * may be used. See the function smp_resolve_args(). */ switch (mask & ARGT_MASK) { case ARGT_FE: if (!(p->cap & PR_CAP_FE)) { msg = "Mandatory argument expected"; goto error; } argp[idx].data.prx = p; argp[idx].type = ARGT_FE; argp[idx+1].type = ARGT_STOP; break; case ARGT_BE: if (!(p->cap & PR_CAP_BE)) { msg = "Mandatory argument expected"; goto error; } argp[idx].data.prx = p; argp[idx].type = ARGT_BE; argp[idx+1].type = ARGT_STOP; break; case ARGT_TAB: if (!p->table) { msg = "Mandatory argument expected"; goto error; } argp[idx].data.t = p->table; argp[idx].type = ARGT_TAB; argp[idx+1].type = ARGT_STOP; break; default: msg = "Mandatory argument expected"; goto error; break; } } break; } /* Check for exceed the number of required argument. */ if ((mask & ARGT_MASK) == ARGT_STOP && argp[idx].type != ARGT_STOP) { msg = "Last argument expected"; goto error; } if ((mask & ARGT_MASK) == ARGT_STOP && argp[idx].type == ARGT_STOP) { break; } /* Convert some argument types. All string in argp[] are for not * duplicated yet. */ switch (mask & ARGT_MASK) { case ARGT_SINT: if (argp[idx].type != ARGT_SINT) { msg = "integer expected"; goto error; } argp[idx].type = ARGT_SINT; break; case ARGT_TIME: if (argp[idx].type != ARGT_SINT) { msg = "integer expected"; goto error; } argp[idx].type = ARGT_TIME; break; case ARGT_SIZE: if (argp[idx].type != ARGT_SINT) { msg = "integer expected"; goto error; } argp[idx].type = ARGT_SIZE; break; case ARGT_FE: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } argp[idx].data.prx = proxy_fe_by_name(argp[idx].data.str.area); if (!argp[idx].data.prx) { msg = "frontend doesn't exist"; goto error; } argp[idx].type = ARGT_FE; break; case ARGT_BE: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } argp[idx].data.prx = proxy_be_by_name(argp[idx].data.str.area); if (!argp[idx].data.prx) { msg = "backend doesn't exist"; goto error; } argp[idx].type = ARGT_BE; break; case ARGT_TAB: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } argp[idx].data.t = stktable_find_by_name(argp[idx].data.str.area); if (!argp[idx].data.t) { msg = "table doesn't exist"; goto error; } argp[idx].type = ARGT_TAB; break; case ARGT_SRV: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } sname = strrchr(argp[idx].data.str.area, '/'); if (sname) { *sname++ = '\0'; pname = argp[idx].data.str.area; px = proxy_be_by_name(pname); if (!px) { msg = "backend doesn't exist"; goto error; } } else { sname = argp[idx].data.str.area; px = p; } argp[idx].data.srv = findserver(px, sname); if (!argp[idx].data.srv) { msg = "server doesn't exist"; goto error; } argp[idx].type = ARGT_SRV; break; case ARGT_IPV4: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } if (inet_pton(AF_INET, argp[idx].data.str.area, &argp[idx].data.ipv4)) { msg = "invalid IPv4 address"; goto error; } argp[idx].type = ARGT_IPV4; break; case ARGT_MSK4: if (argp[idx].type == ARGT_SINT) len2mask4(argp[idx].data.sint, &argp[idx].data.ipv4); else if (argp[idx].type == ARGT_STR) { if (!str2mask(argp[idx].data.str.area, &argp[idx].data.ipv4)) { msg = "invalid IPv4 mask"; goto error; } } else { msg = "integer or string expected"; goto error; } argp[idx].type = ARGT_MSK4; break; case ARGT_IPV6: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } if (inet_pton(AF_INET6, argp[idx].data.str.area, &argp[idx].data.ipv6)) { msg = "invalid IPv6 address"; goto error; } argp[idx].type = ARGT_IPV6; break; case ARGT_MSK6: if (argp[idx].type == ARGT_SINT) len2mask6(argp[idx].data.sint, &argp[idx].data.ipv6); else if (argp[idx].type == ARGT_STR) { if (!str2mask6(argp[idx].data.str.area, &argp[idx].data.ipv6)) { msg = "invalid IPv6 mask"; goto error; } } else { msg = "integer or string expected"; goto error; } argp[idx].type = ARGT_MSK6; break; case ARGT_REG: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } reg = regex_comp(argp[idx].data.str.area, !(argp[idx].type_flags & ARGF_REG_ICASE), 1, &err); if (!reg) { msg = lua_pushfstring(L, "error compiling regex '%s' : '%s'", argp[idx].data.str.area, err); free(err); goto error; } argp[idx].type = ARGT_REG; argp[idx].data.reg = reg; break; case ARGT_USR: if (argp[idx].type != ARGT_STR) { msg = "string expected"; goto error; } if (p->uri_auth && p->uri_auth->userlist && strcmp(p->uri_auth->userlist->name, argp[idx].data.str.area) == 0) ul = p->uri_auth->userlist; else ul = auth_find_userlist(argp[idx].data.str.area); if (!ul) { msg = lua_pushfstring(L, "unable to find userlist '%s'", argp[idx].data.str.area); goto error; } argp[idx].type = ARGT_USR; argp[idx].data.usr = ul; break; case ARGT_STR: if (!chunk_dup(&tmp, &argp[idx].data.str)) { msg = "unable to duplicate string arg"; goto error; } argp[idx].data.str = tmp; break; case ARGT_MAP: msg = "type not yet supported"; goto error; break; } /* Check for type of argument. */ if ((mask & ARGT_MASK) != argp[idx].type) { msg = lua_pushfstring(L, "'%s' expected, got '%s'", arg_type_names[(mask & ARGT_MASK)], arg_type_names[argp[idx].type & ARGT_MASK]); goto error; } /* Next argument. */ mask >>= ARGT_BITS; idx++; } return 0; error: argp[idx].type = ARGT_STOP; free_args(argp); WILL_LJMP(luaL_argerror(L, first + idx, msg)); return 0; /* Never reached */ } /* * The following functions are used to make correspondence between the the * executed lua pointer and the "struct hlua *" that contain the context. * * - hlua_gethlua : return the hlua context associated with an lua_State. * - hlua_sethlua : create the association between hlua context and lua_state. */ inline struct hlua *hlua_gethlua(lua_State *L) { struct hlua **hlua = lua_getextraspace(L); return *hlua; } static inline void hlua_sethlua(struct hlua *hlua) { struct hlua **hlua_store = lua_getextraspace(hlua->T); *hlua_store = hlua; } /* Will return a non-NULL string indicating the Lua call trace if the caller * currently is executing from within a Lua function. One line per entry will * be emitted, and each extra line will be prefixed with . If a current * Lua function is not detected, NULL is returned. */ const char *hlua_show_current_location(const char *pfx) { lua_State *L; lua_Debug ar; /* global or per-thread stack initializing ? */ if (hlua_state_id != -1 && (L = hlua_states[hlua_state_id]) && lua_getstack(L, 0, &ar)) return hlua_traceback(L, pfx); /* per-thread stack running ? */ if (hlua_states[tid + 1] && (L = hlua_states[tid + 1]) && lua_getstack(L, 0, &ar)) return hlua_traceback(L, pfx); /* global stack running ? */ if (hlua_states[0] && (L = hlua_states[0]) && lua_getstack(L, 0, &ar)) return hlua_traceback(L, pfx); return NULL; } /* This function is used to send logs. It tries to send them to: * - the log target applicable in the current context, OR * - stderr when no logger is in use for the current context */ static inline void hlua_sendlog(struct proxy *px, int level, const char *msg) { struct tm tm; char *p; /* Cleanup the log message. */ p = trash.area; for (; *msg != '\0'; msg++, p++) { if (p >= trash.area + trash.size - 1) { /* Break the message if exceed the buffer size. */ *(p-4) = ' '; *(p-3) = '.'; *(p-2) = '.'; *(p-1) = '.'; break; } if (isprint((unsigned char)*msg)) *p = *msg; else *p = '.'; } *p = '\0'; if (hlua_log_opts & HLUA_LOG_LOGGERS_ON) send_log(px, level, "%s\n", trash.area); if (!(global.mode & MODE_QUIET) || (global.mode & (MODE_VERBOSE | MODE_STARTING))) { if (!(hlua_log_opts & HLUA_LOG_STDERR_MASK)) return; /* when logging via stderr is set to 'auto', it behaves like 'off' unless one of: * - logging via loggers is disabled * - this is a non-proxy context and there is no global logger configured * - this is a proxy context and the proxy has no logger configured */ if ((hlua_log_opts & (HLUA_LOG_STDERR_MASK | HLUA_LOG_LOGGERS_ON)) == (HLUA_LOG_STDERR_AUTO | HLUA_LOG_LOGGERS_ON)) { /* AUTO=OFF in non-proxy context only if at least one global logger is defined */ if ((px == NULL) && (!LIST_ISEMPTY(&global.loggers))) return; /* AUTO=OFF in proxy context only if at least one logger is configured for the proxy */ if ((px != NULL) && (!LIST_ISEMPTY(&px->loggers))) return; } if (level == LOG_DEBUG && !(global.mode & MODE_DEBUG)) return; get_localtime(date.tv_sec, &tm); fprintf(stderr, "[%s] %03d/%02d%02d%02d (%d) : %s\n", log_levels[level], tm.tm_yday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)getpid(), trash.area); fflush(stderr); } } /* This function just ensure that the yield will be always * returned with a timeout and permit to set some flags * is a tick value */ __LJMP void hlua_yieldk(lua_State *L, int nresults, lua_KContext ctx, lua_KFunction k, int timeout, unsigned int flags) { struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) { return; } /* Set the wake timeout. If timeout is required, we set * the expiration time. */ hlua->wake_time = timeout; hlua->flags |= flags; /* Process the yield. */ MAY_LJMP(lua_yieldk(L, nresults, ctx, k)); } /* This function initialises the Lua environment stored in the stream. * It must be called at the start of the stream. This function creates * an LUA coroutine. It can not be use to crete the main LUA context. * * This function is particular. it initialises a new Lua thread. If the * initialisation fails (example: out of memory error), the lua function * throws an error (longjmp). * * This function manipulates two Lua stacks: the main and the thread. Only * the main stack can fail. The thread is not manipulated. This function * MUST NOT manipulate the created thread stack state, because it is not * protected against errors thrown by the thread stack. */ int hlua_ctx_init(struct hlua *lua, int state_id, struct task *task) { lua->Mref = LUA_REFNIL; lua->flags = 0; lua->gc_count = 0; lua->wake_time = TICK_ETERNITY; lua->state_id = state_id; hlua_timer_init(&lua->timer, 0); /* default value, no timeout */ LIST_INIT(&lua->com); MT_LIST_INIT(&lua->hc_list); if (!SET_SAFE_LJMP_PARENT(lua)) { lua->Tref = LUA_REFNIL; return 0; } lua->T = lua_newthread(hlua_states[state_id]); if (!lua->T) { lua->Tref = LUA_REFNIL; RESET_SAFE_LJMP_PARENT(lua); return 0; } hlua_sethlua(lua); lua->Tref = luaL_ref(hlua_states[state_id], LUA_REGISTRYINDEX); lua->task = task; RESET_SAFE_LJMP_PARENT(lua); return 1; } /* kill all associated httpclient to this hlua task * We must take extra precautions as we're manipulating lua-exposed * objects without the main lua lock. */ static void hlua_httpclient_destroy_all(struct hlua *hlua) { struct hlua_httpclient *hlua_hc; /* use thread-safe accessors for hc_list since GC cycle initiated by * another thread sharing the same main lua stack (lua coroutine) * could execute hlua_httpclient_gc() on the hlua->hc_list items * in parallel: Lua GC applies on the main stack, it is not limited to * a single coroutine stack, see Github issue #2037 for reference. * Remember, coroutines created using lua_newthread() are not meant to * be thread safe in Lua. (From lua co-author: * http://lua-users.org/lists/lua-l/2011-07/msg00072.html) * * This security measure is superfluous when 'lua-load-per-thread' is used * since in this case coroutines exclusively run on the same thread * (main stack is not shared between OS threads). */ while ((hlua_hc = MT_LIST_POP(&hlua->hc_list, typeof(hlua_hc), by_hlua))) { httpclient_stop_and_destroy(hlua_hc->hc); hlua_hc->hc = NULL; } } /* Used to destroy the Lua coroutine when the attached stream or task * is destroyed. The destroy also the memory context. The struct "lua" * will be freed. */ void hlua_ctx_destroy(struct hlua *lua) { if (!lua) return; if (!lua->T) goto end; /* clean all running httpclient */ hlua_httpclient_destroy_all(lua); /* Purge all the pending signals. */ notification_purge(&lua->com); if (!SET_SAFE_LJMP(lua)) return; luaL_unref(lua->T, LUA_REGISTRYINDEX, lua->Mref); RESET_SAFE_LJMP(lua); if (!SET_SAFE_LJMP_PARENT(lua)) return; luaL_unref(hlua_states[lua->state_id], LUA_REGISTRYINDEX, lua->Tref); RESET_SAFE_LJMP_PARENT(lua); /* Forces a garbage collecting process. If the Lua program is finished * without error, we run the GC on the thread pointer. Its freed all * the unused memory. * If the thread is finnish with an error or is currently yielded, * it seems that the GC applied on the thread doesn't clean anything, * so e run the GC on the main thread. * NOTE: maybe this action locks all the Lua threads untiml the en of * the garbage collection. */ if (lua->gc_count) { if (!SET_SAFE_LJMP_PARENT(lua)) return; lua_gc(hlua_states[lua->state_id], LUA_GCCOLLECT, 0); RESET_SAFE_LJMP_PARENT(lua); } lua->T = NULL; end: pool_free(pool_head_hlua, lua); } /* This function is used to restore the Lua context when a coroutine * fails. This function copy the common memory between old coroutine * and the new coroutine. The old coroutine is destroyed, and its * replaced by the new coroutine. * If the flag "keep_msg" is set, the last entry of the old is assumed * as string error message and it is copied in the new stack. */ static int hlua_ctx_renew(struct hlua *lua, int keep_msg) { lua_State *T; int new_ref; /* New Lua coroutine. */ T = lua_newthread(hlua_states[lua->state_id]); if (!T) return 0; /* Copy last error message. */ if (keep_msg) lua_xmove(lua->T, T, 1); /* Copy data between the coroutines. */ lua_rawgeti(lua->T, LUA_REGISTRYINDEX, lua->Mref); lua_xmove(lua->T, T, 1); new_ref = luaL_ref(T, LUA_REGISTRYINDEX); /* Value popped. */ /* Destroy old data. */ luaL_unref(lua->T, LUA_REGISTRYINDEX, lua->Mref); /* The thread is garbage collected by Lua. */ luaL_unref(hlua_states[lua->state_id], LUA_REGISTRYINDEX, lua->Tref); /* Fill the struct with the new coroutine values. */ lua->Mref = new_ref; lua->T = T; lua->Tref = luaL_ref(hlua_states[lua->state_id], LUA_REGISTRYINDEX); /* Set context. */ hlua_sethlua(lua); return 1; } /* Helper function to get the lua ctx for a given stream and state_id */ static inline struct hlua *hlua_stream_ctx_get(struct stream *s, int state_id) { /* state_id == 0 -> global runtime ctx * state_id != 0 -> per-thread runtime ctx */ return s->hlua[!!state_id]; } /* Helper function to prepare the lua ctx for a given stream and state id * * It uses the global or per-thread ctx depending on the expected * . * * Returns hlua ctx on success and NULL on failure */ static struct hlua *hlua_stream_ctx_prepare(struct stream *s, int state_id) { /* In the execution wrappers linked with a stream, the * Lua context can be not initialized. This behavior * permits to save performances because a systematic * Lua initialization cause 5% performances loss. */ if (!s->hlua[!!state_id]) { struct hlua *hlua; hlua = pool_alloc(pool_head_hlua); if (!hlua) return NULL; HLUA_INIT(hlua); if (!hlua_ctx_init(hlua, state_id, s->task)) { pool_free(pool_head_hlua, hlua); return NULL; } s->hlua[!!state_id] = hlua; } return s->hlua[!!state_id]; } void hlua_hook(lua_State *L, lua_Debug *ar) { struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return; if (hlua->T != L) { /* We don't want to enforce a yield on a sub coroutine, since * we have no guarantees that the yield will be handled properly. * Indeed, only the hlua->T coroutine is being handled through * hlua_ctx_resume() function. * * Instead, we simply check for timeouts and wait for the sub * coroutine to finish.. */ goto check_timeout; } /* Lua cannot yield when its returning from a function, * so, we can fix the interrupt hook to 1 instruction, * expecting that the function is finished. */ if (lua_gethookmask(L) & LUA_MASKRET) { lua_sethook(hlua->T, hlua_hook, LUA_MASKCOUNT, 1); return; } /* If we interrupt the Lua processing in yieldable state, we yield. * If the state is not yieldable, trying yield causes an error. */ if (lua_isyieldable(L)) { /* note: for converters/fetches.. where yielding is not allowed * hlua_ctx_resume() will simply perform a goto resume_execution * instead of rescheduling hlua->task. * also: hlua_ctx_resume() will take care of checking execution * timeout and re-applying the hook as needed. */ MAY_LJMP(hlua_yieldk(L, 0, 0, NULL, TICK_ETERNITY, HLUA_CTRLYIELD)); /* lua docs says that the hook should return immediately after lua_yieldk * * From: https://www.lua.org/manual/5.3/manual.html#lua_yieldk * * Moreover, it seems that we don't want to continue after the yield * because the end of the function is about handling unyieldable function, * which is not the case here. * * ->if we don't return lua_sethook gets incorrectly set with MASKRET later * in the function. */ return; } check_timeout: /* If we cannot yield, check the timeout. */ if (!hlua_timer_check(&hlua->timer)) { lua_pushfstring(L, "execution timeout"); WILL_LJMP(lua_error(L)); } /* Try to interrupt the process at the end of the current * unyieldable function. */ lua_sethook(hlua->T, hlua_hook, LUA_MASKRET|LUA_MASKCOUNT, hlua_nb_instruction); } /* This function start or resumes the Lua stack execution. If the flag * "yield_allowed" if no set and the LUA stack execution returns a yield * The function return an error. * * The function can returns 4 values: * - HLUA_E_OK : The execution is terminated without any errors. * - HLUA_E_AGAIN : The execution must continue at the next associated * task wakeup. * - HLUA_E_ERRMSG : An error has occurred, an error message is set in * the top of the stack. * - HLUA_E_ERR : An error has occurred without error message. * * If an error occurred, the stack is renewed and it is ready to run new * LUA code. */ static enum hlua_exec hlua_ctx_resume(struct hlua *lua, int yield_allowed) { #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 int nres; #endif int ret; const char *msg; const char *trace; /* Lock the whole Lua execution. This lock must be before the * label "resume_execution". */ hlua_lock(lua); /* reset the timer as we might be re-entering the function to * resume the coroutine after a successful yield * (cumulative time will be updated) */ hlua_timer_reset(&lua->timer); resume_execution: /* This hook interrupts the Lua processing each 'hlua_nb_instruction' * instructions. it is used for preventing infinite loops. */ lua_sethook(lua->T, hlua_hook, LUA_MASKCOUNT, hlua_nb_instruction); /* Remove all flags except the running flags. */ HLUA_SET_RUN(lua); HLUA_CLR_CTRLYIELD(lua); HLUA_CLR_WAKERESWR(lua); HLUA_CLR_WAKEREQWR(lua); HLUA_CLR_NOYIELD(lua); if (!yield_allowed) HLUA_SET_NOYIELD(lua); /* reset wake_time. */ lua->wake_time = TICK_ETERNITY; /* start the timer as we're about to start lua processing */ hlua_timer_start(&lua->timer); HLUA_SET_BUSY(lua); /* Call the function. */ #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 ret = lua_resume(lua->T, hlua_states[lua->state_id], lua->nargs, &nres); #else ret = lua_resume(lua->T, hlua_states[lua->state_id], lua->nargs); #endif HLUA_CLR_BUSY(lua); /* out of lua processing, stop the timer */ hlua_timer_stop(&lua->timer); /* reset nargs because those possibly passed to the lua_resume() call * were already consumed, and since we may call lua_resume() again * after a successful yield, we don't want to pass stale nargs hint * to the Lua API. As such, nargs should be set explicitly before each * lua_resume() (or hlua_ctx_resume()) invocation if needed. */ lua->nargs = 0; switch (ret) { case LUA_OK: ret = HLUA_E_OK; break; case LUA_YIELD: /* Check if the execution timeout is expired. If it is the case, we * break the Lua execution. */ if (!hlua_timer_check(&lua->timer)) { lua_settop(lua->T, 0); /* Empty the stack. */ ret = HLUA_E_ETMOUT; break; } /* Process the forced yield. if the general yield is not allowed or * if no task were associated this the current Lua execution * coroutine, we resume the execution. Else we want to return in the * scheduler and we want to be waked up again, to continue the * current Lua execution. So we schedule our own task. */ if (HLUA_IS_CTRLYIELDING(lua)) { if (!yield_allowed || !lua->task) goto resume_execution; task_wakeup(lua->task, TASK_WOKEN_MSG); } if (!yield_allowed) { lua_settop(lua->T, 0); /* Empty the stack. */ ret = HLUA_E_YIELD; break; } ret = HLUA_E_AGAIN; break; case LUA_ERRRUN: /* Special exit case. The traditional exit is returned as an error * because the errors ares the only one mean to return immediately * from and lua execution. */ if (lua->flags & HLUA_EXIT) { ret = HLUA_E_OK; hlua_ctx_renew(lua, 1); break; } lua->wake_time = TICK_ETERNITY; if (!lua_checkstack(lua->T, 1)) { ret = HLUA_E_ERR; break; } msg = hlua_tostring_safe(lua->T, -1); trace = hlua_traceback(lua->T, ", "); if (msg) lua_pushfstring(lua->T, "[state-id %d] runtime error: %s from %s", lua->state_id, msg, trace); else lua_pushfstring(lua->T, "[state-id %d] unknown runtime error from %s", lua->state_id, trace); /* Move the error msg at the top and then empty the stack except last msg */ lua_insert(lua->T, -lua_gettop(lua->T)); lua_settop(lua->T, 1); ret = HLUA_E_ERRMSG; break; case LUA_ERRMEM: lua->wake_time = TICK_ETERNITY; lua_settop(lua->T, 0); /* Empty the stack. */ ret = HLUA_E_NOMEM; break; case LUA_ERRERR: lua->wake_time = TICK_ETERNITY; if (!lua_checkstack(lua->T, 1)) { ret = HLUA_E_ERR; break; } msg = hlua_tostring_safe(lua->T, -1); if (msg) lua_pushfstring(lua->T, "[state-id %d] message handler error: %s", lua->state_id, msg); else lua_pushfstring(lua->T, "[state-id %d] message handler error", lua->state_id); /* Move the error msg at the top and then empty the stack except last msg */ lua_insert(lua->T, -lua_gettop(lua->T)); lua_settop(lua->T, 1); ret = HLUA_E_ERRMSG; break; default: lua->wake_time = TICK_ETERNITY; lua_settop(lua->T, 0); /* Empty the stack. */ ret = HLUA_E_ERR; break; } switch (ret) { case HLUA_E_AGAIN: break; case HLUA_E_ERRMSG: notification_purge(&lua->com); hlua_ctx_renew(lua, 1); HLUA_CLR_RUN(lua); break; case HLUA_E_ETMOUT: case HLUA_E_NOMEM: case HLUA_E_YIELD: case HLUA_E_ERR: HLUA_CLR_RUN(lua); notification_purge(&lua->com); hlua_ctx_renew(lua, 0); break; case HLUA_E_OK: HLUA_CLR_RUN(lua); notification_purge(&lua->com); break; } /* This is the main exit point, remove the Lua lock. */ hlua_unlock(lua); return ret; } /* This function exit the current code. */ __LJMP static int hlua_done(lua_State *L) { struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return 0; hlua->flags |= HLUA_EXIT; WILL_LJMP(lua_error(L)); return 0; } /* This function is an LUA binding. It provides a function * for deleting ACL from a referenced ACL file. */ __LJMP static int hlua_del_acl(lua_State *L) { const char *name; const char *key; struct pat_ref *ref; MAY_LJMP(check_args(L, 2, "del_acl")); name = MAY_LJMP(luaL_checkstring(L, 1)); key = MAY_LJMP(luaL_checkstring(L, 2)); ref = pat_ref_lookup(name); if (!ref) WILL_LJMP(luaL_error(L, "'del_acl': unknown acl file '%s'", name)); HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock); pat_ref_delete(ref, key); HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock); return 0; } /* This function is an LUA binding. It provides a function * for deleting map entry from a referenced map file. */ static int hlua_del_map(lua_State *L) { const char *name; const char *key; struct pat_ref *ref; MAY_LJMP(check_args(L, 2, "del_map")); name = MAY_LJMP(luaL_checkstring(L, 1)); key = MAY_LJMP(luaL_checkstring(L, 2)); ref = pat_ref_lookup(name); if (!ref) WILL_LJMP(luaL_error(L, "'del_map': unknown acl file '%s'", name)); HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock); pat_ref_delete(ref, key); HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock); return 0; } /* This function is an LUA binding. It provides a function * for adding ACL pattern from a referenced ACL file. */ static int hlua_add_acl(lua_State *L) { const char *name; const char *key; struct pat_ref *ref; MAY_LJMP(check_args(L, 2, "add_acl")); name = MAY_LJMP(luaL_checkstring(L, 1)); key = MAY_LJMP(luaL_checkstring(L, 2)); ref = pat_ref_lookup(name); if (!ref) WILL_LJMP(luaL_error(L, "'add_acl': unknown acl file '%s'", name)); HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock); if (pat_ref_find_elt(ref, key) == NULL) pat_ref_add(ref, key, NULL, NULL); HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock); return 0; } /* This function is an LUA binding. It provides a function * for setting map pattern and sample from a referenced map * file. */ static int hlua_set_map(lua_State *L) { const char *name; const char *key; const char *value; struct pat_ref *ref; MAY_LJMP(check_args(L, 3, "set_map")); name = MAY_LJMP(luaL_checkstring(L, 1)); key = MAY_LJMP(luaL_checkstring(L, 2)); value = MAY_LJMP(luaL_checkstring(L, 3)); ref = pat_ref_lookup(name); if (!ref) WILL_LJMP(luaL_error(L, "'set_map': unknown map file '%s'", name)); HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock); if (pat_ref_find_elt(ref, key) != NULL) pat_ref_set(ref, key, value, NULL, NULL); else pat_ref_add(ref, key, value, NULL); HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock); return 0; } /* This function is an LUA binding. It provides a function * for retrieving a var from the proc scope in core. */ static int hlua_core_get_var(lua_State *L) { const char *name; size_t len; struct sample smp; MAY_LJMP(check_args(L, 1, "get_var")); name = MAY_LJMP(luaL_checklstring(L, 1, &len)); /* We can only retrieve information from the proc. scope */ /* FIXME: I didn't want to expose vars_hash_name from vars.c */ if (len < 5 || strncmp(name, "proc.", 5) != 0) WILL_LJMP(luaL_error(L, "'get_var': Only 'proc.' scope allowed to be retrieved in 'core.get_var()'.")); memset(&smp, 0, sizeof(smp)); if (!vars_get_by_name(name, len, &smp, NULL)) { lua_pushnil(L); return 1; } return MAY_LJMP(hlua_smp2lua(L, &smp)); return 1; } /* This function disables the sending of email through the * legacy email sending function which is implemented using * checks. * * It may not be used during runtime. */ __LJMP static int hlua_disable_legacy_mailers(lua_State *L) { if (hlua_gethlua(L)) WILL_LJMP(luaL_error(L, "disable_legacy_mailers: " "not available outside of init or body context")); send_email_disabled = 1; return 0; } /* A class is a lot of memory that contain data. This data can be a table, * an integer or user data. This data is associated with a metatable. This * metatable have an original version registered in the global context with * the name of the object (_G[] = ). * * A metable is a table that modify the standard behavior of a standard * access to the associated data. The entries of this new metatable are * defined as is: * * http://lua-users.org/wiki/MetatableEvents * * __index * * we access an absent field in a table, the result is nil. This is * true, but it is not the whole truth. Actually, such access triggers * the interpreter to look for an __index metamethod: If there is no * such method, as usually happens, then the access results in nil; * otherwise, the metamethod will provide the result. * * Control 'prototype' inheritance. When accessing "myTable[key]" and * the key does not appear in the table, but the metatable has an __index * property: * * - if the value is a function, the function is called, passing in the * table and the key; the return value of that function is returned as * the result. * * - if the value is another table, the value of the key in that table is * asked for and returned (and if it doesn't exist in that table, but that * table's metatable has an __index property, then it continues on up) * * - Use "rawget(myTable,key)" to skip this metamethod. * * http://www.lua.org/pil/13.4.1.html * * __newindex * * Like __index, but control property assignment. * * __mode - Control weak references. A string value with one or both * of the characters 'k' and 'v' which specifies that the the * keys and/or values in the table are weak references. * * __call - Treat a table like a function. When a table is followed by * parenthesis such as "myTable( 'foo' )" and the metatable has * a __call key pointing to a function, that function is invoked * (passing any specified arguments) and the return value is * returned. * * __metatable - Hide the metatable. When "getmetatable( myTable )" is * called, if the metatable for myTable has a __metatable * key, the value of that key is returned instead of the * actual metatable. * * __tostring - Control string representation. When the builtin * "tostring( myTable )" function is called, if the metatable * for myTable has a __tostring property set to a function, * that function is invoked (passing myTable to it) and the * return value is used as the string representation. * * __len - Control table length. When the table length is requested using * the length operator ( '#' ), if the metatable for myTable has * a __len key pointing to a function, that function is invoked * (passing myTable to it) and the return value used as the value * of "#myTable". * * __gc - Userdata finalizer code. When userdata is set to be garbage * collected, if the metatable has a __gc field pointing to a * function, that function is first invoked, passing the userdata * to it. The __gc metamethod is not called for tables. * (See http://lua-users.org/lists/lua-l/2006-11/msg00508.html) * * Special metamethods for redefining standard operators: * http://www.lua.org/pil/13.1.html * * __add "+" * __sub "-" * __mul "*" * __div "/" * __unm "!" * __pow "^" * __concat ".." * * Special methods for redefining standard relations * http://www.lua.org/pil/13.2.html * * __eq "==" * __lt "<" * __le "<=" */ /* * * * Class Map * * */ /* Returns a struct hlua_map if the stack entry "ud" is * a class session, otherwise it throws an error. */ __LJMP static struct map_descriptor *hlua_checkmap(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_map_ref)); } /* This function is the map constructor. It don't need * the class Map object. It creates and return a new Map * object. It must be called only during "body" or "init" * context because it process some filesystem accesses. */ __LJMP static int hlua_map_new(struct lua_State *L) { const char *fn; int match = PAT_MATCH_STR; struct sample_conv conv; const char *file = ""; int line = 0; lua_Debug ar; char *err = NULL; struct arg args[2]; if (lua_gettop(L) < 1 || lua_gettop(L) > 2) WILL_LJMP(luaL_error(L, "'new' needs at least 1 argument.")); fn = MAY_LJMP(luaL_checkstring(L, 1)); if (lua_gettop(L) >= 2) { match = MAY_LJMP(luaL_checkinteger(L, 2)); if (match < 0 || match >= PAT_MATCH_NUM) WILL_LJMP(luaL_error(L, "'new' needs a valid match method.")); } /* Get Lua filename and line number. */ if (lua_getstack(L, 1, &ar)) { /* check function at level */ lua_getinfo(L, "Sl", &ar); /* get info about it */ if (ar.currentline > 0) { /* is there info? */ file = ar.short_src; line = ar.currentline; } } /* fill fake sample_conv struct. */ conv.kw = ""; /* unused. */ conv.process = NULL; /* unused. */ conv.arg_mask = 0; /* unused. */ conv.val_args = NULL; /* unused. */ conv.out_type = SMP_T_STR; conv.private = (void *)(long)match; switch (match) { case PAT_MATCH_STR: conv.in_type = SMP_T_STR; break; case PAT_MATCH_BEG: conv.in_type = SMP_T_STR; break; case PAT_MATCH_SUB: conv.in_type = SMP_T_STR; break; case PAT_MATCH_DIR: conv.in_type = SMP_T_STR; break; case PAT_MATCH_DOM: conv.in_type = SMP_T_STR; break; case PAT_MATCH_END: conv.in_type = SMP_T_STR; break; case PAT_MATCH_REG: conv.in_type = SMP_T_STR; break; case PAT_MATCH_INT: conv.in_type = SMP_T_SINT; break; case PAT_MATCH_IP: conv.in_type = SMP_T_ADDR; break; default: WILL_LJMP(luaL_error(L, "'new' doesn't support this match mode.")); } /* fill fake args. */ args[0].type = ARGT_STR; args[0].data.str.area = strdup(fn); args[0].data.str.data = strlen(fn); args[0].data.str.size = args[0].data.str.data+1; args[1].type = ARGT_STOP; /* load the map. */ if (!sample_load_map(args, &conv, file, line, &err)) { /* error case: we can't use luaL_error because we must * free the err variable. */ luaL_where(L, 1); lua_pushfstring(L, "'new': %s.", err); lua_concat(L, 2); free(err); chunk_destroy(&args[0].data.str); WILL_LJMP(lua_error(L)); } /* create the lua object. */ lua_newtable(L); lua_pushlightuserdata(L, args[0].data.map); lua_rawseti(L, -2, 0); /* Pop a class Map metatable and affect it to the userdata. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_map_ref); lua_setmetatable(L, -2); return 1; } __LJMP static inline int _hlua_map_lookup(struct lua_State *L, int str) { struct map_descriptor *desc; struct pattern *pat; struct sample smp; MAY_LJMP(check_args(L, 2, "lookup")); desc = MAY_LJMP(hlua_checkmap(L, 1)); if (desc->pat.expect_type == SMP_T_SINT) { smp.data.type = SMP_T_SINT; smp.data.u.sint = MAY_LJMP(luaL_checkinteger(L, 2)); } else { smp.data.type = SMP_T_STR; smp.flags = SMP_F_CONST; smp.data.u.str.area = (char *)MAY_LJMP(luaL_checklstring(L, 2, (size_t *)&smp.data.u.str.data)); smp.data.u.str.size = smp.data.u.str.data + 1; } pat = pattern_exec_match(&desc->pat, &smp, 1); if (!pat || !pat->data) { if (str) lua_pushstring(L, ""); else lua_pushnil(L); return 1; } /* The Lua pattern must return a string, so we can't check the returned type */ lua_pushlstring(L, pat->data->u.str.area, pat->data->u.str.data); return 1; } __LJMP static int hlua_map_lookup(struct lua_State *L) { return _hlua_map_lookup(L, 0); } __LJMP static int hlua_map_slookup(struct lua_State *L) { return _hlua_map_lookup(L, 1); } /* * * * Class Socket * * */ __LJMP static struct hlua_socket *hlua_checksocket(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_socket_ref)); } /* This function is the handler called for each I/O on the established * connection. It is used for notify space available to send or data * received. */ static void hlua_socket_handler(struct appctx *appctx) { struct hlua_csk_ctx *ctx = appctx->svcctx; struct stconn *sc = appctx_sc(appctx); if (unlikely(se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) { co_skip(sc_oc(sc), co_data(sc_oc(sc))); notification_wake(&ctx->wake_on_read); notification_wake(&ctx->wake_on_write); return; } if (ctx->die) { se_fl_set(appctx->sedesc, SE_FL_EOI|SE_FL_EOS); notification_wake(&ctx->wake_on_read); notification_wake(&ctx->wake_on_write); return; } /* If we can't write, wakeup the pending write signals. */ if (channel_output_closed(sc_ic(sc))) notification_wake(&ctx->wake_on_write); /* If we can't read, wakeup the pending read signals. */ if (channel_input_closed(sc_oc(sc))) notification_wake(&ctx->wake_on_read); /* if the connection is not established, inform the stream that we want * to be notified whenever the connection completes. */ if (sc_opposite(sc)->state < SC_ST_EST) { applet_need_more_data(appctx); se_need_remote_conn(appctx->sedesc); applet_have_more_data(appctx); return; } /* This function is called after the connect. */ ctx->connected = 1; /* Wake the tasks which wants to write if the buffer have available space. */ if (channel_may_recv(sc_ic(sc))) notification_wake(&ctx->wake_on_write); /* Wake the tasks which wants to read if the buffer contains data. */ if (co_data(sc_oc(sc))) { notification_wake(&ctx->wake_on_read); applet_wont_consume(appctx); } /* If write notifications are registered, we considers we want * to write, so we clear the blocking flag. */ if (notification_registered(&ctx->wake_on_write)) applet_have_more_data(appctx); } static int hlua_socket_init(struct appctx *appctx) { struct hlua_csk_ctx *csk_ctx = appctx->svcctx; struct stream *s; if (appctx_finalize_startup(appctx, socket_proxy, &BUF_NULL) == -1) goto error; s = appctx_strm(appctx); /* Configure "right" stream connector. This stconn is used to connect * and retrieve data from the server. The connection is initialized * with the "struct server". */ sc_set_state(s->scb, SC_ST_ASS); /* Force destination server. */ s->flags |= SF_DIRECT | SF_ASSIGNED | SF_BE_ASSIGNED; s->target = &csk_ctx->srv->obj_type; if (csk_ctx->timeout) { s->sess->fe->timeout.connect = csk_ctx->timeout; s->scf->ioto = csk_ctx->timeout; s->scb->ioto = csk_ctx->timeout; } return 0; error: return -1; } /* This function is called when the "struct stream" is destroyed. * Remove the link from the object to this stream. * Wake all the pending signals. */ static void hlua_socket_release(struct appctx *appctx) { struct hlua_csk_ctx *ctx = appctx->svcctx; struct xref *peer; /* Remove my link in the original objects. */ peer = xref_get_peer_and_lock(&ctx->xref); if (peer) xref_disconnect(&ctx->xref, peer); /* Wake all the task waiting for me. */ notification_wake(&ctx->wake_on_read); notification_wake(&ctx->wake_on_write); } /* If the garbage collectio of the object is launch, nobody * uses this object. If the stream does not exists, just quit. * Send the shutdown signal to the stream. In some cases, * pending signal can rest in the read and write lists. destroy * it. */ __LJMP static int hlua_socket_gc(lua_State *L) { struct hlua_socket *socket; struct hlua_csk_ctx *ctx; struct xref *peer; MAY_LJMP(check_args(L, 1, "__gc")); socket = MAY_LJMP(hlua_checksocket(L, 1)); peer = xref_get_peer_and_lock(&socket->xref); if (!peer) return 0; ctx = container_of(peer, struct hlua_csk_ctx, xref); /* Remove all reference between the Lua stack and the coroutine stream. */ xref_disconnect(&socket->xref, peer); if (se_fl_test(ctx->appctx->sedesc, SE_FL_ORPHAN)) { /* The applet was never initialized, just release it */ appctx_free(ctx->appctx); } else { /* Otherwise, notify it that is must die and wake it up */ ctx->die = 1; appctx_wakeup(ctx->appctx); } return 0; } /* The close function send shutdown signal and break the * links between the stream and the object. */ __LJMP static int hlua_socket_close_helper(lua_State *L) { struct hlua_socket *socket; struct hlua_csk_ctx *ctx; struct xref *peer; struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return 0; socket = MAY_LJMP(hlua_checksocket(L, 1)); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); peer = xref_get_peer_and_lock(&socket->xref); if (!peer) return 0; hlua->gc_count--; ctx = container_of(peer, struct hlua_csk_ctx, xref); /* Set the flag which destroy the session. */ ctx->die = 1; appctx_wakeup(ctx->appctx); /* Remove all reference between the Lua stack and the coroutine stream. */ xref_disconnect(&socket->xref, peer); return 0; } /* The close function calls close_helper. */ __LJMP static int hlua_socket_close(lua_State *L) { MAY_LJMP(check_args(L, 1, "close")); return hlua_socket_close_helper(L); } /* This Lua function assumes that the stack contain three parameters. * 1 - USERDATA containing a struct socket * 2 - INTEGER with values of the macro defined below * If the integer is -1, we must read at most one line. * If the integer is -2, we ust read all the data until the * end of the stream. * If the integer is positive value, we must read a number of * bytes corresponding to this value. */ #define HLSR_READ_LINE (-1) #define HLSR_READ_ALL (-2) __LJMP static int hlua_socket_receive_yield(struct lua_State *L, int status, lua_KContext ctx) { struct hlua_socket *socket = MAY_LJMP(hlua_checksocket(L, 1)); int wanted = lua_tointeger(L, 2); struct hlua *hlua; struct hlua_csk_ctx *csk_ctx; struct appctx *appctx; size_t len; int nblk; const char *blk1; size_t len1; const char *blk2; size_t len2; int skip_at_end = 0; struct channel *oc; struct stream *s; struct xref *peer; int missing_bytes; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); /* Check if this lua stack is schedulable. */ if (!hlua || !hlua->task) WILL_LJMP(luaL_error(L, "The 'receive' function is only allowed in " "'frontend', 'backend' or 'task'")); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); /* check for connection break. If some data where read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) goto no_peer; csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); if (!csk_ctx->connected) goto connection_closed; appctx = csk_ctx->appctx; s = appctx_strm(appctx); oc = &s->res; if (wanted == HLSR_READ_LINE) { /* Read line. */ nblk = co_getline_nc(oc, &blk1, &len1, &blk2, &len2); if (nblk < 0) /* Connection close. */ goto connection_closed; if (nblk == 0) /* No data available. */ goto connection_empty; /* remove final \r\n. */ if (nblk == 1) { if (blk1[len1-1] == '\n') { len1--; skip_at_end++; if (blk1[len1-1] == '\r') { len1--; skip_at_end++; } } } else { if (blk2[len2-1] == '\n') { len2--; skip_at_end++; if (blk2[len2-1] == '\r') { len2--; skip_at_end++; } } } } else if (wanted == HLSR_READ_ALL) { /* Read all the available data. */ nblk = co_getblk_nc(oc, &blk1, &len1, &blk2, &len2); if (nblk < 0) /* Connection close. */ goto connection_closed; if (nblk == 0) /* No data available. */ goto connection_empty; } else { /* Read a block of data. */ nblk = co_getblk_nc(oc, &blk1, &len1, &blk2, &len2); if (nblk < 0) /* Connection close. */ goto connection_closed; if (nblk == 0) /* No data available. */ goto connection_empty; missing_bytes = wanted - socket->b.n; if (len1 > missing_bytes) { nblk = 1; len1 = missing_bytes; } if (nblk == 2 && len1 + len2 > missing_bytes) len2 = missing_bytes - len1; } len = len1; luaL_addlstring(&socket->b, blk1, len1); if (nblk == 2) { len += len2; luaL_addlstring(&socket->b, blk2, len2); } /* Consume data. */ co_skip(oc, len + skip_at_end); /* Don't wait anything. */ applet_will_consume(appctx); appctx_wakeup(appctx); /* If the pattern reclaim to read all the data * in the connection, got out. */ if (wanted == HLSR_READ_ALL) goto connection_empty; else if (wanted >= 0 && socket->b.n < wanted) goto connection_empty; /* Return result. */ luaL_pushresult(&socket->b); xref_unlock(&socket->xref, peer); return 1; connection_closed: xref_unlock(&socket->xref, peer); no_peer: /* If the buffer containds data. */ if (socket->b.n > 0) { luaL_pushresult(&socket->b); return 1; } lua_pushnil(L); lua_pushstring(L, "connection closed."); return 2; connection_empty: if (!notification_new(&hlua->com, &csk_ctx->wake_on_read, hlua->task)) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "out of memory")); } xref_unlock(&socket->xref, peer); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_receive_yield, TICK_ETERNITY, 0)); return 0; } /* This Lua function gets two parameters. The first one can be string * or a number. If the string is "*l", the user requires one line. If * the string is "*a", the user requires all the contents of the stream. * If the value is a number, the user require a number of bytes equal * to the value. The default value is "*l" (a line). * * This parameter with a variable type is converted in integer. This * integer takes this values: * -1 : read a line * -2 : read all the stream * >0 : amount of bytes. * * The second parameter is optional. It contains a string that must be * concatenated with the read data. */ __LJMP static int hlua_socket_receive(struct lua_State *L) { int wanted = HLSR_READ_LINE; const char *pattern; int lastarg, type; char *error; size_t len; struct hlua_socket *socket; if (lua_gettop(L) < 1 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "The 'receive' function requires between 1 and 3 arguments.")); socket = MAY_LJMP(hlua_checksocket(L, 1)); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); /* check for pattern. */ if (lua_gettop(L) >= 2) { type = lua_type(L, 2); if (type == LUA_TSTRING) { pattern = lua_tostring(L, 2); if (strcmp(pattern, "*a") == 0) wanted = HLSR_READ_ALL; else if (strcmp(pattern, "*l") == 0) wanted = HLSR_READ_LINE; else { wanted = strtoll(pattern, &error, 10); if (*error != '\0') WILL_LJMP(luaL_error(L, "Unsupported pattern.")); } } else if (type == LUA_TNUMBER) { wanted = lua_tointeger(L, 2); if (wanted < 0) WILL_LJMP(luaL_error(L, "Unsupported size.")); } } /* Set pattern. */ lua_pushinteger(L, wanted); /* Check if we would replace the top by itself. */ if (lua_gettop(L) != 2) lua_replace(L, 2); /* Save index of the top of the stack because since buffers are used, it * may change */ lastarg = lua_gettop(L); /* init buffer, and fill it with prefix. */ luaL_buffinit(L, &socket->b); /* Check prefix. */ if (lastarg >= 3) { if (lua_type(L, 3) != LUA_TSTRING) WILL_LJMP(luaL_error(L, "Expect a 'string' for the prefix")); pattern = lua_tolstring(L, 3, &len); luaL_addlstring(&socket->b, pattern, len); } return __LJMP(hlua_socket_receive_yield(L, 0, 0)); } /* Write the Lua input string in the output buffer. * This function returns a yield if no space is available. */ static int hlua_socket_write_yield(struct lua_State *L,int status, lua_KContext ctx) { struct hlua_socket *socket; struct hlua *hlua; struct hlua_csk_ctx *csk_ctx; struct appctx *appctx; size_t buf_len; const char *buf; int len; int send_len; int sent; struct xref *peer; struct stream *s; struct stconn *sc; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); /* Check if this lua stack is schedulable. */ if (!hlua || !hlua->task) WILL_LJMP(luaL_error(L, "The 'write' function is only allowed in " "'frontend', 'backend' or 'task'")); /* Get object */ socket = MAY_LJMP(hlua_checksocket(L, 1)); buf = MAY_LJMP(luaL_checklstring(L, 2, &buf_len)); sent = MAY_LJMP(luaL_checkinteger(L, 3)); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); /* check for connection break. If some data where read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) { lua_pushinteger(L, -1); return 1; } csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); if (!csk_ctx->connected) { xref_unlock(&socket->xref, peer); lua_pushinteger(L, -1); return 1; } appctx = csk_ctx->appctx; sc = appctx_sc(appctx); s = __sc_strm(sc); /* Check for connection close. */ if (channel_output_closed(&s->req)) { xref_unlock(&socket->xref, peer); lua_pushinteger(L, -1); return 1; } /* Update the input buffer data. */ buf += sent; send_len = buf_len - sent; /* All the data are sent. */ if (sent >= buf_len) { xref_unlock(&socket->xref, peer); return 1; /* Implicitly return the length sent. */ } /* Check if the buffer is available because HAProxy doesn't allocate * the request buffer if its not required. */ if (s->req.buf.size == 0) { if (!sc_alloc_ibuf(sc, &appctx->buffer_wait)) goto hlua_socket_write_yield_return; } /* Check for available space. */ len = b_room(&s->req.buf); if (len <= 0) { goto hlua_socket_write_yield_return; } /* send data */ if (len < send_len) send_len = len; len = ci_putblk(&s->req, buf, send_len); /* "Not enough space" (-1), "Buffer too little to contain * the data" (-2) are not expected because the available length * is tested. * Other unknown error are also not expected. */ if (len <= 0) { if (len == -1) s->req.flags |= CF_WAKE_WRITE; MAY_LJMP(hlua_socket_close_helper(L)); lua_pop(L, 1); lua_pushinteger(L, -1); xref_unlock(&socket->xref, peer); return 1; } /* update buffers. */ appctx_wakeup(appctx); /* Update length sent. */ lua_pop(L, 1); lua_pushinteger(L, sent + len); /* All the data buffer is sent ? */ if (sent + len >= buf_len) { xref_unlock(&socket->xref, peer); return 1; } hlua_socket_write_yield_return: if (!notification_new(&hlua->com, &csk_ctx->wake_on_write, hlua->task)) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "out of memory")); } xref_unlock(&socket->xref, peer); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_write_yield, TICK_ETERNITY, 0)); return 0; } /* This function initiate the send of data. It just check the input * parameters and push an integer in the Lua stack that contain the * amount of data written to the buffer. This is used by the function * "hlua_socket_write_yield" that can yield. * * The Lua function gets between 3 and 4 parameters. The first one is * the associated object. The second is a string buffer. The third is * a facultative integer that represents where is the buffer position * of the start of the data that can send. The first byte is the * position "1". The default value is "1". The fourth argument is a * facultative integer that represents where is the buffer position * of the end of the data that can send. The default is the last byte. */ static int hlua_socket_send(struct lua_State *L) { int i; int j; const char *buf; size_t buf_len; /* Check number of arguments. */ if (lua_gettop(L) < 2 || lua_gettop(L) > 4) WILL_LJMP(luaL_error(L, "'send' needs between 2 and 4 arguments")); /* Get the string. */ buf = MAY_LJMP(luaL_checklstring(L, 2, &buf_len)); /* Get and check j. */ if (lua_gettop(L) == 4) { j = MAY_LJMP(luaL_checkinteger(L, 4)); if (j < 0) j = buf_len + j + 1; if (j > buf_len) j = buf_len + 1; lua_pop(L, 1); } else j = buf_len; /* Get and check i. */ if (lua_gettop(L) == 3) { i = MAY_LJMP(luaL_checkinteger(L, 3)); if (i < 0) i = buf_len + i + 1; if (i > buf_len) i = buf_len + 1; lua_pop(L, 1); } else i = 1; /* Check bth i and j. */ if (i > j) { lua_pushinteger(L, 0); return 1; } if (i == 0 && j == 0) { lua_pushinteger(L, 0); return 1; } if (i == 0) i = 1; if (j == 0) j = 1; /* Pop the string. */ lua_pop(L, 1); /* Update the buffer length. */ buf += i - 1; buf_len = j - i + 1; lua_pushlstring(L, buf, buf_len); /* This unsigned is used to remember the amount of sent data. */ lua_pushinteger(L, 0); return MAY_LJMP(hlua_socket_write_yield(L, 0, 0)); } #define SOCKET_INFO_MAX_LEN sizeof("[0000:0000:0000:0000:0000:0000:0000:0000]:12345") __LJMP static inline int hlua_socket_info(struct lua_State *L, const struct sockaddr_storage *addr) { static char buffer[SOCKET_INFO_MAX_LEN]; int ret; int len; char *p; ret = addr_to_str(addr, buffer+1, SOCKET_INFO_MAX_LEN-1); if (ret <= 0) { lua_pushnil(L); return 1; } if (ret == AF_UNIX) { lua_pushstring(L, buffer+1); return 1; } else if (ret == AF_INET6) { buffer[0] = '['; len = strlen(buffer); buffer[len] = ']'; len++; buffer[len] = ':'; len++; p = buffer; } else if (ret == AF_INET) { p = buffer + 1; len = strlen(p); p[len] = ':'; len++; } else { lua_pushnil(L); return 1; } if (port_to_str(addr, p + len, SOCKET_INFO_MAX_LEN-1 - len) <= 0) { lua_pushnil(L); return 1; } lua_pushstring(L, p); return 1; } /* Returns information about the peer of the connection. */ __LJMP static int hlua_socket_getpeername(struct lua_State *L) { struct hlua_socket *socket; struct xref *peer; struct hlua_csk_ctx *csk_ctx; struct appctx *appctx; struct stconn *sc; const struct sockaddr_storage *dst; int ret; MAY_LJMP(check_args(L, 1, "getpeername")); socket = MAY_LJMP(hlua_checksocket(L, 1)); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); /* check for connection break. If some data where read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) { lua_pushnil(L); return 1; } csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); if (!csk_ctx->connected) { xref_unlock(&socket->xref, peer); lua_pushnil(L); return 1; } appctx = csk_ctx->appctx; sc = appctx_sc(appctx); dst = sc_dst(sc_opposite(sc)); if (!dst) { xref_unlock(&socket->xref, peer); lua_pushnil(L); return 1; } ret = MAY_LJMP(hlua_socket_info(L, dst)); xref_unlock(&socket->xref, peer); return ret; } /* Returns information about my connection side. */ static int hlua_socket_getsockname(struct lua_State *L) { struct hlua_socket *socket; struct connection *conn; struct appctx *appctx; struct xref *peer; struct hlua_csk_ctx *csk_ctx; struct stream *s; int ret; MAY_LJMP(check_args(L, 1, "getsockname")); socket = MAY_LJMP(hlua_checksocket(L, 1)); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); /* check for connection break. If some data where read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) { lua_pushnil(L); return 1; } csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); if (!csk_ctx->connected) { xref_unlock(&socket->xref, peer); lua_pushnil(L); return 1; } appctx = csk_ctx->appctx; s = appctx_strm(appctx); conn = sc_conn(s->scb); if (!conn || !conn_get_src(conn)) { xref_unlock(&socket->xref, peer); lua_pushnil(L); return 1; } ret = hlua_socket_info(L, conn->src); xref_unlock(&socket->xref, peer); return ret; } /* This struct define the applet. */ static struct applet update_applet = { .obj_type = OBJ_TYPE_APPLET, .name = "", .fct = hlua_socket_handler, .init = hlua_socket_init, .release = hlua_socket_release, }; __LJMP static int hlua_socket_connect_yield(struct lua_State *L, int status, lua_KContext ctx) { struct hlua_socket *socket = MAY_LJMP(hlua_checksocket(L, 1)); struct hlua *hlua; struct xref *peer; struct hlua_csk_ctx *csk_ctx; struct appctx *appctx; struct stream *s; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return 0; /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); /* check for connection break. If some data where read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) { lua_pushnil(L); lua_pushstring(L, "Can't connect"); return 2; } csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); appctx = csk_ctx->appctx; s = appctx_strm(appctx); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); } /* Check for connection close. */ if (!hlua || channel_output_closed(&s->req)) { xref_unlock(&socket->xref, peer); lua_pushnil(L); lua_pushstring(L, "Can't connect"); return 2; } appctx = __sc_appctx(s->scf); /* Check for connection established. */ if (csk_ctx->connected) { xref_unlock(&socket->xref, peer); lua_pushinteger(L, 1); return 1; } if (!notification_new(&hlua->com, &csk_ctx->wake_on_write, hlua->task)) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "out of memory error")); } xref_unlock(&socket->xref, peer); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_connect_yield, TICK_ETERNITY, 0)); return 0; } /* This function fail or initite the connection. */ __LJMP static int hlua_socket_connect(struct lua_State *L) { struct hlua_socket *socket; int port = -1; const char *ip; struct hlua *hlua; struct hlua_csk_ctx *csk_ctx; struct appctx *appctx; int low, high; struct sockaddr_storage *addr; struct xref *peer; struct stconn *sc; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return 0; if (lua_gettop(L) < 2) WILL_LJMP(luaL_error(L, "connect: need at least 2 arguments")); /* Get args. */ socket = MAY_LJMP(hlua_checksocket(L, 1)); /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); ip = MAY_LJMP(luaL_checkstring(L, 2)); if (lua_gettop(L) >= 3) { luaL_Buffer b; port = MAY_LJMP(luaL_checkinteger(L, 3)); /* Force the ip to end with a colon, to support IPv6 addresses * that are not enclosed within square brackets. */ if (port > 0) { luaL_buffinit(L, &b); luaL_addstring(&b, ip); luaL_addchar(&b, ':'); luaL_pushresult(&b); ip = lua_tolstring(L, lua_gettop(L), NULL); } } /* check for connection break. If some data where read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) { lua_pushnil(L); return 1; } csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); if (!csk_ctx->srv) csk_ctx->srv = socket_tcp; /* Parse ip address. */ addr = str2sa_range(ip, NULL, &low, &high, NULL, NULL, NULL, NULL, NULL, NULL, PA_O_PORT_OK | PA_O_STREAM); if (!addr) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "connect: cannot parse destination address '%s'", ip)); } /* Set port. */ if (low == 0) { if (addr->ss_family == AF_INET) { if (port == -1) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "connect: port missing")); } ((struct sockaddr_in *)addr)->sin_port = htons(port); } else if (addr->ss_family == AF_INET6) { if (port == -1) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "connect: port missing")); } ((struct sockaddr_in6 *)addr)->sin6_port = htons(port); } } appctx = csk_ctx->appctx; if (appctx_sc(appctx)) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "connect: connect already performed\n")); } if (appctx_init(appctx) == -1) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "connect: fail to init applet.")); } sc = appctx_sc(appctx); if (!sockaddr_alloc(&sc_opposite(sc)->dst, addr, sizeof(*addr))) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "connect: internal error")); } /* inform the stream that we want to be notified whenever the * connection completes. */ applet_need_more_data(appctx); applet_have_more_data(appctx); appctx_wakeup(appctx); if (!notification_new(&hlua->com, &csk_ctx->wake_on_write, hlua->task)) { xref_unlock(&socket->xref, peer); WILL_LJMP(luaL_error(L, "out of memory")); } xref_unlock(&socket->xref, peer); /* Return yield waiting for connection. */ MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_connect_yield, TICK_ETERNITY, 0)); return 0; } #ifdef USE_OPENSSL __LJMP static int hlua_socket_connect_ssl(struct lua_State *L) { struct hlua_socket *socket; struct xref *peer; MAY_LJMP(check_args(L, 3, "connect_ssl")); socket = MAY_LJMP(hlua_checksocket(L, 1)); /* check for connection break. If some data where read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) { lua_pushnil(L); return 1; } container_of(peer, struct hlua_csk_ctx, xref)->srv = socket_ssl; xref_unlock(&socket->xref, peer); return MAY_LJMP(hlua_socket_connect(L)); } #endif __LJMP static int hlua_socket_setoption(struct lua_State *L) { return 0; } __LJMP static int hlua_socket_settimeout(struct lua_State *L) { struct hlua_socket *socket; int tmout; double dtmout; struct xref *peer; struct hlua_csk_ctx *csk_ctx; struct appctx *appctx; struct stream *s; MAY_LJMP(check_args(L, 2, "settimeout")); socket = MAY_LJMP(hlua_checksocket(L, 1)); /* convert the timeout to millis */ dtmout = MAY_LJMP(luaL_checknumber(L, 2)) * 1000; /* Check for negative values */ if (dtmout < 0) WILL_LJMP(luaL_error(L, "settimeout: cannot set negatives values")); if (dtmout > INT_MAX) /* overflow check */ WILL_LJMP(luaL_error(L, "settimeout: cannot set values larger than %d ms", INT_MAX)); tmout = MS_TO_TICKS((int)dtmout); if (tmout == 0) tmout++; /* very small timeouts are adjusted to a minimum of 1ms */ /* Check if we run on the same thread than the xreator thread. * We cannot access to the socket if the thread is different. */ if (socket->tid != tid) WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); /* check for connection break. If some data were read, return it. */ peer = xref_get_peer_and_lock(&socket->xref); if (!peer) { hlua_pusherror(L, "socket: not yet initialised, you can't set timeouts."); WILL_LJMP(lua_error(L)); return 0; } csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); csk_ctx->timeout = tmout; appctx = csk_ctx->appctx; if (!appctx_sc(appctx)) goto end; s = appctx_strm(csk_ctx->appctx); s->sess->fe->timeout.connect = tmout; s->scf->ioto = tmout; s->scb->ioto = tmout; s->task->expire = (tick_is_expired(s->task->expire, now_ms) ? 0 : s->task->expire); s->task->expire = tick_first(s->task->expire, tick_add_ifset(now_ms, tmout)); task_queue(s->task); end: xref_unlock(&socket->xref, peer); lua_pushinteger(L, 1); return 1; } __LJMP static int hlua_socket_new(lua_State *L) { struct hlua_socket *socket; struct hlua_csk_ctx *ctx; struct appctx *appctx; struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return 0; /* Check stack size. */ if (!lua_checkstack(L, 3)) { hlua_pusherror(L, "socket: full stack"); goto out_fail_conf; } /* Create the object: obj[0] = userdata. */ lua_newtable(L); socket = MAY_LJMP(lua_newuserdata(L, sizeof(*socket))); lua_rawseti(L, -2, 0); memset(socket, 0, sizeof(*socket)); socket->tid = tid; /* Check if the various memory pools are initialized. */ if (!pool_head_stream || !pool_head_buffer) { hlua_pusherror(L, "socket: uninitialized pools."); goto out_fail_conf; } /* Pop a class stream metatable and affect it to the userdata. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_socket_ref); lua_setmetatable(L, -2); /* Create the applet context */ appctx = appctx_new_here(&update_applet, NULL); if (!appctx) { hlua_pusherror(L, "socket: out of memory"); goto out_fail_conf; } ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); ctx->connected = 0; ctx->die = 0; ctx->srv = NULL; ctx->timeout = 0; ctx->appctx = appctx; LIST_INIT(&ctx->wake_on_write); LIST_INIT(&ctx->wake_on_read); hlua->gc_count++; /* Initialise cross reference between stream and Lua socket object. */ xref_create(&socket->xref, &ctx->xref); return 1; out_fail_conf: WILL_LJMP(lua_error(L)); return 0; } /* * * * Class Channel * * */ /* Returns the struct hlua_channel join to the class channel in the * stack entry "ud" or throws an argument error. */ __LJMP static struct channel *hlua_checkchannel(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_channel_ref)); } /* Pushes the channel onto the top of the stack. If the stask does not have a * free slots, the function fails and returns 0; */ static int hlua_channel_new(lua_State *L, struct channel *channel) { /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; lua_newtable(L); lua_pushlightuserdata(L, channel); lua_rawseti(L, -2, 0); /* Pop a class sesison metatable and affect it to the userdata. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_channel_ref); lua_setmetatable(L, -2); return 1; } /* Helper function returning a filter attached to a channel at the position * in the stack, filling the current offset and length of the filter. If no * filter is attached, NULL is returned and and are not * initialized. */ static struct filter *hlua_channel_filter(lua_State *L, int ud, struct channel *chn, size_t *offset, size_t *len) { struct filter *filter = NULL; if (lua_getfield(L, ud, "__filter") == LUA_TLIGHTUSERDATA) { struct hlua_flt_ctx *flt_ctx; filter = lua_touserdata (L, -1); flt_ctx = filter->ctx; if (hlua_filter_from_payload(filter)) { *offset = flt_ctx->cur_off[CHN_IDX(chn)]; *len = flt_ctx->cur_len[CHN_IDX(chn)]; } } lua_pop(L, 1); return filter; } /* Copies bytes of data present in the channel's buffer, starting at the * offset , and put it in a LUA string variable. It is the caller * responsibility to ensure and are valid. It always return the * length of the built string. may be 0, in this case, an empty string is * created and 0 is returned. */ static inline int _hlua_channel_dup(struct channel *chn, lua_State *L, size_t offset, size_t len) { size_t block1, block2; luaL_Buffer b; block1 = len; if (block1 > b_contig_data(&chn->buf, b_peek_ofs(&chn->buf, offset))) block1 = b_contig_data(&chn->buf, b_peek_ofs(&chn->buf, offset)); block2 = len - block1; luaL_buffinit(L, &b); luaL_addlstring(&b, b_peek(&chn->buf, offset), block1); if (block2) luaL_addlstring(&b, b_orig(&chn->buf), block2); luaL_pushresult(&b); return len; } /* Inserts the string to the channel's buffer at the offset . This * function returns -1 if data cannot be copied. Otherwise, it returns the * number of bytes copied. */ static int _hlua_channel_insert(struct channel *chn, lua_State *L, struct ist str, size_t offset) { int ret = 0; /* Nothing to do, just return */ if (unlikely(istlen(str) == 0)) goto end; if (istlen(str) > c_room(chn)) { ret = -1; goto end; } ret = b_insert_blk(&chn->buf, offset, istptr(str), istlen(str)); end: return ret; } /* Removes bytes of data at the absolute position . */ static void _hlua_channel_delete(struct channel *chn, size_t offset, size_t len) { size_t end = offset + len; if (b_peek(&chn->buf, end) != b_tail(&chn->buf)) b_move(&chn->buf, b_peek_ofs(&chn->buf, end), b_data(&chn->buf) - end, -len); b_sub(&chn->buf, len); } /* Copies input data in the channel's buffer. It is possible to set a specific * offset (0 by default) and a length (all remaining input data starting for the * offset by default). If there is not enough input data and more data can be * received, this function yields. * * From an action, All input data are considered. For a filter, the offset and * the length of input data to consider are retrieved from the filter context. */ __LJMP static int hlua_channel_get_data_yield(lua_State *L, int status, lua_KContext ctx) { struct channel *chn; struct filter *filter; size_t input, output; int offset, len; chn = MAY_LJMP(hlua_checkchannel(L, 1)); output = co_data(chn); input = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &output, &input); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 1) { offset = MAY_LJMP(luaL_checkinteger(L, 2)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset < output || offset > input + output) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } len = output + input - offset; if (lua_gettop(L) == 3) { len = MAY_LJMP(luaL_checkinteger(L, 3)); if (!len) goto dup; if (len == -1) len = global.tune.bufsize; if (len < 0) { lua_pushfstring(L, "length out of range."); WILL_LJMP(lua_error(L)); } } /* Wait for more data if possible if no length was specified and there * is no data or not enough data was received. */ if (!len || offset + len > output + input) { if (!HLUA_CANT_YIELD(hlua_gethlua(L)) && !channel_input_closed(chn) && channel_may_recv(chn)) { /* Yield waiting for more data, as requested */ MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_get_data_yield, TICK_ETERNITY, 0)); } /* Return 'nil' if there is no data and the channel can't receive more data */ if (!len) { lua_pushnil(L); return -1; } /* Otherwise, return all data */ len = output + input - offset; } dup: _hlua_channel_dup(chn, L, offset, len); return 1; } /* Copies the first line (including the trailing LF) of input data in the * channel's buffer. It is possible to set a specific offset (0 by default) and * a length (all remaining input data starting for the offset by default). If * there is not enough input data and more data can be received, the function * yields. If a length is explicitly specified, no more data are * copied. Otherwise, if no LF is found and more data can be received, this * function yields. * * From an action, All input data are considered. For a filter, the offset and * the length of input data to consider are retrieved from the filter context. */ __LJMP static int hlua_channel_get_line_yield(lua_State *L, int status, lua_KContext ctx) { struct channel *chn; struct filter *filter; size_t l, input, output; int offset, len; chn = MAY_LJMP(hlua_checkchannel(L, 1)); output = co_data(chn); input = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &output, &input); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 1) { offset = MAY_LJMP(luaL_checkinteger(L, 2)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset < output || offset > input + output) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } len = output + input - offset; if (lua_gettop(L) == 3) { len = MAY_LJMP(luaL_checkinteger(L, 3)); if (!len) goto dup; if (len == -1) len = global.tune.bufsize; if (len < 0) { lua_pushfstring(L, "length out of range."); WILL_LJMP(lua_error(L)); } } for (l = 0; l < len; l++) { if (l + offset >= output + input) break; if (*(b_peek(&chn->buf, offset + l)) == '\n') { len = l+1; goto dup; } } /* Wait for more data if possible if no line is found and no length was * specified or not enough data was received. */ if (lua_gettop(L) != 3 || offset + len > output + input) { if (!HLUA_CANT_YIELD(hlua_gethlua(L)) && !channel_input_closed(chn) && channel_may_recv(chn)) { /* Yield waiting for more data */ MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_get_line_yield, TICK_ETERNITY, 0)); } /* Return 'nil' if there is no data and the channel can't receive more data */ if (!len) { lua_pushnil(L); return -1; } /* Otherwise, return all data */ len = output + input - offset; } dup: _hlua_channel_dup(chn, L, offset, len); return 1; } /* [ DEPRECATED ] * * Duplicate all input data foud in the channel's buffer. The data are not * removed from the buffer. This function relies on _hlua_channel_dup(). * * From an action, All input data are considered. For a filter, the offset and * the length of input data to consider are retrieved from the filter context. */ __LJMP static int hlua_channel_dup(lua_State *L) { struct channel *chn; struct filter *filter; size_t offset, len; MAY_LJMP(check_args(L, 1, "dup")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } offset = co_data(chn); len = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &offset, &len); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); if (!ci_data(chn) && channel_input_closed(chn)) { lua_pushnil(L); return 1; } _hlua_channel_dup(chn, L, offset, len); return 1; } /* [ DEPRECATED ] * * Get all input data foud in the channel's buffer. The data are removed from * the buffer after the copy. This function relies on _hlua_channel_dup() and * _hlua_channel_delete(). * * From an action, All input data are considered. For a filter, the offset and * the length of input data to consider are retrieved from the filter context. */ __LJMP static int hlua_channel_get(lua_State *L) { struct channel *chn; struct filter *filter; size_t offset, len; int ret; MAY_LJMP(check_args(L, 1, "get")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } offset = co_data(chn); len = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &offset, &len); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); if (!ci_data(chn) && channel_input_closed(chn)) { lua_pushnil(L); return 1; } ret = _hlua_channel_dup(chn, L, offset, len); _hlua_channel_delete(chn, offset, ret); return 1; } /* This functions consumes and returns one line. If the channel is closed, * and the last data does not contains a final '\n', the data are returned * without the final '\n'. When no more data are available, it returns nil * value. * * From an action, All input data are considered. For a filter, the offset and * the length of input data to consider are retrieved from the filter context. */ __LJMP static int hlua_channel_getline_yield(lua_State *L, int status, lua_KContext ctx) { struct channel *chn; struct filter *filter; size_t l, offset, len; int ret; chn = MAY_LJMP(hlua_checkchannel(L, 1)); offset = co_data(chn); len = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &offset, &len); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); if (!ci_data(chn) && channel_input_closed(chn)) { lua_pushnil(L); return 1; } for (l = 0; l < len; l++) { if (*(b_peek(&chn->buf, offset+l)) == '\n') { len = l+1; goto dup; } } if (!HLUA_CANT_YIELD(hlua_gethlua(L)) && !channel_input_closed(chn) && channel_may_recv(chn)) { /* Yield waiting for more data */ MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_getline_yield, TICK_ETERNITY, 0)); } dup: ret = _hlua_channel_dup(chn, L, offset, len); _hlua_channel_delete(chn, offset, ret); return 1; } /* [ DEPRECATED ] * * Check arguments for the function "hlua_channel_getline_yield". */ __LJMP static int hlua_channel_getline(lua_State *L) { struct channel *chn; MAY_LJMP(check_args(L, 1, "getline")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } return MAY_LJMP(hlua_channel_getline_yield(L, 0, 0)); } /* Retrieves a given amount of input data at the given offset. By default all * available input data are returned. The offset may be negactive to start from * the end of input data. The length may be -1 to set it to the maximum buffer * size. */ __LJMP static int hlua_channel_get_data(lua_State *L) { struct channel *chn; if (lua_gettop(L) < 1 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "'data' expects at most 2 arguments")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } return MAY_LJMP(hlua_channel_get_data_yield(L, 0, 0)); } /* Retrieves a given amount of input data at the given offset. By default all * available input data are returned. The offset may be negactive to start from * the end of input data. The length may be -1 to set it to the maximum buffer * size. */ __LJMP static int hlua_channel_get_line(lua_State *L) { struct channel *chn; if (lua_gettop(L) < 1 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "'line' expects at most 2 arguments")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } return MAY_LJMP(hlua_channel_get_line_yield(L, 0, 0)); } /* Appends a string into the input side of channel. It returns the length of the * written string, or -1 if the channel is closed or if the buffer size is too * little for the data. 0 may be returned if nothing is copied. This function * does not yield. * * For a filter, the context is updated on success. */ __LJMP static int hlua_channel_append(lua_State *L) { struct channel *chn; struct filter *filter; const char *str; size_t sz, offset, len; int ret; MAY_LJMP(check_args(L, 2, "append")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } offset = co_data(chn); len = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &offset, &len); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); if (ret > 0 && filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_update_offsets(filter, chn, ret); flt_ctx->cur_len[CHN_IDX(chn)] += ret; } lua_pushinteger(L, ret); return 1; } /* Prepends a string into the input side of channel. It returns the length of the * written string, or -1 if the channel is closed or if the buffer size is too * little for the data. 0 may be returned if nothing is copied. This function * does not yield. * * For a filter, the context is updated on success. */ __LJMP static int hlua_channel_prepend(lua_State *L) { struct channel *chn; struct filter *filter; const char *str; size_t sz, offset, len; int ret; MAY_LJMP(check_args(L, 2, "prepend")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } offset = co_data(chn); len = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &offset, &len); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); if (ret > 0 && filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_update_offsets(filter, chn, ret); flt_ctx->cur_len[CHN_IDX(chn)] += ret; } lua_pushinteger(L, ret); return 1; } /* Inserts a given amount of input data at the given offset by a string * content. By default the string is appended in front of input data. It * returns the length of the written string, or -1 if the channel is closed or * if the buffer size is too little for the data. * * For a filter, the context is updated on success. */ __LJMP static int hlua_channel_insert_data(lua_State *L) { struct channel *chn; struct filter *filter; const char *str; size_t sz, input, output; int ret, offset; if (lua_gettop(L) < 2 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "'insert' expects at least 1 argument and at most 2 arguments")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); output = co_data(chn); input = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &output, &input); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 2) { offset = MAY_LJMP(luaL_checkinteger(L, 3)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset > output + input) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); if (ret > 0 && filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_update_offsets(filter, chn, ret); flt_ctx->cur_len[CHN_IDX(chn)] += ret; } lua_pushinteger(L, ret); return 1; } /* Replaces a given amount of input data at the given offset by a string * content. By default all remaining data are removed (offset = 0 and len = * -1). It returns the length of the written string, or -1 if the channel is * closed or if the buffer size is too little for the data. * * For a filter, the context is updated on success. */ __LJMP static int hlua_channel_set_data(lua_State *L) { struct channel *chn; struct filter *filter; const char *str; size_t sz, input, output; int ret, offset, len; if (lua_gettop(L) < 2 || lua_gettop(L) > 4) WILL_LJMP(luaL_error(L, "'set' expects at least 1 argument and at most 3 arguments")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } output = co_data(chn); input = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &output, &input); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 2) { offset = MAY_LJMP(luaL_checkinteger(L, 3)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset < output || offset > input + output) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } len = output + input - offset; if (lua_gettop(L) == 4) { len = MAY_LJMP(luaL_checkinteger(L, 4)); if (!len) goto set; if (len == -1) len = output + input - offset; if (len < 0 || offset + len > output + input) { lua_pushfstring(L, "length out of range."); WILL_LJMP(lua_error(L)); } } set: /* Be sure we can copied the string once input data will be removed. */ if (sz > c_room(chn) + len) lua_pushinteger(L, -1); else { _hlua_channel_delete(chn, offset, len); ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); if (filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; len -= (ret > 0 ? ret : 0); flt_update_offsets(filter, chn, -len); flt_ctx->cur_len[CHN_IDX(chn)] -= len; } lua_pushinteger(L, ret); } return 1; } /* Removes a given amount of input data at the given offset. By default all * input data are removed (offset = 0 and len = -1). It returns the amount of * the removed data. * * For a filter, the context is updated on success. */ __LJMP static int hlua_channel_del_data(lua_State *L) { struct channel *chn; struct filter *filter; size_t input, output; int offset, len; if (lua_gettop(L) < 1 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "'remove' expects at most 2 arguments")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } output = co_data(chn); input = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &output, &input); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 1) { offset = MAY_LJMP(luaL_checkinteger(L, 2)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset < output || offset > input + output) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } len = output + input - offset; if (lua_gettop(L) == 3) { len = MAY_LJMP(luaL_checkinteger(L, 3)); if (!len) goto end; if (len == -1) len = output + input - offset; if (len < 0 || offset + len > output + input) { lua_pushfstring(L, "length out of range."); WILL_LJMP(lua_error(L)); } } _hlua_channel_delete(chn, offset, len); if (filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_update_offsets(filter, chn, -len); flt_ctx->cur_len[CHN_IDX(chn)] -= len; } end: lua_pushinteger(L, len); return 1; } /* Append data in the output side of the buffer. This data is immediately * sent. The function returns the amount of data written. If the buffer * cannot contain the data, the function yields. The function returns -1 * if the channel is closed. */ __LJMP static int hlua_channel_send_yield(lua_State *L, int status, lua_KContext ctx) { struct channel *chn; struct filter *filter; const char *str; size_t offset, len, sz; int l, ret; struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) { lua_pushnil(L); return 1; } chn = MAY_LJMP(hlua_checkchannel(L, 1)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); l = MAY_LJMP(luaL_checkinteger(L, 3)); offset = co_data(chn); len = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &offset, &len); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); if (unlikely(channel_output_closed(chn))) { lua_pushinteger(L, -1); return 1; } len = c_room(chn); if (len > sz -l) { if (filter) { lua_pushinteger(L, -1); return 1; } len = sz - l; } ret = _hlua_channel_insert(chn, L, ist2(str, len), offset); if (ret == -1) { lua_pop(L, 1); lua_pushinteger(L, -1); return 1; } if (ret) { if (filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_update_offsets(filter, chn, ret); FLT_OFF(filter, chn) += ret; flt_ctx->cur_off[CHN_IDX(chn)] += ret; } else c_adv(chn, ret); l += ret; lua_pop(L, 1); lua_pushinteger(L, l); } if (l < sz) { /* Yield only if the channel's output is not empty. * Otherwise it means we cannot add more data. */ if (co_data(chn) == 0 || HLUA_CANT_YIELD(hlua_gethlua(L))) return 1; /* If we are waiting for space in the response buffer, we * must set the flag WAKERESWR. This flag required the task * wake up if any activity is detected on the response buffer. */ if (chn->flags & CF_ISRESP) HLUA_SET_WAKERESWR(hlua); else HLUA_SET_WAKEREQWR(hlua); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_send_yield, TICK_ETERNITY, 0)); } return 1; } /* Just a wrapper of "_hlua_channel_send". This wrapper permits * yield the LUA process, and resume it without checking the * input arguments. * * This function cannot be called from a filter. */ __LJMP static int hlua_channel_send(lua_State *L) { struct channel *chn; MAY_LJMP(check_args(L, 2, "send")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } lua_pushinteger(L, 0); return MAY_LJMP(hlua_channel_send_yield(L, 0, 0)); } /* This function forward and amount of butes. The data pass from * the input side of the buffer to the output side, and can be * forwarded. This function never fails. * * The Lua function takes an amount of bytes to be forwarded in * input. It returns the number of bytes forwarded. */ __LJMP static int hlua_channel_forward_yield(lua_State *L, int status, lua_KContext ctx) { struct channel *chn; struct filter *filter; size_t offset, len, fwd; int l, max; struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) { lua_pushnil(L); return 1; } chn = MAY_LJMP(hlua_checkchannel(L, 1)); fwd = MAY_LJMP(luaL_checkinteger(L, 2)); l = MAY_LJMP(luaL_checkinteger(L, -1)); offset = co_data(chn); len = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &offset, &len); if (filter && !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); max = fwd - l; if (max > len) max = len; if (filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; FLT_OFF(filter, chn) += max; flt_ctx->cur_off[CHN_IDX(chn)] += max; flt_ctx->cur_len[CHN_IDX(chn)] -= max; } else channel_forward(chn, max); l += max; lua_pop(L, 1); lua_pushinteger(L, l); /* Check if it miss bytes to forward. */ if (l < fwd) { /* The the input channel or the output channel are closed, we * must return the amount of data forwarded. */ if (channel_input_closed(chn) || channel_output_closed(chn) || HLUA_CANT_YIELD(hlua_gethlua(L))) return 1; /* If we are waiting for space data in the response buffer, we * must set the flag WAKERESWR. This flag required the task * wake up if any activity is detected on the response buffer. */ if (chn->flags & CF_ISRESP) HLUA_SET_WAKERESWR(hlua); else HLUA_SET_WAKEREQWR(hlua); /* Otherwise, we can yield waiting for new data in the input side. */ MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_forward_yield, TICK_ETERNITY, 0)); } return 1; } /* Just check the input and prepare the stack for the previous * function "hlua_channel_forward_yield" * * This function cannot be called from a filter. */ __LJMP static int hlua_channel_forward(lua_State *L) { struct channel *chn; MAY_LJMP(check_args(L, 2, "forward")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); if (IS_HTX_STRM(chn_strm(chn))) { lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); WILL_LJMP(lua_error(L)); } lua_pushinteger(L, 0); return MAY_LJMP(hlua_channel_forward_yield(L, 0, 0)); } /* Just returns the number of bytes available in the input * side of the buffer. This function never fails. */ __LJMP static int hlua_channel_get_in_len(lua_State *L) { struct channel *chn; struct filter *filter; size_t output, input; MAY_LJMP(check_args(L, 1, "input")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); output = co_data(chn); input = ci_data(chn); filter = hlua_channel_filter(L, 1, chn, &output, &input); if (filter || !IS_HTX_STRM(chn_strm(chn))) lua_pushinteger(L, input); else { struct htx *htx = htxbuf(&chn->buf); lua_pushinteger(L, htx->data - co_data(chn)); } return 1; } /* Returns true if the channel is full. */ __LJMP static int hlua_channel_is_full(lua_State *L) { struct channel *chn; MAY_LJMP(check_args(L, 1, "is_full")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); /* ignore the reserve, we are not on a producer side (ie in an * applet). */ lua_pushboolean(L, channel_full(chn, 0)); return 1; } /* Returns true if the channel may still receive data. */ __LJMP static int hlua_channel_may_recv(lua_State *L) { struct channel *chn; MAY_LJMP(check_args(L, 1, "may_recv")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); lua_pushboolean(L, (!channel_input_closed(chn) && channel_may_recv(chn))); return 1; } /* Returns true if the channel is the response channel. */ __LJMP static int hlua_channel_is_resp(lua_State *L) { struct channel *chn; MAY_LJMP(check_args(L, 1, "is_resp")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); lua_pushboolean(L, !!(chn->flags & CF_ISRESP)); return 1; } /* Just returns the number of bytes available in the output * side of the buffer. This function never fails. */ __LJMP static int hlua_channel_get_out_len(lua_State *L) { struct channel *chn; size_t output, input; MAY_LJMP(check_args(L, 1, "output")); chn = MAY_LJMP(hlua_checkchannel(L, 1)); output = co_data(chn); input = ci_data(chn); hlua_channel_filter(L, 1, chn, &output, &input); lua_pushinteger(L, output); return 1; } /* * * * Class Fetches * * */ /* Returns a struct hlua_session if the stack entry "ud" is * a class stream, otherwise it throws an error. */ __LJMP static struct hlua_smp *hlua_checkfetches(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_fetches_ref)); } /* This function creates and push in the stack a fetch object according * with a current TXN. */ static int hlua_fetches_new(lua_State *L, struct hlua_txn *txn, unsigned int flags) { struct hlua_smp *hsmp; /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; /* Create the object: obj[0] = userdata. * Note that the base of the Fetches object is the * transaction object. */ lua_newtable(L); hsmp = lua_newuserdata(L, sizeof(*hsmp)); lua_rawseti(L, -2, 0); hsmp->s = txn->s; hsmp->p = txn->p; hsmp->dir = txn->dir; hsmp->flags = flags; /* Pop a class sesison metatable and affect it to the userdata. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_fetches_ref); lua_setmetatable(L, -2); return 1; } /* This function is an LUA binding. It is called with each sample-fetch. * It uses closure argument to store the associated sample-fetch. It * returns only one argument or throws an error. An error is thrown * only if an error is encountered during the argument parsing. If * the "sample-fetch" function fails, nil is returned. */ __LJMP static int hlua_run_sample_fetch(lua_State *L) { struct hlua_smp *hsmp; struct sample_fetch *f; struct arg args[ARGM_NBARGS + 1] = {{0}}; int i; struct sample smp; /* Get closure arguments. */ f = lua_touserdata(L, lua_upvalueindex(1)); /* Get traditional arguments. */ hsmp = MAY_LJMP(hlua_checkfetches(L, 1)); /* Check execution authorization. */ if (f->use & SMP_USE_HTTP_ANY && !(hsmp->flags & HLUA_F_MAY_USE_HTTP)) { lua_pushfstring(L, "the sample-fetch '%s' needs an HTTP parser which " "is not available in Lua services", f->kw); WILL_LJMP(lua_error(L)); } /* Get extra arguments. */ for (i = 0; i < lua_gettop(L) - 1; i++) { if (i >= ARGM_NBARGS) break; hlua_lua2arg(L, i + 2, &args[i]); } args[i].type = ARGT_STOP; args[i].data.str.area = NULL; /* Check arguments. */ MAY_LJMP(hlua_lua2arg_check(L, 2, args, f->arg_mask, hsmp->p)); /* Run the special args checker. */ if (f->val_args && !f->val_args(args, NULL)) { lua_pushfstring(L, "error in arguments"); goto error; } /* Initialise the sample. */ memset(&smp, 0, sizeof(smp)); /* Run the sample fetch process. */ smp_set_owner(&smp, hsmp->p, hsmp->s->sess, hsmp->s, hsmp->dir & SMP_OPT_DIR); if (!f->process(args, &smp, f->kw, f->private)) { if (hsmp->flags & HLUA_F_AS_STRING) lua_pushstring(L, ""); else lua_pushnil(L); goto end; } /* Convert the returned sample in lua value. */ if (hsmp->flags & HLUA_F_AS_STRING) MAY_LJMP(hlua_smp2lua_str(L, &smp)); else MAY_LJMP(hlua_smp2lua(L, &smp)); end: free_args(args); return 1; error: free_args(args); WILL_LJMP(lua_error(L)); return 0; /* Never reached */ } /* * * * Class Converters * * */ /* Returns a struct hlua_session if the stack entry "ud" is * a class stream, otherwise it throws an error. */ __LJMP static struct hlua_smp *hlua_checkconverters(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_converters_ref)); } /* This function creates and push in the stack a Converters object * according with a current TXN. */ static int hlua_converters_new(lua_State *L, struct hlua_txn *txn, unsigned int flags) { struct hlua_smp *hsmp; /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; /* Create the object: obj[0] = userdata. * Note that the base of the Converters object is the * same than the TXN object. */ lua_newtable(L); hsmp = lua_newuserdata(L, sizeof(*hsmp)); lua_rawseti(L, -2, 0); hsmp->s = txn->s; hsmp->p = txn->p; hsmp->dir = txn->dir; hsmp->flags = flags; /* Pop a class stream metatable and affect it to the table. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_converters_ref); lua_setmetatable(L, -2); return 1; } /* This function is an LUA binding. It is called with each converter. * It uses closure argument to store the associated converter. It * returns only one argument or throws an error. An error is thrown * only if an error is encountered during the argument parsing. If * the converter function function fails, nil is returned. */ __LJMP static int hlua_run_sample_conv(lua_State *L) { struct hlua_smp *hsmp; struct sample_conv *conv; struct arg args[ARGM_NBARGS + 1] = {{0}}; int i; struct sample smp; /* Get closure arguments. */ conv = lua_touserdata(L, lua_upvalueindex(1)); /* Get traditional arguments. */ hsmp = MAY_LJMP(hlua_checkconverters(L, 1)); /* Get extra arguments. */ for (i = 0; i < lua_gettop(L) - 2; i++) { if (i >= ARGM_NBARGS) break; hlua_lua2arg(L, i + 3, &args[i]); } args[i].type = ARGT_STOP; args[i].data.str.area = NULL; /* Check arguments. */ MAY_LJMP(hlua_lua2arg_check(L, 3, args, conv->arg_mask, hsmp->p)); /* Run the special args checker. */ if (conv->val_args && !conv->val_args(args, conv, "", 0, NULL)) { hlua_pusherror(L, "error in arguments"); goto error; } /* Initialise the sample. */ memset(&smp, 0, sizeof(smp)); if (!hlua_lua2smp(L, 2, &smp)) { hlua_pusherror(L, "error in the input argument"); goto error; } smp_set_owner(&smp, hsmp->p, hsmp->s->sess, hsmp->s, hsmp->dir & SMP_OPT_DIR); /* Apply expected cast. */ if (!sample_casts[smp.data.type][conv->in_type]) { hlua_pusherror(L, "invalid input argument: cannot cast '%s' to '%s'", smp_to_type[smp.data.type], smp_to_type[conv->in_type]); goto error; } if (sample_casts[smp.data.type][conv->in_type] != c_none && !sample_casts[smp.data.type][conv->in_type](&smp)) { hlua_pusherror(L, "error during the input argument casting"); goto error; } /* Run the sample conversion process. */ if (!conv->process(args, &smp, conv->private)) { if (hsmp->flags & HLUA_F_AS_STRING) lua_pushstring(L, ""); else lua_pushnil(L); goto end; } /* Convert the returned sample in lua value. */ if (hsmp->flags & HLUA_F_AS_STRING) MAY_LJMP(hlua_smp2lua_str(L, &smp)); else MAY_LJMP(hlua_smp2lua(L, &smp)); end: free_args(args); return 1; error: free_args(args); WILL_LJMP(lua_error(L)); return 0; /* Never reached */ } /* * * * Class AppletTCP * * */ /* Returns a struct hlua_txn if the stack entry "ud" is * a class stream, otherwise it throws an error. */ __LJMP static struct hlua_appctx *hlua_checkapplet_tcp(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_applet_tcp_ref)); } /* This function creates and push in the stack an Applet object * according with a current TXN. */ static int hlua_applet_tcp_new(lua_State *L, struct appctx *ctx) { struct hlua_appctx *luactx; struct stream *s = appctx_strm(ctx); struct proxy *p; ALREADY_CHECKED(s); p = s->be; /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; /* Create the object: obj[0] = userdata. * Note that the base of the Converters object is the * same than the TXN object. */ lua_newtable(L); luactx = lua_newuserdata(L, sizeof(*luactx)); lua_rawseti(L, -2, 0); luactx->appctx = ctx; luactx->htxn.s = s; luactx->htxn.p = p; /* Create the "f" field that contains a list of fetches. */ lua_pushstring(L, "f"); if (!hlua_fetches_new(L, &luactx->htxn, 0)) return 0; lua_settable(L, -3); /* Create the "sf" field that contains a list of stringsafe fetches. */ lua_pushstring(L, "sf"); if (!hlua_fetches_new(L, &luactx->htxn, HLUA_F_AS_STRING)) return 0; lua_settable(L, -3); /* Create the "c" field that contains a list of converters. */ lua_pushstring(L, "c"); if (!hlua_converters_new(L, &luactx->htxn, 0)) return 0; lua_settable(L, -3); /* Create the "sc" field that contains a list of stringsafe converters. */ lua_pushstring(L, "sc"); if (!hlua_converters_new(L, &luactx->htxn, HLUA_F_AS_STRING)) return 0; lua_settable(L, -3); /* Pop a class stream metatable and affect it to the table. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_applet_tcp_ref); lua_setmetatable(L, -2); return 1; } __LJMP static int hlua_applet_tcp_set_var(lua_State *L) { struct hlua_appctx *luactx; struct stream *s; const char *name; size_t len; struct sample smp; if (lua_gettop(L) < 3 || lua_gettop(L) > 4) WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); s = luactx->htxn.s; /* Converts the third argument in a sample. */ memset(&smp, 0, sizeof(smp)); hlua_lua2smp(L, 3, &smp); /* Store the sample in a variable. We don't need to dup the smp, vars API * already takes care of duplicating dynamic var data. */ smp_set_owner(&smp, s->be, s->sess, s, 0); if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); else lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); return 1; } __LJMP static int hlua_applet_tcp_unset_var(lua_State *L) { struct hlua_appctx *luactx; struct stream *s; const char *name; size_t len; struct sample smp; MAY_LJMP(check_args(L, 2, "unset_var")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); s = luactx->htxn.s; /* Unset the variable. */ smp_set_owner(&smp, s->be, s->sess, s, 0); lua_pushboolean(L, vars_unset_by_name_ifexist(name, len, &smp) != 0); return 1; } __LJMP static int hlua_applet_tcp_get_var(lua_State *L) { struct hlua_appctx *luactx; struct stream *s; const char *name; size_t len; struct sample smp; MAY_LJMP(check_args(L, 2, "get_var")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); s = luactx->htxn.s; smp_set_owner(&smp, s->be, s->sess, s, 0); if (!vars_get_by_name(name, len, &smp, NULL)) { lua_pushnil(L); return 1; } return MAY_LJMP(hlua_smp2lua(L, &smp)); } __LJMP static int hlua_applet_tcp_set_priv(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); struct hlua_cli_ctx *cli_ctx = luactx->appctx->svcctx; struct stream *s = luactx->htxn.s; struct hlua *hlua = hlua_stream_ctx_get(s, cli_ctx->hlua->state_id); /* Note that this hlua struct is from the session and not from the applet. */ if (!hlua) return 0; MAY_LJMP(check_args(L, 2, "set_priv")); /* Remove previous value. */ luaL_unref(L, LUA_REGISTRYINDEX, hlua->Mref); /* Get and store new value. */ lua_pushvalue(L, 2); /* Copy the element 2 at the top of the stack. */ hlua->Mref = luaL_ref(L, LUA_REGISTRYINDEX); /* pop the previously pushed value. */ return 0; } __LJMP static int hlua_applet_tcp_get_priv(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); struct hlua_cli_ctx *cli_ctx = luactx->appctx->svcctx; struct stream *s = luactx->htxn.s; struct hlua *hlua = hlua_stream_ctx_get(s, cli_ctx->hlua->state_id); /* Note that this hlua struct is from the session and not from the applet. */ if (!hlua) { lua_pushnil(L); return 1; } /* Push configuration index in the stack. */ lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref); return 1; } /* If expected data not yet available, it returns a yield. This function * consumes the data in the buffer. It returns a string containing the * data. This string can be empty. */ __LJMP static int hlua_applet_tcp_getline_yield(lua_State *L, int status, lua_KContext ctx) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); struct stconn *sc = appctx_sc(luactx->appctx); int ret; const char *blk1; size_t len1; const char *blk2; size_t len2; /* Read the maximum amount of data available. */ ret = co_getline_nc(sc_oc(sc), &blk1, &len1, &blk2, &len2); /* Data not yet available. return yield. */ if (ret == 0) { applet_need_more_data(luactx->appctx); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_getline_yield, TICK_ETERNITY, 0)); } /* End of data: commit the total strings and return. */ if (ret < 0) { luaL_pushresult(&luactx->b); return 1; } /* Ensure that the block 2 length is usable. */ if (ret == 1) len2 = 0; /* don't check the max length read and don't check. */ luaL_addlstring(&luactx->b, blk1, len1); luaL_addlstring(&luactx->b, blk2, len2); /* Consume input channel output buffer data. */ co_skip(sc_oc(sc), len1 + len2); luaL_pushresult(&luactx->b); return 1; } /* Check arguments for the function "hlua_channel_get_yield". */ __LJMP static int hlua_applet_tcp_getline(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); /* Initialise the string catenation. */ luaL_buffinit(L, &luactx->b); return MAY_LJMP(hlua_applet_tcp_getline_yield(L, 0, 0)); } /* If expected data not yet available, it returns a yield. This function * consumes the data in the buffer. It returns a string containing the * data. This string can be empty. */ __LJMP static int hlua_applet_tcp_recv_yield(lua_State *L, int status, lua_KContext ctx) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); struct stconn *sc = appctx_sc(luactx->appctx); size_t len = MAY_LJMP(luaL_checkinteger(L, 2)); int ret; const char *blk1; size_t len1; const char *blk2; size_t len2; /* Read the maximum amount of data available. */ ret = co_getblk_nc(sc_oc(sc), &blk1, &len1, &blk2, &len2); /* Data not yet available. return yield. */ if (ret == 0) { applet_need_more_data(luactx->appctx); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_recv_yield, TICK_ETERNITY, 0)); } /* End of data: commit the total strings and return. */ if (ret < 0) { luaL_pushresult(&luactx->b); return 1; } /* Ensure that the block 2 length is usable. */ if (ret == 1) len2 = 0; if (len == -1) { /* If len == -1, catenate all the data avalaile and * yield because we want to get all the data until * the end of data stream. */ luaL_addlstring(&luactx->b, blk1, len1); luaL_addlstring(&luactx->b, blk2, len2); co_skip(sc_oc(sc), len1 + len2); applet_need_more_data(luactx->appctx); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_recv_yield, TICK_ETERNITY, 0)); } else { /* Copy the first block caping to the length required. */ if (len1 > len) len1 = len; luaL_addlstring(&luactx->b, blk1, len1); len -= len1; /* Copy the second block. */ if (len2 > len) len2 = len; luaL_addlstring(&luactx->b, blk2, len2); len -= len2; /* Consume input channel output buffer data. */ co_skip(sc_oc(sc), len1 + len2); /* If there is no other data available, yield waiting for new data. */ if (len > 0) { lua_pushinteger(L, len); lua_replace(L, 2); applet_need_more_data(luactx->appctx); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_recv_yield, TICK_ETERNITY, 0)); } /* return the result. */ luaL_pushresult(&luactx->b); return 1; } /* we never execute this */ hlua_pusherror(L, "Lua: internal error"); WILL_LJMP(lua_error(L)); return 0; } /* Check arguments for the function "hlua_channel_get_yield". */ __LJMP static int hlua_applet_tcp_recv(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); int len = -1; if (lua_gettop(L) > 2) WILL_LJMP(luaL_error(L, "The 'recv' function requires between 1 and 2 arguments.")); if (lua_gettop(L) >= 2) { len = MAY_LJMP(luaL_checkinteger(L, 2)); lua_pop(L, 1); } /* Confirm or set the required length */ lua_pushinteger(L, len); /* Initialise the string catenation. */ luaL_buffinit(L, &luactx->b); return MAY_LJMP(hlua_applet_tcp_recv_yield(L, 0, 0)); } /* Append data in the output side of the buffer. This data is immediately * sent. The function returns the amount of data written. If the buffer * cannot contain the data, the function yields. The function returns -1 * if the channel is closed. */ __LJMP static int hlua_applet_tcp_send_yield(lua_State *L, int status, lua_KContext ctx) { size_t len; struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); const char *str = MAY_LJMP(luaL_checklstring(L, 2, &len)); int l = MAY_LJMP(luaL_checkinteger(L, 3)); struct stconn *sc = appctx_sc(luactx->appctx); struct channel *chn = sc_ic(sc); int max; /* Get the max amount of data which can write as input in the channel. */ max = channel_recv_max(chn); if (max > (len - l)) max = len - l; /* Copy data. */ ci_putblk(chn, str + l, max); /* update counters. */ l += max; lua_pop(L, 1); lua_pushinteger(L, l); /* If some data is not send, declares the situation to the * applet, and returns a yield. */ if (l < len) { sc_need_room(sc, channel_recv_max(chn) + 1); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_send_yield, TICK_ETERNITY, 0)); } return 1; } /* Just a wrapper of "hlua_applet_tcp_send_yield". This wrapper permits * yield the LUA process, and resume it without checking the * input arguments. */ __LJMP static int hlua_applet_tcp_send(lua_State *L) { MAY_LJMP(check_args(L, 2, "send")); lua_pushinteger(L, 0); return MAY_LJMP(hlua_applet_tcp_send_yield(L, 0, 0)); } /* * * * Class AppletHTTP * * */ /* Returns a struct hlua_txn if the stack entry "ud" is * a class stream, otherwise it throws an error. */ __LJMP static struct hlua_appctx *hlua_checkapplet_http(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_applet_http_ref)); } /* This function creates and push in the stack an Applet object * according with a current TXN. * It relies on the caller to have already reserved the room in ctx->svcctx * for the local storage of hlua_http_ctx. */ static int hlua_applet_http_new(lua_State *L, struct appctx *ctx) { struct hlua_http_ctx *http_ctx = ctx->svcctx; struct hlua_appctx *luactx; struct hlua_txn htxn; struct stream *s = appctx_strm(ctx); struct proxy *px = s->be; struct htx *htx; struct htx_blk *blk; struct htx_sl *sl; struct ist path; unsigned long long len = 0; int32_t pos; struct http_uri_parser parser; /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; /* Create the object: obj[0] = userdata. * Note that the base of the Converters object is the * same than the TXN object. */ lua_newtable(L); luactx = lua_newuserdata(L, sizeof(*luactx)); lua_rawseti(L, -2, 0); luactx->appctx = ctx; http_ctx->status = 200; /* Default status code returned. */ http_ctx->reason = NULL; /* Use default reason based on status */ luactx->htxn.s = s; luactx->htxn.p = px; /* Create the "f" field that contains a list of fetches. */ lua_pushstring(L, "f"); if (!hlua_fetches_new(L, &luactx->htxn, 0)) return 0; lua_settable(L, -3); /* Create the "sf" field that contains a list of stringsafe fetches. */ lua_pushstring(L, "sf"); if (!hlua_fetches_new(L, &luactx->htxn, HLUA_F_AS_STRING)) return 0; lua_settable(L, -3); /* Create the "c" field that contains a list of converters. */ lua_pushstring(L, "c"); if (!hlua_converters_new(L, &luactx->htxn, 0)) return 0; lua_settable(L, -3); /* Create the "sc" field that contains a list of stringsafe converters. */ lua_pushstring(L, "sc"); if (!hlua_converters_new(L, &luactx->htxn, HLUA_F_AS_STRING)) return 0; lua_settable(L, -3); htx = htxbuf(&s->req.buf); blk = htx_get_first_blk(htx); BUG_ON(!blk || htx_get_blk_type(blk) != HTX_BLK_REQ_SL); sl = htx_get_blk_ptr(htx, blk); /* Stores the request method. */ lua_pushstring(L, "method"); lua_pushlstring(L, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); lua_settable(L, -3); /* Stores the http version. */ lua_pushstring(L, "version"); lua_pushlstring(L, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); lua_settable(L, -3); /* creates an array of headers. hlua_http_get_headers() crates and push * the array on the top of the stack. */ lua_pushstring(L, "headers"); htxn.s = s; htxn.p = px; htxn.dir = SMP_OPT_DIR_REQ; if (!hlua_http_get_headers(L, &htxn.s->txn->req)) return 0; lua_settable(L, -3); parser = http_uri_parser_init(htx_sl_req_uri(sl)); path = http_parse_path(&parser); if (isttest(path)) { char *p, *q, *end; p = path.ptr; end = istend(path); q = p; while (q < end && *q != '?') q++; /* Stores the request path. */ lua_pushstring(L, "path"); lua_pushlstring(L, p, q - p); lua_settable(L, -3); /* Stores the query string. */ lua_pushstring(L, "qs"); if (*q == '?') q++; lua_pushlstring(L, q, end - q); lua_settable(L, -3); } for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) { struct htx_blk *blk = htx_get_blk(htx, pos); enum htx_blk_type type = htx_get_blk_type(blk); if (type == HTX_BLK_TLR || type == HTX_BLK_EOT) break; if (type == HTX_BLK_DATA) len += htx_get_blksz(blk); } if (htx->extra != HTX_UNKOWN_PAYLOAD_LENGTH) len += htx->extra; /* Stores the request path. */ lua_pushstring(L, "length"); lua_pushinteger(L, len); lua_settable(L, -3); /* Create an empty array of HTTP request headers. */ lua_pushstring(L, "response"); lua_newtable(L); lua_settable(L, -3); /* Pop a class stream metatable and affect it to the table. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_applet_http_ref); lua_setmetatable(L, -2); return 1; } __LJMP static int hlua_applet_http_set_var(lua_State *L) { struct hlua_appctx *luactx; struct stream *s; const char *name; size_t len; struct sample smp; if (lua_gettop(L) < 3 || lua_gettop(L) > 4) WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); s = luactx->htxn.s; /* Converts the third argument in a sample. */ memset(&smp, 0, sizeof(smp)); hlua_lua2smp(L, 3, &smp); /* Store the sample in a variable. We don't need to dup the smp, vars API * already takes care of duplicating dynamic var data. */ smp_set_owner(&smp, s->be, s->sess, s, 0); if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); else lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); return 1; } __LJMP static int hlua_applet_http_unset_var(lua_State *L) { struct hlua_appctx *luactx; struct stream *s; const char *name; size_t len; struct sample smp; MAY_LJMP(check_args(L, 2, "unset_var")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); s = luactx->htxn.s; /* Unset the variable. */ smp_set_owner(&smp, s->be, s->sess, s, 0); lua_pushboolean(L, vars_unset_by_name_ifexist(name, len, &smp) != 0); return 1; } __LJMP static int hlua_applet_http_get_var(lua_State *L) { struct hlua_appctx *luactx; struct stream *s; const char *name; size_t len; struct sample smp; MAY_LJMP(check_args(L, 2, "get_var")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); s = luactx->htxn.s; smp_set_owner(&smp, s->be, s->sess, s, 0); if (!vars_get_by_name(name, len, &smp, NULL)) { lua_pushnil(L); return 1; } return MAY_LJMP(hlua_smp2lua(L, &smp)); } __LJMP static int hlua_applet_http_set_priv(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; struct stream *s = luactx->htxn.s; struct hlua *hlua = hlua_stream_ctx_get(s, http_ctx->hlua->state_id); /* Note that this hlua struct is from the session and not from the applet. */ if (!hlua) return 0; MAY_LJMP(check_args(L, 2, "set_priv")); /* Remove previous value. */ luaL_unref(L, LUA_REGISTRYINDEX, hlua->Mref); /* Get and store new value. */ lua_pushvalue(L, 2); /* Copy the element 2 at the top of the stack. */ hlua->Mref = luaL_ref(L, LUA_REGISTRYINDEX); /* pop the previously pushed value. */ return 0; } __LJMP static int hlua_applet_http_get_priv(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; struct stream *s = luactx->htxn.s; struct hlua *hlua = hlua_stream_ctx_get(s, http_ctx->hlua->state_id); /* Note that this hlua struct is from the session and not from the applet. */ if (!hlua) { lua_pushnil(L); return 1; } /* Push configuration index in the stack. */ lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref); return 1; } /* If expected data not yet available, it returns a yield. This function * consumes the data in the buffer. It returns a string containing the * data. This string can be empty. */ __LJMP static int hlua_applet_http_getline_yield(lua_State *L, int status, lua_KContext ctx) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct stconn *sc = appctx_sc(luactx->appctx); struct channel *req = sc_oc(sc); struct htx *htx; struct htx_blk *blk; size_t count; int stop = 0; htx = htx_from_buf(&req->buf); count = co_data(req); blk = htx_get_first_blk(htx); while (count && !stop && blk) { enum htx_blk_type type = htx_get_blk_type(blk); uint32_t sz = htx_get_blksz(blk); struct ist v; uint32_t vlen; char *nl; vlen = sz; if (vlen > count) { if (type != HTX_BLK_DATA) break; vlen = count; } switch (type) { case HTX_BLK_UNUSED: break; case HTX_BLK_DATA: v = htx_get_blk_value(htx, blk); v.len = vlen; nl = istchr(v, '\n'); if (nl != NULL) { stop = 1; vlen = nl - v.ptr + 1; } luaL_addlstring(&luactx->b, v.ptr, vlen); break; case HTX_BLK_TLR: case HTX_BLK_EOT: stop = 1; break; default: break; } c_rew(req, vlen); count -= vlen; if (sz == vlen) blk = htx_remove_blk(htx, blk); else { htx_cut_data_blk(htx, blk, vlen); break; } } /* The message was fully consumed and no more data are expected * (EOM flag set). */ if (htx_is_empty(htx) && (sc_opposite(sc)->flags & SC_FL_EOI)) stop = 1; htx_to_buf(htx, &req->buf); if (!stop) { applet_need_more_data(luactx->appctx); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_getline_yield, TICK_ETERNITY, 0)); } /* return the result. */ luaL_pushresult(&luactx->b); return 1; } /* Check arguments for the function "hlua_channel_get_yield". */ __LJMP static int hlua_applet_http_getline(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); /* Initialise the string catenation. */ luaL_buffinit(L, &luactx->b); return MAY_LJMP(hlua_applet_http_getline_yield(L, 0, 0)); } /* If expected data not yet available, it returns a yield. This function * consumes the data in the buffer. It returns a string containing the * data. This string can be empty. */ __LJMP static int hlua_applet_http_recv_yield(lua_State *L, int status, lua_KContext ctx) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct stconn *sc = appctx_sc(luactx->appctx); struct channel *req = sc_oc(sc); struct htx *htx; struct htx_blk *blk; size_t count; int len; htx = htx_from_buf(&req->buf); len = MAY_LJMP(luaL_checkinteger(L, 2)); count = co_data(req); blk = htx_get_head_blk(htx); while (count && len && blk) { enum htx_blk_type type = htx_get_blk_type(blk); uint32_t sz = htx_get_blksz(blk); struct ist v; uint32_t vlen; vlen = sz; if (len > 0 && vlen > len) vlen = len; if (vlen > count) { if (type != HTX_BLK_DATA) break; vlen = count; } switch (type) { case HTX_BLK_UNUSED: break; case HTX_BLK_DATA: v = htx_get_blk_value(htx, blk); luaL_addlstring(&luactx->b, v.ptr, vlen); break; case HTX_BLK_TLR: case HTX_BLK_EOT: len = 0; break; default: break; } c_rew(req, vlen); count -= vlen; if (len > 0) len -= vlen; if (sz == vlen) blk = htx_remove_blk(htx, blk); else { htx_cut_data_blk(htx, blk, vlen); break; } } /* The message was fully consumed and no more data are expected * (EOM flag set). */ if (htx_is_empty(htx) && (sc_opposite(sc)->flags & SC_FL_EOI)) len = 0; htx_to_buf(htx, &req->buf); /* If we are no other data available, yield waiting for new data. */ if (len) { if (len > 0) { lua_pushinteger(L, len); lua_replace(L, 2); } applet_need_more_data(luactx->appctx); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_recv_yield, TICK_ETERNITY, 0)); } /* return the result. */ luaL_pushresult(&luactx->b); return 1; } /* Check arguments for the function "hlua_channel_get_yield". */ __LJMP static int hlua_applet_http_recv(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); int len = -1; /* Check arguments. */ if (lua_gettop(L) > 2) WILL_LJMP(luaL_error(L, "The 'recv' function requires between 1 and 2 arguments.")); if (lua_gettop(L) >= 2) { len = MAY_LJMP(luaL_checkinteger(L, 2)); lua_pop(L, 1); } lua_pushinteger(L, len); /* Initialise the string catenation. */ luaL_buffinit(L, &luactx->b); return MAY_LJMP(hlua_applet_http_recv_yield(L, 0, 0)); } /* Append data in the output side of the buffer. This data is immediately * sent. The function returns the amount of data written. If the buffer * cannot contain the data, the function yields. The function returns -1 * if the channel is closed. */ __LJMP static int hlua_applet_http_send_yield(lua_State *L, int status, lua_KContext ctx) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct stconn *sc = appctx_sc(luactx->appctx); struct channel *res = sc_ic(sc); struct htx *htx = htx_from_buf(&res->buf); const char *data; size_t len; int l = MAY_LJMP(luaL_checkinteger(L, 3)); int max; max = htx_get_max_blksz(htx, channel_htx_recv_max(res, htx)); if (!max) goto snd_yield; data = MAY_LJMP(luaL_checklstring(L, 2, &len)); /* Get the max amount of data which can write as input in the channel. */ if (max > (len - l)) max = len - l; /* Copy data. */ max = htx_add_data(htx, ist2(data + l, max)); channel_add_input(res, max); /* update counters. */ l += max; lua_pop(L, 1); lua_pushinteger(L, l); /* If some data is not send, declares the situation to the * applet, and returns a yield. */ if (l < len) { snd_yield: htx_to_buf(htx, &res->buf); sc_need_room(sc, channel_recv_max(res) + 1); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_send_yield, TICK_ETERNITY, 0)); } htx_to_buf(htx, &res->buf); return 1; } /* Just a wrapper of "hlua_applet_send_yield". This wrapper permits * yield the LUA process, and resume it without checking the * input arguments. */ __LJMP static int hlua_applet_http_send(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; /* We want to send some data. Headers must be sent. */ if (!(http_ctx->flags & APPLET_HDR_SENT)) { hlua_pusherror(L, "Lua: 'send' you must call start_response() before sending data."); WILL_LJMP(lua_error(L)); } /* This integer is used for followinf the amount of data sent. */ lua_pushinteger(L, 0); return MAY_LJMP(hlua_applet_http_send_yield(L, 0, 0)); } __LJMP static int hlua_applet_http_addheader(lua_State *L) { const char *name; int ret; MAY_LJMP(hlua_checkapplet_http(L, 1)); name = MAY_LJMP(luaL_checkstring(L, 2)); MAY_LJMP(luaL_checkstring(L, 3)); /* Push in the stack the "response" entry. */ ret = lua_getfield(L, 1, "response"); if (ret != LUA_TTABLE) { hlua_pusherror(L, "Lua: 'add_header' internal error: AppletHTTP['response'] " "is expected as an array. %s found", lua_typename(L, ret)); WILL_LJMP(lua_error(L)); } /* check if the header is already registered if it is not * the case, register it. */ ret = lua_getfield(L, -1, name); if (ret == LUA_TNIL) { /* Entry not found. */ lua_pop(L, 1); /* remove the nil. The "response" table is the top of the stack. */ /* Insert the new header name in the array in the top of the stack. * It left the new array in the top of the stack. */ lua_newtable(L); lua_pushvalue(L, 2); lua_pushvalue(L, -2); lua_settable(L, -4); } else if (ret != LUA_TTABLE) { /* corruption error. */ hlua_pusherror(L, "Lua: 'add_header' internal error: AppletHTTP['response']['%s'] " "is expected as an array. %s found", name, lua_typename(L, ret)); WILL_LJMP(lua_error(L)); } /* Now the top of thestack is an array of values. We push * the header value as new entry. */ lua_pushvalue(L, 3); ret = lua_rawlen(L, -2); lua_rawseti(L, -2, ret + 1); lua_pushboolean(L, 1); return 1; } __LJMP static int hlua_applet_http_status(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); int status = MAY_LJMP(luaL_checkinteger(L, 2)); const char *reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL)); struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; if (status < 100 || status > 599) { lua_pushboolean(L, 0); return 1; } http_ctx->status = status; http_ctx->reason = reason; lua_pushboolean(L, 1); return 1; } __LJMP static int hlua_applet_http_send_response(lua_State *L) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; struct stconn *sc = appctx_sc(luactx->appctx); struct channel *res = sc_ic(sc); struct htx *htx; struct htx_sl *sl; struct h1m h1m; const char *status, *reason; const char *name, *value; size_t nlen, vlen; unsigned int flags; /* Send the message at once. */ htx = htx_from_buf(&res->buf); h1m_init_res(&h1m); /* Use the same http version than the request. */ status = ultoa_r(http_ctx->status, trash.area, trash.size); reason = http_ctx->reason; if (reason == NULL) reason = http_get_reason(http_ctx->status); if (http_ctx->flags & APPLET_HTTP11) { flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11); sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist(status), ist(reason)); } else { flags = HTX_SL_F_IS_RESP; sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.0"), ist(status), ist(reason)); } if (!sl) { hlua_pusherror(L, "Lua applet http '%s': Failed to create response.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name); WILL_LJMP(lua_error(L)); } sl->info.res.status = http_ctx->status; /* Get the array associated to the field "response" in the object AppletHTTP. */ if (lua_getfield(L, 1, "response") != LUA_TTABLE) { hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response'] missing.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name); WILL_LJMP(lua_error(L)); } /* Browse the list of headers. */ lua_pushnil(L); while(lua_next(L, -2) != 0) { /* We expect a string as -2. */ if (lua_type(L, -2) != LUA_TSTRING) { hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response'][] element must be a string. got %s.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name, lua_typename(L, lua_type(L, -2))); WILL_LJMP(lua_error(L)); } name = lua_tolstring(L, -2, &nlen); /* We expect an array as -1. */ if (lua_type(L, -1) != LUA_TTABLE) { hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'] element must be an table. got %s.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name, name, lua_typename(L, lua_type(L, -1))); WILL_LJMP(lua_error(L)); } /* Browse the table who is on the top of the stack. */ lua_pushnil(L); while(lua_next(L, -2) != 0) { int id; /* We expect a number as -2. */ if (lua_type(L, -2) != LUA_TNUMBER) { hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'][] element must be a number. got %s.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name, name, lua_typename(L, lua_type(L, -2))); WILL_LJMP(lua_error(L)); } id = lua_tointeger(L, -2); /* We expect a string as -2. */ if (lua_type(L, -1) != LUA_TSTRING) { hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'][%d] element must be a string. got %s.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name, name, id, lua_typename(L, lua_type(L, -1))); WILL_LJMP(lua_error(L)); } value = lua_tolstring(L, -1, &vlen); /* Simple Protocol checks. */ if (isteqi(ist2(name, nlen), ist("transfer-encoding"))) { int ret; ret = h1_parse_xfer_enc_header(&h1m, ist2(value, vlen)); if (ret < 0) { hlua_pusherror(L, "Lua applet http '%s': Invalid '%s' header.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name, name); WILL_LJMP(lua_error(L)); } else if (ret == 0) goto next; /* Skip it */ } else if (isteqi(ist2(name, nlen), ist("content-length"))) { struct ist v = ist2(value, vlen); int ret; ret = h1_parse_cont_len_header(&h1m, &v); if (ret < 0) { hlua_pusherror(L, "Lua applet http '%s': Invalid '%s' header.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name, name); WILL_LJMP(lua_error(L)); } else if (ret == 0) goto next; /* Skip it */ } /* Add a new header */ if (!htx_add_header(htx, ist2(name, nlen), ist2(value, vlen))) { hlua_pusherror(L, "Lua applet http '%s': Failed to add header '%s' in the response.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name, name); WILL_LJMP(lua_error(L)); } next: /* Remove the array from the stack, and get next element with a remaining string. */ lua_pop(L, 1); } /* Remove the array from the stack, and get next element with a remaining string. */ lua_pop(L, 1); } if (h1m.flags & H1_MF_CHNK) h1m.flags &= ~H1_MF_CLEN; if (h1m.flags & (H1_MF_CLEN|H1_MF_CHNK)) h1m.flags |= H1_MF_XFER_LEN; /* Uset HTX start-line flags */ if (h1m.flags & H1_MF_XFER_ENC) flags |= HTX_SL_F_XFER_ENC; if (h1m.flags & H1_MF_XFER_LEN) { flags |= HTX_SL_F_XFER_LEN; if (h1m.flags & H1_MF_CHNK) flags |= HTX_SL_F_CHNK; else if (h1m.flags & H1_MF_CLEN) flags |= HTX_SL_F_CLEN; if (h1m.body_len == 0) flags |= HTX_SL_F_BODYLESS; } sl->flags |= flags; /* If we don't have a content-length set, and the HTTP version is 1.1 * and the status code implies the presence of a message body, we must * announce a transfer encoding chunked. This is required by haproxy * for the keepalive compliance. If the applet announces a transfer-encoding * chunked itself, don't do anything. */ if ((flags & (HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN)) == HTX_SL_F_VER_11 && http_ctx->status >= 200 && http_ctx->status != 204 && http_ctx->status != 304) { /* Add a new header */ sl->flags |= (HTX_SL_F_XFER_ENC|H1_MF_CHNK|H1_MF_XFER_LEN); if (!htx_add_header(htx, ist("transfer-encoding"), ist("chunked"))) { hlua_pusherror(L, "Lua applet http '%s': Failed to add header 'transfer-encoding' in the response.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name); WILL_LJMP(lua_error(L)); } } /* Finalize headers. */ if (!htx_add_endof(htx, HTX_BLK_EOH)) { hlua_pusherror(L, "Lua applet http '%s': Failed create the response.\n", luactx->appctx->rule->arg.hlua_rule->fcn->name); WILL_LJMP(lua_error(L)); } if (htx_used_space(htx) > b_size(&res->buf) - global.tune.maxrewrite) { b_reset(&res->buf); hlua_pusherror(L, "Lua: 'start_response': response header block too big"); WILL_LJMP(lua_error(L)); } htx_to_buf(htx, &res->buf); channel_add_input(res, htx->data); /* Headers sent, set the flag. */ http_ctx->flags |= APPLET_HDR_SENT; return 0; } /* We will build the status line and the headers of the HTTP response. * We will try send at once if its not possible, we give back the hand * waiting for more room. */ __LJMP static int hlua_applet_http_start_response_yield(lua_State *L, int status, lua_KContext ctx) { struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); struct stconn *sc = appctx_sc(luactx->appctx); struct channel *res = sc_ic(sc); if (co_data(res)) { sc_need_room(sc, -1); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_start_response_yield, TICK_ETERNITY, 0)); } return MAY_LJMP(hlua_applet_http_send_response(L)); } __LJMP static int hlua_applet_http_start_response(lua_State *L) { return MAY_LJMP(hlua_applet_http_start_response_yield(L, 0, 0)); } /* * * * Class HTTP * * */ /* Returns a struct hlua_txn if the stack entry "ud" is * a class stream, otherwise it throws an error. */ __LJMP static struct hlua_txn *hlua_checkhttp(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_http_ref)); } /* This function creates and push in the stack a HTTP object * according with a current TXN. */ static int hlua_http_new(lua_State *L, struct hlua_txn *txn) { struct hlua_txn *htxn; /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; /* Create the object: obj[0] = userdata. * Note that the base of the Converters object is the * same than the TXN object. */ lua_newtable(L); htxn = lua_newuserdata(L, sizeof(*htxn)); lua_rawseti(L, -2, 0); htxn->s = txn->s; htxn->p = txn->p; htxn->dir = txn->dir; htxn->flags = txn->flags; /* Pop a class stream metatable and affect it to the table. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_ref); lua_setmetatable(L, -2); return 1; } /* This function creates and returns an array containing the status-line * elements. This function does not fails. */ __LJMP static int hlua_http_get_stline(lua_State *L, struct htx_sl *sl) { /* Create the table. */ lua_newtable(L); if (sl->flags & HTX_SL_F_IS_RESP) { lua_pushstring(L, "version"); lua_pushlstring(L, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); lua_settable(L, -3); lua_pushstring(L, "code"); lua_pushlstring(L, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); lua_settable(L, -3); lua_pushstring(L, "reason"); lua_pushlstring(L, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); lua_settable(L, -3); } else { lua_pushstring(L, "method"); lua_pushlstring(L, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); lua_settable(L, -3); lua_pushstring(L, "uri"); lua_pushlstring(L, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); lua_settable(L, -3); lua_pushstring(L, "version"); lua_pushlstring(L, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); lua_settable(L, -3); } return 1; } /* This function creates ans returns an array of HTTP headers. * This function does not fails. It is used as wrapper with the * 2 following functions. */ __LJMP static int hlua_http_get_headers(lua_State *L, struct http_msg *msg) { struct htx *htx; int32_t pos; /* Create the table. */ lua_newtable(L); htx = htxbuf(&msg->chn->buf); for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) { struct htx_blk *blk = htx_get_blk(htx, pos); enum htx_blk_type type = htx_get_blk_type(blk); struct ist n, v; int len; if (type == HTX_BLK_HDR) { n = htx_get_blk_name(htx,blk); v = htx_get_blk_value(htx, blk); } else if (type == HTX_BLK_EOH) break; else continue; /* Check for existing entry: * assume that the table is on the top of the stack, and * push the key in the stack, the function lua_gettable() * perform the lookup. */ lua_pushlstring(L, n.ptr, n.len); lua_gettable(L, -2); switch (lua_type(L, -1)) { case LUA_TNIL: /* Table not found, create it. */ lua_pop(L, 1); /* remove the nil value. */ lua_pushlstring(L, n.ptr, n.len); /* push the header name as key. */ lua_newtable(L); /* create and push empty table. */ lua_pushlstring(L, v.ptr, v.len); /* push header value. */ lua_rawseti(L, -2, 0); /* index header value (pop it). */ lua_rawset(L, -3); /* index new table with header name (pop the values). */ break; case LUA_TTABLE: /* Entry found: push the value in the table. */ len = lua_rawlen(L, -1); lua_pushlstring(L, v.ptr, v.len); /* push header value. */ lua_rawseti(L, -2, len+1); /* index header value (pop it). */ lua_pop(L, 1); /* remove the table (it is stored in the main table). */ break; default: /* Other cases are errors. */ hlua_pusherror(L, "internal error during the parsing of headers."); WILL_LJMP(lua_error(L)); } } return 1; } __LJMP static int hlua_http_req_get_headers(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 1, "req_get_headers")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return hlua_http_get_headers(L, &htxn->s->txn->req); } __LJMP static int hlua_http_res_get_headers(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 1, "res_get_headers")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return hlua_http_get_headers(L, &htxn->s->txn->rsp); } /* This function replace full header, or just a value in * the request or in the response. It is a wrapper fir the * 4 following functions. */ __LJMP static inline int hlua_http_rep_hdr(lua_State *L, struct http_msg *msg, int full) { size_t name_len; const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); const char *reg = MAY_LJMP(luaL_checkstring(L, 3)); const char *value = MAY_LJMP(luaL_checkstring(L, 4)); struct htx *htx; struct my_regex *re; if (!(re = regex_comp(reg, 1, 1, NULL))) WILL_LJMP(luaL_argerror(L, 3, "invalid regex")); htx = htxbuf(&msg->chn->buf); http_replace_hdrs(chn_strm(msg->chn), htx, ist2(name, name_len), value, re, full); regex_free(re); return 0; } __LJMP static int hlua_http_req_rep_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 4, "req_rep_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->req, 1)); } __LJMP static int hlua_http_res_rep_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 4, "res_rep_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->rsp, 1)); } __LJMP static int hlua_http_req_rep_val(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 4, "req_rep_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->req, 0)); } __LJMP static int hlua_http_res_rep_val(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 4, "res_rep_val")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->rsp, 0)); } /* This function deletes all the occurrences of an header. * It is a wrapper for the 2 following functions. */ __LJMP static inline int hlua_http_del_hdr(lua_State *L, struct http_msg *msg) { size_t len; const char *name = MAY_LJMP(luaL_checklstring(L, 2, &len)); struct htx *htx = htxbuf(&msg->chn->buf); struct http_hdr_ctx ctx; ctx.blk = NULL; while (http_find_header(htx, ist2(name, len), &ctx, 1)) http_remove_header(htx, &ctx); return 0; } __LJMP static int hlua_http_req_del_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "req_del_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return hlua_http_del_hdr(L, &htxn->s->txn->req); } __LJMP static int hlua_http_res_del_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "res_del_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return hlua_http_del_hdr(L, &htxn->s->txn->rsp); } /* This function adds an header. It is a wrapper used by * the 2 following functions. */ __LJMP static inline int hlua_http_add_hdr(lua_State *L, struct http_msg *msg) { size_t name_len; const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); size_t value_len; const char *value = MAY_LJMP(luaL_checklstring(L, 3, &value_len)); struct htx *htx = htxbuf(&msg->chn->buf); lua_pushboolean(L, http_add_header(htx, ist2(name, name_len), ist2(value, value_len))); return 0; } __LJMP static int hlua_http_req_add_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 3, "req_add_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return hlua_http_add_hdr(L, &htxn->s->txn->req); } __LJMP static int hlua_http_res_add_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 3, "res_add_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); return hlua_http_add_hdr(L, &htxn->s->txn->rsp); } static int hlua_http_req_set_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 3, "req_set_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); hlua_http_del_hdr(L, &htxn->s->txn->req); return hlua_http_add_hdr(L, &htxn->s->txn->req); } static int hlua_http_res_set_hdr(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 3, "res_set_hdr")); htxn = MAY_LJMP(hlua_checkhttp(L, 1)); if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); hlua_http_del_hdr(L, &htxn->s->txn->rsp); return hlua_http_add_hdr(L, &htxn->s->txn->rsp); } /* This function set the method. */ static int hlua_http_req_set_meth(lua_State *L) { struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); size_t name_len; const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); lua_pushboolean(L, http_req_replace_stline(0, name, name_len, htxn->p, htxn->s) != -1); return 1; } /* This function set the method. */ static int hlua_http_req_set_path(lua_State *L) { struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); size_t name_len; const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); lua_pushboolean(L, http_req_replace_stline(1, name, name_len, htxn->p, htxn->s) != -1); return 1; } /* This function set the query-string. */ static int hlua_http_req_set_query(lua_State *L) { struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); size_t name_len; const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); /* Check length. */ if (name_len > trash.size - 1) { lua_pushboolean(L, 0); return 1; } /* Add the mark question as prefix. */ chunk_reset(&trash); trash.area[trash.data++] = '?'; memcpy(trash.area + trash.data, name, name_len); trash.data += name_len; lua_pushboolean(L, http_req_replace_stline(2, trash.area, trash.data, htxn->p, htxn->s) != -1); return 1; } /* This function set the uri. */ static int hlua_http_req_set_uri(lua_State *L) { struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); size_t name_len; const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); lua_pushboolean(L, http_req_replace_stline(3, name, name_len, htxn->p, htxn->s) != -1); return 1; } /* This function set the response code & optionally reason. */ static int hlua_http_res_set_status(lua_State *L) { struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); unsigned int code = MAY_LJMP(luaL_checkinteger(L, 2)); const char *str = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL)); const struct ist reason = ist2(str, (str ? strlen(str) : 0)); if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) WILL_LJMP(lua_error(L)); http_res_set_status(code, reason, htxn->s); return 0; } /* * * * Class HTTPMessage * * */ /* Returns a struct http_msg if the stack entry "ud" is a class HTTPMessage, * otherwise it throws an error. */ __LJMP static struct http_msg *hlua_checkhttpmsg(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_http_msg_ref)); } /* Creates and pushes on the stack a HTTP object according with a current TXN. */ static int hlua_http_msg_new(lua_State *L, struct http_msg *msg) { /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; lua_newtable(L); lua_pushlightuserdata(L, msg); lua_rawseti(L, -2, 0); /* Create the "channel" field that contains the request channel object. */ lua_pushstring(L, "channel"); if (!hlua_channel_new(L, msg->chn)) return 0; lua_rawset(L, -3); /* Pop a class stream metatable and affect it to the table. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_msg_ref); lua_setmetatable(L, -2); return 1; } /* Helper function returning a filter attached to the HTTP message at the * position in the stack, filling the current offset and length of the * filter. If no filter is attached, NULL is returned and and are * filled with output and input length respectively. */ static struct filter *hlua_http_msg_filter(lua_State *L, int ud, struct http_msg *msg, size_t *offset, size_t *len) { struct channel *chn = msg->chn; struct htx *htx = htxbuf(&chn->buf); struct filter *filter = NULL; *offset = co_data(msg->chn); *len = htx->data - co_data(msg->chn); if (lua_getfield(L, ud, "__filter") == LUA_TLIGHTUSERDATA) { filter = lua_touserdata (L, -1); if (msg->msg_state >= HTTP_MSG_DATA) { struct hlua_flt_ctx *flt_ctx = filter->ctx; *offset = flt_ctx->cur_off[CHN_IDX(chn)]; *len = flt_ctx->cur_len[CHN_IDX(chn)]; } } lua_pop(L, 1); return filter; } /* Returns true if the channel attached to the HTTP message is the response * channel. */ __LJMP static int hlua_http_msg_is_resp(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 1, "is_resp")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); lua_pushboolean(L, !!(msg->chn->flags & CF_ISRESP)); return 1; } /* Returns an array containing the elements status-line of the HTTP message. It relies * on hlua_http_get_stline(). */ __LJMP static int hlua_http_msg_get_stline(lua_State *L) { struct http_msg *msg; struct htx *htx; struct htx_sl *sl; MAY_LJMP(check_args(L, 1, "get_stline")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); htx = htxbuf(&msg->chn->buf); sl = http_get_stline(htx); if (!sl) return 0; return hlua_http_get_stline(L, sl); } /* Returns an array containing all headers of the HTTP message. it relies on * hlua_http_get_headers(). */ __LJMP static int hlua_http_msg_get_headers(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 1, "get_headers")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); return hlua_http_get_headers(L, msg); } /* Deletes all occurrences of an header in the HTTP message matching on its * name. It relies on hlua_http_del_hdr(). */ __LJMP static int hlua_http_msg_del_hdr(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 2, "del_header")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); return hlua_http_del_hdr(L, msg); } /* Matches the full value line of all occurrences of an header in the HTTP * message given its name against a regex and replaces it if it matches. It * relies on hlua_http_rep_hdr(). */ __LJMP static int hlua_http_msg_rep_hdr(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 4, "rep_header")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); return hlua_http_rep_hdr(L, msg, 1); } /* Matches all comma-separated values of all occurrences of an header in the HTTP * message given its name against a regex and replaces it if it matches. It * relies on hlua_http_rep_hdr(). */ __LJMP static int hlua_http_msg_rep_val(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 4, "rep_value")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); return hlua_http_rep_hdr(L, msg, 0); } /* Add an header in the HTTP message. It relies on hlua_http_add_hdr() */ __LJMP static int hlua_http_msg_add_hdr(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 3, "add_header")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); return hlua_http_add_hdr(L, msg); } /* Add an header in the HTTP message removing existing headers with the same * name. It relies on hlua_http_del_hdr() and hlua_http_add_hdr(). */ __LJMP static int hlua_http_msg_set_hdr(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 3, "set_header")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); hlua_http_del_hdr(L, msg); return hlua_http_add_hdr(L, msg); } /* Rewrites the request method. It relies on http_req_replace_stline(). */ __LJMP static int hlua_http_msg_set_meth(lua_State *L) { struct stream *s; struct http_msg *msg; const char *name; size_t name_len; MAY_LJMP(check_args(L, 2, "set_method")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); s = chn_strm(msg->chn); lua_pushboolean(L, http_req_replace_stline(0, name, name_len, s->be, s) != -1); return 1; } /* Rewrites the request path. It relies on http_req_replace_stline(). */ __LJMP static int hlua_http_msg_set_path(lua_State *L) { struct stream *s; struct http_msg *msg; const char *name; size_t name_len; MAY_LJMP(check_args(L, 2, "set_path")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); s = chn_strm(msg->chn); lua_pushboolean(L, http_req_replace_stline(1, name, name_len, s->be, s) != -1); return 1; } /* Rewrites the request query-string. It relies on http_req_replace_stline(). */ __LJMP static int hlua_http_msg_set_query(lua_State *L) { struct stream *s; struct http_msg *msg; const char *name; size_t name_len; MAY_LJMP(check_args(L, 2, "set_query")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); /* Check length. */ if (name_len > trash.size - 1) { lua_pushboolean(L, 0); return 1; } /* Add the mark question as prefix. */ chunk_reset(&trash); trash.area[trash.data++] = '?'; memcpy(trash.area + trash.data, name, name_len); trash.data += name_len; s = chn_strm(msg->chn); lua_pushboolean(L, http_req_replace_stline(2, trash.area, trash.data, s->be, s) != -1); return 1; } /* Rewrites the request URI. It relies on http_req_replace_stline(). */ __LJMP static int hlua_http_msg_set_uri(lua_State *L) { struct stream *s; struct http_msg *msg; const char *name; size_t name_len; MAY_LJMP(check_args(L, 2, "set_uri")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); s = chn_strm(msg->chn); lua_pushboolean(L, http_req_replace_stline(3, name, name_len, s->be, s) != -1); return 1; } /* Rewrites the response status code. It relies on http_res_set_status(). */ __LJMP static int hlua_http_msg_set_status(lua_State *L) { struct http_msg *msg; unsigned int code; const char *reason; size_t reason_len; msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); code = MAY_LJMP(luaL_checkinteger(L, 2)); reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, &reason_len)); if (!(msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) WILL_LJMP(lua_error(L)); lua_pushboolean(L, http_res_set_status(code, ist2(reason, reason_len), chn_strm(msg->chn)) != -1); return 1; } /* Returns true if the HTTP message is full. */ __LJMP static int hlua_http_msg_is_full(lua_State *L) { struct http_msg *msg; MAY_LJMP(check_args(L, 1, "is_full")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); lua_pushboolean(L, channel_full(msg->chn, 0)); return 1; } /* Returns true if the HTTP message may still receive data. */ __LJMP static int hlua_http_msg_may_recv(lua_State *L) { struct http_msg *msg; struct htx *htx; MAY_LJMP(check_args(L, 1, "may_recv")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); htx = htxbuf(&msg->chn->buf); lua_pushboolean(L, (htx_expect_more(htx) && !channel_input_closed(msg->chn) && channel_may_recv(msg->chn))); return 1; } /* Returns true if the HTTP message EOM was received */ __LJMP static int hlua_http_msg_is_eom(lua_State *L) { struct http_msg *msg; struct htx *htx; MAY_LJMP(check_args(L, 1, "may_recv")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); htx = htxbuf(&msg->chn->buf); lua_pushboolean(L, !htx_expect_more(htx)); return 1; } /* Returns the number of bytes available in the input side of the HTTP * message. This function never fails. */ __LJMP static int hlua_http_msg_get_in_len(lua_State *L) { struct http_msg *msg; size_t output, input; MAY_LJMP(check_args(L, 1, "input")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); hlua_http_msg_filter(L, 1, msg, &output, &input); lua_pushinteger(L, input); return 1; } /* Returns the number of bytes available in the output side of the HTTP * message. This function never fails. */ __LJMP static int hlua_http_msg_get_out_len(lua_State *L) { struct http_msg *msg; size_t output, input; MAY_LJMP(check_args(L, 1, "output")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); hlua_http_msg_filter(L, 1, msg, &output, &input); lua_pushinteger(L, output); return 1; } /* Copies at most bytes of DATA blocks from the HTTP message * starting at the offset and put it in a string LUA variables. It * returns the built string length. It stops on the first non-DATA HTX * block. This function is called during the payload filtering, so the headers * are already scheduled for output (from the filter point of view). */ static int _hlua_http_msg_dup(struct http_msg *msg, lua_State *L, size_t offset, size_t len) { struct htx *htx = htxbuf(&msg->chn->buf); struct htx_blk *blk; struct htx_ret htxret; luaL_Buffer b; int ret = 0; luaL_buffinit(L, &b); htxret = htx_find_offset(htx, offset); for (blk = htxret.blk, offset = htxret.ret; blk && len; blk = htx_get_next_blk(htx, blk)) { enum htx_blk_type type = htx_get_blk_type(blk); struct ist v; switch (type) { case HTX_BLK_UNUSED: break; case HTX_BLK_DATA: v = htx_get_blk_value(htx, blk); v = istadv(v, offset); v = isttrim(v, len); luaL_addlstring(&b, v.ptr, v.len); ret += v.len; break; default: if (!ret) goto no_data; goto end; } offset = 0; } end: if (!ret && (htx->flags & HTX_FL_EOM)) goto no_data; luaL_pushresult(&b); return ret; no_data: /* Remove the empty string and push nil on the stack */ lua_pop(L, 1); lua_pushnil(L); return 0; } /* Copies the string to the HTTP message at the offset * . This function returns -1 if data cannot be copied. Otherwise, it * returns the amount of data written. This function is responsible to update * the filter context. */ static int _hlua_http_msg_insert(struct http_msg *msg, struct filter *filter, struct ist str, size_t offset) { struct htx *htx = htx_from_buf(&msg->chn->buf); struct htx_ret htxret; int /*max, */ret = 0; /* Nothing to do, just return */ if (unlikely(istlen(str) == 0)) goto end; if (istlen(str) > htx_free_data_space(htx)) { ret = -1; goto end; } htxret = htx_find_offset(htx, offset); if (!htxret.blk || htx_get_blk_type(htxret.blk) != HTX_BLK_DATA) { if (!htx_add_last_data(htx, str)) goto end; } else { struct ist v = htx_get_blk_value(htx, htxret.blk); v.ptr += htxret.ret; v.len = 0; if (!htx_replace_blk_value(htx, htxret.blk, v, str)) goto end; } ret = str.len; if (ret) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_update_offsets(filter, msg->chn, ret); flt_ctx->cur_len[CHN_IDX(msg->chn)] += ret; } end: htx_to_buf(htx, &msg->chn->buf); return ret; } /* Helper function removing at most bytes of DATA blocks at the absolute * position . It stops on the first non-DATA HTX block. This function is * called during the payload filtering, so the headers are already scheduled for * output (from the filter point of view). This function is responsible to * update the filter context. */ static void _hlua_http_msg_delete(struct http_msg *msg, struct filter *filter, size_t offset, size_t len) { struct hlua_flt_ctx *flt_ctx = filter->ctx; struct htx *htx = htx_from_buf(&msg->chn->buf); struct htx_blk *blk; struct htx_ret htxret; size_t ret = 0; /* Be sure is always the amount of DATA to remove */ if (htx->data == offset+len && htx_get_tail_type(htx) == HTX_BLK_DATA) { /* When htx tail type == HTX_BLK_DATA, no need to take care * of special blocks like HTX_BLK_EOT. * We simply truncate after offset * (truncate targeted blk and discard the following ones) */ htx_truncate(htx, offset); ret = len; goto end; } htxret = htx_find_offset(htx, offset); blk = htxret.blk; if (htxret.ret) { /* dealing with offset: we need to trim targeted blk */ struct ist v; if (htx_get_blk_type(blk) != HTX_BLK_DATA) goto end; v = htx_get_blk_value(htx, blk); v = istadv(v, htxret.ret); v = isttrim(v, len); /* trimming data in blk: discard everything after the offset * (replace 'v' with 'IST_NULL') */ blk = htx_replace_blk_value(htx, blk, v, IST_NULL); if (blk && v.len < len) { /* In this case, caller wants to keep removing data, * but we need to spare current blk * because it was already trimmed */ blk = htx_get_next_blk(htx, blk); } len -= v.len; ret += v.len; } while (blk && len) { /* there is more data that needs to be discarded */ enum htx_blk_type type = htx_get_blk_type(blk); uint32_t sz = htx_get_blksz(blk); switch (type) { case HTX_BLK_UNUSED: break; case HTX_BLK_DATA: if (len < sz) { /* don't discard whole blk, only part of it * (from the beginning) */ htx_cut_data_blk(htx, blk, len); ret += len; goto end; } break; default: /* HTX_BLK_EOT blk won't be removed */ goto end; } /* Remove all the data block */ len -= sz; ret += sz; blk = htx_remove_blk(htx, blk); } end: flt_update_offsets(filter, msg->chn, -ret); flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; /* WARNING: we don't call htx_to_buf() on purpose, because we don't want * to loose the EOM flag if the message is empty. */ } /* Copies input data found in an HTTP message. Unlike the channel function used * to duplicate raw data, this one can only be called inside a filter, from * http_payload callback. So it cannot yield. An exception is returned if it is * called from another callback. If nothing was copied, a nil value is pushed on * the stack. */ __LJMP static int hlua_http_msg_get_body(lua_State *L) { struct http_msg *msg; struct filter *filter; size_t output, input; int offset, len; if (lua_gettop(L) < 1 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "'data' expects at most 2 arguments")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); filter = hlua_http_msg_filter(L, 1, msg, &output, &input); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); if (!ci_data(msg->chn) && channel_input_closed(msg->chn)) { lua_pushnil(L); return 1; } offset = output; if (lua_gettop(L) > 1) { offset = MAY_LJMP(luaL_checkinteger(L, 2)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset < output || offset > input + output) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } len = output + input - offset; if (lua_gettop(L) == 3) { len = MAY_LJMP(luaL_checkinteger(L, 3)); if (!len) goto dup; if (len == -1) len = global.tune.bufsize; if (len < 0) { lua_pushfstring(L, "length out of range."); WILL_LJMP(lua_error(L)); } } dup: _hlua_http_msg_dup(msg, L, offset, len); return 1; } /* Appends a string to the HTTP message, after all existing DATA blocks but * before the trailers, if any. It returns the amount of data written or -1 if * nothing was copied. Unlike the channel function used to append data, this one * can only be called inside a filter, from http_payload callback. So it cannot * yield. An exception is returned if it is called from another callback. */ __LJMP static int hlua_http_msg_append(lua_State *L) { struct http_msg *msg; struct filter *filter; const char *str; size_t offset, len, sz; int ret; MAY_LJMP(check_args(L, 2, "append")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset+len); lua_pushinteger(L, ret); return 1; } /* Prepends a string to the HTTP message, before all existing DATA blocks. It * returns the amount of data written or -1 if nothing was copied. Unlike the * channel function used to prepend data, this one can only be called inside a * filter, from http_payload callback. So it cannot yield. An exception is * returned if it is called from another callback. */ __LJMP static int hlua_http_msg_prepend(lua_State *L) { struct http_msg *msg; struct filter *filter; const char *str; size_t offset, len, sz; int ret; MAY_LJMP(check_args(L, 2, "prepend")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); lua_pushinteger(L, ret); return 1; } /* Inserts a string to the HTTP message at a given offset. By default the string * is appended at the end of DATA blocks. It returns the amount of data written * or -1 if nothing was copied. Unlike the channel function used to insert data, * this one can only be called inside a filter, from http_payload callback. So * it cannot yield. An exception is returned if it is called from another * callback. */ __LJMP static int hlua_http_msg_insert_data(lua_State *L) { struct http_msg *msg; struct filter *filter; const char *str; size_t input, output, sz; int offset; int ret; if (lua_gettop(L) < 2 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "'insert' expects at least 1 argument and at most 2 arguments")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); filter = hlua_http_msg_filter(L, 1, msg, &output, &input); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 2) { offset = MAY_LJMP(luaL_checkinteger(L, 3)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset > output + input) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); lua_pushinteger(L, ret); return 1; } /* Removes a given amount of data from the HTTP message at a given offset. By * default all DATA blocks are removed. It returns the amount of data * removed. Unlike the channel function used to remove data, this one can only * be called inside a filter, from http_payload callback. So it cannot yield. An * exception is returned if it is called from another callback. */ __LJMP static int hlua_http_msg_del_data(lua_State *L) { struct http_msg *msg; struct filter *filter; size_t input, output; int offset, len; if (lua_gettop(L) < 1 || lua_gettop(L) > 3) WILL_LJMP(luaL_error(L, "'remove' expects at most 2 arguments")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); filter = hlua_http_msg_filter(L, 1, msg, &output, &input); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 1) { offset = MAY_LJMP(luaL_checkinteger(L, 2)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset > output + input) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } len = output + input - offset; if (lua_gettop(L) == 3) { len = MAY_LJMP(luaL_checkinteger(L, 3)); if (!len) goto end; if (len == -1) len = output + input - offset; if (len < 0 || offset + len > output + input) { lua_pushfstring(L, "length out of range."); WILL_LJMP(lua_error(L)); } } _hlua_http_msg_delete(msg, filter, offset, len); end: lua_pushinteger(L, len); return 1; } /* Replaces a given amount of data at the given offset by a string. By default, * all remaining data are removed, accordingly to the filter context. It returns * the amount of data written or -1 if nothing was copied. Unlike the channel * function used to replace data, this one can only be called inside a filter, * from http_payload callback. So it cannot yield. An exception is returned if * it is called from another callback. */ __LJMP static int hlua_http_msg_set_data(lua_State *L) { struct http_msg *msg; struct filter *filter; struct htx *htx; const char *str; size_t input, output, sz; int offset, len; int ret; if (lua_gettop(L) < 2 || lua_gettop(L) > 4) WILL_LJMP(luaL_error(L, "'set' expects at least 1 argument and at most 3 arguments")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); filter = hlua_http_msg_filter(L, 1, msg, &output, &input); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); offset = output; if (lua_gettop(L) > 2) { offset = MAY_LJMP(luaL_checkinteger(L, 3)); if (offset < 0) offset = MAX(0, (int)input + offset); offset += output; if (offset < output || offset > input + output) { lua_pushfstring(L, "offset out of range."); WILL_LJMP(lua_error(L)); } } len = output + input - offset; if (lua_gettop(L) == 4) { len = MAY_LJMP(luaL_checkinteger(L, 4)); if (!len) goto set; if (len == -1) len = output + input - offset; if (len < 0 || offset + len > output + input) { lua_pushfstring(L, "length out of range."); WILL_LJMP(lua_error(L)); } } set: /* Be sure we can copied the string once input data will be removed. */ htx = htx_from_buf(&msg->chn->buf); if (sz > htx_free_data_space(htx) + len) lua_pushinteger(L, -1); else { _hlua_http_msg_delete(msg, filter, offset, len); ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); lua_pushinteger(L, ret); } return 1; } /* Prepends data into an HTTP message and forward it, from the filter point of * view. It returns the amount of data written or -1 if nothing was sent. Unlike * the channel function used to send data, this one can only be called inside a * filter, from http_payload callback. So it cannot yield. An exception is * returned if it is called from another callback. */ __LJMP static int hlua_http_msg_send(lua_State *L) { struct http_msg *msg; struct filter *filter; struct htx *htx; const char *str; size_t offset, len, sz; int ret; MAY_LJMP(check_args(L, 2, "send")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); /* Return an error if the channel's output is closed */ if (unlikely(channel_output_closed(msg->chn))) { lua_pushinteger(L, -1); return 1; } htx = htx_from_buf(&msg->chn->buf); if (sz > htx_free_data_space(htx)) { lua_pushinteger(L, -1); return 1; } ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); if (ret > 0) { struct hlua_flt_ctx *flt_ctx = filter->ctx; FLT_OFF(filter, msg->chn) += ret; flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret; } lua_pushinteger(L, ret); return 1; } /* Forwards a given amount of bytes. It return -1 if the channel's output is * closed. Otherwise, it returns the number of bytes forwarded. Unlike the * channel function used to forward data, this one can only be called inside a * filter, from http_payload callback. So it cannot yield. An exception is * returned if it is called from another callback. All other functions deal with * DATA block, this one not. */ __LJMP static int hlua_http_msg_forward(lua_State *L) { struct http_msg *msg; struct filter *filter; size_t offset, len; int fwd, ret = 0; MAY_LJMP(check_args(L, 2, "forward")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); if (msg->msg_state < HTTP_MSG_DATA) WILL_LJMP(lua_error(L)); fwd = MAY_LJMP(luaL_checkinteger(L, 2)); filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); if (!filter || !hlua_filter_from_payload(filter)) WILL_LJMP(lua_error(L)); /* Nothing to do, just return */ if (!fwd) goto end; /* Return an error if the channel's output is closed */ if (unlikely(channel_output_closed(msg->chn))) { ret = -1; goto end; } ret = fwd; if (ret > len) ret = len; if (ret) { struct hlua_flt_ctx *flt_ctx = filter->ctx; FLT_OFF(filter, msg->chn) += ret; flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret; flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; } end: lua_pushinteger(L, ret); return 1; } /* Set EOM flag on the HTX message. * * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't * really know how to do without this feature. */ __LJMP static int hlua_http_msg_set_eom(lua_State *L) { struct http_msg *msg; struct htx *htx; MAY_LJMP(check_args(L, 1, "set_eom")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); htx = htxbuf(&msg->chn->buf); htx->flags |= HTX_FL_EOM; return 0; } /* Unset EOM flag on the HTX message. * * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't * really know how to do without this feature. */ __LJMP static int hlua_http_msg_unset_eom(lua_State *L) { struct http_msg *msg; struct htx *htx; MAY_LJMP(check_args(L, 1, "set_eom")); msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); htx = htxbuf(&msg->chn->buf); htx->flags &= ~HTX_FL_EOM; return 0; } /* * * * Class HTTPClient * * */ __LJMP static struct hlua_httpclient *hlua_checkhttpclient(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_httpclient_ref)); } /* stops the httpclient and ask it to kill itself */ __LJMP static int hlua_httpclient_gc(lua_State *L) { struct hlua_httpclient *hlua_hc; MAY_LJMP(check_args(L, 1, "__gc")); hlua_hc = MAY_LJMP(hlua_checkhttpclient(L, 1)); if (MT_LIST_DELETE(&hlua_hc->by_hlua)) { /* we won the race against hlua_httpclient_destroy_all() */ httpclient_stop_and_destroy(hlua_hc->hc); hlua_hc->hc = NULL; } return 0; } __LJMP static int hlua_httpclient_new(lua_State *L) { struct hlua_httpclient *hlua_hc; struct hlua *hlua; /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return 0; /* Check stack size. */ if (!lua_checkstack(L, 3)) { hlua_pusherror(L, "httpclient: full stack"); goto err; } /* Create the object: obj[0] = userdata. */ lua_newtable(L); hlua_hc = MAY_LJMP(lua_newuserdata(L, sizeof(*hlua_hc))); lua_rawseti(L, -2, 0); memset(hlua_hc, 0, sizeof(*hlua_hc)); hlua_hc->hc = httpclient_new(hlua, 0, IST_NULL); if (!hlua_hc->hc) goto err; MT_LIST_APPEND(&hlua->hc_list, &hlua_hc->by_hlua); /* Pop a class stream metatable and affect it to the userdata. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_httpclient_ref); lua_setmetatable(L, -2); return 1; err: WILL_LJMP(lua_error(L)); return 0; } /* * Callback of the httpclient, this callback wakes the lua task up, once the * httpclient receives some data * */ static void hlua_httpclient_cb(struct httpclient *hc) { struct hlua *hlua = hc->caller; if (!hlua || !hlua->task) return; task_wakeup(hlua->task, TASK_WOKEN_MSG); } /* * Fill the lua stack with headers from the httpclient response * This works the same way as the hlua_http_get_headers() function */ __LJMP static int hlua_httpclient_get_headers(lua_State *L, struct hlua_httpclient *hlua_hc) { struct http_hdr *hdr; lua_newtable(L); for (hdr = hlua_hc->hc->res.hdrs; hdr && isttest(hdr->n); hdr++) { struct ist n, v; int len; n = hdr->n; v = hdr->v; /* Check for existing entry: * assume that the table is on the top of the stack, and * push the key in the stack, the function lua_gettable() * perform the lookup. */ lua_pushlstring(L, n.ptr, n.len); lua_gettable(L, -2); switch (lua_type(L, -1)) { case LUA_TNIL: /* Table not found, create it. */ lua_pop(L, 1); /* remove the nil value. */ lua_pushlstring(L, n.ptr, n.len); /* push the header name as key. */ lua_newtable(L); /* create and push empty table. */ lua_pushlstring(L, v.ptr, v.len); /* push header value. */ lua_rawseti(L, -2, 0); /* index header value (pop it). */ lua_rawset(L, -3); /* index new table with header name (pop the values). */ break; case LUA_TTABLE: /* Entry found: push the value in the table. */ len = lua_rawlen(L, -1); lua_pushlstring(L, v.ptr, v.len); /* push header value. */ lua_rawseti(L, -2, len+1); /* index header value (pop it). */ lua_pop(L, 1); /* remove the table (it is stored in the main table). */ break; default: /* Other cases are errors. */ hlua_pusherror(L, "internal error during the parsing of headers."); WILL_LJMP(lua_error(L)); } } return 1; } /* * Allocate and return an array of http_hdr ist extracted from the lua table * * Caller must free the result */ struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L) { struct http_hdr hdrs[global.tune.max_http_hdr]; struct http_hdr *result = NULL; uint32_t hdr_num = 0; lua_pushnil(L); while (lua_next(L, -2) != 0) { struct ist name, value; const char *n, *v; size_t nlen, vlen; if (!lua_isstring(L, -2) || !lua_istable(L, -1)) { /* Skip element if the key is not a string or if the value is not a table */ goto next_hdr; } n = lua_tolstring(L, -2, &nlen); name = ist2(n, nlen); /* Loop on header's values */ lua_pushnil(L); while (lua_next(L, -2)) { if (!lua_isstring(L, -1)) { /* Skip the value if it is not a string */ goto next_value; } v = lua_tolstring(L, -1, &vlen); value = ist2(v, vlen); name = ist2(n, nlen); hdrs[hdr_num].n = istdup(name); hdrs[hdr_num].v = istdup(value); hdr_num++; next_value: lua_pop(L, 1); } next_hdr: lua_pop(L, 1); } if (hdr_num) { /* alloc and copy the headers in the httpclient struct */ result = calloc((hdr_num + 1), sizeof(*result)); if (!result) goto skip_headers; memcpy(result, hdrs, sizeof(struct http_hdr) * (hdr_num + 1)); result[hdr_num].n = IST_NULL; result[hdr_num].v = IST_NULL; } skip_headers: return result; } /* * For each yield, checks if there is some data in the httpclient and push them * in the lua buffer, once the httpclient finished its job, push the result on * the stack */ __LJMP static int hlua_httpclient_rcv_yield(lua_State *L, int status, lua_KContext ctx) { struct buffer *tr; int res; struct hlua *hlua = hlua_gethlua(L); struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1); tr = get_trash_chunk(); res = httpclient_res_xfer(hlua_hc->hc, tr); luaL_addlstring(&hlua_hc->b, b_orig(tr), res); if (!httpclient_data(hlua_hc->hc) && httpclient_ended(hlua_hc->hc)) { luaL_pushresult(&hlua_hc->b); lua_settable(L, -3); lua_pushstring(L, "status"); lua_pushinteger(L, hlua_hc->hc->res.status); lua_settable(L, -3); lua_pushstring(L, "reason"); lua_pushlstring(L, hlua_hc->hc->res.reason.ptr, hlua_hc->hc->res.reason.len); lua_settable(L, -3); lua_pushstring(L, "headers"); hlua_httpclient_get_headers(L, hlua_hc); lua_settable(L, -3); return 1; } if (httpclient_data(hlua_hc->hc)) task_wakeup(hlua->task, TASK_WOKEN_MSG); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0)); return 0; } /* * Call this when trying to stream a body during a request */ __LJMP static int hlua_httpclient_snd_yield(lua_State *L, int status, lua_KContext ctx) { struct hlua *hlua; struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1); const char *body_str = NULL; int ret; int end = 0; size_t buf_len; size_t to_send = 0; hlua = hlua_gethlua(L); if (!hlua || !hlua->task) WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in " "'frontend', 'backend' or 'task'")); ret = lua_getfield(L, -1, "body"); if (ret != LUA_TSTRING) goto rcv; body_str = lua_tolstring(L, -1, &buf_len); lua_pop(L, 1); to_send = buf_len - hlua_hc->sent; if ((hlua_hc->sent + to_send) >= buf_len) end = 1; /* the end flag is always set since we are using the whole remaining size */ hlua_hc->sent += httpclient_req_xfer(hlua_hc->hc, ist2(body_str + hlua_hc->sent, to_send), end); if (buf_len > hlua_hc->sent) { /* still need to process the buffer */ MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0)); } else { goto rcv; /* we sent the whole request buffer we can recv */ } return 0; rcv: /* we return a "res" object */ lua_newtable(L); lua_pushstring(L, "body"); luaL_buffinit(L, &hlua_hc->b); task_wakeup(hlua->task, TASK_WOKEN_MSG); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0)); return 1; } /* * Send an HTTP request and wait for a response */ __LJMP static int hlua_httpclient_send(lua_State *L, enum http_meth_t meth) { struct hlua_httpclient *hlua_hc; struct http_hdr *hdrs = NULL; struct http_hdr *hdrs_i = NULL; struct hlua *hlua; const char *url_str = NULL; const char *body_str = NULL; size_t buf_len = 0; int ret; hlua = hlua_gethlua(L); if (!hlua || !hlua->task) WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in " "'frontend', 'backend' or 'task'")); if (lua_gettop(L) != 2 || lua_type(L, -1) != LUA_TTABLE) WILL_LJMP(luaL_error(L, "'get' needs a table as argument")); hlua_hc = hlua_checkhttpclient(L, 1); lua_pushnil(L); /* first key */ while (lua_next(L, 2)) { if (strcmp(lua_tostring(L, -2), "dst") == 0) { if (httpclient_set_dst(hlua_hc->hc, lua_tostring(L, -1)) < 0) WILL_LJMP(luaL_error(L, "Can't use the 'dst' argument")); } else if (strcmp(lua_tostring(L, -2), "url") == 0) { if (lua_type(L, -1) != LUA_TSTRING) WILL_LJMP(luaL_error(L, "invalid parameter in 'url', must be a string")); url_str = lua_tostring(L, -1); } else if (strcmp(lua_tostring(L, -2), "timeout") == 0) { if (lua_type(L, -1) != LUA_TNUMBER) WILL_LJMP(luaL_error(L, "invalid parameter in 'timeout', must be a number")); httpclient_set_timeout(hlua_hc->hc, lua_tointeger(L, -1)); } else if (strcmp(lua_tostring(L, -2), "headers") == 0) { if (lua_type(L, -1) != LUA_TTABLE) WILL_LJMP(luaL_error(L, "invalid parameter in 'headers', must be a table")); hdrs = hlua_httpclient_table_to_hdrs(L); } else if (strcmp(lua_tostring(L, -2), "body") == 0) { if (lua_type(L, -1) != LUA_TSTRING) WILL_LJMP(luaL_error(L, "invalid parameter in 'body', must be a string")); body_str = lua_tolstring(L, -1, &buf_len); } else { WILL_LJMP(luaL_error(L, "'%s' invalid parameter name", lua_tostring(L, -2))); } /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } if (!url_str) { WILL_LJMP(luaL_error(L, "'get' need a 'url' argument")); return 0; } hlua_hc->sent = 0; istfree(&hlua_hc->hc->req.url); hlua_hc->hc->req.url = istdup(ist(url_str)); hlua_hc->hc->req.meth = meth; /* update the httpclient callbacks */ hlua_hc->hc->ops.res_stline = hlua_httpclient_cb; hlua_hc->hc->ops.res_headers = hlua_httpclient_cb; hlua_hc->hc->ops.res_payload = hlua_httpclient_cb; hlua_hc->hc->ops.res_end = hlua_httpclient_cb; /* a body is available, it will use the request callback */ if (body_str && buf_len) { hlua_hc->hc->ops.req_payload = hlua_httpclient_cb; } ret = httpclient_req_gen(hlua_hc->hc, hlua_hc->hc->req.url, meth, hdrs, IST_NULL); /* free the temporary headers array */ hdrs_i = hdrs; while (hdrs_i && isttest(hdrs_i->n)) { istfree(&hdrs_i->n); istfree(&hdrs_i->v); hdrs_i++; } ha_free(&hdrs); if (ret != ERR_NONE) { WILL_LJMP(luaL_error(L, "Can't generate the HTTP request")); return 0; } if (!httpclient_start(hlua_hc->hc)) WILL_LJMP(luaL_error(L, "couldn't start the httpclient")); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0)); return 0; } /* * Sends an HTTP HEAD request and wait for a response * * httpclient:head(url, headers, payload) */ __LJMP static int hlua_httpclient_head(lua_State *L) { return hlua_httpclient_send(L, HTTP_METH_HEAD); } /* * Send an HTTP GET request and wait for a response * * httpclient:get(url, headers, payload) */ __LJMP static int hlua_httpclient_get(lua_State *L) { return hlua_httpclient_send(L, HTTP_METH_GET); } /* * Sends an HTTP PUT request and wait for a response * * httpclient:put(url, headers, payload) */ __LJMP static int hlua_httpclient_put(lua_State *L) { return hlua_httpclient_send(L, HTTP_METH_PUT); } /* * Send an HTTP POST request and wait for a response * * httpclient:post(url, headers, payload) */ __LJMP static int hlua_httpclient_post(lua_State *L) { return hlua_httpclient_send(L, HTTP_METH_POST); } /* * Sends an HTTP DELETE request and wait for a response * * httpclient:delete(url, headers, payload) */ __LJMP static int hlua_httpclient_delete(lua_State *L) { return hlua_httpclient_send(L, HTTP_METH_DELETE); } /* * * * Class TXN * * */ /* Returns a struct hlua_session if the stack entry "ud" is * a class stream, otherwise it throws an error. */ __LJMP static struct hlua_txn *hlua_checktxn(lua_State *L, int ud) { return MAY_LJMP(hlua_checkudata(L, ud, class_txn_ref)); } __LJMP static int hlua_set_var(lua_State *L) { struct hlua_txn *htxn; const char *name; size_t len; struct sample smp; if (lua_gettop(L) < 3 || lua_gettop(L) > 4) WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ htxn = MAY_LJMP(hlua_checktxn(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); /* Converts the third argument in a sample. */ memset(&smp, 0, sizeof(smp)); hlua_lua2smp(L, 3, &smp); /* Store the sample in a variable. We don't need to dup the smp, vars API * already takes care of duplicating dynamic var data. */ smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); else lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); return 1; } __LJMP static int hlua_unset_var(lua_State *L) { struct hlua_txn *htxn; const char *name; size_t len; struct sample smp; MAY_LJMP(check_args(L, 2, "unset_var")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ htxn = MAY_LJMP(hlua_checktxn(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); /* Unset the variable. */ smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); lua_pushboolean(L, vars_unset_by_name_ifexist(name, len, &smp) != 0); return 1; } __LJMP static int hlua_get_var(lua_State *L) { struct hlua_txn *htxn; const char *name; size_t len; struct sample smp; MAY_LJMP(check_args(L, 2, "get_var")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ htxn = MAY_LJMP(hlua_checktxn(L, 1)); name = MAY_LJMP(luaL_checklstring(L, 2, &len)); smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); if (!vars_get_by_name(name, len, &smp, NULL)) { lua_pushnil(L); return 1; } return MAY_LJMP(hlua_smp2lua(L, &smp)); } __LJMP static int hlua_set_priv(lua_State *L) { struct hlua *hlua; MAY_LJMP(check_args(L, 2, "set_priv")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ MAY_LJMP(hlua_checktxn(L, 1)); /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) return 0; /* Remove previous value. */ luaL_unref(L, LUA_REGISTRYINDEX, hlua->Mref); /* Get and store new value. */ lua_pushvalue(L, 2); /* Copy the element 2 at the top of the stack. */ hlua->Mref = luaL_ref(L, LUA_REGISTRYINDEX); /* pop the previously pushed value. */ return 0; } __LJMP static int hlua_get_priv(lua_State *L) { struct hlua *hlua; MAY_LJMP(check_args(L, 1, "get_priv")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. */ MAY_LJMP(hlua_checktxn(L, 1)); /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) { lua_pushnil(L); return 1; } /* Push configuration index in the stack. */ lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref); return 1; } /* Create stack entry containing a class TXN. This function * return 0 if the stack does not contains free slots, * otherwise it returns 1. */ static int hlua_txn_new(lua_State *L, struct stream *s, struct proxy *p, int dir, int flags) { struct hlua_txn *htxn; /* Check stack size. */ if (!lua_checkstack(L, 3)) return 0; /* NOTE: The allocation never fails. The failure * throw an error, and the function never returns. * if the throw is not available, the process is aborted. */ /* Create the object: obj[0] = userdata. */ lua_newtable(L); htxn = lua_newuserdata(L, sizeof(*htxn)); lua_rawseti(L, -2, 0); htxn->s = s; htxn->p = p; htxn->dir = dir; htxn->flags = flags; /* Create the "f" field that contains a list of fetches. */ lua_pushstring(L, "f"); if (!hlua_fetches_new(L, htxn, HLUA_F_MAY_USE_HTTP)) return 0; lua_rawset(L, -3); /* Create the "sf" field that contains a list of stringsafe fetches. */ lua_pushstring(L, "sf"); if (!hlua_fetches_new(L, htxn, HLUA_F_MAY_USE_HTTP | HLUA_F_AS_STRING)) return 0; lua_rawset(L, -3); /* Create the "c" field that contains a list of converters. */ lua_pushstring(L, "c"); if (!hlua_converters_new(L, htxn, 0)) return 0; lua_rawset(L, -3); /* Create the "sc" field that contains a list of stringsafe converters. */ lua_pushstring(L, "sc"); if (!hlua_converters_new(L, htxn, HLUA_F_AS_STRING)) return 0; lua_rawset(L, -3); /* Create the "req" field that contains the request channel object. */ lua_pushstring(L, "req"); if (!hlua_channel_new(L, &s->req)) return 0; lua_rawset(L, -3); /* Create the "res" field that contains the response channel object. */ lua_pushstring(L, "res"); if (!hlua_channel_new(L, &s->res)) return 0; lua_rawset(L, -3); /* Creates the HTTP object is the current proxy allows http. */ lua_pushstring(L, "http"); if (IS_HTX_STRM(s)) { if (!hlua_http_new(L, htxn)) return 0; } else lua_pushnil(L); lua_rawset(L, -3); if ((htxn->flags & HLUA_TXN_CTX_MASK) == HLUA_TXN_FLT_CTX) { /* HTTPMessage object are created when a lua TXN is created from * a filter context only */ /* Creates the HTTP-Request object is the current proxy allows http. */ lua_pushstring(L, "http_req"); if (p->mode == PR_MODE_HTTP) { if (!hlua_http_msg_new(L, &s->txn->req)) return 0; } else lua_pushnil(L); lua_rawset(L, -3); /* Creates the HTTP-Response object is the current proxy allows http. */ lua_pushstring(L, "http_res"); if (p->mode == PR_MODE_HTTP) { if (!hlua_http_msg_new(L, &s->txn->rsp)) return 0; } else lua_pushnil(L); lua_rawset(L, -3); } /* Pop a class sesison metatable and affect it to the userdata. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_txn_ref); lua_setmetatable(L, -2); return 1; } __LJMP static int hlua_txn_deflog(lua_State *L) { const char *msg; struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "deflog")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); msg = MAY_LJMP(luaL_checkstring(L, 2)); hlua_sendlog(htxn->s->be, htxn->s->logs.level, msg); return 0; } __LJMP static int hlua_txn_log(lua_State *L) { int level; const char *msg; struct hlua_txn *htxn; MAY_LJMP(check_args(L, 3, "log")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); level = MAY_LJMP(luaL_checkinteger(L, 2)); msg = MAY_LJMP(luaL_checkstring(L, 3)); if (level < 0 || level >= NB_LOG_LEVELS) WILL_LJMP(luaL_argerror(L, 1, "Invalid loglevel.")); hlua_sendlog(htxn->s->be, level, msg); return 0; } __LJMP static int hlua_txn_log_debug(lua_State *L) { const char *msg; struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "Debug")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); msg = MAY_LJMP(luaL_checkstring(L, 2)); hlua_sendlog(htxn->s->be, LOG_DEBUG, msg); return 0; } __LJMP static int hlua_txn_log_info(lua_State *L) { const char *msg; struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "Info")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); msg = MAY_LJMP(luaL_checkstring(L, 2)); hlua_sendlog(htxn->s->be, LOG_INFO, msg); return 0; } __LJMP static int hlua_txn_log_warning(lua_State *L) { const char *msg; struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "Warning")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); msg = MAY_LJMP(luaL_checkstring(L, 2)); hlua_sendlog(htxn->s->be, LOG_WARNING, msg); return 0; } __LJMP static int hlua_txn_log_alert(lua_State *L) { const char *msg; struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "Alert")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); msg = MAY_LJMP(luaL_checkstring(L, 2)); hlua_sendlog(htxn->s->be, LOG_ALERT, msg); return 0; } __LJMP static int hlua_txn_set_loglevel(lua_State *L) { struct hlua_txn *htxn; int ll; MAY_LJMP(check_args(L, 2, "set_loglevel")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); ll = MAY_LJMP(luaL_checkinteger(L, 2)); if (ll < -1 || ll > NB_LOG_LEVELS) WILL_LJMP(luaL_argerror(L, 2, "Bad log level. It must be one of the following value:" " core.silent(-1), core.emerg(0), core.alert(1), core.crit(2), core.error(3)," " core.warning(4), core.notice(5), core.info(6) or core.debug(7)")); htxn->s->logs.level = (ll == -1) ? ll : ll + 1; return 0; } __LJMP static int hlua_txn_set_tos(lua_State *L) { struct hlua_txn *htxn; int tos; MAY_LJMP(check_args(L, 2, "set_tos")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); tos = MAY_LJMP(luaL_checkinteger(L, 2)); conn_set_tos(objt_conn(htxn->s->sess->origin), tos); return 0; } __LJMP static int hlua_txn_set_mark(lua_State *L) { struct hlua_txn *htxn; int mark; MAY_LJMP(check_args(L, 2, "set_mark")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); mark = MAY_LJMP(luaL_checkinteger(L, 2)); conn_set_mark(objt_conn(htxn->s->sess->origin), mark); return 0; } __LJMP static int hlua_txn_set_priority_class(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "set_priority_class")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); htxn->s->priority_class = queue_limit_class(MAY_LJMP(luaL_checkinteger(L, 2))); return 0; } __LJMP static int hlua_txn_set_priority_offset(lua_State *L) { struct hlua_txn *htxn; MAY_LJMP(check_args(L, 2, "set_priority_offset")); htxn = MAY_LJMP(hlua_checktxn(L, 1)); htxn->s->priority_offset = queue_limit_offset(MAY_LJMP(luaL_checkinteger(L, 2))); return 0; } /* Forward the Reply object to the client. This function converts the reply in * HTX an push it to into the response channel. It is response to forward the * message and terminate the transaction. It returns 1 on success and 0 on * error. The Reply must be on top of the stack. */ __LJMP static int hlua_txn_forward_reply(lua_State *L, struct stream *s) { struct htx *htx; struct htx_sl *sl; struct h1m h1m; const char *status, *reason, *body; size_t status_len, reason_len, body_len; int ret, code, flags; code = 200; status = "200"; status_len = 3; ret = lua_getfield(L, -1, "status"); if (ret == LUA_TNUMBER) { code = lua_tointeger(L, -1); status = lua_tolstring(L, -1, &status_len); } lua_pop(L, 1); reason = http_get_reason(code); reason_len = strlen(reason); ret = lua_getfield(L, -1, "reason"); if (ret == LUA_TSTRING) reason = lua_tolstring(L, -1, &reason_len); lua_pop(L, 1); body = NULL; body_len = 0; ret = lua_getfield(L, -1, "body"); if (ret == LUA_TSTRING) body = lua_tolstring(L, -1, &body_len); lua_pop(L, 1); /* Prepare the response before inserting the headers */ h1m_init_res(&h1m); htx = htx_from_buf(&s->res.buf); channel_htx_truncate(&s->res, htx); if (s->txn->req.flags & HTTP_MSGF_VER_11) { flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11); sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist2(status, status_len), ist2(reason, reason_len)); } else { flags = HTX_SL_F_IS_RESP; sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.0"), ist2(status, status_len), ist2(reason, reason_len)); } if (!sl) goto fail; sl->info.res.status = code; /* Push in the stack the "headers" entry. */ ret = lua_getfield(L, -1, "headers"); if (ret != LUA_TTABLE) goto skip_headers; lua_pushnil(L); while (lua_next(L, -2) != 0) { struct ist name, value; const char *n, *v; size_t nlen, vlen; if (!lua_isstring(L, -2) || !lua_istable(L, -1)) { /* Skip element if the key is not a string or if the value is not a table */ goto next_hdr; } n = lua_tolstring(L, -2, &nlen); name = ist2(n, nlen); if (isteqi(name, ist("content-length"))) { /* Always skip content-length header. It will be added * later with the correct len */ goto next_hdr; } /* Loop on header's values */ lua_pushnil(L); while (lua_next(L, -2)) { if (!lua_isstring(L, -1)) { /* Skip the value if it is not a string */ goto next_value; } v = lua_tolstring(L, -1, &vlen); value = ist2(v, vlen); if (isteqi(name, ist("transfer-encoding"))) h1_parse_xfer_enc_header(&h1m, value); if (!htx_add_header(htx, ist2(n, nlen), ist2(v, vlen))) goto fail; next_value: lua_pop(L, 1); } next_hdr: lua_pop(L, 1); } skip_headers: lua_pop(L, 1); /* Update h1m flags: CLEN is set if CHNK is not present */ if (!(h1m.flags & H1_MF_CHNK)) { const char *clen = ultoa(body_len); h1m.flags |= H1_MF_CLEN; if (!htx_add_header(htx, ist("content-length"), ist(clen))) goto fail; } if (h1m.flags & (H1_MF_CLEN|H1_MF_CHNK)) h1m.flags |= H1_MF_XFER_LEN; /* Update HTX start-line flags */ if (h1m.flags & H1_MF_XFER_ENC) flags |= HTX_SL_F_XFER_ENC; if (h1m.flags & H1_MF_XFER_LEN) { flags |= HTX_SL_F_XFER_LEN; if (h1m.flags & H1_MF_CHNK) flags |= HTX_SL_F_CHNK; else if (h1m.flags & H1_MF_CLEN) flags |= HTX_SL_F_CLEN; if (h1m.body_len == 0) flags |= HTX_SL_F_BODYLESS; } sl->flags |= flags; if (!htx_add_endof(htx, HTX_BLK_EOH) || (body_len && !htx_add_data_atonce(htx, ist2(body, body_len)))) goto fail; htx->flags |= HTX_FL_EOM; /* Now, forward the response and terminate the transaction */ s->txn->status = code; htx_to_buf(htx, &s->res.buf); if (!http_forward_proxy_resp(s, 1)) goto fail; return 1; fail: channel_htx_truncate(&s->res, htx); return 0; } /* Terminate a transaction if called from a lua action. For TCP streams, * processing is just aborted. Nothing is returned to the client and all * arguments are ignored. For HTTP streams, if a reply is passed as argument, it * is forwarded to the client before terminating the transaction. On success, * the function exits with ACT_RET_DONE code. If an error occurred, it exits * with ACT_RET_ERR code. If this function is not called from a lua action, it * just exits without any processing. */ __LJMP static int hlua_txn_done(lua_State *L) { struct hlua_txn *htxn; struct stream *s; int finst; htxn = MAY_LJMP(hlua_checktxn(L, 1)); /* If the flags NOTERM is set, we cannot terminate the session, so we * just end the execution of the current lua code. */ if (htxn->flags & HLUA_TXN_NOTERM) WILL_LJMP(hlua_done(L)); s = htxn->s; if (!IS_HTX_STRM(htxn->s)) { struct channel *req = &s->req; struct channel *res = &s->res; channel_auto_read(req); channel_abort(req); channel_erase(req); channel_auto_read(res); channel_auto_close(res); sc_schedule_abort(s->scb); finst = ((htxn->dir == SMP_OPT_DIR_REQ) ? SF_FINST_R : SF_FINST_D); goto done; } if (lua_gettop(L) == 1 || !lua_istable(L, 2)) { /* No reply or invalid reply */ s->txn->status = 0; http_reply_and_close(s, 0, NULL); } else { /* Remove extra args to have the reply on top of the stack */ if (lua_gettop(L) > 2) lua_pop(L, lua_gettop(L) - 2); if (!hlua_txn_forward_reply(L, s)) { if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_PRXCOND; lua_pushinteger(L, ACT_RET_ERR); WILL_LJMP(hlua_done(L)); return 0; /* Never reached */ } } finst = ((htxn->dir == SMP_OPT_DIR_REQ) ? SF_FINST_R : SF_FINST_H); if (htxn->dir == SMP_OPT_DIR_REQ) { /* let's log the request time */ s->logs.request_ts = now_ns; if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */ _HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req); } done: if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_LOCAL; if (!(s->flags & SF_FINST_MASK)) s->flags |= finst; if ((htxn->flags & HLUA_TXN_CTX_MASK) == HLUA_TXN_FLT_CTX) lua_pushinteger(L, -1); else lua_pushinteger(L, ACT_RET_ABRT); WILL_LJMP(hlua_done(L)); return 0; } /* * * * Class REPLY * * */ /* Pushes the TXN reply onto the top of the stack. If the stask does not have a * free slots, the function fails and returns 0; */ static int hlua_txn_reply_new(lua_State *L) { struct hlua_txn *htxn; const char *reason, *body = NULL; int ret, status; htxn = MAY_LJMP(hlua_checktxn(L, 1)); if (!IS_HTX_STRM(htxn->s)) { hlua_pusherror(L, "txn object is not an HTTP transaction."); WILL_LJMP(lua_error(L)); } /* Default value */ status = 200; reason = http_get_reason(status); if (lua_istable(L, 2)) { /* load status and reason from the table argument at index 2 */ ret = lua_getfield(L, 2, "status"); if (ret == LUA_TNIL) goto reason; else if (ret != LUA_TNUMBER) { /* invalid status: ignore the reason */ goto body; } status = lua_tointeger(L, -1); reason: lua_pop(L, 1); /* restore the stack: remove status */ ret = lua_getfield(L, 2, "reason"); if (ret == LUA_TSTRING) reason = lua_tostring(L, -1); body: lua_pop(L, 1); /* restore the stack: remove invalid status or reason */ ret = lua_getfield(L, 2, "body"); if (ret == LUA_TSTRING) body = lua_tostring(L, -1); lua_pop(L, 1); /* restore the stack: remove body */ } /* Create the Reply table */ lua_newtable(L); /* Add status element */ lua_pushstring(L, "status"); lua_pushinteger(L, status); lua_settable(L, -3); /* Add reason element */ reason = http_get_reason(status); lua_pushstring(L, "reason"); lua_pushstring(L, reason); lua_settable(L, -3); /* Add body element, nil if undefined */ lua_pushstring(L, "body"); if (body) lua_pushstring(L, body); else lua_pushnil(L); lua_settable(L, -3); /* Add headers element */ lua_pushstring(L, "headers"); lua_newtable(L); /* stack: [ txn, , , "headers", ] */ if (lua_istable(L, 2)) { /* load headers from the table argument at index 2. If it is a table, copy it. */ ret = lua_getfield(L, 2, "headers"); if (ret == LUA_TTABLE) { /* stack: [ ... , ] */ lua_pushnil(L); while (lua_next(L, -2) != 0) { /* stack: [ ... ,
, k, v] */ if (!lua_isstring(L, -1) && !lua_istable(L, -1)) { /* invalid value type, skip it */ lua_pop(L, 1); continue; } /* Duplicate the key and swap it with the value. */ lua_pushvalue(L, -2); lua_insert(L, -2); /* stack: [ ... ,
, k, k, v ] */ lua_newtable(L); lua_insert(L, -2); /* stack: [ ... ,
, k, k, , v ] */ if (lua_isstring(L, -1)) { /* push the value in the inner table */ lua_rawseti(L, -2, 1); } else { /* table */ lua_pushnil(L); while (lua_next(L, -2) != 0) { /* stack: [ ... ,
, k, k, , , k2, v2 ] */ if (!lua_isstring(L, -1)) { /* invalid value type, skip it*/ lua_pop(L, 1); continue; } /* push the value in the inner table */ lua_rawseti(L, -4, lua_rawlen(L, -4) + 1); /* stack: [ ... ,
, k, k, , , k2 ] */ } lua_pop(L, 1); /* stack: [ ... ,
, k, k, ] */ } /* push (k,v) on the stack in the headers table: * stack: [ ... ,
, k, k, v ] */ lua_settable(L, -5); /* stack: [ ... ,
, k ] */ } } lua_pop(L, 1); } /* stack: [ txn, , , "headers", ] */ lua_settable(L, -3); /* stack: [ txn, , ] */ /* Pop a class sesison metatable and affect it to the userdata. */ lua_rawgeti(L, LUA_REGISTRYINDEX, class_txn_reply_ref); lua_setmetatable(L, -2); return 1; } /* Set the reply status code, and optionally the reason. If no reason is * provided, the default one corresponding to the status code is used. */ __LJMP static int hlua_txn_reply_set_status(lua_State *L) { int status = MAY_LJMP(luaL_checkinteger(L, 2)); const char *reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL)); /* First argument (self) must be a table */ MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); if (status < 100 || status > 599) { lua_pushboolean(L, 0); return 1; } if (!reason) reason = http_get_reason(status); lua_pushinteger(L, status); lua_setfield(L, 1, "status"); lua_pushstring(L, reason); lua_setfield(L, 1, "reason"); lua_pushboolean(L, 1); return 1; } /* Add a header into the reply object. Each header name is associated to an * array of values in the "headers" table. If the header name is not found, a * new entry is created. */ __LJMP static int hlua_txn_reply_add_header(lua_State *L) { const char *name = MAY_LJMP(luaL_checkstring(L, 2)); const char *value = MAY_LJMP(luaL_checkstring(L, 3)); int ret; /* First argument (self) must be a table */ MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); /* Push in the stack the "headers" entry. */ ret = lua_getfield(L, 1, "headers"); if (ret != LUA_TTABLE) { hlua_pusherror(L, "Reply['headers'] is expected to a an array. %s found", lua_typename(L, ret)); WILL_LJMP(lua_error(L)); } /* check if the header is already registered. If not, register it. */ ret = lua_getfield(L, -1, name); if (ret == LUA_TNIL) { /* Entry not found. */ lua_pop(L, 1); /* remove the nil. The "headers" table is the top of the stack. */ /* Insert the new header name in the array in the top of the stack. * It left the new array in the top of the stack. */ lua_newtable(L); lua_pushstring(L, name); lua_pushvalue(L, -2); lua_settable(L, -4); } else if (ret != LUA_TTABLE) { hlua_pusherror(L, "Reply['headers']['%s'] is expected to be an array. %s found", name, lua_typename(L, ret)); WILL_LJMP(lua_error(L)); } /* Now the top of thestack is an array of values. We push * the header value as new entry. */ lua_pushstring(L, value); ret = lua_rawlen(L, -2); lua_rawseti(L, -2, ret + 1); lua_pushboolean(L, 1); return 1; } /* Remove all occurrences of a given header name. */ __LJMP static int hlua_txn_reply_del_header(lua_State *L) { const char *name = MAY_LJMP(luaL_checkstring(L, 2)); int ret; /* First argument (self) must be a table */ MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); /* Push in the stack the "headers" entry. */ ret = lua_getfield(L, 1, "headers"); if (ret != LUA_TTABLE) { hlua_pusherror(L, "Reply['headers'] is expected to be an array. %s found", lua_typename(L, ret)); WILL_LJMP(lua_error(L)); } lua_pushstring(L, name); lua_pushnil(L); lua_settable(L, -3); lua_pushboolean(L, 1); return 1; } /* Set the reply's body. Overwrite any existing entry. */ __LJMP static int hlua_txn_reply_set_body(lua_State *L) { const char *payload = MAY_LJMP(luaL_checkstring(L, 2)); /* First argument (self) must be a table */ MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); lua_pushstring(L, payload); lua_setfield(L, 1, "body"); lua_pushboolean(L, 1); return 1; } __LJMP static int hlua_log(lua_State *L) { int level; const char *msg; MAY_LJMP(check_args(L, 2, "log")); level = MAY_LJMP(luaL_checkinteger(L, 1)); msg = MAY_LJMP(luaL_checkstring(L, 2)); if (level < 0 || level >= NB_LOG_LEVELS) WILL_LJMP(luaL_argerror(L, 1, "Invalid loglevel.")); hlua_sendlog(NULL, level, msg); return 0; } __LJMP static int hlua_log_debug(lua_State *L) { const char *msg; MAY_LJMP(check_args(L, 1, "debug")); msg = MAY_LJMP(luaL_checkstring(L, 1)); hlua_sendlog(NULL, LOG_DEBUG, msg); return 0; } __LJMP static int hlua_log_info(lua_State *L) { const char *msg; MAY_LJMP(check_args(L, 1, "info")); msg = MAY_LJMP(luaL_checkstring(L, 1)); hlua_sendlog(NULL, LOG_INFO, msg); return 0; } __LJMP static int hlua_log_warning(lua_State *L) { const char *msg; MAY_LJMP(check_args(L, 1, "warning")); msg = MAY_LJMP(luaL_checkstring(L, 1)); hlua_sendlog(NULL, LOG_WARNING, msg); return 0; } __LJMP static int hlua_log_alert(lua_State *L) { const char *msg; MAY_LJMP(check_args(L, 1, "alert")); msg = MAY_LJMP(luaL_checkstring(L, 1)); hlua_sendlog(NULL, LOG_ALERT, msg); return 0; } __LJMP static int hlua_sleep_yield(lua_State *L, int status, lua_KContext ctx) { int wakeup_ms = lua_tointeger(L, -1); if (!tick_is_expired(wakeup_ms, now_ms)) MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_sleep_yield, wakeup_ms, 0)); return 0; } __LJMP static int hlua_sleep(lua_State *L) { unsigned int delay; int wakeup_ms; // tick value MAY_LJMP(check_args(L, 1, "sleep")); delay = MAY_LJMP(luaL_checkinteger(L, 1)) * 1000; wakeup_ms = tick_add(now_ms, delay); lua_pushinteger(L, wakeup_ms); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_sleep_yield, wakeup_ms, 0)); return 0; } __LJMP static int hlua_msleep(lua_State *L) { unsigned int delay; int wakeup_ms; // tick value MAY_LJMP(check_args(L, 1, "msleep")); delay = MAY_LJMP(luaL_checkinteger(L, 1)); wakeup_ms = tick_add(now_ms, delay); lua_pushinteger(L, wakeup_ms); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_sleep_yield, wakeup_ms, 0)); return 0; } /* This functionis an LUA binding. it permits to give back * the hand at the HAProxy scheduler. It is used when the * LUA processing consumes a lot of time. */ __LJMP static int hlua_yield_yield(lua_State *L, int status, lua_KContext ctx) { return 0; } __LJMP static int hlua_yield(lua_State *L) { MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_yield_yield, TICK_ETERNITY, HLUA_CTRLYIELD)); return 0; } /* This function change the nice of the currently executed * task. It is used set low or high priority at the current * task. */ __LJMP static int hlua_set_nice(lua_State *L) { struct hlua *hlua; int nice; MAY_LJMP(check_args(L, 1, "set_nice")); nice = MAY_LJMP(luaL_checkinteger(L, 1)); /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); /* If the task is not set, I'm in a start mode. */ if (!hlua || !hlua->task) return 0; if (nice < -1024) nice = -1024; else if (nice > 1024) nice = 1024; hlua->task->nice = nice; return 0; } /* safe lua coroutine.create() function: * * This is a simple wrapper for coroutine.create() that * ensures the current hlua state ctx is available from * the new subroutine state */ __LJMP static int hlua_coroutine_create(lua_State *L) { lua_State *new; /* new coroutine state */ struct hlua **hlua_store; struct hlua *hlua = hlua_gethlua(L); new = lua_newthread(L); if (!new) return 0; hlua_store = lua_getextraspace(new); /* Expose current hlua ctx on new lua thread * (hlua_gethlua() will properly return the last "known" * hlua ctx instead of NULL when it is called from such coroutines) */ *hlua_store = hlua; /* new lua thread is on the top of the stack, we * need to duplicate first stack argument ( from coroutine.create()) * on the top of the stack to be able to use xmove() to move it on the new * stack */ lua_pushvalue(L, 1); /* move function to the new stack */ lua_xmove(L, new, 1); /* new lua thread is back at the top of the stack */ return 1; } /* This function is used as a callback of a task. It is called by the * HAProxy task subsystem when the task is awaked. The LUA runtime can * return an E_AGAIN signal, the emmiter of this signal must set a * signal to wake the task. * * Task wrapper are longjmp safe because the only one Lua code * executed is the safe hlua_ctx_resume(); */ struct task *hlua_process_task(struct task *task, void *context, unsigned int state) { struct hlua *hlua = context; enum hlua_exec status; if (task->tid < 0) task->tid = tid; /* If it is the first call to the task, we must initialize the * execution timeouts. */ if (!HLUA_IS_RUNNING(hlua)) hlua_timer_init(&hlua->timer, hlua_timeout_task); /* Execute the Lua code. */ status = hlua_ctx_resume(hlua, 1); switch (status) { /* finished or yield */ case HLUA_E_OK: hlua_ctx_destroy(hlua); task_destroy(task); task = NULL; break; case HLUA_E_AGAIN: /* co process or timeout wake me later. */ notification_gc(&hlua->com); task->expire = hlua->wake_time; break; /* finished with error. */ case HLUA_E_ETMOUT: SEND_ERR(NULL, "Lua task: execution timeout.\n"); goto err_task_abort; case HLUA_E_ERRMSG: hlua_lock(hlua); SEND_ERR(NULL, "Lua task: %s.\n", hlua_tostring_safe(hlua->T, -1)); hlua_unlock(hlua); goto err_task_abort; case HLUA_E_ERR: default: SEND_ERR(NULL, "Lua task: unknown error.\n"); err_task_abort: hlua_ctx_destroy(hlua); task_destroy(task); task = NULL; break; } return task; } /* This function is an LUA binding that register LUA function to be * executed after the HAProxy configuration parsing and before the * HAProxy scheduler starts. This function expect only one LUA * argument that is a function. This function returns nothing, but * throws if an error is encountered. */ __LJMP static int hlua_register_init(lua_State *L) { struct hlua_init_function *init; int ref; MAY_LJMP(check_args(L, 1, "register_init")); if (hlua_gethlua(L)) { /* runtime processing */ WILL_LJMP(luaL_error(L, "register_init: not available outside of body context")); } ref = MAY_LJMP(hlua_checkfunction(L, 1)); init = calloc(1, sizeof(*init)); if (!init) { hlua_unref(L, ref); WILL_LJMP(luaL_error(L, "Lua out of memory error.")); } init->function_ref = ref; LIST_APPEND(&hlua_init_functions[hlua_state_id], &init->l); return 0; } /* This function is an LUA binding. It permits to register a task * executed in parallel of the main HAroxy activity. The task is * created and it is set in the HAProxy scheduler. It can be called * from the "init" section, "post init" or during the runtime. * * Lua prototype: * * core.register_task([, [, [, ...[, ]]]]) * * are optional arguments that will be provided to */ __LJMP static int hlua_register_task(lua_State *L) { struct hlua *hlua = NULL; struct task *task = NULL; int ref; int nb_arg; int it; int arg_ref[4]; /* optional arguments */ int state_id; nb_arg = lua_gettop(L); if (nb_arg < 1) WILL_LJMP(luaL_error(L, "register_task: argument is required")); else if (nb_arg > 5) WILL_LJMP(luaL_error(L, "register_task: no more that 4 optional arguments may be provided")); /* first arg: function ref */ ref = MAY_LJMP(hlua_checkfunction(L, 1)); /* extract optional args (if any) */ it = 0; while (--nb_arg) { lua_pushvalue(L, 2 + it); arg_ref[it] = hlua_ref(L); /* get arg reference */ it += 1; } nb_arg = it; /* Get the reference state. If the reference is NULL, L is the master * state, otherwise hlua->T is. */ hlua = hlua_gethlua(L); if (hlua) /* we are in runtime processing */ state_id = hlua->state_id; else /* we are in initialization mode */ state_id = hlua_state_id; hlua = pool_alloc(pool_head_hlua); if (!hlua) goto alloc_error; HLUA_INIT(hlua); /* We are in the common lua state, execute the task anywhere, * otherwise, inherit the current thread identifier */ if (state_id == 0) task = task_new_anywhere(); else task = task_new_here(); if (!task) goto alloc_error; task->context = hlua; task->process = hlua_process_task; if (!hlua_ctx_init(hlua, state_id, task)) goto alloc_error; /* Ensure there is enough space on the stack for the function * plus optional arguments */ if (!lua_checkstack(hlua->T, (1 + nb_arg))) goto alloc_error; /* Restore the function in the stack. */ hlua_pushref(hlua->T, ref); /* function ref not needed anymore since it was pushed to the substack */ hlua_unref(L, ref); hlua->nargs = nb_arg; /* push optional arguments to the function */ for (it = 0; it < nb_arg; it++) { /* push arg to the stack */ hlua_pushref(hlua->T, arg_ref[it]); /* arg ref not needed anymore since it was pushed to the substack */ hlua_unref(L, arg_ref[it]); } /* Schedule task. */ task_wakeup(task, TASK_WOKEN_INIT); return 0; alloc_error: task_destroy(task); hlua_unref(L, ref); for (it = 0; it < nb_arg; it++) { hlua_unref(L, arg_ref[it]); } hlua_ctx_destroy(hlua); WILL_LJMP(luaL_error(L, "Lua out of memory error.")); return 0; /* Never reached */ } /* called from unsafe location */ static void hlua_event_subscription_destroy(struct hlua_event_sub *hlua_sub) { /* hlua cleanup */ hlua_lock(hlua_sub->hlua); /* registry is shared between coroutines */ hlua_unref(hlua_sub->hlua->T, hlua_sub->fcn_ref); hlua_unlock(hlua_sub->hlua); hlua_ctx_destroy(hlua_sub->hlua); /* free */ pool_free(pool_head_hlua_event_sub, hlua_sub); } /* single event handler: hlua ctx is shared between multiple events handlers * issued from the same subscription. Thus, it is not destroyed when the event * is processed: it is destroyed when no more events are expected for the * subscription (ie: when the subscription ends). * * Moreover, events are processed sequentially within the subscription: * one event must be fully processed before another one may be processed. * This ensures proper consistency for lua event handling from an ordering * point of view. This is especially useful with server events for example * where ADD/DEL/UP/DOWN events ordering really matters to trigger specific * actions from lua (e.g.: sending emails or making API calls). * * Due to this design, each lua event handler is pleased to process the event * as fast as possible to prevent the event queue from growing up. * Strictly speaking, there is no runtime limit for the callback function * (timeout set to default task timeout), but if the event queue goes past * the limit of unconsumed events an error will be reported and the * susbscription will pause itself for as long as it takes for the handler to * catch up (events will be lost as a result). * If the event handler does not need the sequential ordering and wants to * process multiple events at a time, it may spawn a new side-task using * 'core.register_task' to delegate the event handling and make parallel event * processing within the same subscription set. */ static void hlua_event_handler(struct hlua *hlua) { enum hlua_exec status; /* If it is the first call to the task, we must initialize the * execution timeouts. */ if (!HLUA_IS_RUNNING(hlua)) hlua_timer_init(&hlua->timer, hlua_timeout_task); /* make sure to reset the task expiry before each hlua_ctx_resume() * since the task is re-used for multiple cb function calls * We couldn't risk to have t->expire pointing to a past date because * it was set during last function invocation but was never reset since * (ie: E_AGAIN) */ hlua->task->expire = TICK_ETERNITY; /* Execute the Lua code. */ status = hlua_ctx_resume(hlua, 1); switch (status) { /* finished or yield */ case HLUA_E_OK: break; case HLUA_E_AGAIN: /* co process or timeout wake me later. */ notification_gc(&hlua->com); hlua->task->expire = hlua->wake_time; break; /* finished with error. */ case HLUA_E_ETMOUT: SEND_ERR(NULL, "Lua event_hdl: execution timeout.\n"); break; case HLUA_E_ERRMSG: hlua_lock(hlua); SEND_ERR(NULL, "Lua event_hdl: %s.\n", hlua_tostring_safe(hlua->T, -1)); hlua_unlock(hlua); break; case HLUA_E_ERR: default: SEND_ERR(NULL, "Lua event_hdl: unknown error.\n"); break; } } __LJMP static void hlua_event_hdl_cb_push_event_checkres(lua_State *L, struct event_hdl_cb_data_server_checkres *check) { lua_pushstring(L, "agent"); lua_pushboolean(L, check->agent); lua_settable(L, -3); lua_pushstring(L, "result"); switch (check->result) { case CHK_RES_FAILED: lua_pushstring(L, "FAILED"); break; case CHK_RES_PASSED: lua_pushstring(L, "PASSED"); break; case CHK_RES_CONDPASS: lua_pushstring(L, "CONDPASS"); break; default: lua_pushnil(L); break; } lua_settable(L, -3); lua_pushstring(L, "duration"); lua_pushinteger(L, check->duration); lua_settable(L, -3); lua_pushstring(L, "reason"); lua_newtable(L); lua_pushstring(L, "short"); lua_pushstring(L, get_check_status_info(check->reason.status)); lua_settable(L, -3); lua_pushstring(L, "desc"); lua_pushstring(L, get_check_status_description(check->reason.status)); lua_settable(L, -3); if (check->reason.status >= HCHK_STATUS_L57DATA) { /* code only available when the check reached data analysis stage */ lua_pushstring(L, "code"); lua_pushinteger(L, check->reason.code); lua_settable(L, -3); } lua_settable(L, -3); /* reason table */ lua_pushstring(L, "health"); lua_newtable(L); lua_pushstring(L, "cur"); lua_pushinteger(L, check->health.cur); lua_settable(L, -3); lua_pushstring(L, "rise"); lua_pushinteger(L, check->health.rise); lua_settable(L, -3); lua_pushstring(L, "fall"); lua_pushinteger(L, check->health.fall); lua_settable(L, -3); lua_settable(L, -3); /* health table */ } /* This function pushes various arguments such as event type and event data to * the lua function that will be called to consume the event. */ __LJMP static void hlua_event_hdl_cb_push_args(struct hlua_event_sub *hlua_sub, struct event_hdl_async_event *e) { struct hlua *hlua = hlua_sub->hlua; struct event_hdl_sub_type event = e->type; void *data = e->data; /* push event type */ hlua->nargs = 1; lua_pushstring(hlua->T, event_hdl_sub_type_to_string(event)); /* push event data (according to event type) */ if (event_hdl_sub_family_equal(EVENT_HDL_SUB_SERVER, event)) { struct event_hdl_cb_data_server *e_server = data; struct proxy *px; struct server *server; hlua->nargs += 1; lua_newtable(hlua->T); /* Add server name */ lua_pushstring(hlua->T, "name"); lua_pushstring(hlua->T, e_server->safe.name); lua_settable(hlua->T, -3); /* Add server puid */ lua_pushstring(hlua->T, "puid"); lua_pushinteger(hlua->T, e_server->safe.puid); lua_settable(hlua->T, -3); /* Add server rid */ lua_pushstring(hlua->T, "rid"); lua_pushinteger(hlua->T, e_server->safe.rid); lua_settable(hlua->T, -3); /* Add server proxy name */ lua_pushstring(hlua->T, "proxy_name"); lua_pushstring(hlua->T, e_server->safe.proxy_name); lua_settable(hlua->T, -3); /* Add server proxy uuid */ lua_pushstring(hlua->T, "proxy_uuid"); lua_pushinteger(hlua->T, e_server->safe.proxy_uuid); lua_settable(hlua->T, -3); /* special events, fetch additional info with explicit type casting */ if (event_hdl_sub_type_equal(EVENT_HDL_SUB_SERVER_STATE, event)) { struct event_hdl_cb_data_server_state *state = data; int it; if (!lua_checkstack(hlua->T, 20)) WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error.")); /* state subclass */ lua_pushstring(hlua->T, "state"); lua_newtable(hlua->T); lua_pushstring(hlua->T, "admin"); lua_pushboolean(hlua->T, state->safe.type); lua_settable(hlua->T, -3); /* is it because of a check ? */ if (!state->safe.type && (state->safe.op_st_chg.cause == SRV_OP_STCHGC_HEALTH || state->safe.op_st_chg.cause == SRV_OP_STCHGC_AGENT)) { /* yes, provide check result */ lua_pushstring(hlua->T, "check"); lua_newtable(hlua->T); hlua_event_hdl_cb_push_event_checkres(hlua->T, &state->safe.op_st_chg.check); lua_settable(hlua->T, -3); /* check table */ } lua_pushstring(hlua->T, "cause"); if (state->safe.type) lua_pushstring(hlua->T, srv_adm_st_chg_cause(state->safe.adm_st_chg.cause)); else lua_pushstring(hlua->T, srv_op_st_chg_cause(state->safe.op_st_chg.cause)); lua_settable(hlua->T, -3); /* old_state, new_state */ for (it = 0; it < 2; it++) { enum srv_state srv_state = (!it) ? state->safe.old_state : state->safe.new_state; lua_pushstring(hlua->T, (!it) ? "old_state" : "new_state"); switch (srv_state) { case SRV_ST_STOPPED: lua_pushstring(hlua->T, "STOPPED"); break; case SRV_ST_STOPPING: lua_pushstring(hlua->T, "STOPPING"); break; case SRV_ST_STARTING: lua_pushstring(hlua->T, "STARTING"); break; case SRV_ST_RUNNING: lua_pushstring(hlua->T, "RUNNING"); break; default: lua_pushnil(hlua->T); break; } lua_settable(hlua->T, -3); } /* requeued */ lua_pushstring(hlua->T, "requeued"); lua_pushinteger(hlua->T, state->safe.requeued); lua_settable(hlua->T, -3); lua_settable(hlua->T, -3); /* state table */ } else if (event_hdl_sub_type_equal(EVENT_HDL_SUB_SERVER_ADMIN, event)) { struct event_hdl_cb_data_server_admin *admin = data; int it; if (!lua_checkstack(hlua->T, 20)) WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error.")); /* admin subclass */ lua_pushstring(hlua->T, "admin"); lua_newtable(hlua->T); lua_pushstring(hlua->T, "cause"); lua_pushstring(hlua->T, srv_adm_st_chg_cause(admin->safe.cause)); lua_settable(hlua->T, -3); /* old_admin, new_admin */ for (it = 0; it < 2; it++) { enum srv_admin srv_admin = (!it) ? admin->safe.old_admin : admin->safe.new_admin; lua_pushstring(hlua->T, (!it) ? "old_admin" : "new_admin"); /* admin state matrix */ lua_newtable(hlua->T); lua_pushstring(hlua->T, "MAINT"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_MAINT); lua_settable(hlua->T, -3); lua_pushstring(hlua->T, "FMAINT"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_FMAINT); lua_settable(hlua->T, -3); lua_pushstring(hlua->T, "IMAINT"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_IMAINT); lua_settable(hlua->T, -3); lua_pushstring(hlua->T, "RMAINT"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_RMAINT); lua_settable(hlua->T, -3); lua_pushstring(hlua->T, "CMAINT"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_CMAINT); lua_settable(hlua->T, -3); lua_pushstring(hlua->T, "DRAIN"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_DRAIN); lua_settable(hlua->T, -3); lua_pushstring(hlua->T, "FDRAIN"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_FDRAIN); lua_settable(hlua->T, -3); lua_pushstring(hlua->T, "IDRAIN"); lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_IDRAIN); lua_settable(hlua->T, -3); lua_settable(hlua->T, -3); /* matrix table */ } /* requeued */ lua_pushstring(hlua->T, "requeued"); lua_pushinteger(hlua->T, admin->safe.requeued); lua_settable(hlua->T, -3); lua_settable(hlua->T, -3); /* admin table */ } else if (event_hdl_sub_type_equal(EVENT_HDL_SUB_SERVER_CHECK, event)) { struct event_hdl_cb_data_server_check *check = data; if (!lua_checkstack(hlua->T, 20)) WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error.")); /* check subclass */ lua_pushstring(hlua->T, "check"); lua_newtable(hlua->T); /* check result snapshot */ hlua_event_hdl_cb_push_event_checkres(hlua->T, &check->safe.res); lua_settable(hlua->T, -3); /* check table */ } /* attempt to provide reference server object * (if it wasn't removed yet, SERVER_DEL will never succeed here) */ px = proxy_find_by_id(e_server->safe.proxy_uuid, PR_CAP_BE, 0); BUG_ON(!px); server = findserver_unique_id(px, e_server->safe.puid, e_server->safe.rid); if (server) { lua_pushstring(hlua->T, "reference"); hlua_fcn_new_server(hlua->T, server); lua_settable(hlua->T, -3); } } /* sub mgmt */ hlua->nargs += 1; hlua_fcn_new_event_sub(hlua->T, hlua_sub->sub); /* when? */ hlua->nargs += 1; lua_pushinteger(hlua->T, e->when.tv_sec); } /* events runner: if there's an ongoing hlua event handling process, finish it * then, check if there are new events waiting to be processed * (events are processed sequentially) * * We have a safety measure to warn/guard if the event queue is growing up * too much due to many events being generated and lua handler is unable to * keep up the pace (e.g.: when the event queue grows past 100 unconsumed events). * TODO: make it tunable */ static struct task *hlua_event_runner(struct task *task, void *context, unsigned int state) { struct hlua_event_sub *hlua_sub = context; struct event_hdl_async_event *event; const char *error = NULL; if (!hlua_sub->paused && event_hdl_async_equeue_size(&hlua_sub->equeue) > 100) { const char *trace = NULL; /* We reached the limit of pending events in the queue: we should * warn the user, and temporarily pause the subscription to give a chance * to the handler to catch up? (it also prevents resource shortage since * the queue could grow indefinitely otherwise) * TODO: find a way to inform the handler that it missed some events * (example: stats within the subscription in event_hdl api exposed via lua api?) * * Nonetheless, reaching this limit means that the handler is not fast enough * and/or that it subscribed to events that happen too frequently and did not * expect it. This could come from an inadequate design in the user's script. */ event_hdl_pause(hlua_sub->sub); hlua_sub->paused = 1; if (SET_SAFE_LJMP(hlua_sub->hlua)) { /* The following Lua call may fail. */ trace = hlua_traceback(hlua_sub->hlua->T, ", "); /* At this point the execution is safe. */ RESET_SAFE_LJMP(hlua_sub->hlua); } else { /* Lua error was raised while fetching lua trace from current ctx */ SEND_ERR(NULL, "Lua event_hdl: unexpected error (memory failure?).\n"); } ha_warning("Lua event_hdl: pausing the subscription because the handler fails " "to keep up the pace (%u unconsumed events) from %s.\n", event_hdl_async_equeue_size(&hlua_sub->equeue), (trace) ? trace : "[unknown]"); } if (HLUA_IS_RUNNING(hlua_sub->hlua)) { /* ongoing hlua event handler, resume it */ hlua_event_handler(hlua_sub->hlua); } else if ((event = event_hdl_async_equeue_pop(&hlua_sub->equeue))) { /* check for new events */ if (event_hdl_sub_type_equal(event->type, EVENT_HDL_SUB_END)) { /* ending event: no more events to come */ event_hdl_async_free_event(event); task_destroy(task); hlua_event_subscription_destroy(hlua_sub); return NULL; } /* new event: start processing it */ /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua_sub->hlua)) { hlua_lock(hlua_sub->hlua); if (lua_type(hlua_sub->hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua_sub->hlua->T, -1); else error = "critical error"; ha_alert("Lua event_hdl: %s.\n", error); hlua_unlock(hlua_sub->hlua); goto skip_event; } /* Check stack available size. */ if (!lua_checkstack(hlua_sub->hlua->T, 5)) { ha_alert("Lua event_hdl: full stack.\n"); RESET_SAFE_LJMP(hlua_sub->hlua); goto skip_event; } /* Restore the function in the stack. */ hlua_pushref(hlua_sub->hlua->T, hlua_sub->fcn_ref); /* push args */ hlua_sub->hlua->nargs = 0; MAY_LJMP(hlua_event_hdl_cb_push_args(hlua_sub, event)); /* At this point the execution is safe. */ RESET_SAFE_LJMP(hlua_sub->hlua); /* At this point the event was successfully translated into hlua ctx, * or hlua error occurred, so we can safely discard it */ event_hdl_async_free_event(event); event = NULL; hlua_event_handler(hlua_sub->hlua); skip_event: if (event) event_hdl_async_free_event(event); } if (!HLUA_IS_RUNNING(hlua_sub->hlua)) { /* we just finished the processing of one event.. * check for new events before becoming idle */ if (!event_hdl_async_equeue_isempty(&hlua_sub->equeue)) { /* more events to process, make sure the task * will be resumed ASAP to process pending events */ task_wakeup(task, TASK_WOKEN_OTHER); } else if (hlua_sub->paused) { /* empty queue, the handler caught up: resume the subscription */ event_hdl_resume(hlua_sub->sub); hlua_sub->paused = 0; } } return task; } /* Must be called directly under lua protected/safe environment * (not from external callback) * should NOT be dropped after the function successfully returns: * it will be done automatically in hlua_event_subscription_destroy() when the * subscription ends. * * Returns the new subscription on success and NULL on failure (memory error) */ static struct event_hdl_sub *hlua_event_subscribe(event_hdl_sub_list *list, struct event_hdl_sub_type e_type, int state_id, int fcn_ref) { struct hlua_event_sub *hlua_sub; struct task *task = NULL; hlua_sub = pool_alloc(pool_head_hlua_event_sub); if (!hlua_sub) goto mem_error; hlua_sub->task = NULL; hlua_sub->hlua = NULL; hlua_sub->paused = 0; if ((task = task_new_here()) == NULL) goto mem_error; task->process = hlua_event_runner; task->context = hlua_sub; event_hdl_async_equeue_init(&hlua_sub->equeue); hlua_sub->task = task; hlua_sub->fcn_ref = fcn_ref; hlua_sub->state_id = state_id; hlua_sub->hlua = pool_alloc(pool_head_hlua); if (!hlua_sub->hlua) goto mem_error; HLUA_INIT(hlua_sub->hlua); if (!hlua_ctx_init(hlua_sub->hlua, hlua_sub->state_id, task)) goto mem_error; hlua_sub->sub = event_hdl_subscribe_ptr(list, e_type, EVENT_HDL_ASYNC_TASK(&hlua_sub->equeue, task, hlua_sub, NULL)); if (!hlua_sub->sub) goto mem_error; return hlua_sub->sub; /* returns pointer to event_hdl_sub struct */ mem_error: if (hlua_sub) { task_destroy(hlua_sub->task); if (hlua_sub->hlua) hlua_ctx_destroy(hlua_sub->hlua); pool_free(pool_head_hlua_event_sub, hlua_sub); } return NULL; } /* looks for an array of strings referring to a composition of event_hdl subscription * types at in stack */ __LJMP static struct event_hdl_sub_type hlua_check_event_sub_types(lua_State *L, int index) { struct event_hdl_sub_type subscriptions; const char *msg; if (lua_type(L, index) != LUA_TTABLE) { msg = lua_pushfstring(L, "table of strings expected, got %s", luaL_typename(L, index)); luaL_argerror(L, index, msg); } subscriptions = EVENT_HDL_SUB_NONE; /* browse the argument as an array. */ lua_pushnil(L); while (lua_next(L, index) != 0) { if (lua_type(L, -1) != LUA_TSTRING) { msg = lua_pushfstring(L, "table of strings expected, got %s", luaL_typename(L, index)); luaL_argerror(L, index, msg); } if (event_hdl_sub_type_equal(EVENT_HDL_SUB_NONE, event_hdl_string_to_sub_type(lua_tostring(L, -1)))) { msg = lua_pushfstring(L, "'%s' event type is unknown", lua_tostring(L, -1)); luaL_argerror(L, index, msg); } /* perform subscriptions |= current sub */ subscriptions = event_hdl_sub_type_add(subscriptions, event_hdl_string_to_sub_type(lua_tostring(L, -1))); /* pop the current value. */ lua_pop(L, 1); } return subscriptions; } /* Wrapper for hlua_fcn_new_event_sub(): catch errors raised by * the function to prevent LJMP * * If no error occurred, the function returns 1, else it returns 0 and * the error message is pushed at the top of the stack */ __LJMP static int _hlua_new_event_sub_safe(lua_State *L) { struct event_hdl_sub *sub = lua_touserdata(L, 1); /* this function may raise errors */ return MAY_LJMP(hlua_fcn_new_event_sub(L, sub)); } static int hlua_new_event_sub_safe(lua_State *L, struct event_hdl_sub *sub) { if (!lua_checkstack(L, 2)) return 0; lua_pushcfunction(L, _hlua_new_event_sub_safe); lua_pushlightuserdata(L, sub); switch (lua_pcall(L, 1, 1, 0)) { case LUA_OK: return 1; default: /* error was caught */ return 0; } } /* This function is a LUA helper used for registering lua event callbacks. * It expects an event subscription array and the function to be executed * when subscribed events occur (stack arguments). * It can be called from the "init" section, "post init" or during the runtime. * * is the subscription list where the subscription will be attempted * * Pushes the newly allocated subscription on the stack on success */ __LJMP int hlua_event_sub(lua_State *L, event_hdl_sub_list *sub_list) { struct hlua *hlua; struct event_hdl_sub *sub; struct event_hdl_sub_type subscriptions; int fcn_ref; int state_id; MAY_LJMP(check_args(L, 2, "event_sub")); /* Get the reference state */ hlua = hlua_gethlua(L); if (hlua) /* we are in runtime processing, any thread may subscribe to events: * subscription events will be handled by the thread who performed * the registration. */ state_id = hlua->state_id; else { /* we are in initialization mode, only thread 0 (actual calling thread) * may subscribe to events to prevent the same handler (from different lua * stacks) from being registered multiple times * * hlua_state_id == 0: monostack (lua-load) * hlua_state_id > 0: hlua_state_id=tid+1, multi-stack (lua-load-per-thread) * (thus if hlua_state_id > 1, it means we are not in primary thread ctx) */ if (hlua_state_id > 1) return 0; /* skip registration */ state_id = hlua_state_id; } /* First argument : event subscriptions. */ subscriptions = MAY_LJMP(hlua_check_event_sub_types(L, 1)); if (event_hdl_sub_type_equal(subscriptions, EVENT_HDL_SUB_NONE)) { WILL_LJMP(luaL_error(L, "event_sub: no valid event types were provided")); return 0; /* Never reached */ } /* Second argument : lua function. */ fcn_ref = MAY_LJMP(hlua_checkfunction(L, 2)); /* try to subscribe */ sub = hlua_event_subscribe(sub_list, subscriptions, state_id, fcn_ref); if (!sub) { hlua_unref(L, fcn_ref); WILL_LJMP(luaL_error(L, "event_sub: lua out of memory error")); return 0; /* Never reached */ } /* push the subscription to the stack * * Here we use the safe function so that lua errors will be * handled explicitly to prevent 'sub' from being lost */ if (!hlua_new_event_sub_safe(L, sub)) { /* Some events could already be pending in the handler's queue. * However it is wiser to cancel the subscription since we are unable to * provide a valid reference to it. * Pending events will be delivered (unless lua keeps raising errors). */ event_hdl_unsubscribe(sub); /* cancel the subscription */ WILL_LJMP(luaL_error(L, "event_sub: cannot push the subscription (%s)", lua_tostring(L, -1))); return 0; /* Never reached */ } event_hdl_drop(sub); /* sub has been duplicated, discard old ref */ return 1; } /* This function is a LUA wrapper used for registering global lua event callbacks * The new subscription is pushed onto the stack on success * Returns the number of arguments pushed to the stack (1 for success) */ __LJMP static int hlua_event_global_sub(lua_State *L) { /* NULL = global subscription list */ return MAY_LJMP(hlua_event_sub(L, NULL)); } /* Wrapper called by HAProxy to execute an LUA converter. This wrapper * doesn't allow "yield" functions because the HAProxy engine cannot * resume converters. */ static int hlua_sample_conv_wrapper(const struct arg *arg_p, struct sample *smp, void *private) { struct hlua_function *fcn = private; struct stream *stream = smp->strm; struct hlua *hlua = NULL; const char *error; if (!stream) return 0; if (!(hlua = hlua_stream_ctx_prepare(stream, fcn_ref_to_stack_id(fcn)))) { SEND_ERR(stream->be, "Lua converter '%s': can't initialize Lua context.\n", fcn->name); return 0; } /* If it is the first run, initialize the data for the call. */ if (!HLUA_IS_RUNNING(hlua)) { /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(stream->be, "Lua converter '%s': %s.\n", fcn->name, error); hlua_unlock(hlua); return 0; } /* Check stack available size. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name); RESET_SAFE_LJMP(hlua); return 0; } /* Restore the function in the stack. */ hlua_pushref(hlua->T, fcn->function_ref[hlua->state_id]); /* convert input sample and pust-it in the stack. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name); RESET_SAFE_LJMP(hlua); return 0; } MAY_LJMP(hlua_smp2lua(hlua->T, smp)); hlua->nargs = 1; /* push keywords in the stack. */ if (arg_p) { for (; arg_p->type != ARGT_STOP; arg_p++) { if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name); RESET_SAFE_LJMP(hlua); return 0; } MAY_LJMP(hlua_arg2lua(hlua->T, arg_p)); hlua->nargs++; } } /* We must initialize the execution timeouts. */ hlua_timer_init(&hlua->timer, hlua_timeout_session); /* At this point the execution is safe. */ RESET_SAFE_LJMP(hlua); } /* Execute the function. */ switch (hlua_ctx_resume(hlua, 0)) { /* finished. */ case HLUA_E_OK: hlua_lock(hlua); /* If the stack is empty, the function fails. */ if (lua_gettop(hlua->T) <= 0) { hlua_unlock(hlua); return 0; } /* Convert the returned value in sample. */ hlua_lua2smp(hlua->T, -1, smp); /* dup the smp before popping the related lua value and * returning it to haproxy */ smp_dup(smp); lua_pop(hlua->T, 1); hlua_unlock(hlua); return 1; /* yield. */ case HLUA_E_AGAIN: SEND_ERR(stream->be, "Lua converter '%s': cannot use yielded functions.\n", fcn->name); return 0; /* finished with error. */ case HLUA_E_ERRMSG: /* Display log. */ hlua_lock(hlua); SEND_ERR(stream->be, "Lua converter '%s': %s.\n", fcn->name, hlua_tostring_safe(hlua->T, -1)); lua_pop(hlua->T, 1); hlua_unlock(hlua); return 0; case HLUA_E_ETMOUT: SEND_ERR(stream->be, "Lua converter '%s': execution timeout.\n", fcn->name); return 0; case HLUA_E_NOMEM: SEND_ERR(stream->be, "Lua converter '%s': out of memory error.\n", fcn->name); return 0; case HLUA_E_YIELD: SEND_ERR(stream->be, "Lua converter '%s': yield functions like core.tcp() or core.sleep() are not allowed.\n", fcn->name); return 0; case HLUA_E_ERR: /* Display log. */ SEND_ERR(stream->be, "Lua converter '%s' returns an unknown error.\n", fcn->name); __fallthrough; default: return 0; } } /* Wrapper called by HAProxy to execute a sample-fetch. this wrapper * doesn't allow "yield" functions because the HAProxy engine cannot * resume sample-fetches. This function will be called by the sample * fetch engine to call lua-based fetch operations. */ static int hlua_sample_fetch_wrapper(const struct arg *arg_p, struct sample *smp, const char *kw, void *private) { struct hlua_function *fcn = private; struct stream *stream = smp->strm; struct hlua *hlua = NULL; const char *error; unsigned int hflags = HLUA_TXN_NOTERM | HLUA_TXN_SMP_CTX; if (!stream) return 0; if (!(hlua = hlua_stream_ctx_prepare(stream, fcn_ref_to_stack_id(fcn)))) { SEND_ERR(stream->be, "Lua sample-fetch '%s': can't initialize Lua context.\n", fcn->name); return 0; } /* If it is the first run, initialize the data for the call. */ if (!HLUA_IS_RUNNING(hlua)) { /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n", fcn->name, error); hlua_unlock(hlua); return 0; } /* Check stack available size. */ if (!lua_checkstack(hlua->T, 2)) { SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name); RESET_SAFE_LJMP(hlua); return 0; } /* Restore the function in the stack. */ hlua_pushref(hlua->T, fcn->function_ref[hlua->state_id]); /* push arguments in the stack. */ if (!hlua_txn_new(hlua->T, stream, smp->px, smp->opt & SMP_OPT_DIR, hflags)) { SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name); RESET_SAFE_LJMP(hlua); return 0; } hlua->nargs = 1; /* push keywords in the stack. */ for (; arg_p && arg_p->type != ARGT_STOP; arg_p++) { /* Check stack available size. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name); RESET_SAFE_LJMP(hlua); return 0; } MAY_LJMP(hlua_arg2lua(hlua->T, arg_p)); hlua->nargs++; } /* We must initialize the execution timeouts. */ hlua_timer_init(&hlua->timer, hlua_timeout_session); /* At this point the execution is safe. */ RESET_SAFE_LJMP(hlua); } /* Execute the function. */ switch (hlua_ctx_resume(hlua, 0)) { /* finished. */ case HLUA_E_OK: hlua_lock(hlua); /* If the stack is empty, the function fails. */ if (lua_gettop(hlua->T) <= 0) { hlua_unlock(hlua); return 0; } /* Convert the returned value in sample. */ hlua_lua2smp(hlua->T, -1, smp); /* dup the smp before popping the related lua value and * returning it to haproxy */ smp_dup(smp); lua_pop(hlua->T, 1); hlua_unlock(hlua); /* Set the end of execution flag. */ smp->flags &= ~SMP_F_MAY_CHANGE; return 1; /* yield. */ case HLUA_E_AGAIN: SEND_ERR(smp->px, "Lua sample-fetch '%s': cannot use yielded functions.\n", fcn->name); return 0; /* finished with error. */ case HLUA_E_ERRMSG: /* Display log. */ hlua_lock(hlua); SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n", fcn->name, hlua_tostring_safe(hlua->T, -1)); lua_pop(hlua->T, 1); hlua_unlock(hlua); return 0; case HLUA_E_ETMOUT: SEND_ERR(smp->px, "Lua sample-fetch '%s': execution timeout.\n", fcn->name); return 0; case HLUA_E_NOMEM: SEND_ERR(smp->px, "Lua sample-fetch '%s': out of memory error.\n", fcn->name); return 0; case HLUA_E_YIELD: SEND_ERR(smp->px, "Lua sample-fetch '%s': yield not allowed.\n", fcn->name); return 0; case HLUA_E_ERR: /* Display log. */ SEND_ERR(smp->px, "Lua sample-fetch '%s' returns an unknown error.\n", fcn->name); __fallthrough; default: return 0; } } /* This function is an LUA binding used for registering * "sample-conv" functions. It expects a converter name used * in the haproxy configuration file, and an LUA function. */ __LJMP static int hlua_register_converters(lua_State *L) { struct sample_conv_kw_list *sck; const char *name; int ref; int len; struct hlua_function *fcn = NULL; struct sample_conv *sc; struct buffer *trash; MAY_LJMP(check_args(L, 2, "register_converters")); if (hlua_gethlua(L)) { /* runtime processing */ WILL_LJMP(luaL_error(L, "register_converters: not available outside of body context")); } /* First argument : converter name. */ name = MAY_LJMP(luaL_checkstring(L, 1)); /* Second argument : lua function. */ ref = MAY_LJMP(hlua_checkfunction(L, 2)); /* Check if the converter is already registered */ trash = get_trash_chunk(); chunk_printf(trash, "lua.%s", name); sc = find_sample_conv(trash->area, trash->data); if (sc != NULL) { fcn = sc->private; if (fcn->function_ref[hlua_state_id] != -1) { ha_warning("Trying to register converter 'lua.%s' more than once. " "This will become a hard error in version 2.5.\n", name); hlua_unref(L, fcn->function_ref[hlua_state_id]); } fcn->function_ref[hlua_state_id] = ref; return 0; } /* Allocate and fill the sample fetch keyword struct. */ sck = calloc(1, sizeof(*sck) + sizeof(struct sample_conv) * 2); if (!sck) goto alloc_error; fcn = new_hlua_function(); if (!fcn) goto alloc_error; /* Fill fcn. */ fcn->name = strdup(name); if (!fcn->name) goto alloc_error; fcn->function_ref[hlua_state_id] = ref; /* List head */ sck->list.n = sck->list.p = NULL; /* converter keyword. */ len = strlen("lua.") + strlen(name) + 1; sck->kw[0].kw = calloc(1, len); if (!sck->kw[0].kw) goto alloc_error; snprintf((char *)sck->kw[0].kw, len, "lua.%s", name); sck->kw[0].process = hlua_sample_conv_wrapper; sck->kw[0].arg_mask = ARG12(0,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR); sck->kw[0].val_args = NULL; sck->kw[0].in_type = SMP_T_STR; sck->kw[0].out_type = SMP_T_STR; sck->kw[0].private = fcn; /* Register this new converter */ sample_register_convs(sck); return 0; alloc_error: release_hlua_function(fcn); hlua_unref(L, ref); ha_free(&sck); WILL_LJMP(luaL_error(L, "Lua out of memory error.")); return 0; /* Never reached */ } /* This function is an LUA binding used for registering * "sample-fetch" functions. It expects a converter name used * in the haproxy configuration file, and an LUA function. */ __LJMP static int hlua_register_fetches(lua_State *L) { const char *name; int ref; int len; struct sample_fetch_kw_list *sfk; struct hlua_function *fcn = NULL; struct sample_fetch *sf; struct buffer *trash; MAY_LJMP(check_args(L, 2, "register_fetches")); if (hlua_gethlua(L)) { /* runtime processing */ WILL_LJMP(luaL_error(L, "register_fetches: not available outside of body context")); } /* First argument : sample-fetch name. */ name = MAY_LJMP(luaL_checkstring(L, 1)); /* Second argument : lua function. */ ref = MAY_LJMP(hlua_checkfunction(L, 2)); /* Check if the sample-fetch is already registered */ trash = get_trash_chunk(); chunk_printf(trash, "lua.%s", name); sf = find_sample_fetch(trash->area, trash->data); if (sf != NULL) { fcn = sf->private; if (fcn->function_ref[hlua_state_id] != -1) { ha_warning("Trying to register sample-fetch 'lua.%s' more than once. " "This will become a hard error in version 2.5.\n", name); hlua_unref(L, fcn->function_ref[hlua_state_id]); } fcn->function_ref[hlua_state_id] = ref; return 0; } /* Allocate and fill the sample fetch keyword struct. */ sfk = calloc(1, sizeof(*sfk) + sizeof(struct sample_fetch) * 2); if (!sfk) goto alloc_error; fcn = new_hlua_function(); if (!fcn) goto alloc_error; /* Fill fcn. */ fcn->name = strdup(name); if (!fcn->name) goto alloc_error; fcn->function_ref[hlua_state_id] = ref; /* List head */ sfk->list.n = sfk->list.p = NULL; /* sample-fetch keyword. */ len = strlen("lua.") + strlen(name) + 1; sfk->kw[0].kw = calloc(1, len); if (!sfk->kw[0].kw) goto alloc_error; snprintf((char *)sfk->kw[0].kw, len, "lua.%s", name); sfk->kw[0].process = hlua_sample_fetch_wrapper; sfk->kw[0].arg_mask = ARG12(0,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR); sfk->kw[0].val_args = NULL; sfk->kw[0].out_type = SMP_T_STR; sfk->kw[0].use = SMP_USE_HTTP_ANY; sfk->kw[0].val = 0; sfk->kw[0].private = fcn; /* Register this new fetch. */ sample_register_fetches(sfk); return 0; alloc_error: release_hlua_function(fcn); hlua_unref(L, ref); ha_free(&sfk); WILL_LJMP(luaL_error(L, "Lua out of memory error.")); return 0; /* Never reached */ } /* This function is a lua binding to set the wake_time. */ __LJMP static int hlua_set_wake_time(lua_State *L) { struct hlua *hlua; unsigned int delay; int wakeup_ms; // tick value /* Get hlua struct, or NULL if we execute from main lua state */ hlua = hlua_gethlua(L); if (!hlua) { return 0; } MAY_LJMP(check_args(L, 1, "wake_time")); delay = MAY_LJMP(luaL_checkinteger(L, 1)); wakeup_ms = tick_add(now_ms, delay); hlua->wake_time = wakeup_ms; return 0; } /* This function is a wrapper to execute each LUA function declared as an action * wrapper during the initialisation period. This function may return any * ACT_RET_* value. On error ACT_RET_CONT is returned and the action is * ignored. If the lua action yields, ACT_RET_YIELD is returned. On success, the * return value is the first element on the stack. */ static enum act_return hlua_action(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int flags) { char **arg; unsigned int hflags = HLUA_TXN_ACT_CTX; int dir, act_ret = ACT_RET_CONT; const char *error; struct hlua *hlua = NULL; switch (rule->from) { case ACT_F_TCP_REQ_CNT: dir = SMP_OPT_DIR_REQ; break; case ACT_F_TCP_RES_CNT: dir = SMP_OPT_DIR_RES; break; case ACT_F_HTTP_REQ: dir = SMP_OPT_DIR_REQ; break; case ACT_F_HTTP_RES: dir = SMP_OPT_DIR_RES; break; default: SEND_ERR(px, "Lua: internal error while execute action.\n"); goto end; } if (!(hlua = hlua_stream_ctx_prepare(s, fcn_ref_to_stack_id(rule->arg.hlua_rule->fcn)))) { SEND_ERR(px, "Lua action '%s': can't initialize Lua context.\n", rule->arg.hlua_rule->fcn->name); goto end; } /* If it is the first run, initialize the data for the call. */ if (!HLUA_IS_RUNNING(hlua)) { /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(px, "Lua function '%s': %s.\n", rule->arg.hlua_rule->fcn->name, error); hlua_unlock(hlua); goto end; } /* Check stack available size. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(px, "Lua function '%s': full stack.\n", rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); goto end; } /* Restore the function in the stack. */ hlua_pushref(hlua->T, rule->arg.hlua_rule->fcn->function_ref[hlua->state_id]); /* Create and and push object stream in the stack. */ if (!hlua_txn_new(hlua->T, s, px, dir, hflags)) { SEND_ERR(px, "Lua function '%s': full stack.\n", rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); goto end; } hlua->nargs = 1; /* push keywords in the stack. */ for (arg = rule->arg.hlua_rule->args; arg && *arg; arg++) { if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(px, "Lua function '%s': full stack.\n", rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); goto end; } lua_pushstring(hlua->T, *arg); hlua->nargs++; } /* Now the execution is safe. */ RESET_SAFE_LJMP(hlua); /* We must initialize the execution timeouts. */ hlua_timer_init(&hlua->timer, hlua_timeout_session); } /* Execute the function. */ switch (hlua_ctx_resume(hlua, !(flags & ACT_OPT_FINAL))) { /* finished. */ case HLUA_E_OK: /* Catch the return value */ hlua_lock(hlua); if (lua_gettop(hlua->T) > 0) act_ret = lua_tointeger(hlua->T, -1); hlua_unlock(hlua); /* Set timeout in the required channel. */ if (act_ret == ACT_RET_YIELD) { if (flags & ACT_OPT_FINAL) goto err_yield; if (dir == SMP_OPT_DIR_REQ) s->req.analyse_exp = tick_first((tick_is_expired(s->req.analyse_exp, now_ms) ? 0 : s->req.analyse_exp), hlua->wake_time); else s->res.analyse_exp = tick_first((tick_is_expired(s->res.analyse_exp, now_ms) ? 0 : s->res.analyse_exp), hlua->wake_time); } goto end; /* yield. */ case HLUA_E_AGAIN: /* Set timeout in the required channel. */ if (dir == SMP_OPT_DIR_REQ) s->req.analyse_exp = tick_first((tick_is_expired(s->req.analyse_exp, now_ms) ? 0 : s->req.analyse_exp), hlua->wake_time); else s->res.analyse_exp = tick_first((tick_is_expired(s->res.analyse_exp, now_ms) ? 0 : s->res.analyse_exp), hlua->wake_time); /* Some actions can be wake up when a "write" event * is detected on a response channel. This is useful * only for actions targeted on the requests. */ if (HLUA_IS_WAKERESWR(hlua)) s->res.flags |= CF_WAKE_WRITE; if (HLUA_IS_WAKEREQWR(hlua)) s->req.flags |= CF_WAKE_WRITE; act_ret = ACT_RET_YIELD; goto end; /* finished with error. */ case HLUA_E_ERRMSG: /* Display log. */ hlua_lock(hlua); SEND_ERR(px, "Lua function '%s': %s.\n", rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1)); lua_pop(hlua->T, 1); hlua_unlock(hlua); goto end; case HLUA_E_ETMOUT: SEND_ERR(px, "Lua function '%s': execution timeout.\n", rule->arg.hlua_rule->fcn->name); goto end; case HLUA_E_NOMEM: SEND_ERR(px, "Lua function '%s': out of memory error.\n", rule->arg.hlua_rule->fcn->name); goto end; case HLUA_E_YIELD: err_yield: act_ret = ACT_RET_CONT; SEND_ERR(px, "Lua function '%s': yield not allowed.\n", rule->arg.hlua_rule->fcn->name); goto end; case HLUA_E_ERR: /* Display log. */ SEND_ERR(px, "Lua function '%s' return an unknown error.\n", rule->arg.hlua_rule->fcn->name); default: goto end; } end: if (act_ret != ACT_RET_YIELD && hlua) hlua->wake_time = TICK_ETERNITY; return act_ret; } struct task *hlua_applet_wakeup(struct task *t, void *context, unsigned int state) { struct appctx *ctx = context; appctx_wakeup(ctx); t->expire = TICK_ETERNITY; return t; } static int hlua_applet_tcp_init(struct appctx *ctx) { struct hlua_tcp_ctx *tcp_ctx = applet_reserve_svcctx(ctx, sizeof(*tcp_ctx)); struct stconn *sc = appctx_sc(ctx); struct stream *strm = __sc_strm(sc); struct hlua *hlua; struct task *task; char **arg; const char *error; hlua = pool_alloc(pool_head_hlua); if (!hlua) { SEND_ERR(strm->be, "Lua applet tcp '%s': out of memory.\n", ctx->rule->arg.hlua_rule->fcn->name); return -1; } HLUA_INIT(hlua); tcp_ctx->hlua = hlua; tcp_ctx->flags = 0; /* Create task used by signal to wakeup applets. */ task = task_new_here(); if (!task) { SEND_ERR(strm->be, "Lua applet tcp '%s': out of memory.\n", ctx->rule->arg.hlua_rule->fcn->name); return -1; } task->nice = 0; task->context = ctx; task->process = hlua_applet_wakeup; tcp_ctx->task = task; /* In the execution wrappers linked with a stream, the * Lua context can be not initialized. This behavior * permits to save performances because a systematic * Lua initialization cause 5% performances loss. */ if (!hlua_ctx_init(hlua, fcn_ref_to_stack_id(ctx->rule->arg.hlua_rule->fcn), task)) { SEND_ERR(strm->be, "Lua applet tcp '%s': can't initialize Lua context.\n", ctx->rule->arg.hlua_rule->fcn->name); return -1; } /* Set timeout according with the applet configuration. */ hlua_timer_init(&hlua->timer, ctx->applet->timeout); /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(strm->be, "Lua applet tcp '%s': %s.\n", ctx->rule->arg.hlua_rule->fcn->name, error); hlua_unlock(hlua); return -1; } /* Check stack available size. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(strm->be, "Lua applet tcp '%s': full stack.\n", ctx->rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); return -1; } /* Restore the function in the stack. */ hlua_pushref(hlua->T, ctx->rule->arg.hlua_rule->fcn->function_ref[hlua->state_id]); /* Create and and push object stream in the stack. */ if (!hlua_applet_tcp_new(hlua->T, ctx)) { SEND_ERR(strm->be, "Lua applet tcp '%s': full stack.\n", ctx->rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); return -1; } hlua->nargs = 1; /* push keywords in the stack. */ for (arg = ctx->rule->arg.hlua_rule->args; arg && *arg; arg++) { if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(strm->be, "Lua applet tcp '%s': full stack.\n", ctx->rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); return -1; } lua_pushstring(hlua->T, *arg); hlua->nargs++; } RESET_SAFE_LJMP(hlua); /* Wakeup the applet ASAP. */ applet_need_more_data(ctx); applet_have_more_data(ctx); return 0; } void hlua_applet_tcp_fct(struct appctx *ctx) { struct hlua_tcp_ctx *tcp_ctx = ctx->svcctx; struct stconn *sc = appctx_sc(ctx); struct stream *strm = __sc_strm(sc); struct act_rule *rule = ctx->rule; struct proxy *px = strm->be; struct hlua *hlua = tcp_ctx->hlua; if (unlikely(se_fl_test(ctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) goto out; /* The applet execution is already done. */ if (tcp_ctx->flags & APPLET_DONE) goto out; /* Execute the function. */ switch (hlua_ctx_resume(hlua, 1)) { /* finished. */ case HLUA_E_OK: tcp_ctx->flags |= APPLET_DONE; se_fl_set(ctx->sedesc, SE_FL_EOI|SE_FL_EOS); break; /* yield. */ case HLUA_E_AGAIN: if (hlua->wake_time != TICK_ETERNITY) task_schedule(tcp_ctx->task, hlua->wake_time); break; /* finished with error. */ case HLUA_E_ERRMSG: /* Display log. */ hlua_lock(hlua); SEND_ERR(px, "Lua applet tcp '%s': %s.\n", rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1)); lua_pop(hlua->T, 1); hlua_unlock(hlua); goto error; case HLUA_E_ETMOUT: SEND_ERR(px, "Lua applet tcp '%s': execution timeout.\n", rule->arg.hlua_rule->fcn->name); goto error; case HLUA_E_NOMEM: SEND_ERR(px, "Lua applet tcp '%s': out of memory error.\n", rule->arg.hlua_rule->fcn->name); goto error; case HLUA_E_YIELD: /* unexpected */ SEND_ERR(px, "Lua applet tcp '%s': yield not allowed.\n", rule->arg.hlua_rule->fcn->name); goto error; case HLUA_E_ERR: /* Display log. */ SEND_ERR(px, "Lua applet tcp '%s' return an unknown error.\n", rule->arg.hlua_rule->fcn->name); goto error; default: goto error; } out: /* eat the whole request */ co_skip(sc_oc(sc), co_data(sc_oc(sc))); return; error: se_fl_set(ctx->sedesc, SE_FL_ERROR); tcp_ctx->flags |= APPLET_DONE; goto out; } static void hlua_applet_tcp_release(struct appctx *ctx) { struct hlua_tcp_ctx *tcp_ctx = ctx->svcctx; task_destroy(tcp_ctx->task); tcp_ctx->task = NULL; hlua_ctx_destroy(tcp_ctx->hlua); tcp_ctx->hlua = NULL; } /* The function returns 0 if the initialisation is complete or -1 if * an errors occurs. It also reserves the appctx for an hlua_http_ctx. */ static int hlua_applet_http_init(struct appctx *ctx) { struct hlua_http_ctx *http_ctx = applet_reserve_svcctx(ctx, sizeof(*http_ctx)); struct stconn *sc = appctx_sc(ctx); struct stream *strm = __sc_strm(sc); struct http_txn *txn; struct hlua *hlua; char **arg; struct task *task; const char *error; txn = strm->txn; hlua = pool_alloc(pool_head_hlua); if (!hlua) { SEND_ERR(strm->be, "Lua applet http '%s': out of memory.\n", ctx->rule->arg.hlua_rule->fcn->name); return -1; } HLUA_INIT(hlua); http_ctx->hlua = hlua; http_ctx->left_bytes = -1; http_ctx->flags = 0; if (txn->req.flags & HTTP_MSGF_VER_11) http_ctx->flags |= APPLET_HTTP11; /* Create task used by signal to wakeup applets. */ task = task_new_here(); if (!task) { SEND_ERR(strm->be, "Lua applet http '%s': out of memory.\n", ctx->rule->arg.hlua_rule->fcn->name); return -1; } task->nice = 0; task->context = ctx; task->process = hlua_applet_wakeup; http_ctx->task = task; /* In the execution wrappers linked with a stream, the * Lua context can be not initialized. This behavior * permits to save performances because a systematic * Lua initialization cause 5% performances loss. */ if (!hlua_ctx_init(hlua, fcn_ref_to_stack_id(ctx->rule->arg.hlua_rule->fcn), task)) { SEND_ERR(strm->be, "Lua applet http '%s': can't initialize Lua context.\n", ctx->rule->arg.hlua_rule->fcn->name); return -1; } /* Set timeout according with the applet configuration. */ hlua_timer_init(&hlua->timer, ctx->applet->timeout); /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(strm->be, "Lua applet http '%s': %s.\n", ctx->rule->arg.hlua_rule->fcn->name, error); hlua_unlock(hlua); return -1; } /* Check stack available size. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(strm->be, "Lua applet http '%s': full stack.\n", ctx->rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); return -1; } /* Restore the function in the stack. */ hlua_pushref(hlua->T, ctx->rule->arg.hlua_rule->fcn->function_ref[hlua->state_id]); /* Create and and push object stream in the stack. */ if (!hlua_applet_http_new(hlua->T, ctx)) { SEND_ERR(strm->be, "Lua applet http '%s': full stack.\n", ctx->rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); return -1; } hlua->nargs = 1; /* push keywords in the stack. */ for (arg = ctx->rule->arg.hlua_rule->args; arg && *arg; arg++) { if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(strm->be, "Lua applet http '%s': full stack.\n", ctx->rule->arg.hlua_rule->fcn->name); RESET_SAFE_LJMP(hlua); return -1; } lua_pushstring(hlua->T, *arg); hlua->nargs++; } RESET_SAFE_LJMP(hlua); /* Wakeup the applet when data is ready for read. */ applet_need_more_data(ctx); return 0; } void hlua_applet_http_fct(struct appctx *ctx) { struct hlua_http_ctx *http_ctx = ctx->svcctx; struct stconn *sc = appctx_sc(ctx); struct stream *strm = __sc_strm(sc); struct channel *req = sc_oc(sc); struct channel *res = sc_ic(sc); struct act_rule *rule = ctx->rule; struct proxy *px = strm->be; struct hlua *hlua = http_ctx->hlua; struct htx *req_htx, *res_htx; res_htx = htx_from_buf(&res->buf); if (unlikely(se_fl_test(ctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) goto out; /* The applet execution is already done. */ if (http_ctx->flags & APPLET_DONE) goto out; /* Check if the input buffer is available. */ if (!b_size(&res->buf)) { sc_need_room(sc, 0); goto out; } /* Set the currently running flag. */ if (!HLUA_IS_RUNNING(hlua) && !(http_ctx->flags & APPLET_DONE)) { if (!co_data(req)) { applet_need_more_data(ctx); goto out; } } /* Execute the function. */ switch (hlua_ctx_resume(hlua, 1)) { /* finished. */ case HLUA_E_OK: http_ctx->flags |= APPLET_DONE; break; /* yield. */ case HLUA_E_AGAIN: if (hlua->wake_time != TICK_ETERNITY) task_schedule(http_ctx->task, hlua->wake_time); goto out; /* finished with error. */ case HLUA_E_ERRMSG: /* Display log. */ hlua_lock(hlua); SEND_ERR(px, "Lua applet http '%s': %s.\n", rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1)); lua_pop(hlua->T, 1); hlua_unlock(hlua); goto error; case HLUA_E_ETMOUT: SEND_ERR(px, "Lua applet http '%s': execution timeout.\n", rule->arg.hlua_rule->fcn->name); goto error; case HLUA_E_NOMEM: SEND_ERR(px, "Lua applet http '%s': out of memory error.\n", rule->arg.hlua_rule->fcn->name); goto error; case HLUA_E_YIELD: /* unexpected */ SEND_ERR(px, "Lua applet http '%s': yield not allowed.\n", rule->arg.hlua_rule->fcn->name); goto error; case HLUA_E_ERR: /* Display log. */ SEND_ERR(px, "Lua applet http '%s' return an unknown error.\n", rule->arg.hlua_rule->fcn->name); goto error; default: goto error; } if (http_ctx->flags & APPLET_DONE) { if (http_ctx->flags & APPLET_RSP_SENT) goto out; if (!(http_ctx->flags & APPLET_HDR_SENT)) goto error; /* no more data are expected. If the response buffer is empty * for a chunked message, be sure to add something (EOT block in * this case) to have something to send. It is important to be * sure the EOM flags will be handled by the endpoint. */ if (htx_is_empty(res_htx) && (strm->txn->rsp.flags & (HTTP_MSGF_XFER_LEN|HTTP_MSGF_CNT_LEN)) == HTTP_MSGF_XFER_LEN) { if (!htx_add_endof(res_htx, HTX_BLK_EOT)) { sc_need_room(sc, sizeof(struct htx_blk)+1); goto out; } channel_add_input(res, 1); } res_htx->flags |= HTX_FL_EOM; se_fl_set(ctx->sedesc, SE_FL_EOI|SE_FL_EOS); strm->txn->status = http_ctx->status; http_ctx->flags |= APPLET_RSP_SENT; } out: htx_to_buf(res_htx, &res->buf); /* eat the whole request */ if (co_data(req)) { req_htx = htx_from_buf(&req->buf); co_htx_skip(req, req_htx, co_data(req)); htx_to_buf(req_htx, &req->buf); } return; error: /* If we are in HTTP mode, and we are not send any * data, return a 500 server error in best effort: * if there is no room available in the buffer, * just close the connection. */ if (!(http_ctx->flags & APPLET_HDR_SENT)) { struct buffer *err = &http_err_chunks[HTTP_ERR_500]; channel_erase(res); res->buf.data = b_data(err); memcpy(res->buf.area, b_head(err), b_data(err)); res_htx = htx_from_buf(&res->buf); channel_add_input(res, res_htx->data); se_fl_set(ctx->sedesc, SE_FL_EOI|SE_FL_EOS); } else se_fl_set(ctx->sedesc, SE_FL_ERROR); if (!(strm->flags & SF_ERR_MASK)) strm->flags |= SF_ERR_RESOURCE; http_ctx->flags |= APPLET_DONE; goto out; } static void hlua_applet_http_release(struct appctx *ctx) { struct hlua_http_ctx *http_ctx = ctx->svcctx; task_destroy(http_ctx->task); http_ctx->task = NULL; hlua_ctx_destroy(http_ctx->hlua); http_ctx->hlua = NULL; } /* global {tcp|http}-request parser. Return ACT_RET_PRS_OK in * success case, else return ACT_RET_PRS_ERR. * * This function can fail with an abort() due to an Lua critical error. * We are in the configuration parsing process of HAProxy, this abort() is * tolerated. */ static enum act_parse_ret action_register_lua(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err) { struct hlua_function *fcn = rule->kw->private; int i; /* Memory for the rule. */ rule->arg.hlua_rule = calloc(1, sizeof(*rule->arg.hlua_rule)); if (!rule->arg.hlua_rule) { memprintf(err, "out of memory error"); goto error; } /* Memory for arguments. */ rule->arg.hlua_rule->args = calloc(fcn->nargs + 1, sizeof(*rule->arg.hlua_rule->args)); if (!rule->arg.hlua_rule->args) { memprintf(err, "out of memory error"); goto error; } /* Reference the Lua function and store the reference. */ rule->arg.hlua_rule->fcn = fcn; /* Expect some arguments */ for (i = 0; i < fcn->nargs; i++) { if (*args[*cur_arg] == '\0') { memprintf(err, "expect %d arguments", fcn->nargs); goto error; } rule->arg.hlua_rule->args[i] = strdup(args[*cur_arg]); if (!rule->arg.hlua_rule->args[i]) { memprintf(err, "out of memory error"); goto error; } (*cur_arg)++; } rule->arg.hlua_rule->args[i] = NULL; rule->action = ACT_CUSTOM; rule->action_ptr = hlua_action; return ACT_RET_PRS_OK; error: if (rule->arg.hlua_rule) { if (rule->arg.hlua_rule->args) { for (i = 0; i < fcn->nargs; i++) ha_free(&rule->arg.hlua_rule->args[i]); ha_free(&rule->arg.hlua_rule->args); } ha_free(&rule->arg.hlua_rule); } return ACT_RET_PRS_ERR; } static enum act_parse_ret action_register_service_http(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err) { struct hlua_function *fcn = rule->kw->private; /* HTTP applets are forbidden in tcp-request rules. * HTTP applet request requires everything initialized by * "http_process_request" (analyzer flag AN_REQ_HTTP_INNER). * The applet will be immediately initialized, but its before * the call of this analyzer. */ if (rule->from != ACT_F_HTTP_REQ) { memprintf(err, "HTTP applets are forbidden from 'tcp-request' rulesets"); return ACT_RET_PRS_ERR; } /* Memory for the rule. */ rule->arg.hlua_rule = calloc(1, sizeof(*rule->arg.hlua_rule)); if (!rule->arg.hlua_rule) { memprintf(err, "out of memory error"); return ACT_RET_PRS_ERR; } /* Reference the Lua function and store the reference. */ rule->arg.hlua_rule->fcn = fcn; /* TODO: later accept arguments. */ rule->arg.hlua_rule->args = NULL; /* Add applet pointer in the rule. */ rule->applet.obj_type = OBJ_TYPE_APPLET; rule->applet.name = fcn->name; rule->applet.init = hlua_applet_http_init; rule->applet.fct = hlua_applet_http_fct; rule->applet.release = hlua_applet_http_release; rule->applet.timeout = hlua_timeout_applet; return ACT_RET_PRS_OK; } /* This function is an LUA binding used for registering * "sample-conv" functions. It expects a converter name used * in the haproxy configuration file, and an LUA function. */ __LJMP static int hlua_register_action(lua_State *L) { struct action_kw_list *akl = NULL; const char *name; int ref; int len; struct hlua_function *fcn = NULL; int nargs; struct buffer *trash; struct action_kw *akw; /* Initialise the number of expected arguments at 0. */ nargs = 0; if (lua_gettop(L) < 3 || lua_gettop(L) > 4) WILL_LJMP(luaL_error(L, "'register_action' needs between 3 and 4 arguments")); if (hlua_gethlua(L)) { /* runtime processing */ WILL_LJMP(luaL_error(L, "register_action: not available outside of body context")); } /* First argument : converter name. */ name = MAY_LJMP(luaL_checkstring(L, 1)); /* Second argument : environment. */ if (lua_type(L, 2) != LUA_TTABLE) WILL_LJMP(luaL_error(L, "register_action: second argument must be a table of strings")); /* Third argument : lua function. */ ref = MAY_LJMP(hlua_checkfunction(L, 3)); /* Fourth argument : number of mandatory arguments expected on the configuration line. */ if (lua_gettop(L) >= 4) nargs = MAY_LJMP(luaL_checkinteger(L, 4)); /* browse the second argument as an array. */ lua_pushnil(L); while (lua_next(L, 2) != 0) { if (lua_type(L, -1) != LUA_TSTRING) { hlua_unref(L, ref); WILL_LJMP(luaL_error(L, "register_action: second argument must be a table of strings")); } /* Check if action exists */ trash = get_trash_chunk(); chunk_printf(trash, "lua.%s", name); if (strcmp(lua_tostring(L, -1), "tcp-req") == 0) { akw = tcp_req_cont_action(trash->area); } else if (strcmp(lua_tostring(L, -1), "tcp-res") == 0) { akw = tcp_res_cont_action(trash->area); } else if (strcmp(lua_tostring(L, -1), "http-req") == 0) { akw = action_http_req_custom(trash->area); } else if (strcmp(lua_tostring(L, -1), "http-res") == 0) { akw = action_http_res_custom(trash->area); } else if (strcmp(lua_tostring(L, -1), "http-after-res") == 0) { akw = action_http_after_res_custom(trash->area); } else { akw = NULL; } if (akw != NULL) { fcn = akw->private; if (fcn->function_ref[hlua_state_id] != -1) { ha_warning("Trying to register action 'lua.%s' more than once. " "This will become a hard error in version 2.5.\n", name); hlua_unref(L, fcn->function_ref[hlua_state_id]); } fcn->function_ref[hlua_state_id] = ref; /* pop the environment string. */ lua_pop(L, 1); continue; } /* Check required environment. Only accepted "http" or "tcp". */ /* Allocate and fill the sample fetch keyword struct. */ akl = calloc(1, sizeof(*akl) + sizeof(struct action_kw) * 2); if (!akl) goto alloc_error;; fcn = new_hlua_function(); if (!fcn) goto alloc_error; /* Fill fcn. */ fcn->name = strdup(name); if (!fcn->name) goto alloc_error; fcn->function_ref[hlua_state_id] = ref; /* Set the expected number of arguments. */ fcn->nargs = nargs; /* List head */ akl->list.n = akl->list.p = NULL; /* action keyword. */ len = strlen("lua.") + strlen(name) + 1; akl->kw[0].kw = calloc(1, len); if (!akl->kw[0].kw) goto alloc_error; snprintf((char *)akl->kw[0].kw, len, "lua.%s", name); akl->kw[0].flags = 0; akl->kw[0].private = fcn; akl->kw[0].parse = action_register_lua; /* select the action registering point. */ if (strcmp(lua_tostring(L, -1), "tcp-req") == 0) tcp_req_cont_keywords_register(akl); else if (strcmp(lua_tostring(L, -1), "tcp-res") == 0) tcp_res_cont_keywords_register(akl); else if (strcmp(lua_tostring(L, -1), "http-req") == 0) http_req_keywords_register(akl); else if (strcmp(lua_tostring(L, -1), "http-res") == 0) http_res_keywords_register(akl); else if (strcmp(lua_tostring(L, -1), "http-after-res") == 0) http_after_res_keywords_register(akl); else { release_hlua_function(fcn); hlua_unref(L, ref); if (akl) ha_free((char **)&(akl->kw[0].kw)); ha_free(&akl); WILL_LJMP(luaL_error(L, "Lua action environment '%s' is unknown. " "'tcp-req', 'tcp-res', 'http-req', 'http-res' " "or 'http-after-res' " "are expected.", lua_tostring(L, -1))); } /* pop the environment string. */ lua_pop(L, 1); /* reset for next loop */ akl = NULL; fcn = NULL; } return ACT_RET_PRS_OK; alloc_error: release_hlua_function(fcn); hlua_unref(L, ref); ha_free(&akl); WILL_LJMP(luaL_error(L, "Lua out of memory error.")); return 0; /* Never reached */ } static enum act_parse_ret action_register_service_tcp(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err) { struct hlua_function *fcn = rule->kw->private; if (px->mode == PR_MODE_HTTP) { memprintf(err, "Lua TCP services cannot be used on HTTP proxies"); return ACT_RET_PRS_ERR; } /* Memory for the rule. */ rule->arg.hlua_rule = calloc(1, sizeof(*rule->arg.hlua_rule)); if (!rule->arg.hlua_rule) { memprintf(err, "out of memory error"); return ACT_RET_PRS_ERR; } /* Reference the Lua function and store the reference. */ rule->arg.hlua_rule->fcn = fcn; /* TODO: later accept arguments. */ rule->arg.hlua_rule->args = NULL; /* Add applet pointer in the rule. */ rule->applet.obj_type = OBJ_TYPE_APPLET; rule->applet.name = fcn->name; rule->applet.init = hlua_applet_tcp_init; rule->applet.fct = hlua_applet_tcp_fct; rule->applet.release = hlua_applet_tcp_release; rule->applet.timeout = hlua_timeout_applet; return 0; } /* This function is an LUA binding used for registering * "sample-conv" functions. It expects a converter name used * in the haproxy configuration file, and an LUA function. */ __LJMP static int hlua_register_service(lua_State *L) { struct action_kw_list *akl; const char *name; const char *env; int ref; int len; struct hlua_function *fcn = NULL; struct buffer *trash; struct action_kw *akw; MAY_LJMP(check_args(L, 3, "register_service")); if (hlua_gethlua(L)) { /* runtime processing */ WILL_LJMP(luaL_error(L, "register_service: not available outside of body context")); } /* First argument : converter name. */ name = MAY_LJMP(luaL_checkstring(L, 1)); /* Second argument : environment. */ env = MAY_LJMP(luaL_checkstring(L, 2)); /* Third argument : lua function. */ ref = MAY_LJMP(hlua_checkfunction(L, 3)); /* Check for service already registered */ trash = get_trash_chunk(); chunk_printf(trash, "lua.%s", name); akw = service_find(trash->area); if (akw != NULL) { fcn = akw->private; if (fcn->function_ref[hlua_state_id] != -1) { ha_warning("Trying to register service 'lua.%s' more than once. " "This will become a hard error in version 2.5.\n", name); hlua_unref(L, fcn->function_ref[hlua_state_id]); } fcn->function_ref[hlua_state_id] = ref; return 0; } /* Allocate and fill the sample fetch keyword struct. */ akl = calloc(1, sizeof(*akl) + sizeof(struct action_kw) * 2); if (!akl) goto alloc_error; fcn = new_hlua_function(); if (!fcn) goto alloc_error; /* Fill fcn. */ len = strlen("") + strlen(name) + 1; fcn->name = calloc(1, len); if (!fcn->name) goto alloc_error; snprintf((char *)fcn->name, len, "", name); fcn->function_ref[hlua_state_id] = ref; /* List head */ akl->list.n = akl->list.p = NULL; /* converter keyword. */ len = strlen("lua.") + strlen(name) + 1; akl->kw[0].kw = calloc(1, len); if (!akl->kw[0].kw) goto alloc_error; snprintf((char *)akl->kw[0].kw, len, "lua.%s", name); /* Check required environment. Only accepted "http" or "tcp". */ if (strcmp(env, "tcp") == 0) akl->kw[0].parse = action_register_service_tcp; else if (strcmp(env, "http") == 0) akl->kw[0].parse = action_register_service_http; else { release_hlua_function(fcn); hlua_unref(L, ref); if (akl) ha_free((char **)&(akl->kw[0].kw)); ha_free(&akl); WILL_LJMP(luaL_error(L, "Lua service environment '%s' is unknown. " "'tcp' or 'http' are expected.", env)); } akl->kw[0].flags = 0; akl->kw[0].private = fcn; /* End of array. */ memset(&akl->kw[1], 0, sizeof(*akl->kw)); /* Register this new converter */ service_keywords_register(akl); return 0; alloc_error: release_hlua_function(fcn); hlua_unref(L, ref); ha_free(&akl); WILL_LJMP(luaL_error(L, "Lua out of memory error.")); return 0; /* Never reached */ } /* This function initialises Lua cli handler. It copies the * arguments in the Lua stack and create channel IO objects. */ static int hlua_cli_parse_fct(char **args, char *payload, struct appctx *appctx, void *private) { struct hlua_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); struct hlua *hlua; struct hlua_function *fcn; int i; const char *error; fcn = private; ctx->fcn = private; hlua = pool_alloc(pool_head_hlua); if (!hlua) { SEND_ERR(NULL, "Lua cli '%s': out of memory.\n", fcn->name); return 1; } HLUA_INIT(hlua); ctx->hlua = hlua; /* Create task used by signal to wakeup applets. * We use the same wakeup function than the Lua applet_tcp and * applet_http. It is absolutely compatible. */ ctx->task = task_new_here(); if (!ctx->task) { SEND_ERR(NULL, "Lua cli '%s': out of memory.\n", fcn->name); goto error; } ctx->task->nice = 0; ctx->task->context = appctx; ctx->task->process = hlua_applet_wakeup; /* Initialises the Lua context */ if (!hlua_ctx_init(hlua, fcn_ref_to_stack_id(fcn), ctx->task)) { SEND_ERR(NULL, "Lua cli '%s': can't initialize Lua context.\n", fcn->name); goto error; } /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(NULL, "Lua cli '%s': %s.\n", fcn->name, error); hlua_unlock(hlua); goto error; } /* Check stack available size. */ if (!lua_checkstack(hlua->T, 2)) { SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name); goto error; } /* Restore the function in the stack. */ hlua_pushref(hlua->T, fcn->function_ref[hlua->state_id]); /* Once the arguments parsed, the CLI is like an AppletTCP, * so push AppletTCP in the stack. */ if (!hlua_applet_tcp_new(hlua->T, appctx)) { SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name); goto error; } hlua->nargs = 1; /* push keywords in the stack. */ for (i = 0; *args[i]; i++) { /* Check stack available size. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name); goto error; } lua_pushstring(hlua->T, args[i]); hlua->nargs++; } /* We must initialize the execution timeouts. */ hlua_timer_init(&hlua->timer, hlua_timeout_session); /* At this point the execution is safe. */ RESET_SAFE_LJMP(hlua); /* It's ok */ return 0; /* It's not ok. */ error: RESET_SAFE_LJMP(hlua); hlua_ctx_destroy(hlua); ctx->hlua = NULL; return 1; } static int hlua_cli_io_handler_fct(struct appctx *appctx) { struct hlua_cli_ctx *ctx = appctx->svcctx; struct hlua *hlua; struct stconn *sc; struct hlua_function *fcn; hlua = ctx->hlua; sc = appctx_sc(appctx); fcn = ctx->fcn; /* Execute the function. */ switch (hlua_ctx_resume(hlua, 1)) { /* finished. */ case HLUA_E_OK: return 1; /* yield. */ case HLUA_E_AGAIN: /* We want write. */ if (HLUA_IS_WAKERESWR(hlua)) sc_need_room(sc, -1); /* Set the timeout. */ if (hlua->wake_time != TICK_ETERNITY) task_schedule(hlua->task, hlua->wake_time); return 0; /* finished with error. */ case HLUA_E_ERRMSG: /* Display log. */ hlua_lock(hlua); SEND_ERR(NULL, "Lua cli '%s': %s.\n", fcn->name, hlua_tostring_safe(hlua->T, -1)); lua_pop(hlua->T, 1); hlua_unlock(hlua); return 1; case HLUA_E_ETMOUT: SEND_ERR(NULL, "Lua converter '%s': execution timeout.\n", fcn->name); return 1; case HLUA_E_NOMEM: SEND_ERR(NULL, "Lua converter '%s': out of memory error.\n", fcn->name); return 1; case HLUA_E_YIELD: /* unexpected */ SEND_ERR(NULL, "Lua converter '%s': yield not allowed.\n", fcn->name); return 1; case HLUA_E_ERR: /* Display log. */ SEND_ERR(NULL, "Lua cli '%s' return an unknown error.\n", fcn->name); return 1; default: return 1; } return 1; } static void hlua_cli_io_release_fct(struct appctx *appctx) { struct hlua_cli_ctx *ctx = appctx->svcctx; hlua_ctx_destroy(ctx->hlua); ctx->hlua = NULL; } /* This function is an LUA binding used for registering * new keywords in the cli. It expects a list of keywords * which are the "path". It is limited to 5 keywords. A * description of the command, a function to be executed * for the parsing and a function for io handlers. */ __LJMP static int hlua_register_cli(lua_State *L) { struct cli_kw_list *cli_kws; const char *message; int ref_io; int len; struct hlua_function *fcn = NULL; int index; int i; struct buffer *trash; const char *kw[5]; struct cli_kw *cli_kw; const char *errmsg; char *end; MAY_LJMP(check_args(L, 3, "register_cli")); if (hlua_gethlua(L)) { /* runtime processing */ WILL_LJMP(luaL_error(L, "register_cli: not available outside of body context")); } /* First argument : an array of maximum 5 keywords. */ if (!lua_istable(L, 1)) WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table")); /* Second argument : string with contextual message. */ message = MAY_LJMP(luaL_checkstring(L, 2)); /* Third and fourth argument : lua function. */ ref_io = MAY_LJMP(hlua_checkfunction(L, 3)); /* Check for CLI service already registered */ trash = get_trash_chunk(); index = 0; lua_pushnil(L); memset(kw, 0, sizeof(kw)); while (lua_next(L, 1) != 0) { if (index >= CLI_PREFIX_KW_NB) { hlua_unref(L, ref_io); WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table with a maximum of 5 entries")); } if (lua_type(L, -1) != LUA_TSTRING) { hlua_unref(L, ref_io); WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table filled with strings")); } kw[index] = lua_tostring(L, -1); if (index == 0) chunk_printf(trash, "%s", kw[index]); else chunk_appendf(trash, " %s", kw[index]); index++; lua_pop(L, 1); } cli_kw = cli_find_kw_exact((char **)kw); if (cli_kw != NULL) { fcn = cli_kw->private; if (fcn->function_ref[hlua_state_id] != -1) { ha_warning("Trying to register CLI keyword 'lua.%s' more than once. " "This will become a hard error in version 2.5.\n", trash->area); hlua_unref(L, fcn->function_ref[hlua_state_id]); } fcn->function_ref[hlua_state_id] = ref_io; return 0; } /* Allocate and fill the sample fetch keyword struct. */ cli_kws = calloc(1, sizeof(*cli_kws) + sizeof(struct cli_kw) * 2); if (!cli_kws) { errmsg = "Lua out of memory error."; goto error; } fcn = new_hlua_function(); if (!fcn) { errmsg = "Lua out of memory error."; goto error; } /* Fill path. */ index = 0; lua_pushnil(L); while(lua_next(L, 1) != 0) { if (index >= 5) { errmsg = "1st argument must be a table with a maximum of 5 entries"; goto error; } if (lua_type(L, -1) != LUA_TSTRING) { errmsg = "1st argument must be a table filled with strings"; goto error; } cli_kws->kw[0].str_kw[index] = strdup(lua_tostring(L, -1)); if (!cli_kws->kw[0].str_kw[index]) { errmsg = "Lua out of memory error."; goto error; } index++; lua_pop(L, 1); } /* Copy help message. */ cli_kws->kw[0].usage = strdup(message); if (!cli_kws->kw[0].usage) { errmsg = "Lua out of memory error."; goto error; } /* Fill fcn io handler. */ len = strlen("") + 1; for (i = 0; i < index; i++) len += strlen(cli_kws->kw[0].str_kw[i]) + 1; fcn->name = calloc(1, len); if (!fcn->name) { errmsg = "Lua out of memory error."; goto error; } end = fcn->name; len = 8; memcpy(end, "kw[0].str_kw[i]); memcpy(end, cli_kws->kw[0].str_kw[i], len); end += len; } *(end++) = '>'; *(end++) = 0; fcn->function_ref[hlua_state_id] = ref_io; /* Fill last entries. */ cli_kws->kw[0].private = fcn; cli_kws->kw[0].parse = hlua_cli_parse_fct; cli_kws->kw[0].io_handler = hlua_cli_io_handler_fct; cli_kws->kw[0].io_release = hlua_cli_io_release_fct; /* Register this new converter */ cli_register_kw(cli_kws); return 0; error: release_hlua_function(fcn); hlua_unref(L, ref_io); if (cli_kws) { for (i = 0; i < index; i++) ha_free((char **)&(cli_kws->kw[0].str_kw[i])); ha_free((char **)&(cli_kws->kw[0].usage)); } ha_free(&cli_kws); WILL_LJMP(luaL_error(L, errmsg)); return 0; /* Never reached */ } static int hlua_filter_init_per_thread(struct proxy *px, struct flt_conf *fconf) { struct hlua_flt_config *conf = fconf->conf; lua_State *L; int error, pos, state_id, flt_ref; state_id = reg_flt_to_stack_id(conf->reg); L = hlua_states[state_id]; pos = lua_gettop(L); /* The filter parsing function */ hlua_pushref(L, conf->reg->fun_ref[state_id]); /* Push the filter class on the stack and resolve all callbacks */ hlua_pushref(L, conf->reg->flt_ref[state_id]); /* Duplicate the filter class so each filter will have its own copy */ lua_newtable(L); lua_pushnil(L); while (lua_next(L, pos+2)) { lua_pushvalue(L, -2); lua_insert(L, -2); lua_settable(L, -4); } flt_ref = hlua_ref(L); /* Remove the original lua filter class from the stack */ lua_pop(L, 1); /* Push the copy on the stack */ hlua_pushref(L, flt_ref); /* extra args are pushed in a table */ lua_newtable(L); for (pos = 0; conf->args[pos]; pos++) { /* Check stack available size. */ if (!lua_checkstack(L, 1)) { ha_alert("Lua filter '%s' : Lua error : full stack.", conf->reg->name); goto error; } lua_pushstring(L, conf->args[pos]); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); } error = lua_pcall(L, 2, LUA_MULTRET, 0); switch (error) { case LUA_OK: /* replace the filter ref */ conf->ref[state_id] = flt_ref; break; case LUA_ERRRUN: ha_alert("Lua filter '%s' : runtime error : %s", conf->reg->name, hlua_tostring_safe(L, -1)); goto error; case LUA_ERRMEM: ha_alert("Lua filter '%s' : out of memory error", conf->reg->name); goto error; case LUA_ERRERR: ha_alert("Lua filter '%s' : message handler error : %s", conf->reg->name, hlua_tostring_safe(L, -1)); goto error; #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503 case LUA_ERRGCMM: ha_alert("Lua filter '%s' : garbage collector error : %s", conf->reg->name, hlua_tostring_safe(L, -1)); goto error; #endif default: ha_alert("Lua filter '%s' : unknown error : %s", conf->reg->name, hlua_tostring_safe(L, -1)); goto error; } lua_settop(L, 0); return 0; error: lua_settop(L, 0); return -1; } static void hlua_filter_deinit_per_thread(struct proxy *px, struct flt_conf *fconf) { struct hlua_flt_config *conf = fconf->conf; lua_State *L; int state_id; if (!conf) return; state_id = reg_flt_to_stack_id(conf->reg); L = hlua_states[state_id]; hlua_unref(L, conf->ref[state_id]); } static int hlua_filter_init(struct proxy *px, struct flt_conf *fconf) { struct hlua_flt_config *conf = fconf->conf; int state_id = reg_flt_to_stack_id(conf->reg); /* Rely on per-thread init for global scripts */ if (!state_id) return hlua_filter_init_per_thread(px, fconf); return 0; } static void hlua_filter_deinit(struct proxy *px, struct flt_conf *fconf) { if (fconf->conf) { struct hlua_flt_config *conf = fconf->conf; int state_id = reg_flt_to_stack_id(conf->reg); int pos; /* Rely on per-thread deinit for global scripts */ if (!state_id) hlua_filter_deinit_per_thread(px, fconf); for (pos = 0; conf->args[pos]; pos++) free(conf->args[pos]); free(conf->args); } ha_free(&fconf->conf); ha_free((char **)&fconf->id); ha_free(&fconf->ops); } static int hlua_filter_new(struct stream *s, struct filter *filter) { struct hlua_flt_config *conf = FLT_CONF(filter); struct hlua_flt_ctx *flt_ctx = NULL; struct hlua *hlua = NULL; int ret = 1; if (!(hlua = hlua_stream_ctx_prepare(s, reg_flt_to_stack_id(conf->reg)))) { SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n", conf->reg->name); ret = 0; goto end; } flt_ctx = pool_zalloc(pool_head_hlua_flt_ctx); if (!flt_ctx) { SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n", conf->reg->name); ret = 0; goto end; } if ((flt_ctx->hlua[0] = pool_alloc(pool_head_hlua))) HLUA_INIT(flt_ctx->hlua[0]); if ((flt_ctx->hlua[1] = pool_alloc(pool_head_hlua))) HLUA_INIT(flt_ctx->hlua[1]); if (!flt_ctx->hlua[0] || !flt_ctx->hlua[1]) { SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n", conf->reg->name); ret = 0; goto end; } if (!hlua_ctx_init(flt_ctx->hlua[0], reg_flt_to_stack_id(conf->reg), s->task) || !hlua_ctx_init(flt_ctx->hlua[1], reg_flt_to_stack_id(conf->reg), s->task)) { SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n", conf->reg->name); ret = 0; goto end; } if (!HLUA_IS_RUNNING(hlua)) { /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { const char *error; hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error); hlua_unlock(hlua); ret = 0; goto end; } /* Check stack size. */ if (!lua_checkstack(hlua->T, 1)) { SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); RESET_SAFE_LJMP(hlua); ret = 0; goto end; } hlua_pushref(hlua->T, conf->ref[hlua->state_id]); if (lua_getfield(hlua->T, -1, "new") != LUA_TFUNCTION) { SEND_ERR(s->be, "Lua filter '%s': 'new' field is not a function.\n", conf->reg->name); RESET_SAFE_LJMP(hlua); ret = 0; goto end; } lua_insert(hlua->T, -2); /* Push the copy on the stack */ hlua->nargs = 1; /* We must initialize the execution timeouts. */ hlua_timer_init(&hlua->timer, hlua_timeout_session); /* At this point the execution is safe. */ RESET_SAFE_LJMP(hlua); } switch (hlua_ctx_resume(hlua, 0)) { case HLUA_E_OK: /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(hlua)) { const char *error; hlua_lock(hlua); if (lua_type(hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(hlua->T, -1); else error = "critical error"; SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error); hlua_unlock(hlua); ret = 0; goto end; } /* Nothing returned or not a table, ignore the filter for current stream */ if (!lua_gettop(hlua->T) || !lua_istable(hlua->T, 1)) { ret = 0; RESET_SAFE_LJMP(hlua); goto end; } /* Attached the filter pointer to the ctx */ lua_pushstring(hlua->T, "__filter"); lua_pushlightuserdata(hlua->T, filter); lua_settable(hlua->T, -3); /* Save a ref on the filter ctx */ lua_pushvalue(hlua->T, 1); flt_ctx->ref = hlua_ref(hlua->T); /* At this point the execution is safe. */ RESET_SAFE_LJMP(hlua); /* save main hlua ctx (from the stream) */ flt_ctx->_hlua = hlua; filter->ctx = flt_ctx; break; case HLUA_E_ERRMSG: hlua_lock(hlua); SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, hlua_tostring_safe(hlua->T, -1)); hlua_unlock(hlua); ret = -1; goto end; case HLUA_E_ETMOUT: SEND_ERR(s->be, "Lua filter '%s' : 'new' execution timeout.\n", conf->reg->name); ret = 0; goto end; case HLUA_E_NOMEM: SEND_ERR(s->be, "Lua filter '%s' : out of memory error.\n", conf->reg->name); ret = 0; goto end; case HLUA_E_AGAIN: case HLUA_E_YIELD: SEND_ERR(s->be, "Lua filter '%s': yield functions like core.tcp() or core.sleep()" " are not allowed from 'new' function.\n", conf->reg->name); ret = 0; goto end; case HLUA_E_ERR: SEND_ERR(s->be, "Lua filter '%s': 'new' returns an unknown error.\n", conf->reg->name); ret = 0; goto end; default: ret = 0; goto end; } end: if (hlua) { hlua_lock(hlua); lua_settop(hlua->T, 0); hlua_unlock(hlua); } if (ret <= 0) { if (flt_ctx) { hlua_ctx_destroy(flt_ctx->hlua[0]); hlua_ctx_destroy(flt_ctx->hlua[1]); pool_free(pool_head_hlua_flt_ctx, flt_ctx); } } return ret; } static void hlua_filter_delete(struct stream *s, struct filter *filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; struct hlua *hlua = hlua_stream_ctx_get(s, flt_ctx->_hlua->state_id); hlua_lock(hlua); hlua_unref(hlua->T, flt_ctx->ref); hlua_unlock(hlua); hlua_ctx_destroy(flt_ctx->hlua[0]); hlua_ctx_destroy(flt_ctx->hlua[1]); pool_free(pool_head_hlua_flt_ctx, flt_ctx); filter->ctx = NULL; } static int hlua_filter_from_payload(struct filter *filter) { struct hlua_flt_ctx *flt_ctx = filter->ctx; return (flt_ctx && !!(flt_ctx->flags & HLUA_FLT_CTX_FL_PAYLOAD)); } static int hlua_filter_callback(struct stream *s, struct filter *filter, const char *fun, int dir, unsigned int flags) { struct hlua *flt_hlua; struct hlua_flt_config *conf = FLT_CONF(filter); struct hlua_flt_ctx *flt_ctx = filter->ctx; unsigned int hflags = HLUA_TXN_FLT_CTX; int ret = 1; flt_hlua = flt_ctx->hlua[(dir == SMP_OPT_DIR_REQ ? 0 : 1)]; if (!flt_hlua) goto end; if (!HLUA_IS_RUNNING(flt_hlua)) { int extra_idx; /* The following Lua calls can fail. */ if (!SET_SAFE_LJMP(flt_hlua)) { const char *error; hlua_lock(flt_hlua); if (lua_type(flt_hlua->T, -1) == LUA_TSTRING) error = hlua_tostring_safe(flt_hlua->T, -1); else error = "critical error"; SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error); hlua_unlock(flt_hlua); goto end; } extra_idx = lua_gettop(flt_hlua->T); /* Check stack size. */ if (!lua_checkstack(flt_hlua->T, 3)) { SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); RESET_SAFE_LJMP(flt_hlua); goto end; } hlua_pushref(flt_hlua->T, flt_ctx->ref); if (lua_getfield(flt_hlua->T, -1, fun) != LUA_TFUNCTION) { RESET_SAFE_LJMP(flt_hlua); goto end; } lua_insert(flt_hlua->T, -2); if (!hlua_txn_new(flt_hlua->T, s, s->be, dir, hflags)) { SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); RESET_SAFE_LJMP(flt_hlua); goto end; } flt_hlua->nargs = 2; if (flags & HLUA_FLT_CB_ARG_CHN) { if (dir == SMP_OPT_DIR_REQ) lua_getfield(flt_hlua->T, -1, "req"); else lua_getfield(flt_hlua->T, -1, "res"); if (lua_type(flt_hlua->T, -1) == LUA_TTABLE) { lua_pushstring(flt_hlua->T, "__filter"); lua_pushlightuserdata(flt_hlua->T, filter); lua_settable(flt_hlua->T, -3); } flt_hlua->nargs++; } else if (flags & HLUA_FLT_CB_ARG_HTTP_MSG) { if (dir == SMP_OPT_DIR_REQ) lua_getfield(flt_hlua->T, -1, "http_req"); else lua_getfield(flt_hlua->T, -1, "http_res"); if (lua_type(flt_hlua->T, -1) == LUA_TTABLE) { lua_pushstring(flt_hlua->T, "__filter"); lua_pushlightuserdata(flt_hlua->T, filter); lua_settable(flt_hlua->T, -3); } flt_hlua->nargs++; } /* Check stack size. */ if (!lua_checkstack(flt_hlua->T, 1)) { SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); RESET_SAFE_LJMP(flt_hlua); goto end; } while (extra_idx--) { lua_pushvalue(flt_hlua->T, 1); lua_remove(flt_hlua->T, 1); flt_hlua->nargs++; } /* We must initialize the execution timeouts. */ hlua_timer_init(&flt_hlua->timer, hlua_timeout_session); /* At this point the execution is safe. */ RESET_SAFE_LJMP(flt_hlua); } switch (hlua_ctx_resume(flt_hlua, !(flags & HLUA_FLT_CB_FINAL))) { case HLUA_E_OK: /* Catch the return value if it required */ hlua_lock(flt_hlua); if ((flags & HLUA_FLT_CB_RETVAL) && lua_gettop(flt_hlua->T) > 0) { ret = lua_tointeger(flt_hlua->T, -1); lua_settop(flt_hlua->T, 0); /* Empty the stack. */ } hlua_unlock(flt_hlua); /* Set timeout in the required channel. */ if (flt_hlua->wake_time != TICK_ETERNITY) { if (dir == SMP_OPT_DIR_REQ) s->req.analyse_exp = flt_hlua->wake_time; else s->res.analyse_exp = flt_hlua->wake_time; } break; case HLUA_E_AGAIN: /* Set timeout in the required channel. */ if (flt_hlua->wake_time != TICK_ETERNITY) { if (dir == SMP_OPT_DIR_REQ) s->req.analyse_exp = flt_hlua->wake_time; else s->res.analyse_exp = flt_hlua->wake_time; } /* Some actions can be wake up when a "write" event * is detected on a response channel. This is useful * only for actions targeted on the requests. */ if (HLUA_IS_WAKERESWR(flt_hlua)) s->res.flags |= CF_WAKE_WRITE; if (HLUA_IS_WAKEREQWR(flt_hlua)) s->req.flags |= CF_WAKE_WRITE; ret = 0; goto end; case HLUA_E_ERRMSG: hlua_lock(flt_hlua); SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, hlua_tostring_safe(flt_hlua->T, -1)); hlua_unlock(flt_hlua); ret = -1; goto end; case HLUA_E_ETMOUT: SEND_ERR(s->be, "Lua filter '%s' : '%s' callback execution timeout.\n", conf->reg->name, fun); goto end; case HLUA_E_NOMEM: SEND_ERR(s->be, "Lua filter '%s' : out of memory error.\n", conf->reg->name); goto end; case HLUA_E_YIELD: SEND_ERR(s->be, "Lua filter '%s': yield functions like core.tcp() or core.sleep()" " are not allowed from '%s' callback.\n", conf->reg->name, fun); goto end; case HLUA_E_ERR: SEND_ERR(s->be, "Lua filter '%s': '%s' returns an unknown error.\n", conf->reg->name, fun); goto end; default: goto end; } end: return ret; } static int hlua_filter_start_analyze(struct stream *s, struct filter *filter, struct channel *chn) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_ctx->flags = 0; return hlua_filter_callback(s, filter, "start_analyze", (!(chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_CHN)); } static int hlua_filter_end_analyze(struct stream *s, struct filter *filter, struct channel *chn) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_ctx->flags &= ~HLUA_FLT_CTX_FL_PAYLOAD; return hlua_filter_callback(s, filter, "end_analyze", (!(chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_CHN)); } static int hlua_filter_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_ctx->flags &= ~HLUA_FLT_CTX_FL_PAYLOAD; return hlua_filter_callback(s, filter, "http_headers", (!(msg->chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_HTTP_MSG)); } static int hlua_filter_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg, unsigned int offset, unsigned int len) { struct hlua_flt_ctx *flt_ctx = filter->ctx; struct hlua *flt_hlua; int dir = (!(msg->chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES); int idx = (dir == SMP_OPT_DIR_REQ ? 0 : 1); int ret; flt_hlua = flt_ctx->hlua[idx]; flt_ctx->cur_off[idx] = offset; flt_ctx->cur_len[idx] = len; flt_ctx->flags |= HLUA_FLT_CTX_FL_PAYLOAD; ret = hlua_filter_callback(s, filter, "http_payload", dir, (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_ARG_HTTP_MSG)); if (ret != -1) { ret = flt_ctx->cur_len[idx]; if (lua_gettop(flt_hlua->T) > 0) { ret = lua_tointeger(flt_hlua->T, -1); if (ret > flt_ctx->cur_len[idx]) ret = flt_ctx->cur_len[idx]; lua_settop(flt_hlua->T, 0); /* Empty the stack. */ } } return ret; } static int hlua_filter_http_end(struct stream *s, struct filter *filter, struct http_msg *msg) { struct hlua_flt_ctx *flt_ctx = filter->ctx; flt_ctx->flags &= ~HLUA_FLT_CTX_FL_PAYLOAD; return hlua_filter_callback(s, filter, "http_end", (!(msg->chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_HTTP_MSG)); } static int hlua_filter_tcp_payload(struct stream *s, struct filter *filter, struct channel *chn, unsigned int offset, unsigned int len) { struct hlua_flt_ctx *flt_ctx = filter->ctx; struct hlua *flt_hlua; int dir = (!(chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES); int idx = (dir == SMP_OPT_DIR_REQ ? 0 : 1); int ret; flt_hlua = flt_ctx->hlua[idx]; flt_ctx->cur_off[idx] = offset; flt_ctx->cur_len[idx] = len; flt_ctx->flags |= HLUA_FLT_CTX_FL_PAYLOAD; ret = hlua_filter_callback(s, filter, "tcp_payload", dir, (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_ARG_CHN)); if (ret != -1) { ret = flt_ctx->cur_len[idx]; if (lua_gettop(flt_hlua->T) > 0) { ret = lua_tointeger(flt_hlua->T, -1); if (ret > flt_ctx->cur_len[idx]) ret = flt_ctx->cur_len[idx]; lua_settop(flt_hlua->T, 0); /* Empty the stack. */ } } return ret; } static int hlua_filter_parse_fct(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf, char **err, void *private) { struct hlua_reg_filter *reg_flt = private; lua_State *L; struct hlua_flt_config *conf = NULL; const char *flt_id = NULL; int state_id, pos, flt_flags = 0; struct flt_ops *hlua_flt_ops = NULL; state_id = reg_flt_to_stack_id(reg_flt); L = hlua_states[state_id]; /* Initialize the filter ops with default callbacks */ hlua_flt_ops = calloc(1, sizeof(*hlua_flt_ops)); if (!hlua_flt_ops) goto error; hlua_flt_ops->init = hlua_filter_init; hlua_flt_ops->deinit = hlua_filter_deinit; if (state_id) { /* Set per-thread callback if script is loaded per-thread */ hlua_flt_ops->init_per_thread = hlua_filter_init_per_thread; hlua_flt_ops->deinit_per_thread = hlua_filter_deinit_per_thread; } hlua_flt_ops->attach = hlua_filter_new; hlua_flt_ops->detach = hlua_filter_delete; /* Push the filter class on the stack and resolve all callbacks */ hlua_pushref(L, reg_flt->flt_ref[state_id]); if (lua_getfield(L, -1, "start_analyze") == LUA_TFUNCTION) hlua_flt_ops->channel_start_analyze = hlua_filter_start_analyze; lua_pop(L, 1); if (lua_getfield(L, -1, "end_analyze") == LUA_TFUNCTION) hlua_flt_ops->channel_end_analyze = hlua_filter_end_analyze; lua_pop(L, 1); if (lua_getfield(L, -1, "http_headers") == LUA_TFUNCTION) hlua_flt_ops->http_headers = hlua_filter_http_headers; lua_pop(L, 1); if (lua_getfield(L, -1, "http_payload") == LUA_TFUNCTION) hlua_flt_ops->http_payload = hlua_filter_http_payload; lua_pop(L, 1); if (lua_getfield(L, -1, "http_end") == LUA_TFUNCTION) hlua_flt_ops->http_end = hlua_filter_http_end; lua_pop(L, 1); if (lua_getfield(L, -1, "tcp_payload") == LUA_TFUNCTION) hlua_flt_ops->tcp_payload = hlua_filter_tcp_payload; lua_pop(L, 1); /* Get id and flags of the filter class */ if (lua_getfield(L, -1, "id") == LUA_TSTRING) flt_id = lua_tostring(L, -1); lua_pop(L, 1); if (lua_getfield(L, -1, "flags") == LUA_TNUMBER) flt_flags = lua_tointeger(L, -1); lua_pop(L, 1); /* Create the filter config */ conf = calloc(1, sizeof(*conf)); if (!conf) goto error; conf->reg = reg_flt; /* duplicate args */ for (pos = 0; *args[*cur_arg + 1 + pos]; pos++); conf->args = calloc(pos + 1, sizeof(*conf->args)); if (!conf->args) goto error; for (pos = 0; *args[*cur_arg + 1 + pos]; pos++) { conf->args[pos] = strdup(args[*cur_arg + 1 + pos]); if (!conf->args[pos]) goto error; } conf->args[pos] = NULL; *cur_arg += pos + 1; if (flt_id) { fconf->id = strdup(flt_id); if (!fconf->id) goto error; } fconf->flags = flt_flags; fconf->conf = conf; fconf->ops = hlua_flt_ops; lua_settop(L, 0); return 0; error: memprintf(err, "Lua filter '%s' : Lua out of memory error", reg_flt->name); free(hlua_flt_ops); if (conf && conf->args) { for (pos = 0; conf->args[pos]; pos++) free(conf->args[pos]); free(conf->args); } free(conf); free((char *)fconf->id); lua_settop(L, 0); return -1; } __LJMP static int hlua_register_data_filter(lua_State *L) { struct filter *filter; struct channel *chn; MAY_LJMP(check_args(L, 2, "register_data_filter")); MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); chn = MAY_LJMP(hlua_checkchannel(L, 2)); lua_getfield(L, 1, "__filter"); MAY_LJMP(luaL_checktype(L, -1, LUA_TLIGHTUSERDATA)); filter = lua_touserdata (L, -1); lua_pop(L, 1); register_data_filter(chn_strm(chn), chn, filter); return 1; } __LJMP static int hlua_unregister_data_filter(lua_State *L) { struct filter *filter; struct channel *chn; MAY_LJMP(check_args(L, 2, "unregister_data_filter")); MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); chn = MAY_LJMP(hlua_checkchannel(L, 2)); lua_getfield(L, 1, "__filter"); MAY_LJMP(luaL_checktype(L, -1, LUA_TLIGHTUSERDATA)); filter = lua_touserdata (L, -1); lua_pop(L, 1); unregister_data_filter(chn_strm(chn), chn, filter); return 1; } /* This function is an LUA binding used for registering a filter. It expects a * filter name used in the haproxy configuration file and a LUA function to * parse configuration arguments. */ __LJMP static int hlua_register_filter(lua_State *L) { struct buffer *trash; struct flt_kw_list *fkl; struct flt_kw *fkw; const char *name; struct hlua_reg_filter *reg_flt= NULL; int flt_ref, fun_ref; int len; MAY_LJMP(check_args(L, 3, "register_filter")); if (hlua_gethlua(L)) { /* runtime processing */ WILL_LJMP(luaL_error(L, "register_filter: not available outside of body context")); } /* First argument : filter name. */ name = MAY_LJMP(luaL_checkstring(L, 1)); /* Second argument : The filter class */ flt_ref = MAY_LJMP(hlua_checktable(L, 2)); /* Third argument : lua function. */ fun_ref = MAY_LJMP(hlua_checkfunction(L, 3)); trash = get_trash_chunk(); chunk_printf(trash, "lua.%s", name); fkw = flt_find_kw(trash->area); if (fkw != NULL) { reg_flt = fkw->private; if (reg_flt->flt_ref[hlua_state_id] != -1 || reg_flt->fun_ref[hlua_state_id] != -1) { ha_warning("Trying to register filter 'lua.%s' more than once. " "This will become a hard error in version 2.5.\n", name); if (reg_flt->flt_ref[hlua_state_id] != -1) hlua_unref(L, reg_flt->flt_ref[hlua_state_id]); if (reg_flt->fun_ref[hlua_state_id] != -1) hlua_unref(L, reg_flt->fun_ref[hlua_state_id]); } reg_flt->flt_ref[hlua_state_id] = flt_ref; reg_flt->fun_ref[hlua_state_id] = fun_ref; return 0; } fkl = calloc(1, sizeof(*fkl) + sizeof(struct flt_kw) * 2); if (!fkl) goto alloc_error; fkl->scope = "HLUA"; reg_flt = new_hlua_reg_filter(name); if (!reg_flt) goto alloc_error; reg_flt->flt_ref[hlua_state_id] = flt_ref; reg_flt->fun_ref[hlua_state_id] = fun_ref; /* The filter keyword */ len = strlen("lua.") + strlen(name) + 1; fkl->kw[0].kw = calloc(1, len); if (!fkl->kw[0].kw) goto alloc_error; snprintf((char *)fkl->kw[0].kw, len, "lua.%s", name); fkl->kw[0].parse = hlua_filter_parse_fct; fkl->kw[0].private = reg_flt; memset(&fkl->kw[1], 0, sizeof(*fkl->kw)); /* Register this new filter */ flt_register_keywords(fkl); return 0; alloc_error: release_hlua_reg_filter(reg_flt); hlua_unref(L, flt_ref); hlua_unref(L, fun_ref); ha_free(&fkl); WILL_LJMP(luaL_error(L, "Lua out of memory error.")); return 0; /* Never reached */ } static int hlua_read_timeout(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err, unsigned int *timeout) { const char *error; error = parse_time_err(args[1], timeout, TIME_UNIT_MS); if (error == PARSE_TIME_OVER) { memprintf(err, "timer overflow in argument <%s> to <%s> (maximum value is 2147483647 ms or ~24.8 days)", args[1], args[0]); return -1; } else if (error == PARSE_TIME_UNDER) { memprintf(err, "timer underflow in argument <%s> to <%s> (minimum non-null value is 1 ms)", args[1], args[0]); return -1; } else if (error) { memprintf(err, "%s: invalid timeout", args[0]); return -1; } return 0; } static int hlua_burst_timeout(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { return hlua_read_timeout(args, section_type, curpx, defpx, file, line, err, &hlua_timeout_burst); } static int hlua_session_timeout(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { return hlua_read_timeout(args, section_type, curpx, defpx, file, line, err, &hlua_timeout_session); } static int hlua_task_timeout(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { return hlua_read_timeout(args, section_type, curpx, defpx, file, line, err, &hlua_timeout_task); } static int hlua_applet_timeout(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { return hlua_read_timeout(args, section_type, curpx, defpx, file, line, err, &hlua_timeout_applet); } static int hlua_forced_yield(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { char *error; hlua_nb_instruction = strtoll(args[1], &error, 10); if (*error != '\0') { memprintf(err, "%s: invalid number", args[0]); return -1; } return 0; } static int hlua_parse_maxmem(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { char *error; if (*(args[1]) == 0) { memprintf(err, "'%s' expects an integer argument (Lua memory size in MB).", args[0]); return -1; } hlua_global_allocator.limit = strtoll(args[1], &error, 10) * 1024L * 1024L; if (*error != '\0') { memprintf(err, "%s: invalid number %s (error at '%c')", args[0], args[1], *error); return -1; } return 0; } static int hlua_cfg_parse_log_loggers(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { if (too_many_args(1, args, err, NULL)) return -1; if (strcmp(args[1], "on") == 0) hlua_log_opts |= HLUA_LOG_LOGGERS_ON; else if (strcmp(args[1], "off") == 0) hlua_log_opts &= ~HLUA_LOG_LOGGERS_ON; else { memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]); return -1; } return 0; } static int hlua_cfg_parse_log_stderr(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { if (too_many_args(1, args, err, NULL)) return -1; if (strcmp(args[1], "on") == 0) hlua_log_opts = (hlua_log_opts & ~HLUA_LOG_STDERR_MASK) | HLUA_LOG_STDERR_ON; else if (strcmp(args[1], "auto") == 0) hlua_log_opts = (hlua_log_opts & ~HLUA_LOG_STDERR_MASK) | HLUA_LOG_STDERR_AUTO; else if (strcmp(args[1], "off") == 0) hlua_log_opts &= ~HLUA_LOG_STDERR_MASK; else { memprintf(err, "'%s' expects either 'on', 'auto', or 'off' but got '%s'.", args[0], args[1]); return -1; } return 0; } /* This function is called by the main configuration key "lua-load". It loads and * execute an lua file during the parsing of the HAProxy configuration file. It is * the main lua entry point. * * This function runs with the HAProxy keywords API. It returns -1 if an error * occurs, otherwise it returns 0. * * In some error case, LUA set an error message in top of the stack. This function * returns this error message in the HAProxy logs and pop it from the stack. * * This function can fail with an abort() due to an Lua critical error. * We are in the configuration parsing process of HAProxy, this abort() is * tolerated. */ static int hlua_load_state(char **args, lua_State *L, char **err) { int error; int nargs; /* Just load and compile the file. */ error = luaL_loadfile(L, args[0]); if (error) { memprintf(err, "error in Lua file '%s': %s", args[0], hlua_tostring_safe(L, -1)); lua_pop(L, 1); return -1; } /* Push args in the Lua stack, except the first one which is the filename */ for (nargs = 1; *(args[nargs]) != 0; nargs++) { /* Check stack size. */ if (!lua_checkstack(L, 1)) { memprintf(err, "Lua runtime error while loading arguments: stack is full."); return -1; } lua_pushstring(L, args[nargs]); } nargs--; /* If no syntax error where detected, execute the code. */ error = lua_pcall(L, nargs, LUA_MULTRET, 0); switch (error) { case LUA_OK: break; case LUA_ERRRUN: memprintf(err, "Lua runtime error: %s", hlua_tostring_safe(L, -1)); lua_pop(L, 1); return -1; case LUA_ERRMEM: memprintf(err, "Lua out of memory error"); return -1; case LUA_ERRERR: memprintf(err, "Lua message handler error: %s", hlua_tostring_safe(L, -1)); lua_pop(L, 1); return -1; #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503 case LUA_ERRGCMM: memprintf(err, "Lua garbage collector error: %s", hlua_tostring_safe(L, -1)); lua_pop(L, 1); return -1; #endif default: memprintf(err, "Lua unknown error: %s", hlua_tostring_safe(L, -1)); lua_pop(L, 1); return -1; } return 0; } static int hlua_load(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { if (*(args[1]) == 0) { memprintf(err, "'%s' expects a file name as parameter.", args[0]); return -1; } /* loading for global state */ hlua_state_id = 0; ha_set_thread(NULL); return hlua_load_state(&args[1], hlua_states[0], err); } static int hlua_load_per_thread(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { int len; int i; if (*(args[1]) == 0) { memprintf(err, "'%s' expects a file as parameter.", args[0]); return -1; } if (per_thread_load == NULL) { /* allocate the first entry large enough to store the final NULL */ per_thread_load = calloc(1, sizeof(*per_thread_load)); if (per_thread_load == NULL) { memprintf(err, "out of memory error"); return -1; } } /* count used entries */ for (len = 0; per_thread_load[len] != NULL; len++) ; per_thread_load = realloc(per_thread_load, (len + 2) * sizeof(*per_thread_load)); if (per_thread_load == NULL) { memprintf(err, "out of memory error"); return -1; } per_thread_load[len + 1] = NULL; /* count args excepting the first, allocate array and copy args */ for (i = 0; *(args[i + 1]) != 0; i++); per_thread_load[len] = calloc(i + 1, sizeof(*per_thread_load[len])); if (per_thread_load[len] == NULL) { memprintf(err, "out of memory error"); return -1; } for (i = 1; *(args[i]) != 0; i++) { per_thread_load[len][i - 1] = strdup(args[i]); if (per_thread_load[len][i - 1] == NULL) { memprintf(err, "out of memory error"); return -1; } } per_thread_load[len][i - 1] = strdup(""); if (per_thread_load[len][i - 1] == NULL) { memprintf(err, "out of memory error"); return -1; } /* loading for thread 1 only */ hlua_state_id = 1; ha_set_thread(NULL); return hlua_load_state(per_thread_load[len], hlua_states[1], err); } /* Prepend the given followed by a semicolon to the `package.` variable * in the given . */ static int hlua_prepend_path(lua_State *L, char *type, char *path) { lua_getglobal(L, "package"); /* push package variable */ lua_pushstring(L, path); /* push given path */ lua_pushstring(L, ";"); /* push semicolon */ lua_getfield(L, -3, type); /* push old path */ lua_concat(L, 3); /* concatenate to new path */ lua_setfield(L, -2, type); /* store new path */ lua_pop(L, 1); /* pop package variable */ return 0; } static int hlua_config_prepend_path(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { char *path; char *type = "path"; struct prepend_path *p = NULL; size_t i; if (too_many_args(2, args, err, NULL)) { goto err; } if (!(*args[1])) { memprintf(err, "'%s' expects to receive a as argument", args[0]); goto err; } path = args[1]; if (*args[2]) { if (strcmp(args[2], "path") != 0 && strcmp(args[2], "cpath") != 0) { memprintf(err, "'%s' expects to either be 'path' or 'cpath'", args[0]); goto err; } type = args[2]; } p = calloc(1, sizeof(*p)); if (p == NULL) { memprintf(err, "memory allocation failed"); goto err; } p->path = strdup(path); if (p->path == NULL) { memprintf(err, "memory allocation failed"); goto err2; } p->type = strdup(type); if (p->type == NULL) { memprintf(err, "memory allocation failed"); goto err2; } LIST_APPEND(&prepend_path_list, &p->l); /* Handle the global state and the per-thread state for the first * thread. The remaining threads will be initialized based on * prepend_path_list. */ for (i = 0; i < 2; i++) { lua_State *L = hlua_states[i]; const char *error; if (setjmp(safe_ljmp_env) != 0) { lua_atpanic(L, hlua_panic_safe); if (lua_type(L, -1) == LUA_TSTRING) error = hlua_tostring_safe(L, -1); else error = "critical error"; fprintf(stderr, "lua-prepend-path: %s.\n", error); exit(1); } else { lua_atpanic(L, hlua_panic_ljmp); } hlua_prepend_path(L, type, path); lua_atpanic(L, hlua_panic_safe); } return 0; err2: free(p->type); free(p->path); err: free(p); return -1; } /* configuration keywords declaration */ static struct cfg_kw_list cfg_kws = {{ },{ { CFG_GLOBAL, "lua-prepend-path", hlua_config_prepend_path }, { CFG_GLOBAL, "lua-load", hlua_load }, { CFG_GLOBAL, "lua-load-per-thread", hlua_load_per_thread }, { CFG_GLOBAL, "tune.lua.session-timeout", hlua_session_timeout }, { CFG_GLOBAL, "tune.lua.task-timeout", hlua_task_timeout }, { CFG_GLOBAL, "tune.lua.service-timeout", hlua_applet_timeout }, { CFG_GLOBAL, "tune.lua.burst-timeout", hlua_burst_timeout }, { CFG_GLOBAL, "tune.lua.forced-yield", hlua_forced_yield }, { CFG_GLOBAL, "tune.lua.maxmem", hlua_parse_maxmem }, { CFG_GLOBAL, "tune.lua.log.loggers", hlua_cfg_parse_log_loggers }, { CFG_GLOBAL, "tune.lua.log.stderr", hlua_cfg_parse_log_stderr }, { 0, NULL, NULL }, }}; INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); #ifdef USE_OPENSSL /* * This function replace a ckch_store by another one, and rebuild the ckch_inst and all its dependencies. * It does the sam as "cli_io_handler_commit_cert" but for lua, the major * difference is that the yield in lua and for the CLI is not handled the same * way. */ __LJMP static int hlua_ckch_commit_yield(lua_State *L, int status, lua_KContext ctx) { struct ckch_inst **lua_ckchi = lua_touserdata(L, -1); struct ckch_store **lua_ckchs = lua_touserdata(L, -2); struct ckch_inst *ckchi = *lua_ckchi; struct ckch_store *old_ckchs = lua_ckchs[0]; struct ckch_store *new_ckchs = lua_ckchs[1]; struct hlua *hlua; char *err = NULL; int y = 1; hlua = hlua_gethlua(L); /* get the first ckchi to copy */ if (ckchi == NULL) ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs); /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */ list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) { struct ckch_inst *new_inst; /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */ if (y % 10 == 0) { *lua_ckchi = ckchi; task_wakeup(hlua->task, TASK_WOKEN_MSG); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_ckch_commit_yield, TICK_ETERNITY, 0)); } if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err)) goto error; /* link the new ckch_inst to the duplicate */ LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs); y++; } /* The generation is finished, we can insert everything */ ckch_store_replace(old_ckchs, new_ckchs); lua_pop(L, 2); /* pop the lua_ckchs and ckchi */ HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); return 0; error: ckch_store_free(new_ckchs); HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); WILL_LJMP(luaL_error(L, "%s", err)); free(err); return 0; } /* * Replace a ckch_store in the ckchs_tree with a ckch_store created * from the table in parameter. * * This is equivalent to "set ssl cert" + "commit ssl cert" over the CLI, which * means it does not need to have a transaction since everything is done in the * same function. * * CertCache.set{filename="", crt="", key="", sctl="", ocsp="", issuer=""} * */ __LJMP static int hlua_ckch_set(lua_State *L) { struct hlua *hlua; struct ckch_inst **lua_ckchi; struct ckch_store **lua_ckchs; struct ckch_store *old_ckchs = NULL; struct ckch_store *new_ckchs = NULL; int errcode = 0; char *err = NULL; struct cert_exts *cert_ext = NULL; char *filename; struct ckch_data *data; int ret; if (lua_type(L, -1) != LUA_TTABLE) WILL_LJMP(luaL_error(L, "'CertCache.set' needs a table as argument")); hlua = hlua_gethlua(L); /* FIXME: this should not return an error but should come back later */ if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) WILL_LJMP(luaL_error(L, "CertCache already under lock")); ret = lua_getfield(L, -1, "filename"); if (ret != LUA_TSTRING) { memprintf(&err, "%sNo filename specified!", err ? err : ""); errcode |= ERR_ALERT | ERR_FATAL; goto end; } filename = (char *)lua_tostring(L, -1); /* look for the filename in the tree */ old_ckchs = ckchs_lookup(filename); if (!old_ckchs) { memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!", err ? err : ""); errcode |= ERR_ALERT | ERR_FATAL; goto end; } /* TODO: handle extra_files_noext */ new_ckchs = ckchs_dup(old_ckchs); if (!new_ckchs) { memprintf(&err, "%sCannot allocate memory!", err ? err : ""); errcode |= ERR_ALERT | ERR_FATAL; goto end; } data = new_ckchs->data; /* loop on the field in the table, which have the same name as the * possible extensions of files */ lua_pushnil(L); while (lua_next(L, 1)) { int i; const char *field = lua_tostring(L, -2); char *payload = (char *)lua_tostring(L, -1); if (!field || strcmp(field, "filename") == 0) { lua_pop(L, 1); continue; } for (i = 0; field && cert_exts[i].ext != NULL; i++) { if (strcmp(field, cert_exts[i].ext) == 0) { cert_ext = &cert_exts[i]; break; } } /* this is the default type, the field is not supported */ if (cert_ext == NULL) { memprintf(&err, "%sUnsupported field '%s'", err ? err : "", field); errcode |= ERR_ALERT | ERR_FATAL; goto end; } /* Reset the OCSP CID */ if (cert_ext->type == CERT_TYPE_PEM || cert_ext->type == CERT_TYPE_KEY || cert_ext->type == CERT_TYPE_ISSUER) { OCSP_CERTID_free(new_ckchs->data->ocsp_cid); new_ckchs->data->ocsp_cid = NULL; } /* apply the change on the duplicate */ if (cert_ext->load(filename, payload, data, &err) != 0) { memprintf(&err, "%sCan't load the payload for '%s'", err ? err : "", cert_ext->ext); errcode |= ERR_ALERT | ERR_FATAL; goto end; } lua_pop(L, 1); } /* store the pointers on the lua stack */ lua_ckchs = lua_newuserdata(L, sizeof(struct ckch_store *) * 2); lua_ckchs[0] = old_ckchs; lua_ckchs[1] = new_ckchs; lua_ckchi = lua_newuserdata(L, sizeof(struct ckch_inst *)); *lua_ckchi = NULL; task_wakeup(hlua->task, TASK_WOKEN_MSG); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_ckch_commit_yield, TICK_ETERNITY, 0)); end: HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); if (errcode & ERR_CODE) { ckch_store_free(new_ckchs); WILL_LJMP(luaL_error(L, "%s", err)); } free(err); return 0; } #else __LJMP static int hlua_ckch_set(lua_State *L) { WILL_LJMP(luaL_error(L, "'CertCache.set' needs an HAProxy built with OpenSSL")); return 0; } #endif /* ! USE_OPENSSL */ /* This function can fail with an abort() due to an Lua critical error. * We are in the initialisation process of HAProxy, this abort() is * tolerated. */ int hlua_post_init_state(lua_State *L) { struct hlua_init_function *init; const char *msg; enum hlua_exec ret; const char *error; const char *kind; const char *trace; int return_status = 1; #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 int nres; #endif /* disable memory limit checks if limit is not set */ if (!hlua_global_allocator.limit) hlua_global_allocator.limit = ~hlua_global_allocator.limit; /* Call post initialisation function in safe environment. */ if (setjmp(safe_ljmp_env) != 0) { lua_atpanic(L, hlua_panic_safe); if (lua_type(L, -1) == LUA_TSTRING) error = hlua_tostring_safe(L, -1); else error = "critical error"; fprintf(stderr, "Lua post-init: %s.\n", error); exit(1); } else { lua_atpanic(L, hlua_panic_ljmp); } list_for_each_entry(init, &hlua_init_functions[hlua_state_id], l) { hlua_pushref(L, init->function_ref); /* function ref should be released right away since it was pushed * on the stack and will not be used anymore */ hlua_unref(L, init->function_ref); #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 ret = lua_resume(L, NULL, 0, &nres); #else ret = lua_resume(L, NULL, 0); #endif kind = NULL; switch (ret) { case LUA_OK: lua_pop(L, -1); break; case LUA_ERRERR: kind = "message handler error"; __fallthrough; case LUA_ERRRUN: if (!kind) kind = "runtime error"; msg = hlua_tostring_safe(L, -1); trace = hlua_traceback(L, ", "); if (msg) ha_alert("Lua init: %s: '%s' from %s\n", kind, msg, trace); else ha_alert("Lua init: unknown %s from %s\n", kind, trace); lua_settop(L, 0); /* Empty the stack. */ return_status = 0; break; default: /* Unknown error */ kind = "Unknown error"; __fallthrough; case LUA_YIELD: /* yield is not configured at this step, this state doesn't happen */ if (!kind) kind = "yield not allowed"; __fallthrough; case LUA_ERRMEM: if (!kind) kind = "out of memory error"; lua_settop(L, 0); /* Empty the stack. */ trace = hlua_traceback(L, ", "); ha_alert("Lua init: %s: %s\n", kind, trace); return_status = 0; break; } if (!return_status) break; } lua_atpanic(L, hlua_panic_safe); return return_status; } int hlua_post_init() { int ret; int i; int errors; char *err = NULL; struct hlua_function *fcn; struct hlua_reg_filter *reg_flt; #if defined(USE_OPENSSL) /* Initialize SSL server. */ if (socket_ssl->xprt->prepare_srv) { int saved_used_backed = global.ssl_used_backend; // don't affect maxconn automatic computation socket_ssl->xprt->prepare_srv(socket_ssl); global.ssl_used_backend = saved_used_backed; } #endif /* Perform post init of common thread */ hlua_state_id = 0; ha_set_thread(&ha_thread_info[0]); ret = hlua_post_init_state(hlua_states[hlua_state_id]); if (ret == 0) return 0; /* init remaining lua states and load files */ for (hlua_state_id = 2; hlua_state_id < global.nbthread + 1; hlua_state_id++) { /* set thread context */ ha_set_thread(&ha_thread_info[hlua_state_id - 1]); /* Init lua state */ hlua_states[hlua_state_id] = hlua_init_state(hlua_state_id); /* Load lua files */ for (i = 0; per_thread_load && per_thread_load[i]; i++) { ret = hlua_load_state(per_thread_load[i], hlua_states[hlua_state_id], &err); if (ret != 0) { ha_alert("Lua init: %s\n", err); return 0; } } } /* Reset thread context */ ha_set_thread(NULL); /* Execute post init for all states */ for (hlua_state_id = 1; hlua_state_id < global.nbthread + 1; hlua_state_id++) { /* set thread context */ ha_set_thread(&ha_thread_info[hlua_state_id - 1]); /* run post init */ ret = hlua_post_init_state(hlua_states[hlua_state_id]); if (ret == 0) return 0; } /* Reset thread context */ ha_set_thread(NULL); /* control functions registering. Each function must have: * - only the function_ref[0] set positive and all other to -1 * - only the function_ref[0] set to -1 and all other positive * This ensure a same reference is not used both in shared * lua state and thread dedicated lua state. Note: is the case * reach, the shared state is priority, but the bug will be * complicated to found for the end user. */ errors = 0; list_for_each_entry(fcn, &referenced_functions, l) { ret = 0; for (i = 1; i < global.nbthread + 1; i++) { if (fcn->function_ref[i] == -1) ret--; else ret++; } if (abs(ret) != global.nbthread) { ha_alert("Lua function '%s' is not referenced in all thread. " "Expect function in all thread or in none thread.\n", fcn->name); errors++; continue; } if ((fcn->function_ref[0] == -1) == (ret < 0)) { ha_alert("Lua function '%s' is referenced both ins shared Lua context (through lua-load) " "and per-thread Lua context (through lua-load-per-thread). these two context " "exclusive.\n", fcn->name); errors++; } } /* Do the same with registered filters */ list_for_each_entry(reg_flt, &referenced_filters, l) { ret = 0; for (i = 1; i < global.nbthread + 1; i++) { if (reg_flt->flt_ref[i] == -1) ret--; else ret++; } if (abs(ret) != global.nbthread) { ha_alert("Lua filter '%s' is not referenced in all thread. " "Expect function in all thread or in none thread.\n", reg_flt->name); errors++; continue; } if ((reg_flt->flt_ref[0] == -1) == (ret < 0)) { ha_alert("Lua filter '%s' is referenced both ins shared Lua context (through lua-load) " "and per-thread Lua context (through lua-load-per-thread). these two context " "exclusive.\n", reg_flt->name); errors++; } } if (errors > 0) return 0; /* after this point, this global will no longer be used, so set to * -1 in order to have probably a segfault if someone use it */ hlua_state_id = -1; return 1; } /* The memory allocator used by the Lua stack. is a pointer to the * allocator's context. is the pointer to alloc/free/realloc. * is the previously allocated size or the kind of object in case of a new * allocation. is the requested new size. A new allocation is * indicated by being NULL. A free is indicated by being * zero. This one verifies that the limits are respected but is optimized * for the fast case where limits are not used, hence stats are not updated. * * Warning: while this API ressembles glibc's realloc() a lot, glibc surpasses * POSIX by making realloc(ptr,0) an effective free(), but others do not do * that and will simply allocate zero as if it were the result of malloc(0), * so mapping this onto realloc() will lead to memory leaks on non-glibc * systems. */ static void *hlua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { struct hlua_mem_allocator *zone = ud; size_t limit, old, new; /* a limit of ~0 means unlimited and boot complete, so there's no need * for accounting anymore. */ if (likely(~zone->limit == 0)) { if (!nsize) ha_free(&ptr); else ptr = realloc(ptr, nsize); return ptr; } if (!ptr) osize = 0; /* enforce strict limits across all threads */ limit = zone->limit; old = _HA_ATOMIC_LOAD(&zone->allocated); do { new = old + nsize - osize; if (unlikely(nsize && limit && new > limit)) return NULL; } while (!_HA_ATOMIC_CAS(&zone->allocated, &old, new)); if (!nsize) ha_free(&ptr); else ptr = realloc(ptr, nsize); if (unlikely(!ptr && nsize)) // failed _HA_ATOMIC_SUB(&zone->allocated, nsize - osize); __ha_barrier_atomic_store(); return ptr; } /* This function can fail with an abort() due to a Lua critical error. * We are in the initialisation process of HAProxy, this abort() is * tolerated. */ lua_State *hlua_init_state(int thread_num) { int i; int idx; struct sample_fetch *sf; struct sample_conv *sc; char *p; const char *error_msg; void **context; lua_State *L; struct prepend_path *pp; /* Init main lua stack. */ L = lua_newstate(hlua_alloc, &hlua_global_allocator); if (!L) { fprintf(stderr, "Lua init: critical error: lua_newstate() returned NULL." " This may possibly be caused by a memory allocation error.\n"); exit(1); } /* Initialise Lua context to NULL */ context = lua_getextraspace(L); *context = NULL; /* From this point, until the end of the initialisation function, * the Lua function can fail with an abort. We are in the initialisation * process of HAProxy, this abort() is tolerated. */ /* Call post initialisation function in safe environment. */ if (setjmp(safe_ljmp_env) != 0) { lua_atpanic(L, hlua_panic_safe); if (lua_type(L, -1) == LUA_TSTRING) error_msg = hlua_tostring_safe(L, -1); else error_msg = "critical error"; fprintf(stderr, "Lua init: %s.\n", error_msg); exit(1); } else { lua_atpanic(L, hlua_panic_ljmp); } /* Initialise lua. */ luaL_openlibs(L); #define HLUA_PREPEND_PATH_TOSTRING1(x) #x #define HLUA_PREPEND_PATH_TOSTRING(x) HLUA_PREPEND_PATH_TOSTRING1(x) #ifdef HLUA_PREPEND_PATH hlua_prepend_path(L, "path", HLUA_PREPEND_PATH_TOSTRING(HLUA_PREPEND_PATH)); #endif #ifdef HLUA_PREPEND_CPATH hlua_prepend_path(L, "cpath", HLUA_PREPEND_PATH_TOSTRING(HLUA_PREPEND_CPATH)); #endif #undef HLUA_PREPEND_PATH_TOSTRING #undef HLUA_PREPEND_PATH_TOSTRING1 /* Apply configured prepend path */ list_for_each_entry(pp, &prepend_path_list, l) hlua_prepend_path(L, pp->type, pp->path); /* * Override some lua functions. * */ /* push our "safe" coroutine.create() function */ lua_getglobal(L, "coroutine"); lua_pushcclosure(L, hlua_coroutine_create, 0); lua_setfield(L, -2, "create"); /* * * Create "core" object. * */ /* This table entry is the object "core" base. */ lua_newtable(L); /* set the thread id */ hlua_class_const_int(L, "thread", thread_num); /* Push the loglevel constants. */ hlua_class_const_int(L, "silent", -1); for (i = 0; i < NB_LOG_LEVELS; i++) hlua_class_const_int(L, log_levels[i], i); /* Register special functions. */ hlua_class_function(L, "register_init", hlua_register_init); hlua_class_function(L, "register_task", hlua_register_task); hlua_class_function(L, "register_fetches", hlua_register_fetches); hlua_class_function(L, "register_converters", hlua_register_converters); hlua_class_function(L, "register_action", hlua_register_action); hlua_class_function(L, "register_service", hlua_register_service); hlua_class_function(L, "register_cli", hlua_register_cli); hlua_class_function(L, "register_filter", hlua_register_filter); hlua_class_function(L, "yield", hlua_yield); hlua_class_function(L, "set_nice", hlua_set_nice); hlua_class_function(L, "sleep", hlua_sleep); hlua_class_function(L, "msleep", hlua_msleep); hlua_class_function(L, "add_acl", hlua_add_acl); hlua_class_function(L, "del_acl", hlua_del_acl); hlua_class_function(L, "set_map", hlua_set_map); hlua_class_function(L, "del_map", hlua_del_map); hlua_class_function(L, "get_var", hlua_core_get_var); hlua_class_function(L, "tcp", hlua_socket_new); hlua_class_function(L, "httpclient", hlua_httpclient_new); hlua_class_function(L, "event_sub", hlua_event_global_sub); hlua_class_function(L, "log", hlua_log); hlua_class_function(L, "Debug", hlua_log_debug); hlua_class_function(L, "Info", hlua_log_info); hlua_class_function(L, "Warning", hlua_log_warning); hlua_class_function(L, "Alert", hlua_log_alert); hlua_class_function(L, "done", hlua_done); hlua_class_function(L, "disable_legacy_mailers", hlua_disable_legacy_mailers); hlua_fcn_reg_core_fcn(L); lua_setglobal(L, "core"); /* * * Create "act" object. * */ /* This table entry is the object "act" base. */ lua_newtable(L); /* push action return constants */ hlua_class_const_int(L, "CONTINUE", ACT_RET_CONT); hlua_class_const_int(L, "STOP", ACT_RET_STOP); hlua_class_const_int(L, "YIELD", ACT_RET_YIELD); hlua_class_const_int(L, "ERROR", ACT_RET_ERR); hlua_class_const_int(L, "DONE", ACT_RET_DONE); hlua_class_const_int(L, "DENY", ACT_RET_DENY); hlua_class_const_int(L, "ABORT", ACT_RET_ABRT); hlua_class_const_int(L, "INVALID", ACT_RET_INV); hlua_class_function(L, "wake_time", hlua_set_wake_time); lua_setglobal(L, "act"); /* * * Create "Filter" object. * */ /* This table entry is the object "filter" base. */ lua_newtable(L); /* push flags and constants */ hlua_class_const_int(L, "CONTINUE", 1); hlua_class_const_int(L, "WAIT", 0); hlua_class_const_int(L, "ERROR", -1); hlua_class_const_int(L, "FLT_CFG_FL_HTX", FLT_CFG_FL_HTX); hlua_class_function(L, "wake_time", hlua_set_wake_time); hlua_class_function(L, "register_data_filter", hlua_register_data_filter); hlua_class_function(L, "unregister_data_filter", hlua_unregister_data_filter); lua_setglobal(L, "filter"); /* * * Register class Map * */ /* This table entry is the object "Map" base. */ lua_newtable(L); /* register pattern types. */ for (i=0; ikw, trash.size); for (p = trash.area; *p; p++) if (*p == '.' || *p == '-' || *p == '+') *p = '_'; /* Register the function. */ lua_pushstring(L, trash.area); lua_pushlightuserdata(L, sf); lua_pushcclosure(L, hlua_run_sample_fetch, 1); lua_rawset(L, -3); } lua_rawset(L, -3); /* Register previous table in the registry with reference and named entry. */ class_fetches_ref = hlua_register_metatable(L, CLASS_FETCHES); /* * * Register class Converters * */ /* Create and fill the metatable. */ lua_newtable(L); /* Create and fill the __index entry. */ lua_pushstring(L, "__index"); lua_newtable(L); /* Browse existing converters and create the associated * object method. */ sc = NULL; while ((sc = sample_conv_getnext(sc, &idx)) != NULL) { /* gL.Tua doesn't support '.' and '-' in the function names, replace it * by an underscore. */ strlcpy2(trash.area, sc->kw, trash.size); for (p = trash.area; *p; p++) if (*p == '.' || *p == '-' || *p == '+') *p = '_'; /* Register the function. */ lua_pushstring(L, trash.area); lua_pushlightuserdata(L, sc); lua_pushcclosure(L, hlua_run_sample_conv, 1); lua_rawset(L, -3); } lua_rawset(L, -3); /* Register previous table in the registry with reference and named entry. */ class_converters_ref = hlua_register_metatable(L, CLASS_CONVERTERS); /* * * Register class HTTP * */ /* Create and fill the metatable. */ lua_newtable(L); /* Create and fill the __index entry. */ lua_pushstring(L, "__index"); lua_newtable(L); /* Register Lua functions. */ hlua_class_function(L, "req_get_headers",hlua_http_req_get_headers); hlua_class_function(L, "req_del_header", hlua_http_req_del_hdr); hlua_class_function(L, "req_rep_header", hlua_http_req_rep_hdr); hlua_class_function(L, "req_rep_value", hlua_http_req_rep_val); hlua_class_function(L, "req_add_header", hlua_http_req_add_hdr); hlua_class_function(L, "req_set_header", hlua_http_req_set_hdr); hlua_class_function(L, "req_set_method", hlua_http_req_set_meth); hlua_class_function(L, "req_set_path", hlua_http_req_set_path); hlua_class_function(L, "req_set_query", hlua_http_req_set_query); hlua_class_function(L, "req_set_uri", hlua_http_req_set_uri); hlua_class_function(L, "res_get_headers",hlua_http_res_get_headers); hlua_class_function(L, "res_del_header", hlua_http_res_del_hdr); hlua_class_function(L, "res_rep_header", hlua_http_res_rep_hdr); hlua_class_function(L, "res_rep_value", hlua_http_res_rep_val); hlua_class_function(L, "res_add_header", hlua_http_res_add_hdr); hlua_class_function(L, "res_set_header", hlua_http_res_set_hdr); hlua_class_function(L, "res_set_status", hlua_http_res_set_status); lua_rawset(L, -3); /* Register previous table in the registry with reference and named entry. */ class_http_ref = hlua_register_metatable(L, CLASS_HTTP); /* * * Register class HTTPMessage * */ /* Create and fill the metatable. */ lua_newtable(L); /* Create and fill the __index entry. */ lua_pushstring(L, "__index"); lua_newtable(L); /* Register Lua functions. */ hlua_class_function(L, "is_resp", hlua_http_msg_is_resp); hlua_class_function(L, "get_stline", hlua_http_msg_get_stline); hlua_class_function(L, "get_headers", hlua_http_msg_get_headers); hlua_class_function(L, "del_header", hlua_http_msg_del_hdr); hlua_class_function(L, "rep_header", hlua_http_msg_rep_hdr); hlua_class_function(L, "rep_value", hlua_http_msg_rep_val); hlua_class_function(L, "add_header", hlua_http_msg_add_hdr); hlua_class_function(L, "set_header", hlua_http_msg_set_hdr); hlua_class_function(L, "set_method", hlua_http_msg_set_meth); hlua_class_function(L, "set_path", hlua_http_msg_set_path); hlua_class_function(L, "set_query", hlua_http_msg_set_query); hlua_class_function(L, "set_uri", hlua_http_msg_set_uri); hlua_class_function(L, "set_status", hlua_http_msg_set_status); hlua_class_function(L, "is_full", hlua_http_msg_is_full); hlua_class_function(L, "may_recv", hlua_http_msg_may_recv); hlua_class_function(L, "eom", hlua_http_msg_is_eom); hlua_class_function(L, "input", hlua_http_msg_get_in_len); hlua_class_function(L, "output", hlua_http_msg_get_out_len); hlua_class_function(L, "body", hlua_http_msg_get_body); hlua_class_function(L, "set", hlua_http_msg_set_data); hlua_class_function(L, "remove", hlua_http_msg_del_data); hlua_class_function(L, "append", hlua_http_msg_append); hlua_class_function(L, "prepend", hlua_http_msg_prepend); hlua_class_function(L, "insert", hlua_http_msg_insert_data); hlua_class_function(L, "set_eom", hlua_http_msg_set_eom); hlua_class_function(L, "unset_eom", hlua_http_msg_unset_eom); hlua_class_function(L, "send", hlua_http_msg_send); hlua_class_function(L, "forward", hlua_http_msg_forward); lua_rawset(L, -3); /* Register previous table in the registry with reference and named entry. */ class_http_msg_ref = hlua_register_metatable(L, CLASS_HTTP_MSG); /* * * Register class HTTPClient * */ /* Create and fill the metatable. */ lua_newtable(L); lua_pushstring(L, "__index"); lua_newtable(L); hlua_class_function(L, "get", hlua_httpclient_get); hlua_class_function(L, "head", hlua_httpclient_head); hlua_class_function(L, "put", hlua_httpclient_put); hlua_class_function(L, "post", hlua_httpclient_post); hlua_class_function(L, "delete", hlua_httpclient_delete); lua_settable(L, -3); /* Sets the __index entry. */ /* Register the garbage collector entry. */ lua_pushstring(L, "__gc"); lua_pushcclosure(L, hlua_httpclient_gc, 0); lua_settable(L, -3); /* Push the last 2 entries in the table at index -3 */ class_httpclient_ref = hlua_register_metatable(L, CLASS_HTTPCLIENT); /* * * Register class AppletTCP * */ /* Create and fill the metatable. */ lua_newtable(L); /* Create and fill the __index entry. */ lua_pushstring(L, "__index"); lua_newtable(L); /* Register Lua functions. */ hlua_class_function(L, "getline", hlua_applet_tcp_getline); hlua_class_function(L, "receive", hlua_applet_tcp_recv); hlua_class_function(L, "send", hlua_applet_tcp_send); hlua_class_function(L, "set_priv", hlua_applet_tcp_set_priv); hlua_class_function(L, "get_priv", hlua_applet_tcp_get_priv); hlua_class_function(L, "set_var", hlua_applet_tcp_set_var); hlua_class_function(L, "unset_var", hlua_applet_tcp_unset_var); hlua_class_function(L, "get_var", hlua_applet_tcp_get_var); lua_settable(L, -3); /* Register previous table in the registry with reference and named entry. */ class_applet_tcp_ref = hlua_register_metatable(L, CLASS_APPLET_TCP); /* * * Register class AppletHTTP * */ /* Create and fill the metatable. */ lua_newtable(L); /* Create and fill the __index entry. */ lua_pushstring(L, "__index"); lua_newtable(L); /* Register Lua functions. */ hlua_class_function(L, "set_priv", hlua_applet_http_set_priv); hlua_class_function(L, "get_priv", hlua_applet_http_get_priv); hlua_class_function(L, "set_var", hlua_applet_http_set_var); hlua_class_function(L, "unset_var", hlua_applet_http_unset_var); hlua_class_function(L, "get_var", hlua_applet_http_get_var); hlua_class_function(L, "getline", hlua_applet_http_getline); hlua_class_function(L, "receive", hlua_applet_http_recv); hlua_class_function(L, "send", hlua_applet_http_send); hlua_class_function(L, "add_header", hlua_applet_http_addheader); hlua_class_function(L, "set_status", hlua_applet_http_status); hlua_class_function(L, "start_response", hlua_applet_http_start_response); lua_settable(L, -3); /* Register previous table in the registry with reference and named entry. */ class_applet_http_ref = hlua_register_metatable(L, CLASS_APPLET_HTTP); /* * * Register class TXN * */ /* Create and fill the metatable. */ lua_newtable(L); /* Create and fill the __index entry. */ lua_pushstring(L, "__index"); lua_newtable(L); /* Register Lua functions. */ hlua_class_function(L, "set_priv", hlua_set_priv); hlua_class_function(L, "get_priv", hlua_get_priv); hlua_class_function(L, "set_var", hlua_set_var); hlua_class_function(L, "unset_var", hlua_unset_var); hlua_class_function(L, "get_var", hlua_get_var); hlua_class_function(L, "done", hlua_txn_done); hlua_class_function(L, "reply", hlua_txn_reply_new); hlua_class_function(L, "set_loglevel", hlua_txn_set_loglevel); hlua_class_function(L, "set_tos", hlua_txn_set_tos); hlua_class_function(L, "set_mark", hlua_txn_set_mark); hlua_class_function(L, "set_priority_class", hlua_txn_set_priority_class); hlua_class_function(L, "set_priority_offset", hlua_txn_set_priority_offset); hlua_class_function(L, "deflog", hlua_txn_deflog); hlua_class_function(L, "log", hlua_txn_log); hlua_class_function(L, "Debug", hlua_txn_log_debug); hlua_class_function(L, "Info", hlua_txn_log_info); hlua_class_function(L, "Warning", hlua_txn_log_warning); hlua_class_function(L, "Alert", hlua_txn_log_alert); lua_rawset(L, -3); /* Register previous table in the registry with reference and named entry. */ class_txn_ref = hlua_register_metatable(L, CLASS_TXN); /* * * Register class reply * */ lua_newtable(L); lua_pushstring(L, "__index"); lua_newtable(L); hlua_class_function(L, "set_status", hlua_txn_reply_set_status); hlua_class_function(L, "add_header", hlua_txn_reply_add_header); hlua_class_function(L, "del_header", hlua_txn_reply_del_header); hlua_class_function(L, "set_body", hlua_txn_reply_set_body); lua_settable(L, -3); /* Sets the __index entry. */ class_txn_reply_ref = luaL_ref(L, LUA_REGISTRYINDEX); /* * * Register class Socket * */ /* Create and fill the metatable. */ lua_newtable(L); /* Create and fill the __index entry. */ lua_pushstring(L, "__index"); lua_newtable(L); #ifdef USE_OPENSSL hlua_class_function(L, "connect_ssl", hlua_socket_connect_ssl); #endif hlua_class_function(L, "connect", hlua_socket_connect); hlua_class_function(L, "send", hlua_socket_send); hlua_class_function(L, "receive", hlua_socket_receive); hlua_class_function(L, "close", hlua_socket_close); hlua_class_function(L, "getpeername", hlua_socket_getpeername); hlua_class_function(L, "getsockname", hlua_socket_getsockname); hlua_class_function(L, "setoption", hlua_socket_setoption); hlua_class_function(L, "settimeout", hlua_socket_settimeout); lua_rawset(L, -3); /* Push the last 2 entries in the table at index -3 */ /* Register the garbage collector entry. */ lua_pushstring(L, "__gc"); lua_pushcclosure(L, hlua_socket_gc, 0); lua_rawset(L, -3); /* Push the last 2 entries in the table at index -3 */ /* Register previous table in the registry with reference and named entry. */ class_socket_ref = hlua_register_metatable(L, CLASS_SOCKET); lua_atpanic(L, hlua_panic_safe); return L; } void hlua_init(void) { int i; char *errmsg; #ifdef USE_OPENSSL struct srv_kw *kw; int tmp_error; char *error; char *args[] = { /* SSL client configuration. */ "ssl", "verify", "none", NULL }; #endif /* Init post init function list head */ for (i = 0; i < MAX_THREADS + 1; i++) LIST_INIT(&hlua_init_functions[i]); /* Init state for common/shared lua parts */ hlua_state_id = 0; ha_set_thread(NULL); hlua_states[0] = hlua_init_state(0); /* Init state 1 for thread 0. We have at least one thread. */ hlua_state_id = 1; ha_set_thread(NULL); hlua_states[1] = hlua_init_state(1); /* Proxy and server configuration initialisation. */ socket_proxy = alloc_new_proxy("LUA-SOCKET", PR_CAP_FE|PR_CAP_BE|PR_CAP_INT, &errmsg); if (!socket_proxy) { fprintf(stderr, "Lua init: %s\n", errmsg); exit(1); } /* Init TCP server: unchanged parameters */ socket_tcp = new_server(socket_proxy); if (!socket_tcp) { fprintf(stderr, "Lua init: failed to allocate tcp server socket\n"); exit(1); } #ifdef USE_OPENSSL /* Init TCP server: unchanged parameters */ socket_ssl = new_server(socket_proxy); if (!socket_ssl) { fprintf(stderr, "Lua init: failed to allocate ssl server socket\n"); exit(1); } socket_ssl->use_ssl = 1; socket_ssl->xprt = xprt_get(XPRT_SSL); for (i = 0; args[i] != NULL; i++) { if ((kw = srv_find_kw(args[i])) != NULL) { /* Maybe it's registered server keyword */ /* * * If the keyword is not known, we can search in the registered * server keywords. This is useful to configure special SSL * features like client certificates and ssl_verify. * */ tmp_error = kw->parse(args, &i, socket_proxy, socket_ssl, &error); if (tmp_error != 0) { fprintf(stderr, "INTERNAL ERROR: %s\n", error); abort(); /* This must be never arrives because the command line not editable by the user. */ } i += kw->skip; } } #endif } static void hlua_deinit() { int thr; struct hlua_reg_filter *reg_flt, *reg_flt_bck; list_for_each_entry_safe(reg_flt, reg_flt_bck, &referenced_filters, l) release_hlua_reg_filter(reg_flt); for (thr = 0; thr < MAX_THREADS+1; thr++) { if (hlua_states[thr]) lua_close(hlua_states[thr]); } srv_drop(socket_tcp); #ifdef USE_OPENSSL srv_drop(socket_ssl); #endif free_proxy(socket_proxy); } REGISTER_POST_DEINIT(hlua_deinit); static void hlua_register_build_options(void) { char *ptr = NULL; memprintf(&ptr, "Built with Lua version : %s", LUA_RELEASE); hap_register_build_opts(ptr, 1); } INITCALL0(STG_REGISTER, hlua_register_build_options);