"Fossies" - the Fresh Open Source Software Archive

Member "netxms-3.4.232/src/server/core/actions.cpp" (6 Jul 2020, 28167 Bytes) of package /linux/misc/netxms-3.4.232.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "actions.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.4.178_vs_3.4.232.

    1 /*
    2 ** NetXMS - Network Management System
    3 ** Copyright (C) 2003-2020 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: actions.cpp
   20 **
   21 **/
   22 
   23 #include "nxcore.h"
   24 
   25 #define DEBUG_TAG _T("action")
   26 
   27 /**
   28  * Create new action
   29  */
   30 Action::Action(const TCHAR *name)
   31 {
   32    id = CreateUniqueId(IDG_ACTION);
   33    guid = uuid::generate();
   34    _tcsncpy(this->name, name, MAX_OBJECT_NAME);
   35    isDisabled = true;
   36    type = ACTION_EXEC;
   37    emailSubject[0] = 0;
   38    rcptAddr[0] = 0;
   39    data = NULL;
   40    channelName[0] = 0;
   41 }
   42 
   43 /**
   44  * Action constructor
   45  */
   46 Action::Action(DB_RESULT hResult, int row)
   47 {
   48    id = DBGetFieldULong(hResult, row, 0);
   49    guid = DBGetFieldGUID(hResult, row, 1);
   50    DBGetField(hResult, row, 2, name, MAX_OBJECT_NAME);
   51    type = DBGetFieldLong(hResult, row, 3);
   52    isDisabled = DBGetFieldLong(hResult, row, 4) ? true : false;
   53    DBGetField(hResult, row, 5, rcptAddr, MAX_RCPT_ADDR_LEN);
   54    DBGetField(hResult, row, 6, emailSubject, MAX_EMAIL_SUBJECT_LEN);
   55    data = DBGetField(hResult, row, 7, NULL, 0);
   56    DBGetField(hResult, row, 8, channelName, MAX_OBJECT_NAME);
   57 }
   58 
   59 /**
   60  * Action copy constructor
   61  */
   62 Action::Action(const Action *act)
   63 {
   64    id = act->id;
   65    guid = act->guid;
   66    _tcsncpy(name, act->name, MAX_OBJECT_NAME);
   67    type = act->type;
   68    isDisabled = act->isDisabled;
   69    _tcsncpy(rcptAddr, act->rcptAddr, MAX_RCPT_ADDR_LEN);
   70    _tcsncpy(emailSubject, act->emailSubject, MAX_EMAIL_SUBJECT_LEN);
   71    data = MemCopyString(act->data);
   72    _tcsncpy(channelName, act->channelName, MAX_OBJECT_NAME);
   73 
   74 }
   75 
   76 /**
   77  * Action destructor
   78  */
   79 Action::~Action()
   80 {
   81    free(data);
   82 }
   83 
   84 /**
   85  * Fill NXCP message with action's data
   86  */
   87 void Action::fillMessage(NXCPMessage *msg) const
   88 {
   89    msg->setField(VID_ACTION_ID, id);
   90    msg->setField(VID_GUID, guid);
   91    msg->setField(VID_IS_DISABLED, isDisabled);
   92    msg->setField(VID_ACTION_TYPE, (UINT16)type);
   93    msg->setField(VID_ACTION_DATA, CHECK_NULL_EX(data));
   94    msg->setField(VID_EMAIL_SUBJECT, emailSubject);
   95    msg->setField(VID_ACTION_NAME, name);
   96    msg->setField(VID_RCPT_ADDR, rcptAddr);
   97    msg->setField(VID_CHANNEL_NAME, channelName);
   98 }
   99 
  100 /**
  101  * Save action record to database
  102  */
  103 void Action::saveToDatabase() const
  104 {
  105    DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
  106 
  107    static const TCHAR *columns[] = { _T("guid"), _T("action_name"), _T("action_type"),
  108             _T("is_disabled"), _T("rcpt_addr"), _T("email_subject"), _T("action_data"),
  109             _T("channel_name"), nullptr };
  110    DB_STATEMENT hStmt = DBPrepareMerge(hdb, _T("actions"), _T("action_id"), id, columns);
  111    if (hStmt != NULL)
  112    {
  113       DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, guid);
  114       DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, name, DB_BIND_STATIC);
  115       DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, type);
  116       DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, (INT32)(isDisabled ? 1 : 0));
  117       DBBind(hStmt, 5, DB_SQLTYPE_VARCHAR, rcptAddr, DB_BIND_STATIC);
  118       DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, emailSubject, DB_BIND_STATIC);
  119       DBBind(hStmt, 7, DB_SQLTYPE_VARCHAR, data, DB_BIND_STATIC);
  120       DBBind(hStmt, 8, DB_SQLTYPE_VARCHAR, channelName, DB_BIND_STATIC);
  121       DBBind(hStmt, 9, DB_SQLTYPE_INTEGER, id);
  122       DBExecute(hStmt);
  123       DBFreeStatement(hStmt);
  124    }
  125 
  126    DBConnectionPoolReleaseConnection(hdb);
  127 }
  128 
  129 /**
  130  * Static data
  131  */
  132 static SynchronizedSharedHashMap<uint32_t, Action> s_actions;
  133 static uint32_t s_updateCode;
  134 
  135 /**
  136  * Send updates to all connected clients
  137  */
  138 static void SendActionDBUpdate(ClientSession *pSession, void *pArg)
  139 {
  140    pSession->onActionDBUpdate(s_updateCode, (const Action *)pArg);
  141 }
  142 
  143 /**
  144  * Load actions list from database
  145  */
  146 bool LoadActions()
  147 {
  148    bool success = false;
  149 
  150    DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
  151    DB_RESULT hResult = DBSelect(hdb, _T("SELECT action_id,guid,action_name,action_type,")
  152                                      _T("is_disabled,rcpt_addr,email_subject,action_data,")
  153                                      _T("channel_name FROM actions ORDER BY action_id"));
  154    if (hResult != NULL)
  155    {
  156       s_actions.clear();
  157 
  158       int count = DBGetNumRows(hResult);
  159       for(int i = 0; i < count; i++)
  160       {
  161          Action *action = new Action(hResult, i);
  162          s_actions.set(action->id, action);
  163       }
  164 
  165       nxlog_debug_tag(DEBUG_TAG, 2, _T("%d actions loaded"), s_actions.size());
  166 
  167       DBFreeResult(hResult);
  168       success = true;
  169    }
  170    else
  171    {
  172       nxlog_write(NXLOG_ERROR, _T("Error loading server action configuration from database"));
  173    }
  174    DBConnectionPoolReleaseConnection(hdb);
  175    return success;
  176 }
  177 
  178 /**
  179  * Cleanup action-related stuff
  180  */
  181 void CleanupActions()
  182 {
  183    s_actions.clear();
  184 }
  185 
  186 /**
  187  * Execute remote action
  188  */
  189 static bool ExecuteRemoteAction(const TCHAR *pszTarget, const TCHAR *pszAction)
  190 {
  191    shared_ptr<AgentConnection> pConn;
  192    if (pszTarget[0] == '@')
  193    {
  194       //Resolve name of node to connection to it. Name should be in @name format.
  195       shared_ptr<Node> node = static_pointer_cast<Node>(FindObjectByName(&pszTarget[1], OBJECT_NODE));
  196       if (node == nullptr)
  197          return false;
  198       pConn = node->createAgentConnection();
  199       if (pConn == nullptr)
  200          return false;
  201    }
  202    else
  203    {
  204       // Resolve hostname
  205       InetAddress addr = InetAddress::resolveHostName(pszTarget);
  206       if (!addr.isValid())
  207          return false;
  208 
  209       shared_ptr<Node> node = FindNodeByIP(0, addr.getAddressV4()); /* TODO: add possibility to specify action target by object id */
  210       if (node != NULL)
  211       {
  212          pConn = node->createAgentConnection();
  213          if (pConn == nullptr)
  214             return false;
  215       }
  216       else
  217       {
  218          // Target node is not in our database, try default communication settings
  219          pConn = AgentConnection::create(addr, AGENT_LISTEN_PORT, nullptr);
  220          pConn->setCommandTimeout(g_agentCommandTimeout);
  221          if (!pConn->connect(g_pServerKey))
  222             return false;
  223       }
  224    }
  225 
  226    StringList *list = SplitCommandLine(pszAction);
  227     TCHAR *pTmp = MemCopyString(pszAction);
  228 
  229    UINT32 rcc = pConn->execAction(pTmp, list);
  230    delete list;
  231    pConn->disconnect();
  232    MemFree(pTmp);
  233    return rcc == ERR_SUCCESS;
  234 }
  235 
  236 /**
  237  * Run external command via system()
  238  */
  239 static void RunCommand(void *arg)
  240 {
  241    if (ConfigReadBoolean(_T("EscapeLocalCommands"), false))
  242    {
  243       StringBuffer s = (TCHAR *)arg;
  244 #ifdef _WIN32
  245       s.replace(_T("\t"), _T("\\t"));
  246       s.replace(_T("\n"), _T("\\n"));
  247       s.replace(_T("\r"), _T("\\r"));
  248 #else
  249       s.replace(_T("\t"), _T("\\\\t"));
  250       s.replace(_T("\n"), _T("\\\\n"));
  251       s.replace(_T("\r"), _T("\\\\r"));
  252 #endif
  253       MemFree(arg);
  254       arg = MemCopyString(s.cstr());
  255    }
  256    nxlog_debug_tag(DEBUG_TAG, 3, _T("Executing command \"%s\""), (TCHAR *)arg);
  257     if (_tsystem((TCHAR *)arg) == -1)
  258        nxlog_debug_tag(DEBUG_TAG, 5, _T("RunCommandThread: failed to execute command \"%s\""), (TCHAR *)arg);
  259     MemFree(arg);
  260 }
  261 
  262 /**
  263  * Forward event to other server
  264  */
  265 static bool ForwardEvent(const TCHAR *server, const Event *event)
  266 {
  267    InetAddress addr = InetAddress::resolveHostName(server);
  268     if (!addr.isValidUnicast())
  269     {
  270         nxlog_debug(2, _T("ForwardEvent: host name %s is invalid or cannot be resolved"), server);
  271         return false;
  272     }
  273 
  274     ISC *isc = new ISC(addr);
  275     UINT32 rcc = isc->connect(ISC_SERVICE_EVENT_FORWARDER);
  276     if (rcc == ISC_ERR_SUCCESS)
  277     {
  278         NXCPMessage msg;
  279         msg.setId(1);
  280         msg.setCode(CMD_FORWARD_EVENT);
  281 
  282         shared_ptr<NetObj> object = FindObjectById(event->getSourceId());
  283         if (object != nullptr)
  284         {
  285          if (object->getObjectClass() == OBJECT_NODE)
  286          {
  287                msg.setField(VID_IP_ADDRESS, static_cast<Node*>(object.get())->getIpAddress());
  288          }
  289             msg.setField(VID_EVENT_CODE, event->getCode());
  290             msg.setField(VID_EVENT_NAME, event->getName());
  291          msg.setField(VID_TAGS, event->getTagsAsList());
  292             msg.setField(VID_NUM_ARGS, (WORD)event->getParametersCount());
  293             for(int i = 0; i < event->getParametersCount(); i++)
  294                 msg.setField(VID_EVENT_ARG_BASE + i, event->getParameter(i));
  295 
  296             if (isc->sendMessage(&msg))
  297             {
  298                 rcc = isc->waitForRCC(1, 10000);
  299             }
  300             else
  301             {
  302                 rcc = ISC_ERR_CONNECTION_BROKEN;
  303             }
  304         }
  305         else
  306         {
  307             rcc = ISC_ERR_INTERNAL_ERROR;
  308         }
  309         isc->disconnect();
  310     }
  311     delete isc;
  312     if (rcc != ISC_ERR_SUCCESS)
  313         nxlog_write(NXLOG_WARNING, _T("Failed to forward event to server %s (%s)"), server, ISCErrorCodeToText(rcc));
  314     return rcc == ISC_ERR_SUCCESS;
  315 }
  316 
  317 /**
  318  * Execute NXSL script
  319  */
  320 static bool ExecuteActionScript(const TCHAR *script, const Event *event)
  321 {
  322    TCHAR name[1024];
  323    _tcslcpy(name, script, 1024);
  324    Trim(name);
  325 
  326    // Can be in form parameter(arg1, arg2, ... argN)
  327    TCHAR *p = _tcschr(name, _T('('));
  328    if (p != nullptr)
  329    {
  330       size_t l = _tcslen(name) - 1;
  331       if (name[l] != _T(')'))
  332          return false;
  333       name[l] = 0;
  334       *p = 0;
  335    }
  336 
  337    bool success = false;
  338     NXSL_VM *vm = CreateServerScriptVM(name, FindObjectById(event->getSourceId()));
  339     if (vm != nullptr)
  340     {
  341         vm->setGlobalVariable("$event", vm->createValue(new NXSL_Object(vm, &g_nxslEventClass, event, true)));
  342 
  343       ObjectRefArray<NXSL_Value> args(16, 16);
  344       if ((p == nullptr) || ParseValueList(vm, &p, args))
  345       {
  346          // Pass event's parameters as arguments
  347          for(int i = 0; i < event->getParametersCount(); i++)
  348             args.add(vm->createValue(event->getParameter(i)));
  349 
  350          if (vm->run(args))
  351          {
  352             nxlog_debug_tag(DEBUG_TAG, 4, _T("ExecuteActionScript: script %s successfully executed"), name);
  353             success = true;
  354          }
  355          else
  356          {
  357             nxlog_debug_tag(DEBUG_TAG, 4, _T("ExecuteActionScript: Script %s execution error: %s"), name, vm->getErrorText());
  358             PostSystemEvent(EVENT_SCRIPT_ERROR, g_dwMgmtNode, "ssd", name, vm->getErrorText(), 0);
  359          }
  360       }
  361       else
  362       {
  363          // argument parsing error
  364          nxlog_debug(6, _T("ExecuteActionScript: Argument parsing error for script %s"), name);
  365       }
  366       delete vm;
  367     }
  368     else
  369     {
  370         nxlog_debug_tag(DEBUG_TAG, 4, _T("ExecuteActionScript: Cannot find script %s"), name);
  371     }
  372     return success;
  373 }
  374 
  375 /**
  376  * Execute action on specific event
  377  */
  378 bool ExecuteAction(uint32_t actionId, const Event *event, const Alarm *alarm)
  379 {
  380    static const TCHAR *actionType[] = { _T("EXEC"), _T("REMOTE"), _T("SEND EMAIL"), _T("SEND NOTIFICATION"), _T("FORWARD EVENT"), _T("NXSL SCRIPT"), _T("XMPP MESSAGE") };
  381 
  382    bool success = false;
  383 
  384    shared_ptr<Action> action = s_actions.getShared(actionId);
  385    if (action != nullptr)
  386    {
  387       if (action->isDisabled)
  388       {
  389          nxlog_debug_tag(DEBUG_TAG, 3, _T("Action %d (%s) is disabled and will not be executed"), actionId, action->name);
  390          success = true;
  391       }
  392       else
  393       {
  394          nxlog_debug_tag(DEBUG_TAG, 3, _T("Executing action %d (%s) of type %s"),
  395             actionId, action->name, actionType[action->type]);
  396 
  397          StringBuffer expandedData = event->expandText(CHECK_NULL_EX(action->data), alarm);
  398          expandedData.trim();
  399 
  400          StringBuffer expandedRcpt = event->expandText(action->rcptAddr, alarm);
  401          expandedRcpt.trim();
  402 
  403          switch(action->type)
  404          {
  405             case ACTION_EXEC:
  406                if (!expandedData.isEmpty())
  407                {
  408                   ThreadPoolExecute(g_mainThreadPool, RunCommand, MemCopyString(expandedData));
  409                }
  410                else
  411                {
  412                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Empty command - nothing to execute"));
  413                }
  414                     success = true;
  415                break;
  416             case ACTION_SEND_EMAIL:
  417                if (!expandedRcpt.isEmpty())
  418                {
  419                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Sending mail to %s: \"%s\""), (const TCHAR *)expandedRcpt, (const TCHAR *)expandedData);
  420                   String expandedSubject = event->expandText(action->emailSubject, alarm);
  421                        TCHAR *curr = expandedRcpt.getBuffer(), *next;
  422                        do
  423                        {
  424                            next = _tcschr(curr, _T(';'));
  425                            if (next != nullptr)
  426                                *next = 0;
  427                            StrStrip(curr);
  428                            PostMail(curr, expandedSubject, expandedData);
  429                            curr = next + 1;
  430                        } while(next != nullptr);
  431                }
  432                else
  433                {
  434                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Empty recipients list - mail will not be sent"));
  435                }
  436                success = true;
  437                break;
  438             case ACTION_NOTIFICATION:
  439                if (action->channelName[0] != 0)
  440                {
  441                   if(expandedRcpt.isEmpty())
  442                      nxlog_debug_tag(DEBUG_TAG, 3, _T("Sending notification using channel %s: \"%s\""), action->channelName, (const TCHAR *)expandedData);
  443 
  444                   else
  445                      nxlog_debug_tag(DEBUG_TAG, 3, _T("Sending notification using channel %s to %s: \"%s\""), action->channelName, (const TCHAR *)expandedRcpt, (const TCHAR *)expandedData);
  446                   String expandedSubject = event->expandText(action->emailSubject, alarm);
  447                   SendNotification(action->channelName, expandedRcpt.getBuffer(), expandedSubject, expandedData);
  448                }
  449                else
  450                {
  451                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Empty channel name - notification will not be sent"));
  452                }
  453                success = true;
  454                break;
  455             case ACTION_XMPP_MESSAGE:
  456                if (!expandedRcpt.isEmpty())
  457                {
  458 #if XMPP_SUPPORTED
  459                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Sending XMPP message to %s: \"%s\""), (const TCHAR *)expandedRcpt, (const TCHAR *)expandedData);
  460                        TCHAR *curr = expandedRcpt.getBuffer(), *next;
  461                        do
  462                        {
  463                            next = _tcschr(curr, _T(';'));
  464                            if (next != NULL)
  465                                *next = 0;
  466                            StrStrip(curr);
  467                       SendXMPPMessage(curr, expandedData);
  468                            curr = next + 1;
  469                        } while(next != NULL);
  470 #else
  471                   nxlog_debug_tag(DEBUG_TAG, 3, _T("cannot send XMPP message to %s (server compiled without XMPP support)"), expandedRcpt.cstr());
  472 #endif
  473                }
  474                else
  475                {
  476                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Empty recipients list - XMPP message will not be sent"));
  477                }
  478                success = true;
  479                break;
  480             case ACTION_REMOTE:
  481                if (!expandedRcpt.isEmpty())
  482                {
  483                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Executing on \"%s\": \"%s\""), expandedRcpt.cstr(), expandedData.cstr());
  484                   success = ExecuteRemoteAction(expandedRcpt, expandedData);
  485                }
  486                else
  487                {
  488                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Empty target list - remote action will not be executed"));
  489                   success = true;
  490                }
  491                break;
  492                 case ACTION_FORWARD_EVENT:
  493                if (!expandedRcpt.isEmpty())
  494                {
  495                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Forwarding event to \"%s\""), expandedRcpt.cstr());
  496                   success = ForwardEvent(expandedRcpt, event);
  497                }
  498                else
  499                {
  500                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Empty destination - event will not be forwarded"));
  501                   success = true;
  502                }
  503                     break;
  504                 case ACTION_NXSL_SCRIPT:
  505                if (!expandedRcpt.isEmpty())
  506                {
  507                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Executing NXSL script \"%s\""), expandedRcpt.cstr());
  508                   success = ExecuteActionScript(expandedRcpt, event);
  509                }
  510                else
  511                {
  512                   nxlog_debug_tag(DEBUG_TAG, 3, _T("Empty script name - nothing to execute"));
  513                   success = true;
  514                }
  515                     break;
  516             default:
  517                break;
  518          }
  519       }
  520    }
  521    return success;
  522 }
  523 
  524 /**
  525  * Action name comparator
  526  */
  527 static bool ActionNameComparator(const uint32_t& id, const Action& action, const TCHAR *name)
  528 {
  529    return _tcsicmp(action.name, name) == 0;
  530 }
  531 
  532 /**
  533  * Create new action
  534  */
  535 uint32_t CreateAction(const TCHAR *name, uint32_t *id)
  536 {
  537    // Check for duplicate name
  538    if (s_actions.findElement(ActionNameComparator, name) != nullptr)
  539       return RCC_OBJECT_ALREADY_EXISTS;
  540 
  541    Action *action = new Action(name);
  542    *id = action->id;
  543    action->saveToDatabase();
  544 
  545    s_actions.set(action->id, action);
  546    s_updateCode = NX_NOTIFY_ACTION_CREATED;
  547    EnumerateClientSessions(SendActionDBUpdate, action);
  548 
  549    return RCC_SUCCESS;
  550 }
  551 
  552 /**
  553  * Delete action
  554  */
  555 uint32_t DeleteAction(uint32_t actionId)
  556 {
  557    uint32_t dwResult = RCC_SUCCESS;
  558 
  559    shared_ptr<Action> action = s_actions.getShared(actionId);
  560    if (action != nullptr)
  561    {
  562       s_updateCode = NX_NOTIFY_ACTION_DELETED;
  563       EnumerateClientSessions(SendActionDBUpdate, action.get());
  564       s_actions.remove(actionId);
  565    }
  566    else
  567    {
  568       dwResult = RCC_INVALID_ACTION_ID;
  569    }
  570 
  571    if (dwResult == RCC_SUCCESS)
  572    {
  573       DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
  574       ExecuteQueryOnObject(hdb, actionId, _T("DELETE FROM actions WHERE action_id=?"));
  575       DBConnectionPoolReleaseConnection(hdb);
  576    }
  577    return dwResult;
  578 }
  579 
  580 /**
  581  * Modify action record from message
  582  */
  583 uint32_t ModifyActionFromMessage(const NXCPMessage *msg)
  584 {
  585    uint32_t dwResult = RCC_INVALID_ACTION_ID;
  586 
  587    TCHAR name[MAX_OBJECT_NAME];
  588    msg->getFieldAsString(VID_ACTION_NAME, name, MAX_OBJECT_NAME);
  589    uint32_t actionId = msg->getFieldAsUInt32(VID_ACTION_ID);
  590 
  591    shared_ptr<Action> tmp = s_actions.getShared(actionId);
  592    if (tmp != nullptr)
  593    {
  594       Action *action = new Action(tmp.get());
  595       action->isDisabled = msg->getFieldAsBoolean(VID_IS_DISABLED);
  596       action->type = msg->getFieldAsUInt16(VID_ACTION_TYPE);
  597       MemFree(action->data);
  598       action->data = msg->getFieldAsString(VID_ACTION_DATA);
  599       msg->getFieldAsString(VID_EMAIL_SUBJECT, action->emailSubject, MAX_EMAIL_SUBJECT_LEN);
  600       msg->getFieldAsString(VID_RCPT_ADDR, action->rcptAddr, MAX_RCPT_ADDR_LEN);
  601       msg->getFieldAsString(VID_CHANNEL_NAME, action->channelName, MAX_OBJECT_NAME);
  602       _tcscpy(action->name, name);
  603 
  604       action->saveToDatabase();
  605 
  606       s_updateCode = NX_NOTIFY_ACTION_MODIFIED;
  607       EnumerateClientSessions(SendActionDBUpdate, action);
  608       s_actions.set(actionId, action);
  609 
  610       dwResult = RCC_SUCCESS;
  611    }
  612 
  613    return dwResult;
  614 }
  615 
  616 /**
  617  * Send action to client
  618  * first - old name
  619  * second - new name
  620  */
  621 static EnumerationCallbackResult RenameChannel(const uint32_t& id, const shared_ptr<Action>& action, std::pair<TCHAR*, TCHAR*> *names)
  622 {
  623    if (!_tcsncmp(action->channelName, names->first, MAX_OBJECT_NAME) && action->type == ACTION_NOTIFICATION)
  624    {
  625       _tcsncpy(action->channelName, names->second, MAX_OBJECT_NAME);
  626       s_updateCode = NX_NOTIFY_ACTION_MODIFIED;
  627       EnumerateClientSessions(SendActionDBUpdate, action.get());
  628    }
  629    return _CONTINUE;
  630 }
  631 
  632 /**
  633  * Update notification channel name in all actions
  634  * first - old name
  635  * second - new name
  636  */
  637 void UpdateChannelNameInActions(std::pair<TCHAR*, TCHAR*> *names)
  638 {
  639    s_actions.forEach(RenameChannel, names);
  640 }
  641 
  642 /**
  643  * Check if notification channel with given name is used in actions
  644  */
  645 static bool CheckChannelUsage(const uint32_t& id, const Action& action, TCHAR *name)
  646 {
  647    return _tcsncmp(action.channelName, name, MAX_OBJECT_NAME) == 0;
  648 }
  649 
  650 /**
  651  * Check if notification channel with given name is used in actions
  652  */
  653 bool CheckChannelIsUsedInAction(TCHAR *name)
  654 {
  655    return s_actions.findElement(CheckChannelUsage, name) != nullptr;
  656 }
  657 
  658 /**
  659  * Sender callbact data
  660  */
  661 struct SendActionData
  662 {
  663    ClientSession *session;
  664    NXCPMessage *message;
  665 };
  666 
  667 /**
  668  * Send action to client
  669  */
  670 static EnumerationCallbackResult SendAction(const uint32_t& id, const shared_ptr<Action>& action, SendActionData *data)
  671 {
  672    action->fillMessage(data->message);
  673    data->session->sendMessage(data->message);
  674    data->message->deleteAllFields();
  675    return _CONTINUE;
  676 }
  677 
  678 /**
  679  * Send all actions to client
  680  */
  681 void SendActionsToClient(ClientSession *session, uint32_t requestId)
  682 {
  683    NXCPMessage msg(CMD_ACTION_DATA, requestId);
  684 
  685    SendActionData data;
  686    data.session = session;
  687    data.message = &msg;
  688    s_actions.forEach(SendAction, &data);
  689 
  690    // Send end-of-list flag
  691    msg.setField(VID_ACTION_ID, (UINT32)0);
  692    session->sendMessage(&msg);
  693 }
  694 
  695 /**
  696  * Export action configuration
  697  */
  698 void CreateActionExportRecord(StringBuffer &xml, uint32_t id)
  699 {
  700    DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
  701    DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT guid,action_name,action_type,")
  702                                        _T("rcpt_addr,email_subject,action_data,")
  703                                        _T("channel_name FROM actions WHERE action_id=?"));
  704    if (hStmt == NULL)
  705    {
  706       DBConnectionPoolReleaseConnection(hdb);
  707       return;
  708    }
  709 
  710    DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, id);
  711    DB_RESULT hResult = DBSelectPrepared(hStmt);
  712    if (hResult != NULL)
  713    {
  714       if (DBGetNumRows(hResult) > 0)
  715       {
  716          xml.append(_T("\t\t<action id=\""));
  717          xml.append(id);
  718          xml.append(_T("\">\n\t\t\t<guid>"));
  719          xml.append(DBGetFieldGUID(hResult, 0, 0));
  720          xml.append(_T("</guid>\n\t\t\t<name>"));
  721          xml.appendPreallocated(DBGetFieldForXML(hResult, 0, 1));
  722          xml.append(_T("</name>\n\t\t\t<type>"));
  723          xml.append(DBGetFieldULong(hResult, 0, 2));
  724          xml.append(_T("</type>\n\t\t\t<recipientAddress>"));
  725          xml.appendPreallocated(DBGetFieldForXML(hResult, 0, 3));
  726          xml.append(_T("</recipientAddress>\n\t\t\t<emailSubject>"));
  727          xml.appendPreallocated(DBGetFieldForXML(hResult, 0, 4));
  728          xml.append(_T("</emailSubject>\n\t\t\t<data>"));
  729          xml.appendPreallocated(DBGetFieldForXML(hResult, 0, 5));
  730          xml.append(_T("</data>\n\t\t\t<channelName>"));
  731          xml.appendPreallocated(DBGetFieldForXML(hResult, 0, 6));
  732          xml.append(_T("</channelName>\n"));
  733          xml.append(_T("\t\t</action>\n"));
  734       }
  735       DBFreeResult(hResult);
  736    }
  737    DBFreeStatement(hStmt);
  738    DBConnectionPoolReleaseConnection(hdb);
  739 }
  740 
  741 /**
  742  * Check if given action ID is valid
  743  */
  744 bool IsValidActionId(uint32_t id)
  745 {
  746    return s_actions.getShared(id) != NULL;
  747 }
  748 
  749 /**
  750  * Get GUID of given action
  751  */
  752 uuid GetActionGUID(uint32_t id)
  753 {
  754    shared_ptr<Action> action = s_actions.getShared(id);
  755    uuid guid = (action != NULL) ? action->guid : uuid::NULL_UUID;
  756    return guid;
  757 }
  758 
  759 /**
  760  * Action GUID comparator
  761  */
  762 static bool ActionGUIDComparator(const uint32_t& id, const Action& action, const uuid *data)
  763 {
  764    return action.guid.equals(*data);
  765 }
  766 
  767 /**
  768  * Find action by GUID
  769  */
  770 uint32_t FindActionByGUID(const uuid& guid)
  771 {
  772    shared_ptr<Action> action = s_actions.findElement(ActionGUIDComparator, &guid);
  773    uint32_t id = (action != nullptr) ? action->id : 0;
  774    return id;
  775 }
  776 
  777 /**
  778  * Import action configuration
  779  */
  780 bool ImportAction(ConfigEntry *config, bool overwrite)
  781 {
  782    if (config->getSubEntryValue(_T("name")) == NULL)
  783    {
  784       nxlog_debug_tag(_T("import"), 4, _T("ImportAction: no name specified"));
  785       return false;
  786    }
  787 
  788    const uuid guid = config->getSubEntryValueAsUUID(_T("guid"));
  789    shared_ptr<Action> action = !guid.isNull() ? s_actions.findElement(ActionGUIDComparator, &guid) : shared_ptr<Action>();
  790    if (action == NULL)
  791    {
  792       // Check for duplicate name
  793       const TCHAR *name = config->getSubEntryValue(_T("name"));
  794       if (s_actions.findElement(ActionNameComparator, name) != NULL)
  795       {
  796          nxlog_debug_tag(_T("import"), 4, _T("ImportAction: name \"%s\" already exists"), name);
  797          return false;
  798       }
  799 
  800       action = make_shared<Action>(name);
  801       action->isDisabled = false;
  802       if (!guid.isNull())
  803          action->guid = guid;
  804       s_updateCode = NX_NOTIFY_ACTION_CREATED;
  805    }
  806    else
  807    {
  808       TCHAR guidText[64];
  809       if (!overwrite)
  810       {
  811          nxlog_debug_tag(_T("import"), 4, _T("ImportAction: found existing action \"%s\" with GUID %s (skipping)"),
  812                   action->name, action->guid.toString(guidText));
  813          return true;
  814       }
  815       nxlog_debug_tag(_T("import"), 4, _T("ImportAction: found existing action \"%s\" with GUID %s"), action->name, action->guid.toString(guidText));
  816       _tcslcpy(action->name, config->getSubEntryValue(_T("name")), MAX_OBJECT_NAME);
  817       s_updateCode = NX_NOTIFY_ACTION_MODIFIED;
  818       action = make_shared<Action>(action.get());
  819    }
  820 
  821    // If not exist, create it
  822    action->type = config->getSubEntryValueAsUInt(_T("type"), 0);
  823    if (config->getSubEntryValue(_T("emailSubject")) == NULL)
  824       action->emailSubject[0] = 0;
  825    else
  826       _tcslcpy(action->emailSubject, config->getSubEntryValue(_T("emailSubject")), MAX_EMAIL_SUBJECT_LEN);
  827    if (config->getSubEntryValue(_T("recipientAddress")) == NULL)
  828       action->rcptAddr[0] = 0;
  829    else
  830       _tcslcpy(action->rcptAddr, config->getSubEntryValue(_T("recipientAddress")), MAX_RCPT_ADDR_LEN);
  831    if (config->getSubEntryValue(_T("channelName")) == NULL)
  832       action->channelName[0] = 0;
  833    else
  834       _tcslcpy(action->channelName, config->getSubEntryValue(_T("channelName")), MAX_OBJECT_NAME);
  835    action->data = MemCopyString(config->getSubEntryValue(_T("data")));
  836    action->saveToDatabase();
  837    EnumerateClientSessions(SendActionDBUpdate, action.get());
  838    s_actions.set(action->id, action);
  839 
  840    return true;
  841 }
  842 
  843 /**
  844  * Task handler for scheduled action execution
  845  */
  846 void ExecuteScheduledAction(const shared_ptr<ScheduledTaskParameters>& parameters)
  847 {
  848    uint32_t actionId = ExtractNamedOptionValueAsUInt(parameters->m_persistentData, _T("action"), 0);
  849    Event *restoredEvent = nullptr;
  850    Alarm *restoredAlarm = nullptr;
  851    const Event *event;
  852    const Alarm *alarm;
  853    if (parameters->m_transientData != nullptr)
  854    {
  855       event = static_cast<ActionExecutionTransientData*>(parameters->m_transientData)->getEvent();
  856       alarm = static_cast<ActionExecutionTransientData*>(parameters->m_transientData)->getAlarm();
  857    }
  858    else
  859    {
  860       uint64_t eventId = ExtractNamedOptionValueAsUInt64(parameters->m_persistentData, _T("event"), 0);
  861       if (eventId != 0)
  862       {
  863          restoredEvent = LoadEventFromDatabase(eventId);
  864       }
  865 
  866       uint32_t alarmId = ExtractNamedOptionValueAsUInt(parameters->m_persistentData, _T("alarm"), 0);
  867       if (alarmId != 0)
  868       {
  869          restoredAlarm = FindAlarmById(alarmId);
  870          if (restoredAlarm == nullptr)
  871             restoredAlarm = LoadAlarmFromDatabase(alarmId);
  872       }
  873 
  874       event = restoredEvent;
  875       alarm = restoredAlarm;
  876    }
  877 
  878    if (event != nullptr)
  879    {
  880       nxlog_debug_tag(DEBUG_TAG, 4, _T("Executing scheduled action [%u] for event %s on node [%u]"),
  881                actionId, event->getName(), parameters->m_objectId);
  882       ExecuteAction(actionId, event, alarm);
  883    }
  884    else
  885    {
  886       nxlog_debug_tag(DEBUG_TAG, 4, _T("Cannot execute scheduled action [%u] on node [%u]: original event is unavailable"),
  887                actionId, parameters->m_objectId);
  888    }
  889    delete restoredEvent;
  890    delete restoredAlarm;
  891 }
  892 
  893 /**
  894  * Constructor for scheduled action execution transient data
  895  */
  896 ActionExecutionTransientData::ActionExecutionTransientData(const Event *e, const Alarm *a)
  897 {
  898    m_event = new Event(e);
  899    m_alarm = (a != nullptr) ? new Alarm(a, false) : nullptr;
  900 }
  901 
  902 /**
  903  * Destructor for scheduled action execution transient data
  904  */
  905 ActionExecutionTransientData::~ActionExecutionTransientData()
  906 {
  907    delete m_event;
  908    delete m_alarm;
  909 }