Refactored code for SNMP traps and NXSL scripts and implemented guids for both objects.
[public/netxms.git] / src / server / core / script.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2017 Victor Kirhenshtein
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 implied 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., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: script.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Script library
27 */
28 NXSL_Library *g_pScriptLibrary = NULL;
29
30 /**
31 * Load scripts from database
32 */
33 void LoadScripts()
34 {
35 DB_RESULT hResult;
36 NXSL_LibraryScript *pScript;
37 TCHAR buffer[MAX_DB_STRING];
38
39 g_pScriptLibrary = new NXSL_Library();
40 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
41 hResult = DBSelect(hdb, _T("SELECT script_id,guid,script_name,script_code FROM script_library"));
42 if (hResult != NULL)
43 {
44 int numRows = DBGetNumRows(hResult);
45 for(int i = 0; i < numRows; i++)
46 {
47 pScript = new NXSL_LibraryScript(DBGetFieldULong(hResult, i, 0), DBGetFieldGUID(hResult, i, 1),
48 DBGetField(hResult, i, 2, buffer, MAX_DB_STRING), DBGetField(hResult, i, 3, NULL, 0));
49 if (pScript->isValid())
50 {
51 g_pScriptLibrary->addScript(pScript);
52 DbgPrintf(2, _T("Script %s added to library"), pScript->getName());
53 }
54 else
55 {
56 nxlog_write(MSG_SCRIPT_COMPILATION_ERROR, NXLOG_WARNING, "dss",
57 pScript->getId(), pScript->getName(), pScript->getError());
58 }
59 }
60 DBFreeResult(hResult);
61 }
62 DBConnectionPoolReleaseConnection(hdb);
63 }
64
65 /**
66 * Load script name and code from database
67 */
68 NXSL_LibraryScript *LoadScriptFromDatabase(UINT32 id)
69 {
70 NXSL_LibraryScript *script = NULL;
71
72 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
73
74 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT script_id,guid,script_name,script_code FROM script_library WHERE script_id=?"));
75 if (hStmt != NULL)
76 {
77 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, id);
78 DB_RESULT hResult = DBSelectPrepared(hStmt);
79 if (hResult != NULL)
80 {
81 if (DBGetNumRows(hResult) > 0)
82 {
83 TCHAR buffer[MAX_DB_STRING];
84 script = new NXSL_LibraryScript(DBGetFieldULong(hResult, 0, 0), DBGetFieldGUID(hResult, 0, 1),
85 DBGetField(hResult, 0, 2, buffer, MAX_DB_STRING), DBGetField(hResult, 0, 3, NULL, 0));
86 }
87 DBFreeResult(hResult);
88 }
89 DBFreeStatement(hStmt);
90 }
91
92 DBConnectionPoolReleaseConnection(hdb);
93 return script;
94 }
95
96 /**
97 * Reload script from database
98 */
99 void ReloadScript(UINT32 id)
100 {
101 NXSL_LibraryScript *script = LoadScriptFromDatabase(id);
102 if (script == NULL)
103 {
104 DbgPrintf(3, _T("ReloadScript: failed to load script with ID %d from database"), (int)id);
105 return;
106 }
107
108 g_pScriptLibrary->lock();
109 g_pScriptLibrary->deleteScript(id);
110 if (script->isValid())
111 {
112 g_pScriptLibrary->addScript(script);
113 }
114 else
115 {
116 nxlog_write(MSG_SCRIPT_COMPILATION_ERROR, NXLOG_WARNING, "dss", id, script->getName(), script->getError());
117 delete script;
118 }
119 g_pScriptLibrary->unlock();
120 }
121
122 /**
123 * Check if script ID is valid
124 */
125 bool IsValidScriptId(UINT32 id)
126 {
127 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
128 bool valid = IsDatabaseRecordExist(hdb, _T("script_library"), _T("script_id"), id);
129 DBConnectionPoolReleaseConnection(hdb);
130 return valid;
131 }
132
133 /**
134 * Resolve script name to ID
135 */
136 UINT32 ResolveScriptName(const TCHAR *name)
137 {
138 UINT32 id = 0;
139
140 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
141
142 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT script_id FROM script_library WHERE script_name=?"));
143 if (hStmt != NULL)
144 {
145 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, name, DB_BIND_STATIC);
146 DB_RESULT hResult = DBSelectPrepared(hStmt);
147 if (hResult != NULL)
148 {
149 if (DBGetNumRows(hResult) > 0)
150 id = DBGetFieldULong(hResult, 0, 0);
151 DBFreeResult(hResult);
152 }
153 DBFreeStatement(hStmt);
154 }
155
156 DBConnectionPoolReleaseConnection(hdb);
157 return id;
158 }
159
160 /**
161 * Resolve script GUID to ID
162 */
163 UINT32 ResolveScriptGuid(const uuid& guid)
164 {
165 UINT32 id = 0;
166
167 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
168
169 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT script_id FROM script_library WHERE guid=?"));
170 if (hStmt != NULL)
171 {
172 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, guid);
173 DB_RESULT hResult = DBSelectPrepared(hStmt);
174 if (hResult != NULL)
175 {
176 if (DBGetNumRows(hResult) > 0)
177 id = DBGetFieldULong(hResult, 0, 0);
178 DBFreeResult(hResult);
179 }
180 DBFreeStatement(hStmt);
181 }
182
183 DBConnectionPoolReleaseConnection(hdb);
184 return id;
185 }
186
187 /**
188 * Update or create new script
189 */
190 UINT32 UpdateScript(const NXCPMessage *request, UINT32 *scriptId)
191 {
192 UINT32 rcc = RCC_DB_FAILURE;
193
194 TCHAR scriptName[MAX_DB_STRING];
195 request->getFieldAsString(VID_NAME, scriptName, MAX_DB_STRING);
196 TCHAR *scriptSource = request->getFieldAsString(VID_SCRIPT_CODE);
197
198 if (scriptSource == NULL)
199 return RCC_INVALID_REQUEST;
200
201 if (IsValidScriptName(scriptName))
202 {
203 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
204 DB_STATEMENT hStmt;
205
206 UINT32 id = ResolveScriptName(scriptName);
207 uuid guid;
208 if (id == 0) // Create new script
209 {
210 guid = uuid::generate();
211 id = CreateUniqueId(IDG_SCRIPT);
212 hStmt = DBPrepare(hdb, _T("INSERT INTO script_library (script_name,script_code,script_id,guid) VALUES (?,?,?,?)"));
213 }
214 else // Update existing script
215 hStmt = DBPrepare(hdb, _T("UPDATE script_library SET script_name=?,script_code=? WHERE script_id=?"));
216
217 if (hStmt != NULL)
218 {
219 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, scriptName, DB_BIND_STATIC);
220 DBBind(hStmt, 2, DB_SQLTYPE_TEXT, scriptSource, DB_BIND_STATIC);
221 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, id);
222 if (!guid.isNull())
223 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, (const TCHAR *)guid.toString(), DB_BIND_STATIC);
224
225 if (DBExecute(hStmt))
226 {
227 ReloadScript(id);
228 *scriptId = id;
229 rcc = RCC_SUCCESS;
230 }
231 DBFreeStatement(hStmt);
232 }
233 DBConnectionPoolReleaseConnection(hdb);
234 }
235 else
236 rcc = RCC_INVALID_SCRIPT_NAME;
237
238 free(scriptSource);
239
240 return rcc;
241 }
242
243 /*
244 * Rename script
245 */
246 UINT32 RenameScript(const NXCPMessage *request)
247 {
248 UINT32 rcc = RCC_DB_FAILURE;
249 UINT32 id = request->getFieldAsUInt32(VID_SCRIPT_ID);
250
251 if (IsValidScriptId(id))
252 {
253 TCHAR scriptName[MAX_DB_STRING];
254 request->getFieldAsString(VID_NAME, scriptName, MAX_DB_STRING);
255 if (IsValidScriptName(scriptName))
256 {
257 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
258 DB_STATEMENT hStmt = DBPrepare(hdb, _T("UPDATE script_library SET script_name=? WHERE script_id=?"));
259 if (hStmt != NULL)
260 {
261 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, scriptName, DB_BIND_STATIC);
262 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, id);
263 if (DBExecute(hStmt))
264 {
265 ReloadScript(id);
266 rcc = RCC_SUCCESS;
267 }
268 DBFreeStatement(hStmt);
269 }
270 DBConnectionPoolReleaseConnection(hdb);
271 }
272 else
273 rcc = RCC_INVALID_SCRIPT_NAME;
274 }
275 else
276 rcc = RCC_INVALID_SCRIPT_ID;
277
278 return rcc;
279 }
280
281 /**
282 * Delete script
283 */
284 UINT32 DeleteScript(const NXCPMessage *request)
285 {
286 UINT32 rcc = RCC_DB_FAILURE;
287 UINT32 id = request->getFieldAsUInt32(VID_SCRIPT_ID);
288
289 if (IsValidScriptId(id))
290 {
291 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
292 DB_STATEMENT hStmt = DBPrepare(hdb, _T("DELETE FROM script_library WHERE script_id=?"));
293 if (hStmt != NULL)
294 {
295 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, id);
296 if (DBExecute(hStmt))
297 {
298 g_pScriptLibrary->lock();
299 g_pScriptLibrary->deleteScript(id);
300 g_pScriptLibrary->unlock();
301 rcc = RCC_SUCCESS;
302 }
303 DBFreeStatement(hStmt);
304 }
305 DBConnectionPoolReleaseConnection(hdb);
306 }
307 else
308 rcc = RCC_INVALID_SCRIPT_ID;
309
310 return rcc;
311 }
312
313 /**
314 * Create export record for library script
315 */
316 void CreateScriptExportRecord(String &xml, UINT32 id)
317 {
318 NXSL_LibraryScript *script = LoadScriptFromDatabase(id);
319 if (script == NULL)
320 {
321 DbgPrintf(3, _T("CreateScriptExportRecord: failed to load script with ID %d from database"), (int)id);
322 return;
323 }
324
325 xml.append(_T("\t\t<script id=\""));
326 xml.append(id);
327 xml.append(_T("\">\n"));
328 xml.append(_T("\t\t\t<guid>"));
329 xml.append((const TCHAR *)script->getGuid().toString());
330 xml.append(_T("</guid>\n"));
331 xml.append(_T("\t\t\t<name>"));
332 xml.append(EscapeStringForXML2(script->getName()));
333 xml.append(_T("</name>\n"));
334 xml.append(_T("\t\t\t<code>"));
335 xml.append(EscapeStringForXML2(script->getCode()));
336 xml.append(_T("</code>\n\t\t</script>\n"));
337 }
338
339 /**
340 * Import script
341 */
342 void ImportScript(ConfigEntry *config)
343 {
344 const TCHAR *name = config->getSubEntryValue(_T("name"));
345 if (name == NULL)
346 {
347 DbgPrintf(4, _T("ImportScript: name missing"));
348 return;
349 }
350
351 uuid guid = config->getSubEntryValueAsUUID(_T("guid"));
352 if (guid.isNull())
353 {
354 guid = uuid::generate();
355 DbgPrintf(4, _T("ImportScript: GUID not found in config, generated GUID %s for script \"%s\""), (const TCHAR *)guid.toString(), name);
356 }
357
358 const TCHAR *code = config->getSubEntryValue(_T("code"));
359 if (code == NULL)
360 {
361 DbgPrintf(4, _T("ImportScript: code missing"));
362 return;
363 }
364
365 if (IsValidScriptName(name))
366 {
367 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
368 DB_STATEMENT hStmt;
369
370 bool newScript = false;
371 UINT32 id = ResolveScriptGuid(guid);
372 if (id == 0) // Create new script
373 {
374 id = CreateUniqueId(IDG_SCRIPT);
375 hStmt = DBPrepare(hdb, _T("INSERT INTO script_library (script_name,script_code,script_id,guid) VALUES (?,?,?,?)"));
376 newScript = true;
377 }
378 else // Update existing script
379 hStmt = DBPrepare(hdb, _T("UPDATE script_library SET script_name=?,script_code=? WHERE script_id=?"));
380
381 if (hStmt != NULL)
382 {
383 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, name, DB_BIND_STATIC);
384 DBBind(hStmt, 2, DB_SQLTYPE_TEXT, code, DB_BIND_STATIC);
385 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, id);
386 if (newScript)
387 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, guid);
388
389 if (DBExecute(hStmt))
390 ReloadScript(id);
391 DBFreeStatement(hStmt);
392 }
393 DBConnectionPoolReleaseConnection(hdb);
394 }
395 }
396
397 /**
398 * Execute library script from scheduler
399 */
400 void ExecuteScheduledScript(const ScheduledTaskParameters *param)
401 {
402 TCHAR name[256];
403 nx_strncpy(name, param->m_params, 256);
404 Trim(name);
405
406 ObjectArray<NXSL_Value> args(16, 16, false);
407
408 Node *object = (Node *)FindObjectById(param->m_objectId, OBJECT_NODE);
409 if (object != NULL)
410 {
411 if (!object->checkAccessRights(param->m_userId, OBJECT_ACCESS_CONTROL))
412 {
413 nxlog_debug(4, _T("ExecuteScheduledScript(%s): access denied for userId %d object \"%s\" [%d]"),
414 param->m_params, param->m_userId, object->getName(), object->getId());
415 return;
416 }
417 }
418
419 // Can be in form parameter(arg1, arg2, ... argN)
420 TCHAR *p = _tcschr(name, _T('('));
421 if (p != NULL)
422 {
423 if (name[_tcslen(name) - 1] != _T(')'))
424 return;
425 name[_tcslen(name) - 1] = 0;
426
427 if (!ParseValueList(&p, args))
428 {
429 // argument parsing error
430 if (object != NULL)
431 nxlog_debug(4, _T("ExecuteScheduledScript(%s): argument parsing error (object \"%s\" [%d])"),
432 param->m_params, object->getName(), object->getId());
433 else
434 nxlog_debug(4, _T("ExecuteScheduledScript(%s): argument parsing error (not attached to object)"), param->m_params);
435 args.setOwner(true);
436 return;
437 }
438 }
439
440 NXSL_VM *vm = g_pScriptLibrary->createVM(name, new NXSL_ServerEnv);
441 if (vm != NULL)
442 {
443 if(object != NULL)
444 {
445 vm->setGlobalVariable(_T("$object"), object->createNXSLObject());
446 if (object->getObjectClass() == OBJECT_NODE)
447 vm->setGlobalVariable(_T("$node"), object->createNXSLObject());
448 }
449 if (vm->run(&args))
450 {
451 if (object != NULL)
452 nxlog_debug(4, _T("ExecuteScheduledScript(%s): Script executed successfully on object \"%s\" [%d]"),
453 param->m_params, object->getName(), object->getId());
454 else
455 nxlog_debug(4, _T("ExecuteScheduledScript(%s): Script executed successfully (not attached to object)"), param->m_params);
456 }
457 else
458 {
459 if (object != NULL)
460 nxlog_debug(4, _T("ExecuteScheduledScript(%s): Script execution error on object \"%s\" [%d]: %s"),
461 param->m_params, object->getName(), object->getId(), vm->getErrorText());
462 else
463 nxlog_debug(4, _T("ExecuteScheduledScript(%s): Script execution error (not attached to object): %s"),
464 param->m_params, vm->getErrorText());
465 }
466 delete vm;
467 }
468 else
469 {
470 args.setOwner(true);
471 }
472 }
473
474 /**
475 * Parse value list
476 */
477 bool ParseValueList(TCHAR **start, ObjectArray<NXSL_Value> &args)
478 {
479 TCHAR *p = *start;
480
481 *p = 0;
482 p++;
483
484 TCHAR *s = p;
485 int state = 1; // normal text
486
487 for(; state > 0; p++)
488 {
489 switch(*p)
490 {
491 case '"':
492 if (state == 1)
493 {
494 state = 2;
495 s = p + 1;
496 }
497 else
498 {
499 state = 3;
500 *p = 0;
501 args.add(new NXSL_Value(s));
502 }
503 break;
504 case ',':
505 if (state == 1)
506 {
507 *p = 0;
508 Trim(s);
509 args.add(new NXSL_Value(s));
510 s = p + 1;
511 }
512 else if (state == 3)
513 {
514 state = 1;
515 s = p + 1;
516 }
517 break;
518 case 0:
519 if (state == 1)
520 {
521 Trim(s);
522 args.add(new NXSL_Value(s));
523 state = 0;
524 }
525 else if (state == 3)
526 {
527 state = 0;
528 }
529 else
530 {
531 state = -1; // error
532 }
533 break;
534 case ' ':
535 break;
536 case ')':
537 if (state == 1)
538 {
539 *p = 0;
540 Trim(s);
541 args.add(new NXSL_Value(s));
542 state = 0;
543 }
544 else if (state == 3)
545 {
546 state = 0;
547 }
548 break;
549 case '\\':
550 if (state == 2)
551 {
552 memmove(p, p + 1, _tcslen(p) * sizeof(TCHAR));
553 switch(*p)
554 {
555 case 'r':
556 *p = '\r';
557 break;
558 case 'n':
559 *p = '\n';
560 break;
561 case 't':
562 *p = '\t';
563 break;
564 default:
565 break;
566 }
567 }
568 else if (state == 3)
569 {
570 state = -1;
571 }
572 break;
573 case '%':
574 if ((state == 1) && (*(p + 1) == '('))
575 {
576 p++;
577 ObjectArray<NXSL_Value> elements(16, 16, false);
578 if (ParseValueList(&p, elements))
579 {
580 NXSL_Array *array = new NXSL_Array();
581 for(int i = 0; i < elements.size(); i++)
582 {
583 array->set(i, elements.get(i));
584 }
585 args.add(new NXSL_Value(array));
586 state = 3;
587 }
588 else
589 {
590 state = -1;
591 elements.clear();
592 }
593 }
594 else if (state == 3)
595 {
596 state = -1;
597 }
598 break;
599 default:
600 if (state == 3)
601 state = -1;
602 break;
603 }
604 }
605
606 *start = p - 1;
607 return (state != -1);
608 }
609
610 /**
611 * Execute server startup scripts
612 */
613 void ExecuteStartupScripts()
614 {
615 TCHAR path[MAX_PATH];
616 GetNetXMSDirectory(nxDirShare, path);
617 _tcscat(path, SDIR_SCRIPTS);
618
619 int count = 0;
620 nxlog_debug(1, _T("Running startup scripts from %s"), path);
621 _TDIR *dir = _topendir(path);
622 if (dir != NULL)
623 {
624 _tcscat(path, FS_PATH_SEPARATOR);
625 int insPos = (int)_tcslen(path);
626
627 struct _tdirent *f;
628 while((f = _treaddir(dir)) != NULL)
629 {
630 if (MatchString(_T("*.nxsl"), f->d_name, FALSE))
631 {
632 count++;
633 _tcscpy(&path[insPos], f->d_name);
634
635 UINT32 size;
636 char *source = (char *)LoadFile(path, &size);
637 if (source != NULL)
638 {
639 TCHAR errorText[1024];
640 #ifdef UNICODE
641 WCHAR *wsource = WideStringFromUTF8String(source);
642 NXSL_VM *vm = NXSLCompileAndCreateVM(wsource, errorText, 1024, new NXSL_ServerEnv());
643 free(wsource);
644 #else
645 NXSL_VM *vm = NXSLCompileAndCreateVM(source, errorText, 1024, new NXSL_ServerEnv());
646 #endif
647 free(source);
648 if (vm != NULL)
649 {
650 if (vm->run())
651 {
652 nxlog_debug(1, _T("Startup script %s completed successfully"), f->d_name);
653 }
654 else
655 {
656 nxlog_debug(1, _T("Runtime error in startup script %s: %s"), f->d_name, vm->getErrorText());
657 }
658 delete vm;
659 }
660 else
661 {
662 nxlog_debug(1, _T("Cannot compile startup script %s (%s)"), f->d_name, errorText);
663 }
664 }
665 else
666 {
667 nxlog_debug(1, _T("Cannot load startup script %s"), f->d_name);
668 }
669 }
670 }
671 _tclosedir(dir);
672 }
673 nxlog_debug(1, _T("%d startup scripts processed"), count);
674 }