/*- * Copyright 2016 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "lua_common.h" LUA_FUNCTION_DEF(xmlrpc, parse_reply); LUA_FUNCTION_DEF(xmlrpc, make_request); static const struct luaL_reg xmlrpclib_m[] = { LUA_INTERFACE_DEF(xmlrpc, parse_reply), LUA_INTERFACE_DEF(xmlrpc, make_request), {"__tostring", rspamd_lua_class_tostring}, {NULL, NULL}}; #define msg_debug_xmlrpc(...) rspamd_conditional_debug_fast(NULL, NULL, \ rspamd_xmlrpc_log_id, "xmlrpc", "", \ RSPAMD_LOG_FUNC, \ __VA_ARGS__) INIT_LOG_MODULE(xmlrpc) enum lua_xmlrpc_state { read_method_response = 0, read_params = 1, read_param = 2, read_param_value = 3, read_param_element = 4, read_struct = 5, read_struct_member_name = 6, read_struct_member_value = 7, read_struct_element = 8, read_string = 9, read_int = 10, read_double = 11, read_array = 12, read_array_value = 13, read_array_element = 14, error_state = 99, success_state = 100, }; enum lua_xmlrpc_stack { st_array = 1, st_struct = 2, }; struct lua_xmlrpc_ud { enum lua_xmlrpc_state parser_state; GQueue *st; int param_count; gboolean got_text; lua_State *L; }; static void xmlrpc_start_element(GMarkupParseContext *context, const char *name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **error); static void xmlrpc_end_element(GMarkupParseContext *context, const char *element_name, gpointer user_data, GError **error); static void xmlrpc_error(GMarkupParseContext *context, GError *error, gpointer user_data); static void xmlrpc_text(GMarkupParseContext *context, const char *text, gsize text_len, gpointer user_data, GError **error); static GMarkupParser xmlrpc_parser = { .start_element = xmlrpc_start_element, .end_element = xmlrpc_end_element, .passthrough = NULL, .text = xmlrpc_text, .error = xmlrpc_error, }; static GQuark xmlrpc_error_quark(void) { return g_quark_from_static_string("xmlrpc-error-quark"); } static void xmlrpc_start_element(GMarkupParseContext *context, const char *name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **error) { struct lua_xmlrpc_ud *ud = user_data; enum lua_xmlrpc_state last_state; last_state = ud->parser_state; msg_debug_xmlrpc("got start element %s on state %d", name, last_state); switch (ud->parser_state) { case read_method_response: /* Expect tag methodResponse */ if (g_ascii_strcasecmp(name, "methodResponse") == 0) { ud->parser_state = read_params; } else { /* Error state */ ud->parser_state = error_state; } break; case read_params: /* Expect tag params */ if (g_ascii_strcasecmp(name, "params") == 0) { ud->parser_state = read_param; /* result -> table of params indexed by int */ lua_newtable(ud->L); } else { /* Error state */ ud->parser_state = error_state; } break; case read_param: /* Expect tag param */ if (g_ascii_strcasecmp(name, "param") == 0) { ud->parser_state = read_param_value; /* Create new param */ } else { /* Error state */ ud->parser_state = error_state; } break; case read_param_value: /* Expect tag value */ if (g_ascii_strcasecmp(name, "value") == 0) { ud->parser_state = read_param_element; } else { /* Error state */ ud->parser_state = error_state; } break; case read_param_element: /* Expect tag struct */ if (g_ascii_strcasecmp(name, "struct") == 0) { ud->parser_state = read_struct; /* Create new param of table type */ lua_newtable(ud->L); g_queue_push_head(ud->st, GINT_TO_POINTER(st_struct)); msg_debug_xmlrpc("push struct"); } else if (g_ascii_strcasecmp(name, "array") == 0) { ud->parser_state = read_array; /* Create new param of table type */ lua_newtable(ud->L); g_queue_push_head(ud->st, GINT_TO_POINTER(st_array)); msg_debug_xmlrpc("push array"); } else if (g_ascii_strcasecmp(name, "string") == 0) { ud->parser_state = read_string; ud->got_text = FALSE; } else if (g_ascii_strcasecmp(name, "int") == 0) { ud->parser_state = read_int; ud->got_text = FALSE; } else if (g_ascii_strcasecmp(name, "double") == 0) { ud->parser_state = read_double; ud->got_text = FALSE; } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct: /* Parse structure */ /* Expect tag member */ if (g_ascii_strcasecmp(name, "member") == 0) { ud->parser_state = read_struct_member_name; } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct_member_name: /* Expect tag name */ if (g_ascii_strcasecmp(name, "name") == 0) { ud->parser_state = read_struct_member_value; } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct_member_value: /* Accept value */ if (g_ascii_strcasecmp(name, "value") == 0) { ud->parser_state = read_struct_element; } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct_element: /* Parse any values */ /* Primitives */ if (g_ascii_strcasecmp(name, "string") == 0) { ud->parser_state = read_string; ud->got_text = FALSE; } else if (g_ascii_strcasecmp(name, "int") == 0) { ud->parser_state = read_int; ud->got_text = FALSE; } else if (g_ascii_strcasecmp(name, "double") == 0) { ud->parser_state = read_double; ud->got_text = FALSE; } /* Structure */ else if (g_ascii_strcasecmp(name, "struct") == 0) { ud->parser_state = read_struct; /* Create new param of table type */ lua_newtable(ud->L); g_queue_push_head(ud->st, GINT_TO_POINTER(st_struct)); msg_debug_xmlrpc("push struct"); } else if (g_ascii_strcasecmp(name, "array") == 0) { ud->parser_state = read_array; /* Create new param of table type */ lua_newtable(ud->L); g_queue_push_head(ud->st, GINT_TO_POINTER(st_array)); msg_debug_xmlrpc("push array"); } else { /* Error state */ ud->parser_state = error_state; } break; case read_array: /* Parse array */ /* Expect data */ if (g_ascii_strcasecmp(name, "data") == 0) { ud->parser_state = read_array_value; } else { /* Error state */ ud->parser_state = error_state; } break; case read_array_value: /* Accept array value */ if (g_ascii_strcasecmp(name, "value") == 0) { ud->parser_state = read_array_element; } else { /* Error state */ ud->parser_state = error_state; } break; case read_array_element: /* Parse any values */ /* Primitives */ if (g_ascii_strcasecmp(name, "string") == 0) { ud->parser_state = read_string; ud->got_text = FALSE; } else if (g_ascii_strcasecmp(name, "int") == 0) { ud->parser_state = read_int; ud->got_text = FALSE; } else if (g_ascii_strcasecmp(name, "double") == 0) { ud->parser_state = read_double; ud->got_text = FALSE; } /* Structure */ else if (g_ascii_strcasecmp(name, "struct") == 0) { ud->parser_state = read_struct; /* Create new param of table type */ lua_newtable(ud->L); g_queue_push_head(ud->st, GINT_TO_POINTER(st_struct)); msg_debug_xmlrpc("push struct"); } else if (g_ascii_strcasecmp(name, "array") == 0) { ud->parser_state = read_array; /* Create new param of table type */ lua_newtable(ud->L); g_queue_push_head(ud->st, GINT_TO_POINTER(st_array)); msg_debug_xmlrpc("push array"); } else { /* Error state */ ud->parser_state = error_state; } break; default: break; } msg_debug_xmlrpc("switched state on start tag %d->%d", last_state, ud->parser_state); if (ud->parser_state == error_state) { g_set_error(error, xmlrpc_error_quark(), 1, "xmlrpc parse error on state: %d, while parsing start tag: %s", last_state, name); } } static void xmlrpc_end_element(GMarkupParseContext *context, const char *name, gpointer user_data, GError **error) { struct lua_xmlrpc_ud *ud = user_data; enum lua_xmlrpc_state last_state; int last_queued; last_state = ud->parser_state; msg_debug_xmlrpc("got end element %s on state %d", name, last_state); switch (ud->parser_state) { case read_method_response: ud->parser_state = error_state; break; case read_params: /* Got methodResponse */ if (g_ascii_strcasecmp(name, "methodResponse") == 0) { /* End processing */ ud->parser_state = success_state; } else { /* Error state */ ud->parser_state = error_state; } break; case read_param: /* Got tag params */ if (g_ascii_strcasecmp(name, "params") == 0) { ud->parser_state = read_params; } else { /* Error state */ ud->parser_state = error_state; } break; case read_param_value: /* Got tag param */ if (g_ascii_strcasecmp(name, "param") == 0) { ud->parser_state = read_param; lua_rawseti(ud->L, -2, ++ud->param_count); msg_debug_xmlrpc("set param element idx: %d", ud->param_count); } else { /* Error state */ ud->parser_state = error_state; } break; case read_param_element: /* Got tag value */ if (g_ascii_strcasecmp(name, "value") == 0) { if (g_queue_get_length(ud->st) == 0) { ud->parser_state = read_param_value; } else { if (GPOINTER_TO_INT(g_queue_peek_head(ud->st)) == st_struct) { ud->parser_state = read_struct_member_name; } else { ud->parser_state = read_array_value; } } } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct: /* Got tag struct */ if (g_ascii_strcasecmp(name, "struct") == 0) { g_assert(GPOINTER_TO_INT(g_queue_pop_head(ud->st)) == st_struct); if (g_queue_get_length(ud->st) == 0) { ud->parser_state = read_param_element; } else { last_queued = GPOINTER_TO_INT(g_queue_peek_head(ud->st)); if (last_queued == st_struct) { ud->parser_state = read_struct_element; } else { ud->parser_state = read_array_element; } } msg_debug_xmlrpc("pop struct"); } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct_member_name: /* Got tag member */ if (g_ascii_strcasecmp(name, "member") == 0) { ud->parser_state = read_struct; /* Set table */ msg_debug_xmlrpc("set struct element idx: %s", lua_tostring(ud->L, -2)); lua_settable(ud->L, -3); } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct_member_value: /* Got tag name */ if (g_ascii_strcasecmp(name, "name") == 0) { ud->parser_state = read_struct_member_value; } else { /* Error state */ ud->parser_state = error_state; } break; case read_struct_element: /* Got tag value */ if (g_ascii_strcasecmp(name, "value") == 0) { ud->parser_state = read_struct_member_name; } else { /* Error state */ ud->parser_state = error_state; } break; case read_string: case read_int: case read_double: /* Parse any values */ /* Handle empty tags */ if (!ud->got_text) { lua_pushnil(ud->L); } else { ud->got_text = FALSE; } /* Primitives */ if (g_ascii_strcasecmp(name, "string") == 0 || g_ascii_strcasecmp(name, "int") == 0 || g_ascii_strcasecmp(name, "double") == 0) { if (GPOINTER_TO_INT(g_queue_peek_head(ud->st)) == st_struct) { ud->parser_state = read_struct_element; } else { ud->parser_state = read_array_element; } } else { /* Error state */ ud->parser_state = error_state; } break; case read_array: /* Got tag array */ if (g_ascii_strcasecmp(name, "array") == 0) { g_assert(GPOINTER_TO_INT(g_queue_pop_head(ud->st)) == st_array); if (g_queue_get_length(ud->st) == 0) { ud->parser_state = read_param_element; } else { last_queued = GPOINTER_TO_INT(g_queue_peek_head(ud->st)); if (last_queued == st_struct) { ud->parser_state = read_struct_element; } else { ud->parser_state = read_array_element; } } msg_debug_xmlrpc("pop array"); } else { /* Error state */ ud->parser_state = error_state; } break; case read_array_value: /* Got tag data */ if (g_ascii_strcasecmp(name, "data") == 0) { ud->parser_state = read_array; } else { /* Error state */ ud->parser_state = error_state; } break; case read_array_element: /* Got tag value */ if (g_ascii_strcasecmp(name, "value") == 0) { unsigned int tbl_len = rspamd_lua_table_size(ud->L, -2); lua_rawseti(ud->L, -2, tbl_len + 1); msg_debug_xmlrpc("set array element idx: %d", tbl_len + 1); ud->parser_state = read_array_value; } else { /* Error state */ ud->parser_state = error_state; } break; default: break; } msg_debug_xmlrpc("switched state on end tag %d->%d", last_state, ud->parser_state); if (ud->parser_state == error_state) { g_set_error(error, xmlrpc_error_quark(), 1, "xmlrpc parse error on state: %d, while parsing end tag: %s", last_state, name); } } static void xmlrpc_text(GMarkupParseContext *context, const char *text, gsize text_len, gpointer user_data, GError **error) { struct lua_xmlrpc_ud *ud = user_data; gulong num; double dnum; /* Strip line */ while (text_len > 0 && g_ascii_isspace(*text)) { text++; text_len--; } while (text_len > 0 && g_ascii_isspace(text[text_len - 1])) { text_len--; } if (text_len > 0) { msg_debug_xmlrpc("got data on state %d", ud->parser_state); switch (ud->parser_state) { case read_struct_member_value: /* Push key */ lua_pushlstring(ud->L, text, text_len); break; case read_string: /* Push string value */ lua_pushlstring(ud->L, text, text_len); break; case read_int: /* Push integer value */ rspamd_strtoul(text, text_len, &num); lua_pushinteger(ud->L, num); break; case read_double: /* Push integer value */ dnum = strtod(text, NULL); lua_pushnumber(ud->L, dnum); break; default: break; } ud->got_text = TRUE; } } static void xmlrpc_error(GMarkupParseContext *context, GError *error, gpointer user_data) { msg_err("xmlrpc parser error: %s", error->message); } static int lua_xmlrpc_parse_reply(lua_State *L) { LUA_TRACE_POINT; const char *data; GMarkupParseContext *ctx; GError *err = NULL; struct lua_xmlrpc_ud ud; gsize s; gboolean res; data = luaL_checklstring(L, 1, &s); if (data != NULL) { ud.L = L; ud.parser_state = read_method_response; ud.param_count = 0; ud.st = g_queue_new(); ctx = g_markup_parse_context_new(&xmlrpc_parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &ud, NULL); res = g_markup_parse_context_parse(ctx, data, s, &err); g_markup_parse_context_free(ctx); if (!res) { lua_pushnil(L); } } else { lua_pushnil(L); } /* Return table or nil */ return 1; } static int lua_xmlrpc_parse_table(lua_State *L, int pos, char *databuf, int pr, gsize size) { int r = pr, num; double dnum; r += rspamd_snprintf(databuf + r, size - r, ""); lua_pushnil(L); /* first key */ while (lua_next(L, pos) != 0) { /* uses 'key' (at index -2) and 'value' (at index -1) */ if (lua_type(L, -2) != LUA_TSTRING) { /* Ignore non sting keys */ lua_pop(L, 1); continue; } r += rspamd_snprintf(databuf + r, size - r, "%s", lua_tostring(L, -2)); switch (lua_type(L, -1)) { case LUA_TNUMBER: num = lua_tointeger(L, -1); dnum = lua_tonumber(L, -1); /* Try to avoid conversion errors */ if (dnum != (double) num) { r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, "%f", dnum); } else { r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, "%d", num); } break; case LUA_TBOOLEAN: r += rspamd_snprintf(databuf + r, size - r, "%d", lua_toboolean(L, -1) ? 1 : 0); break; case LUA_TSTRING: r += rspamd_snprintf(databuf + r, size - r, "%s", lua_tostring(L, -1)); break; case LUA_TTABLE: /* Recursive call */ r += lua_xmlrpc_parse_table(L, -1, databuf + r, r, size); break; } r += rspamd_snprintf(databuf + r, size - r, ""); /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } r += rspamd_snprintf(databuf + r, size - r, ""); return r - pr; } /* * Internal limitation: xmlrpc request must NOT be more than * BUFSIZ * 2 (16384 bytes) */ static int lua_xmlrpc_make_request(lua_State *L) { LUA_TRACE_POINT; char databuf[BUFSIZ * 2]; const char *func; int r, top, i, num; double dnum; func = luaL_checkstring(L, 1); if (func) { r = rspamd_snprintf(databuf, sizeof(databuf), "" "%s", func); /* Extract arguments */ top = lua_gettop(L); /* Get additional options */ for (i = 2; i <= top; i++) { r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, ""); switch (lua_type(L, i)) { case LUA_TNUMBER: num = lua_tointeger(L, i); dnum = lua_tonumber(L, i); /* Try to avoid conversion errors */ if (dnum != (double) num) { r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, "%f", dnum); } else { r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, "%d", num); } break; case LUA_TBOOLEAN: r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, "%d", lua_toboolean(L, i) ? 1 : 0); break; case LUA_TSTRING: r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, "%s", lua_tostring(L, i)); break; case LUA_TTABLE: r += lua_xmlrpc_parse_table(L, i, databuf, r, sizeof(databuf)); break; } r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, ""); } r += rspamd_snprintf(databuf + r, sizeof(databuf) - r, ""); lua_pushlstring(L, databuf, r); } else { lua_pushnil(L); } return 1; } static int lua_load_xmlrpc(lua_State *L) { lua_newtable(L); luaL_register(L, NULL, xmlrpclib_m); return 1; } void luaopen_xmlrpc(lua_State *L) { rspamd_lua_add_preload(L, "rspamd_xmlrpc", lua_load_xmlrpc); }