diff --git a/src/box/iproto.cc b/src/box/iproto.cc index 814ad86818fe5fb7feafd740cf12a17739138947..08f1a0b42414196ee04fec413c7a01477e597d34 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -483,7 +483,7 @@ iproto_enqueue_batch(struct iproto_connection *con, struct ibuf *in) * stay in sync. */ if (ireq->header.type >= IPROTO_SELECT && - ireq->header.type <= IPROTO_AUTH) { + ireq->header.type <= IPROTO_EVAL) { /* Pre-parse request before putting it into the queue */ if (ireq->header.bodycnt == 0) { tnt_raise(ClientError, ER_INVALID_MSGPACK, @@ -661,16 +661,24 @@ iproto_process(struct iproto_request *ireq) case IPROTO_REPLACE: case IPROTO_UPDATE: case IPROTO_DELETE: + assert(ireq->request.type == ireq->header.type); struct iproto_port port; iproto_port_init(&port, out, ireq->header.sync); box_process(&ireq->request, (struct port *) &port); break; case IPROTO_CALL: + assert(ireq->request.type == ireq->header.type); stat_collect(stat_base, ireq->request.type, 1); box_lua_call(&ireq->request, &iobuf->out); break; + case IPROTO_EVAL: + assert(ireq->request.type == ireq->header.type); + stat_collect(stat_base, ireq->request.type, 1); + box_lua_eval(&ireq->request, &iobuf->out); + break; case IPROTO_AUTH: { + assert(ireq->request.type == ireq->header.type); const char *user = ireq->request.key; uint32_t len = mp_decode_strl(&user); authenticate(user, len, ireq->request.tuple, diff --git a/src/box/iproto_constants.c b/src/box/iproto_constants.c index 230454326d9df8f0866228558ca60f4fa1023d20..7497fec094801a9608ca3d2dbe4ce51b1511eee1 100644 --- a/src/box/iproto_constants.c +++ b/src/box/iproto_constants.c @@ -94,11 +94,12 @@ const char *iproto_type_strs[] = "UPDATE", "DELETE", "CALL", - "AUTH" + "AUTH", + "EVAL" }; #define bit(c) (1ULL<<IPROTO_##c) -const uint64_t iproto_body_key_map[IPROTO_AUTH + 1] = { +const uint64_t iproto_body_key_map[IPROTO_EVAL + 1] = { 0, /* unused */ bit(SPACE_ID) | bit(LIMIT) | bit(KEY), /* SELECT */ bit(SPACE_ID) | bit(TUPLE), /* INSERT */ @@ -106,7 +107,8 @@ const uint64_t iproto_body_key_map[IPROTO_AUTH + 1] = { bit(SPACE_ID) | bit(KEY) | bit(TUPLE), /* UPDATE */ bit(SPACE_ID) | bit(KEY), /* DELETE */ bit(FUNCTION_NAME) | bit(TUPLE), /* CALL */ - bit(USER_NAME) | bit(TUPLE) /* AUTH */ + bit(USER_NAME) | bit(TUPLE), /* AUTH */ + bit(FUNCTION_NAME) | bit(TUPLE), /* EVAL */ }; #undef bit diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h index 917dbe62d72b0f3bfb5862e210f98dd2c7592093..55572cac8c78d21033cc5163440c003b7decf613 100644 --- a/src/box/iproto_constants.h +++ b/src/box/iproto_constants.h @@ -118,8 +118,9 @@ enum iproto_type { IPROTO_DELETE = 5, IPROTO_TYPE_DML_MAX = IPROTO_DELETE + 1, IPROTO_CALL = 6, - IPROTO_TYPE_STAT_MAX = IPROTO_CALL + 1, IPROTO_AUTH = 7, + IPROTO_EVAL = 8, + IPROTO_TYPE_STAT_MAX = IPROTO_EVAL + 1, /* admin command codes */ IPROTO_PING = 64, IPROTO_JOIN = 65, @@ -138,7 +139,7 @@ extern const uint64_t iproto_body_key_map[]; static inline const char * iproto_type_name(uint32_t type) { - if (type >= IPROTO_TYPE_DML_MAX) + if (type >= IPROTO_TYPE_STAT_MAX) return "unknown"; return iproto_type_strs[type]; } diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc index eb661774c1ad8e9c71f2948c7cf36df5dc6cac72..89cf3dbd57fcd0a214a42740cfcfe19bddf87778 100644 --- a/src/box/lua/call.cc +++ b/src/box/lua/call.cc @@ -590,6 +590,61 @@ box_lua_call(struct request *request, struct obuf *out) } } +static inline void +execute_eval(lua_State *L, struct request *request, struct obuf *out) +{ + /* Check permissions */ + struct credentials *credentials = current_user(); + if (!(credentials->universal_access & PRIV_X)) { + /* Access violation, report error. */ + struct user *user = user_cache_find(credentials->uid); + tnt_raise(ClientError, ER_FUNCTION_ACCESS_DENIED, + priv_name(PRIV_X), user->name, "eval"); + } + + /* Compile expression */ + const char *expr = request->key; + uint32_t expr_len = mp_decode_strl(&expr); + if (luaL_loadbuffer(L, expr, expr_len, "=eval")) + tnt_raise(LuajitError, L); + + /* Unpack arguments */ + const char *args = request->tuple; + uint32_t arg_count = mp_decode_array(&args); + luaL_checkstack(L, arg_count, "call: out of stack"); + for (uint32_t i = 0; i < arg_count; i++) { + luamp_decode(L, luaL_msgpack_default, &args); + } + + /* Call compiled code */ + lua_call(L, arg_count, LUA_MULTRET); + + /* Send results of the called procedure to the client. */ + struct obuf_svp svp = iproto_prepare_select(out); + int nrets = lua_gettop(L); + for (int k = 1; k <= nrets; ++k) { + luamp_encode(L, luaL_msgpack_default, out, k); + } + iproto_reply_select(out, &svp, request->header->sync, nrets); +} + +void +box_lua_eval(struct request *request, struct obuf *out) +{ + lua_State *L = NULL; + try { + L = lua_newthread(tarantool_L); + LuarefGuard coro_ref(tarantool_L); + execute_eval(L, request, out); + } catch (Exception *e) { + /* Let all well-behaved exceptions pass through. */ + throw; + } catch (...) { + /* Convert Lua error to a Tarantool exception. */ + tnt_raise(LuajitError, L != NULL ? L : tarantool_L); + } +} + static int lbox_snapshot(struct lua_State *L) { diff --git a/src/box/lua/call.h b/src/box/lua/call.h index bbb1a41f5a10613d5554b66a216ad2de24285996..2562554c52e83c295ecefbf8eddefc34f58af767 100644 --- a/src/box/lua/call.h +++ b/src/box/lua/call.h @@ -41,6 +41,9 @@ struct port; void box_lua_call(struct request *request, struct obuf *out); +void +box_lua_eval(struct request *request, struct obuf *out); + extern "C" { struct port_ffi { diff --git a/src/lua/box_net_box.lua b/src/lua/box_net_box.lua index 0a7218ae26cff3ec7d915bce1693a947a8aed566..5fcb5c35391e4e1a2548c402cb80f558add88a5c 100644 --- a/src/lua/box_net_box.lua +++ b/src/lua/box_net_box.lua @@ -19,6 +19,7 @@ local UPDATE = 4 local DELETE = 5 local CALL = 6 local AUTH = 7 +local EVAL = 8 local PING = 64 local ERROR_TYPE = 65536 @@ -122,6 +123,17 @@ local proto = { ) end, + -- lua eval + eval = function(sync, expr, args) + if args == nil then + args = {} + end + return request( + { [SYNC] = sync, [TYPE] = EVAL }, + { [FUNCTION_NAME] = expr, [TUPLE] = args } + ) + end, + -- insert insert = function(sync, spaceno, tuple) return request( @@ -472,6 +484,24 @@ local remote_methods = { return res.body[DATA] end, + eval = function(self, expr, ...) + if type(self) ~= 'table' then + box.error(box.error.PROC_LUA, "usage: remote:eval(expr, ...)") + end + + expr = tostring(expr) + local res = self:_request('eval', true, expr, {...}) + local data = res.body[DATA] + local data_len = #data + if data_len == 1 then + return data[1] + elseif data_len == 0 then + return + else + return unpack(data) + end + end, + is_connected = function(self) if self.state == 'active' then return true @@ -1013,10 +1043,10 @@ local remote_methods = { if raise == nil then raise = true end - return self:_request_raw(CONSOLE_FAKESYNC, request_body, raise) + return self:_request_raw('call', CONSOLE_FAKESYNC, request_body, raise) end, - _request_raw = function(self, sync, request, raise) + _request_raw = function(self, name, sync, request, raise) local fid = fiber.id() if self.timeouts[fid] == nil then @@ -1058,7 +1088,7 @@ local remote_methods = { end if response.body[DATA] ~= nil then - if rawget(box, 'tuple') ~= nil then + if rawget(box, 'tuple') ~= nil and name ~= 'eval' then for i, v in pairs(response.body[DATA]) do response.body[DATA][i] = box.tuple.new(response.body[DATA][i]) @@ -1074,7 +1104,7 @@ local remote_methods = { _request_internal = function(self, name, raise, ...) local sync = self.proto:sync() local request = self.proto[name](sync, ...) - return self:_request_raw(sync, request, raise) + return self:_request_raw(name, sync, request, raise) end, -- private (low level) methods @@ -1135,6 +1165,16 @@ remote.self = { end return result end, + eval = function(_box, expr, ...) + local proc, errmsg = loadstring(expr) + if not proc then + proc, errmsg = loadstring("return "..expr) + end + if not proc then + box.error(box.error.PROC_LUA, errmsg) + end + return proc(...) + end, console = false } diff --git a/src/lua/console.lua b/src/lua/console.lua index 7a591139111ecb596d5a780cc007e187bb32b9db..c1f19bd340479aac0ffddfe15b053871ca70a457 100644 --- a/src/lua/console.lua +++ b/src/lua/console.lua @@ -82,7 +82,7 @@ local function remote_eval(self, line) -- -- call remote 'console.eval' function using 'dostring' and return result -- - local status, res = pcall(self.remote.call, self.remote, "dostring", + local status, res = pcall(self.remote.eval, self.remote, "return require('console').eval(...)", line) if not status then -- remote request failed diff --git a/test/box/box.net.box.result b/test/box/box.net.box.result index 448a5644ed972235e3ca459023019b4bf34e0857..25a221beb9abd4ee0b7e79fd51e8048ffad57ec0 100644 --- a/test/box/box.net.box.result +++ b/test/box/box.net.box.result @@ -10,9 +10,6 @@ log = require 'log' msgpack = require 'msgpack' --- ... -box.schema.user.grant('guest', 'read,write,execute', 'universe') ---- -... LISTEN = require('uri').parse(box.cfg.listen) --- ... @@ -54,6 +51,7 @@ cn:ping() --- - true ... +-- check permissions cn:call('unexists_procedure') --- - error: Procedure 'unexists_procedure' is not defined @@ -63,6 +61,27 @@ function test_foo(a,b,c) return { {{ [a] = 1 }}, {{ [b] = 2 }}, c } end ... cn:call('test_foo', 'a', 'b', 'c') --- +- error: Execute access denied for user 'guest' to function 'test_foo' +... +cn:eval('return 2+2') +--- +- error: Execute access denied for user 'guest' to function 'eval' +... +box.schema.user.grant('guest','execute','universe') +--- +... +cn:close() +--- +... +cn = remote:new(box.cfg.listen) +--- +... +cn:call('unexists_procedure') +--- +- error: Procedure 'unexists_procedure' is not defined +... +cn:call('test_foo', 'a', 'b', 'c') +--- - - [{'a': 1}] - [{'b': 2}] - ['c'] @@ -71,6 +90,60 @@ cn:call(nil, 'a', 'b', 'c') --- - error: Procedure 'nil' is not defined ... +cn:eval('return 2+2') +--- +- 4 +... +cn:eval('return 1, 2, 3') +--- +- 1 +- 2 +- 3 +... +cn:eval('return ...', 1, 2, 3) +--- +- 1 +- 2 +- 3 +... +cn:eval('return { k = "v1" }, true, { xx = 10, yy = 15 }, nil') +--- +- {k: v1} +- true +- {yy: 15, xx: 10} +- null +... +cn:eval('return nil') +--- +- null +... +cn:eval('return') +--- +... +cn:eval('error("exception")') +--- +- error: 'eval:1: exception' +... +cn:eval('box.error(0)') +--- +- error: Unknown error +... +cn:eval('!invalid expression') +--- +- error: 'eval:1: unexpected symbol near ''!''' +... +box.schema.user.revoke('guest','execute','universe') +--- +... +box.schema.user.grant('guest','read,write,execute','universe') +--- +... +cn:close() +--- +... +cn = remote:new(box.cfg.listen) +--- +... cn:_select(space.id, space.index.primary.id, 123) --- - [] @@ -141,7 +214,7 @@ cn.space.net_box_test_space:insert{234, 1,2,3} ... cn.space.net_box_test_space.insert{234, 1,2,3} --- -- error: 'builtin/net.box.lua:226: Use space:method(...) instead space.method(...)' +- error: 'builtin/net.box.lua:238: Use space:method(...) instead space.method(...)' ... cn.space.net_box_test_space:replace{354, 1,2,3} --- @@ -737,7 +810,7 @@ require('fiber').create( --- - status: suspended name: lua - id: 151 + id: 157 ... while true do local line = file_log:read(2048) diff --git a/test/box/box.net.box.test.lua b/test/box/box.net.box.test.lua index 2839f9eb834b96bce96fbfa101a55f3eb3f6dfb9..526e23586e25de3f30a02687624100782f3842e5 100644 --- a/test/box/box.net.box.test.lua +++ b/test/box/box.net.box.test.lua @@ -3,7 +3,6 @@ fiber = require 'fiber' log = require 'log' msgpack = require 'msgpack' -box.schema.user.grant('guest', 'read,write,execute', 'universe') LISTEN = require('uri').parse(box.cfg.listen) space = box.schema.create_space('net_box_test_space') index = space:create_index('primary', { type = 'tree' }) @@ -23,12 +22,33 @@ log.info("ping is done") cn:ping() +-- check permissions cn:call('unexists_procedure') - function test_foo(a,b,c) return { {{ [a] = 1 }}, {{ [b] = 2 }}, c } end +cn:call('test_foo', 'a', 'b', 'c') +cn:eval('return 2+2') + +box.schema.user.grant('guest','execute','universe') +cn:close() +cn = remote:new(box.cfg.listen) +cn:call('unexists_procedure') cn:call('test_foo', 'a', 'b', 'c') cn:call(nil, 'a', 'b', 'c') +cn:eval('return 2+2') +cn:eval('return 1, 2, 3') +cn:eval('return ...', 1, 2, 3) +cn:eval('return { k = "v1" }, true, { xx = 10, yy = 15 }, nil') +cn:eval('return nil') +cn:eval('return') +cn:eval('error("exception")') +cn:eval('box.error(0)') +cn:eval('!invalid expression') + +box.schema.user.revoke('guest','execute','universe') +box.schema.user.grant('guest','read,write,execute','universe') +cn:close() +cn = remote:new(box.cfg.listen) cn:_select(space.id, space.index.primary.id, 123) space:insert{123, 345} diff --git a/test/box/call.result b/test/box/call.result index b23515307bdc5e832ec3a19ea029e2f93f3ea62a..9e606bc540a08514ba7eed7c25a3bdb42aae436b 100644 --- a/test/box/call.result +++ b/test/box/call.result @@ -133,7 +133,7 @@ function f3() return {'hello', {'world'}} end ... call f3() --- -- ['hello', ('world',)] +- ['hello', ['world']] ... function f3() return 'hello', {{'world'}, {'canada'}} end --- @@ -141,7 +141,7 @@ function f3() return 'hello', {{'world'}, {'canada'}} end call f3() --- - ['hello'] -- [('world',), ('canada',)] +- [['world'], ['canada']] ... function f3() return {}, '123', {{}, {}} end --- @@ -150,14 +150,14 @@ call f3() --- - [] - ['123'] -- [(), ()] +- [[], []] ... function f3() return { {{'hello'}} } end --- ... call f3() --- -- [('hello',)] +- [['hello']] ... function f3() return { box.tuple.new('hello'), {'world'} } end --- @@ -375,3 +375,42 @@ space:drop() box.schema.user.drop('test') --- ... +eval (return 2+2)() +--- +[4] + +eval (return 1, 2, 3)() +--- +[1, 2, 3] + +eval (return ...)(1,2,3) +--- +[1, 2, 3] + +eval (return { k = "v1" }, true, { xx = 10, yy = 15 }, nil)() +--- +- {k: v1} +- true +- {xx: 10, yy: 15} +- null + +eval (return nil)() +--- +[null] + +eval (return)() +--- +[] + +eval (error("exception"))() +--- +error: {code: ER_PROC_LUA, reason: 'eval:1: exception'} + +eval (box.error(0))() +--- +error: {code: ER_OK, reason: Unknown error} + +eval (!invalid expression)() +--- +error: {code: ER_PROC_LUA, reason: 'eval:1: unexpected symbol near ''!'''} + diff --git a/test/box/call.test.py b/test/box/call.test.py index d1681f28ea299c8e617b9086e4f74bbef3cf2420..cb8710bf0046ca1b6f0b7b8ab6806c94a59584fa 100644 --- a/test/box/call.test.py +++ b/test/box/call.test.py @@ -123,5 +123,20 @@ sql("call space:delete(4)") admin("space:drop()") admin("box.schema.user.drop('test')") +def lua_eval(name, *args): + print 'eval (%s)(%s)' % (name, ','.join([ str(arg) for arg in args])) + print '---' + print sql.py_con.eval(name, *args) + +lua_eval('return 2+2') +lua_eval('return 1, 2, 3') +lua_eval('return ...', 1, 2, 3) +lua_eval('return { k = "v1" }, true, { xx = 10, yy = 15 }, nil') +lua_eval('return nil') +lua_eval('return') +lua_eval('error("exception")') +lua_eval('box.error(0)') +lua_eval('!invalid expression') + # Re-connect after removing user sql.py_con.close() diff --git a/test/box/iproto.result b/test/box/iproto.result index fa577523ee906d71b657bd6fb7875cc39808a46c..a9988eca43c2b9e523e69fd06725f1f9e6e861a5 100644 --- a/test/box/iproto.result +++ b/test/box/iproto.result @@ -82,30 +82,22 @@ index = space:create_index('primary', { type = 'hash' }) box.schema.user.grant('guest', 'read,write,execute', 'space', 'test') --- ... ---- -- [1, 'baobab'] -... ---- -- [2, 'obbaba'] -... ---- -- [1, 'baobab'] -... ---- -- [3, 'occama'] -... ---- -- [2, 'obbaba'] -... ---- -- [4, 'ockham'] -... ---- -- [1, 'baobab'] -... ---- -- [2, 'obbaba'] -... +- [1, baobab] + +- [2, obbaba] + +- [1, baobab] + +- [3, occama] + +- [2, obbaba] + +- [4, ockham] + +- [1, baobab] + +- [2, obbaba] + space:drop() --- ... diff --git a/test/box/misc.result b/test/box/misc.result index fd4be8cb229ab86d67ac48a1041754790c504888..dcf5c0415461fa6c1101d3fe404e30a05d2561e4 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -85,9 +85,11 @@ end; t; --- - - DELETE + - EVAL - SELECT - REPLACE - INSERT + - AUTH - CALL - UPDATE - total diff --git a/test/lib/tarantool-python b/test/lib/tarantool-python index b2bcd37691d3f4664bd34646078b02709fa102cd..c9f2bd36736a8198920b35af072cdf12cbac8dec 160000 --- a/test/lib/tarantool-python +++ b/test/lib/tarantool-python @@ -1 +1 @@ -Subproject commit b2bcd37691d3f4664bd34646078b02709fa102cd +Subproject commit c9f2bd36736a8198920b35af072cdf12cbac8dec