1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the envied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "log.h"
21 #include "zbxembed.h"
22
23 #include "httprequest.h"
24 #include "zabbix.h"
25 #include "global.h"
26 #include "console.h"
27 #include "xml.h"
28 #include "embed.h"
29
30 #define ZBX_ES_MEMORY_LIMIT (1024 * 1024 * 64)
31 #define ZBX_ES_TIMEOUT 10
32
33 #define ZBX_ES_STACK_LIMIT 1000
34
35 /* maximum number of consequent runtime errors after which it's treated as fatal error */
36 #define ZBX_ES_MAX_CONSEQUENT_RT_ERROR 3
37
38 #define ZBX_ES_SCRIPT_HEADER "function(value){"
39 #define ZBX_ES_SCRIPT_FOOTER "\n}"
40
41 /******************************************************************************
42 * *
43 * Function: es_handle_error *
44 * *
45 * Purpose: fatal error handler *
46 * *
47 ******************************************************************************/
es_handle_error(void * udata,const char * msg)48 static void es_handle_error(void *udata, const char *msg)
49 {
50 zbx_es_env_t *env = (zbx_es_env_t *)udata;
51
52 zabbix_log(LOG_LEVEL_WARNING, "Cannot process javascript, fatal error: %s", msg);
53
54 env->fatal_error = 1;
55 env->error = zbx_strdup(env->error, msg);
56 longjmp(env->loc, 1);
57 }
58
59 /*
60 * Memory allocation routines to track and limit script memory usage.
61 */
62
es_malloc(void * udata,duk_size_t size)63 static void *es_malloc(void *udata, duk_size_t size)
64 {
65 zbx_es_env_t *env = (zbx_es_env_t *)udata;
66 uint64_t *uptr;
67
68 if (env->total_alloc + size + 8 > ZBX_ES_MEMORY_LIMIT)
69 {
70 if (NULL == env->ctx)
71 env->error = zbx_strdup(env->error, "cannot allocate memory");
72
73 return NULL;
74 }
75
76 env->total_alloc += (size + 8);
77 uptr = zbx_malloc(NULL, size + 8);
78 *uptr++ = size;
79
80 return uptr;
81 }
82
es_realloc(void * udata,void * ptr,duk_size_t size)83 static void *es_realloc(void *udata, void *ptr, duk_size_t size)
84 {
85 zbx_es_env_t *env = (zbx_es_env_t *)udata;
86 uint64_t *uptr = ptr;
87 size_t old_size;
88
89 if (NULL != uptr)
90 {
91 --uptr;
92 old_size = *uptr + 8;
93 }
94 else
95 old_size = 0;
96
97 if (env->total_alloc + size + 8 - old_size > ZBX_ES_MEMORY_LIMIT)
98 {
99 if (NULL == env->ctx)
100 env->error = zbx_strdup(env->error, "cannot allocate memory");
101
102 return NULL;
103 }
104
105 env->total_alloc += size + 8 - old_size;
106 uptr = zbx_realloc(uptr, size + 8);
107 *uptr++ = size;
108
109 return uptr;
110 }
111
es_free(void * udata,void * ptr)112 static void es_free(void *udata, void *ptr)
113 {
114 zbx_es_env_t *env = (zbx_es_env_t *)udata;
115 uint64_t *uptr = ptr;
116
117 if (NULL != ptr)
118 {
119 env->total_alloc -= (*(--uptr) + 8);
120 zbx_free(uptr);
121 }
122 }
123
124 /******************************************************************************
125 * *
126 * Function: zbx_es_check_timeout *
127 * *
128 * Purpose: timeout checking callback *
129 * *
130 ******************************************************************************/
zbx_es_check_timeout(void * udata)131 int zbx_es_check_timeout(void *udata)
132 {
133 zbx_es_env_t *env = (zbx_es_env_t *)udata;
134
135 if (time(NULL) - env->start_time.sec > env->timeout)
136 return 1;
137
138 return 0;
139 }
140
141 /******************************************************************************
142 * *
143 * Function: zbx_es_init *
144 * *
145 * Purpose: initializes embedded scripting engine *
146 * *
147 ******************************************************************************/
zbx_es_init(zbx_es_t * es)148 void zbx_es_init(zbx_es_t *es)
149 {
150 es->env = NULL;
151 }
152
153 /******************************************************************************
154 * *
155 * Function: zbx_es_destroy *
156 * *
157 * Purpose: destroys embedded scripting engine *
158 * *
159 ******************************************************************************/
zbx_es_destroy(zbx_es_t * es)160 void zbx_es_destroy(zbx_es_t *es)
161 {
162 char *error = NULL;
163
164 if (SUCCEED != zbx_es_destroy_env(es, &error))
165 {
166 zabbix_log(LOG_LEVEL_WARNING, "Cannot destroy embedded scripting engine environment: %s", error);
167 }
168 }
169
170 /******************************************************************************
171 * *
172 * Function: zbx_es_init_env *
173 * *
174 * Purpose: initializes embedded scripting engine environment *
175 * *
176 * Parameters: es - [IN] the embedded scripting engine *
177 * error - [OUT] the error message *
178 * *
179 * Return value: SUCCEED *
180 * FAIL *
181 * *
182 ******************************************************************************/
zbx_es_init_env(zbx_es_t * es,char ** error)183 int zbx_es_init_env(zbx_es_t *es, char **error)
184 {
185 volatile int ret = FAIL;
186
187 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
188
189 es->env = zbx_malloc(NULL, sizeof(zbx_es_env_t));
190 memset(es->env, 0, sizeof(zbx_es_env_t));
191
192 if (0 != setjmp(es->env->loc))
193 {
194 *error = zbx_strdup(*error, es->env->error);
195 goto out;
196 }
197
198 if (NULL == (es->env->ctx = duk_create_heap(es_malloc, es_realloc, es_free, es->env, es_handle_error)))
199 {
200 *error = zbx_strdup(*error, "cannot create context");
201 goto out;
202 }
203
204 /* initialize Zabbix object */
205 zbx_es_init_zabbix(es, error);
206
207 /* initialize console object */
208 zbx_es_init_console(es, error);
209
210 /* remove Duktape object */
211 duk_push_global_object(es->env->ctx);
212 duk_del_prop_string(es->env->ctx, -1, "Duktape");
213 duk_pop(es->env->ctx);
214
215 es_init_global_functions(es);
216
217 /* put environment object to be accessible from duktape C calls */
218 duk_push_global_stash(es->env->ctx);
219 duk_push_pointer(es->env->ctx, (void *)es->env);
220 if (1 != duk_put_prop_string(es->env->ctx, -2, "\xff""\xff""zbx_env"))
221 {
222 *error = zbx_strdup(*error, duk_safe_to_string(es->env->ctx, -1));
223 duk_pop(es->env->ctx);
224 return FAIL;
225 }
226
227 /* initialize HttpRequest and CurlHttpRequest prototypes */
228 if (FAIL == zbx_es_init_httprequest(es, error))
229 goto out;
230
231 if (FAIL == zbx_es_init_xml(es, error))
232 goto out;
233
234 es->env->timeout = ZBX_ES_TIMEOUT;
235 ret = SUCCEED;
236 out:
237 if (SUCCEED != ret)
238 {
239 zbx_es_debug_disable(es);
240 zbx_free(es->env->error);
241 zbx_free(es->env);
242 }
243
244 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, zbx_result_string(ret),
245 ZBX_NULL2EMPTY_STR(*error));
246
247 return ret;
248 }
249
250 /******************************************************************************
251 * *
252 * Function: zbx_es_destroy_env *
253 * *
254 * Purpose: destroys initialized embedded scripting engine environment *
255 * *
256 * Parameters: es - [IN] the embedded scripting engine *
257 * error - [OUT] the error message *
258 * *
259 * Return value: SUCCEED *
260 * FAIL *
261 * *
262 ******************************************************************************/
zbx_es_destroy_env(zbx_es_t * es,char ** error)263 int zbx_es_destroy_env(zbx_es_t *es, char **error)
264 {
265 int ret;
266
267 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
268
269 if (0 != setjmp(es->env->loc))
270 {
271 ret = FAIL;
272 *error = zbx_strdup(*error, es->env->error);
273 goto out;
274 }
275
276 duk_destroy_heap(es->env->ctx);
277 zbx_es_debug_disable(es);
278 zbx_free(es->env->error);
279 zbx_free(es->env);
280
281 ret = SUCCEED;
282 out:
283 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, zbx_result_string(ret),
284 ZBX_NULL2EMPTY_STR(*error));
285
286 return ret;
287 }
288
289 /******************************************************************************
290 * *
291 * Function: zbx_es_ready *
292 * *
293 * Purpose: checks if the scripting engine environment is initialized *
294 * *
295 * Parameters: es - [IN] the embedded scripting engine *
296 * *
297 * Return value: SUCCEED - the scripting engine is initialized *
298 * FAIL - otherwise *
299 * *
300 ******************************************************************************/
zbx_es_is_env_initialized(zbx_es_t * es)301 int zbx_es_is_env_initialized(zbx_es_t *es)
302 {
303 return (NULL == es->env ? FAIL : SUCCEED);
304 }
305
306 /******************************************************************************
307 * *
308 * Function: zbx_es_fatal_error *
309 * *
310 * Purpose: checks if fatal error has occurred *
311 * *
312 * Comments: Fatal error may put the scripting engine in unknown state, it's *
313 * safer to destroy it instead of continuing to work with it. *
314 * *
315 ******************************************************************************/
zbx_es_fatal_error(zbx_es_t * es)316 int zbx_es_fatal_error(zbx_es_t *es)
317 {
318 if (0 != es->env->fatal_error || ZBX_ES_MAX_CONSEQUENT_RT_ERROR < es->env->rt_error_num)
319 return SUCCEED;
320
321 if (ZBX_ES_STACK_LIMIT < duk_get_top(es->env->ctx))
322 {
323 zabbix_log(LOG_LEVEL_WARNING, "embedded scripting engine stack exceeded limits,"
324 " resetting scripting environment");
325 return SUCCEED;
326 }
327
328 return FAIL;
329 }
330
331 /******************************************************************************
332 * *
333 * Function: zbx_es_compile *
334 * *
335 * Purpose: compiles script into bytecode *
336 * *
337 * Parameters: es - [IN] the embedded scripting engine *
338 * script - [IN] the script to compile *
339 * code - [OUT] the bytecode *
340 * size - [OUT] the size of compiled bytecode *
341 * error - [OUT] the error message *
342 * *
343 * Return value: SUCCEED *
344 * FAIL *
345 * *
346 * Comments: The this function allocates the bytecode array, which must be *
347 * freed by the caller after being used. *
348 * *
349 ******************************************************************************/
zbx_es_compile(zbx_es_t * es,const char * script,char ** code,int * size,char ** error)350 int zbx_es_compile(zbx_es_t *es, const char *script, char **code, int *size, char **error)
351 {
352 unsigned char *buffer;
353 duk_size_t sz;
354 size_t len;
355 char * volatile func = NULL, *ptr;
356 volatile int ret = FAIL;
357
358 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
359
360 if (SUCCEED == zbx_es_fatal_error(es))
361 {
362 *error = zbx_strdup(*error, "cannot continue javascript processing after fatal scripting engine error");
363 goto out;
364 }
365
366 if (0 != setjmp(es->env->loc))
367 {
368 *error = zbx_strdup(*error, es->env->error);
369 goto out;
370 }
371
372 /* wrap the code block into a function: function(value){<code>\n} */
373 len = strlen(script);
374 ptr = func = zbx_malloc(NULL, len + ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_HEADER) +
375 ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_FOOTER) + 1);
376 memcpy(ptr, ZBX_ES_SCRIPT_HEADER, ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_HEADER));
377 ptr += ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_HEADER);
378 memcpy(ptr, script, len);
379 ptr += len;
380 memcpy(ptr, ZBX_ES_SCRIPT_FOOTER, ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_FOOTER));
381 ptr += ZBX_CONST_STRLEN(ZBX_ES_SCRIPT_FOOTER);
382 *ptr = '\0';
383
384 duk_push_lstring(es->env->ctx, func, ptr - func);
385 duk_push_lstring(es->env->ctx, "function", ZBX_CONST_STRLEN("function"));
386
387 if (0 != duk_pcompile(es->env->ctx, DUK_COMPILE_FUNCTION))
388 {
389 *error = zbx_strdup(*error, duk_safe_to_string(es->env->ctx, -1));
390 duk_pop(es->env->ctx);
391 goto out;
392 }
393
394 duk_dump_function(es->env->ctx);
395
396 if (NULL != (buffer = (unsigned char *)duk_get_buffer(es->env->ctx, -1, &sz)))
397 {
398 *size = sz;
399 *code = zbx_malloc(NULL, sz);
400 memcpy(*code, buffer, sz);
401 ret = SUCCEED;
402 }
403 else
404 *error = zbx_strdup(*error, "empty function compilation result");
405
406 duk_pop(es->env->ctx);
407 out:
408 zbx_free(func);
409
410 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, zbx_result_string(ret), ZBX_NULL2EMPTY_STR(*error));
411
412 return ret;
413 }
414
415 /******************************************************************************
416 * *
417 * Function: zbx_es_execute *
418 * *
419 * Purpose: executes script *
420 * *
421 * Parameters: es - [IN] the embedded scripting engine *
422 * script - [IN] the script to execute *
423 * code - [IN] the precompiled bytecode *
424 * size - [IN] the size of precompiled bytecode *
425 * param - [IN] the parameter to pass to the script *
426 * script_ret - [OUT] the result value *
427 * error - [OUT] the error message *
428 * *
429 * Return value: SUCCEED *
430 * FAIL *
431 * *
432 * Comments: Some scripting engines cannot compile into bytecode, but can *
433 * cache some compilation data that can be reused for the next *
434 * compilation. Because of that execute function accepts script and *
435 * bytecode parameters. *
436 * *
437 ******************************************************************************/
zbx_es_execute(zbx_es_t * es,const char * script,const char * code,int size,const char * param,char ** script_ret,char ** error)438 int zbx_es_execute(zbx_es_t *es, const char *script, const char *code, int size, const char *param, char **script_ret,
439 char **error)
440 {
441 void *buffer;
442 volatile int ret = FAIL;
443
444 zabbix_log(LOG_LEVEL_DEBUG, "In %s() param:%s", __func__, param);
445
446 zbx_timespec(&es->env->start_time);
447
448 if (NULL != es->env->json)
449 {
450 zbx_json_clean(es->env->json);
451 zbx_json_addarray(es->env->json, "logs");
452 }
453
454 if (SUCCEED == zbx_es_fatal_error(es))
455 {
456 *error = zbx_strdup(*error, "cannot continue javascript processing after fatal scripting engine error");
457 goto out;
458 }
459
460 ZBX_UNUSED(script);
461
462 if (0 != setjmp(es->env->loc))
463 {
464 *error = zbx_strdup(*error, es->env->error);
465 goto out;
466 }
467
468 buffer = duk_push_fixed_buffer(es->env->ctx, size);
469 memcpy(buffer, code, size);
470 duk_load_function(es->env->ctx);
471 duk_push_string(es->env->ctx, param);
472
473 if (DUK_EXEC_SUCCESS != duk_pcall(es->env->ctx, 1))
474 {
475 duk_small_int_t rc = 0;
476
477 es->env->rt_error_num++;
478
479 if (0 != duk_is_object(es->env->ctx, -1))
480 {
481 /* try to get 'stack' property of the object on stack, assuming it's an Error object */
482 if (0 != (rc = duk_get_prop_string(es->env->ctx, -1, "stack")))
483 *error = zbx_strdup(*error, duk_get_string(es->env->ctx, -1));
484
485 duk_pop(es->env->ctx);
486 }
487
488 /* If the object does not have stack property, return the object itself as error. */
489 /* This allows to simply throw "error message" from scripts */
490 if (0 == rc)
491 *error = zbx_strdup(*error, duk_safe_to_string(es->env->ctx, -1));
492
493 duk_pop(es->env->ctx);
494
495 goto out;
496 }
497
498 if (NULL != script_ret || SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
499 {
500 if (0 == duk_check_type(es->env->ctx, -1, DUK_TYPE_UNDEFINED))
501 {
502 if (0 != duk_check_type(es->env->ctx, -1, DUK_TYPE_NULL))
503 {
504 ret = SUCCEED;
505
506 if (NULL != script_ret)
507 *script_ret = NULL;
508
509 zabbix_log(LOG_LEVEL_DEBUG, "%s() output: null", __func__);
510 }
511 else
512 {
513 char *output = NULL;
514
515 if (SUCCEED != (ret = zbx_cesu8_to_utf8(duk_safe_to_string(es->env->ctx, -1), &output)))
516 *error = zbx_strdup(*error, "could not convert return value to utf8");
517 else
518 zabbix_log(LOG_LEVEL_DEBUG, "%s() output:'%s'", __func__, output);
519
520 if (SUCCEED == ret && NULL != script_ret)
521 *script_ret = output;
522 else
523 zbx_free(output);
524 }
525 }
526 else
527 {
528 if (NULL == script_ret)
529 {
530 zabbix_log(LOG_LEVEL_DEBUG, "%s(): undefined return value", __func__);
531 ret = SUCCEED;
532 }
533 else
534 *error = zbx_strdup(*error, "undefined return value");
535 }
536 }
537 else
538 ret = SUCCEED;
539
540 duk_pop(es->env->ctx);
541 es->env->rt_error_num = 0;
542 out:
543 if (NULL != es->env->json)
544 {
545 zbx_json_close(es->env->json);
546 zbx_json_adduint64(es->env->json, "ms", zbx_get_duration_ms(&es->env->start_time));
547 }
548
549 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, zbx_result_string(ret), ZBX_NULL2EMPTY_STR(*error));
550
551 return ret;
552 }
553
554 /******************************************************************************
555 * *
556 * Function: zbx_es_set_timeout *
557 * *
558 * Purpose: sets script execution timeout *
559 * *
560 * Parameters: es - [IN] the embedded scripting engine *
561 * timeout - [IN] the script execution timeout in seconds *
562 * *
563 ******************************************************************************/
zbx_es_set_timeout(zbx_es_t * es,int timeout)564 void zbx_es_set_timeout(zbx_es_t *es, int timeout)
565 {
566 es->env->timeout = timeout;
567 }
568
zbx_es_debug_enable(zbx_es_t * es)569 void zbx_es_debug_enable(zbx_es_t *es)
570 {
571 if (NULL == es->env->json)
572 {
573 es->env->json = zbx_malloc(NULL, sizeof(struct zbx_json));
574 zbx_json_init(es->env->json, ZBX_JSON_STAT_BUF_LEN);
575 }
576 }
577
zbx_es_debug_info(const zbx_es_t * es)578 const char *zbx_es_debug_info(const zbx_es_t *es)
579 {
580 if (NULL == es->env->json)
581 return NULL;
582
583 return es->env->json->buffer;
584 }
585
zbx_es_debug_disable(zbx_es_t * es)586 void zbx_es_debug_disable(zbx_es_t *es)
587 {
588 if (NULL == es->env->json)
589 return;
590
591 zbx_json_free(es->env->json);
592 zbx_free(es->env->json);
593 }
594
595 /******************************************************************************
596 * *
597 * Function: zbx_es_execute_command *
598 * *
599 * Purpose: executes command (script in form of a text) *
600 * *
601 * Parameters: command - [IN] the command in form of a text *
602 * param - [IN] the script parameters *
603 * timeout - [IN] the timeout for the execution (seconds) *
604 * result - [OUT] the result of an execution *
605 * error - [OUT] the error message *
606 * max_error_len - [IN] the maximum length of an error *
607 * debug - [OUT] the debug data (optional) *
608 * *
609 * Return value: SUCCEED *
610 * FAIL *
611 * *
612 ******************************************************************************/
zbx_es_execute_command(const char * command,const char * param,int timeout,char ** result,char * error,size_t max_error_len,char ** debug)613 int zbx_es_execute_command(const char *command, const char *param, int timeout, char **result,
614 char *error, size_t max_error_len, char **debug)
615 {
616 int size, ret = SUCCEED;
617 char *code = NULL, *errmsg = NULL;
618 zbx_es_t es;
619
620 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
621
622 zbx_es_init(&es);
623 if (FAIL == zbx_es_init_env(&es, &errmsg))
624 {
625 zbx_snprintf(error, max_error_len, "cannot initialize scripting environment: %s", errmsg);
626 zbx_free(errmsg);
627 ret = FAIL;
628 goto failure;
629 }
630
631 if (NULL != debug)
632 zbx_es_debug_enable(&es);
633
634 if (FAIL == zbx_es_compile(&es, command, &code, &size, &errmsg))
635 {
636 zbx_snprintf(error, max_error_len, "cannot compile script: %s", errmsg);
637 zbx_free(errmsg);
638 ret = FAIL;
639 goto out;
640 }
641
642 if (0 != timeout)
643 zbx_es_set_timeout(&es, timeout);
644
645 if (FAIL == zbx_es_execute(&es, NULL, code, size, param, result, &errmsg))
646 {
647 zbx_snprintf(error, max_error_len, "cannot execute script: %s", errmsg);
648 zbx_free(errmsg);
649 ret = FAIL;
650 goto out;
651 }
652 out:
653 if (NULL != debug)
654 *debug = zbx_strdup(NULL, zbx_es_debug_info(&es));
655
656 if (FAIL == zbx_es_destroy_env(&es, &errmsg))
657 {
658 zabbix_log(LOG_LEVEL_WARNING, "cannot destroy embedded scripting engine environment: %s", errmsg);
659 zbx_free(errmsg);
660 }
661
662 zbx_free(code);
663 zbx_free(errmsg);
664 failure:
665 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
666
667 return ret;
668 }
669