source: nscp/modules/NRPEListener/NRPEListener.cpp @ b0ae738

0.4.00.4.10.4.2stable
Last change on this file since b0ae738 was b0ae738, checked in by Michael Medin <michael@…>, 6 years ago

2007-11-14 MickeM

+ Added multi-line output support ( issue #91 )

  • Improved error handling for external commands
  • Fixed an issue with NRPE/NSClient socket-reading (introduced by the socket_timeout)

(and added the scripts folder to the SVN, and hopefully the build as well :)

  • Property mode set to 100644
File size: 17.7 KB
Line 
1/**************************************************************************
2*   Copyright (C) 2004-2007 by Michael Medin <michael@medin.name>         *
3*                                                                         *
4*   This code is part of NSClient++ - http://trac.nakednuns.org/nscp      *
5*                                                                         *
6*   This program is free software; you can redistribute it and/or modify  *
7*   it under the terms of the GNU General Public License as published by  *
8*   the Free Software Foundation; either version 2 of the License, or     *
9*   (at your option) any later version.                                   *
10*                                                                         *
11*   This program is distributed in the hope that it will be useful,       *
12*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14*   GNU General Public License for more details.                          *
15*                                                                         *
16*   You should have received a copy of the GNU General Public License     *
17*   along with this program; if not, write to the                         *
18*   Free Software Foundation, Inc.,                                       *
19*   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
20***************************************************************************/
21#include "stdafx.h"
22#include "NRPEListener.h"
23#include <strEx.h>
24#include <time.h>
25#include <config.h>
26#include "NRPEPacket.h"
27#include <msvc_wrappers.h>
28
29NRPEListener gNRPEListener;
30
31BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
32{
33        NSCModuleWrapper::wrapDllMain(hModule, ul_reason_for_call);
34        return TRUE;
35}
36
37NRPEListener::NRPEListener() : noPerfData_(false) {
38}
39NRPEListener::~NRPEListener() {
40}
41
42std::string getAllowedHosts() {
43        std::string ret = NSCModuleHelper::getSettingsString(NRPE_SECTION_TITLE, MAIN_ALLOWED_HOSTS, "");
44        if (ret.empty())
45                ret = NSCModuleHelper::getSettingsString(MAIN_SECTION_TITLE, MAIN_ALLOWED_HOSTS, MAIN_ALLOWED_HOSTS_DEFAULT);
46        return ret;
47}
48bool getCacheAllowedHosts() {
49        int val = NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, MAIN_ALLOWED_HOSTS_CACHE, -1);
50        if (val == -1)
51                val = NSCModuleHelper::getSettingsInt(MAIN_SECTION_TITLE, MAIN_ALLOWED_HOSTS_CACHE, MAIN_ALLOWED_HOSTS_CACHE_DEFAULT);
52        return val==1?true:false;
53}
54
55
56void NRPEListener::addAllScriptsFrom(std::string path) {
57        std::string baseDir;
58        std::string::size_type pos = path.find_last_of('*');
59        if (pos == std::string::npos) {
60                path += "*.*";
61        }
62        WIN32_FIND_DATA wfd;
63        HANDLE hFind = FindFirstFile(path.c_str(), &wfd);
64        if (hFind != INVALID_HANDLE_VALUE) {
65                do {
66                        if ((wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) {
67                                addCommand(script_dir, wfd.cFileName);
68                        }
69                } while (FindNextFile(hFind, &wfd));
70        } else {
71                NSC_LOG_ERROR_STD("No scripts found in path: " + path);
72                return;
73        }
74        FindClose(hFind);
75}
76
77bool NRPEListener::loadModule() {
78        bUseSSL_ = NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_USE_SSL ,NRPE_SETTINGS_USE_SSL_DEFAULT)==1;
79        noPerfData_ = NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_PERFDATA,NRPE_SETTINGS_PERFDATA_DEFAULT)==0;
80        timeout = NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_TIMEOUT ,NRPE_SETTINGS_TIMEOUT_DEFAULT);
81        socketTimeout_ = NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_READ_TIMEOUT ,NRPE_SETTINGS_READ_TIMEOUT_DEFAULT);
82        scriptDirectory_ = NSCModuleHelper::getSettingsString(NRPE_SECTION_TITLE, NRPE_SETTINGS_SCRIPTDIR ,NRPE_SETTINGS_SCRIPTDIR_DEFAULT);
83        std::list<std::string> commands = NSCModuleHelper::getSettingsSection(NRPE_HANDLER_SECTION_TITLE);
84        std::list<std::string>::const_iterator it;
85        for (it = commands.begin(); it != commands.end(); ++it) {
86                std::string command_name;
87                if (((*it).length() > 7)&&((*it).substr(0,7) == "command")) {
88                        strEx::token t = strEx::getToken((*it), '[');
89                        t = strEx::getToken(t.second, ']');
90                        command_name = t.first;
91                } else {
92                        command_name = (*it);
93                }
94                std::string s = NSCModuleHelper::getSettingsString(NRPE_HANDLER_SECTION_TITLE, (*it), "");
95                if (command_name.empty() || s.empty()) {
96                        NSC_LOG_ERROR_STD("Invalid command definition: " + (*it));
97                } else {
98                        if ((s.length() > 7)&&(s.substr(0,6) == "inject")) {
99                                addCommand(inject, command_name.c_str(), s.substr(7));
100                        } else {
101                                addCommand(script, command_name.c_str(), s);
102                        }
103                }
104        }
105
106        if (!scriptDirectory_.empty()) {
107                addAllScriptsFrom(scriptDirectory_);
108        }
109
110        allowedHosts.setAllowedHosts(strEx::splitEx(getAllowedHosts(), ","), getCacheAllowedHosts());
111        try {
112                unsigned short port = NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_PORT, NRPE_SETTINGS_PORT_DEFAULT);
113                std::string host = NSCModuleHelper::getSettingsString(NRPE_SECTION_TITLE, NRPE_SETTINGS_BINDADDR, NRPE_SETTINGS_BINDADDR_DEFAULT);
114                unsigned int backLog = NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_LISTENQUE, NRPE_SETTINGS_LISTENQUE_DEFAULT);
115                if (bUseSSL_) {
116                        socket_ssl_.setHandler(this);
117                        socket_ssl_.StartListener(host, port, backLog);
118                } else {
119                        socket_.setHandler(this);
120                        socket_.StartListener(host, port, backLog);
121                }
122        } catch (simpleSocket::SocketException e) {
123                NSC_LOG_ERROR_STD("Exception caught: " + e.getMessage());
124                return false;
125        } catch (simpleSSL::SSLException e) {
126                NSC_LOG_ERROR_STD("Exception caught: " + e.getMessage());
127                return false;
128        }
129
130        return true;
131}
132bool NRPEListener::unloadModule() {
133        try {
134                if (bUseSSL_) {
135                        socket_ssl_.removeHandler(this);
136                        if (socket_ssl_.hasListener())
137                                socket_ssl_.StopListener();
138                } else {
139                        socket_.removeHandler(this);
140                        if (socket_.hasListener())
141                                socket_.StopListener();
142                }
143        } catch (simpleSocket::SocketException e) {
144                NSC_LOG_ERROR_STD("Exception caught: " + e.getMessage());
145                return false;
146        } catch (simpleSSL::SSLException e) {
147                NSC_LOG_ERROR_STD("Exception caught: " + e.getMessage());
148                return false;
149        }
150        return true;
151}
152
153
154bool NRPEListener::hasCommandHandler() {
155        return true;
156}
157bool NRPEListener::hasMessageHandler() {
158        return false;
159}
160
161
162NSCAPI::nagiosReturn NRPEListener::handleCommand(const strEx::blindstr command, const unsigned int argLen, char **char_args, std::string &message, std::string &perf) {
163        command_list::const_iterator cit = commands.find(command);
164        if (cit == commands.end())
165                return NSCAPI::returnIgnored;
166
167        const command_data cd = (*cit).second;
168        std::string args = cd.arguments;
169        if (NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_ALLOW_ARGUMENTS, NRPE_SETTINGS_ALLOW_ARGUMENTS_DEFAULT) == 1) {
170                arrayBuffer::arrayList arr = arrayBuffer::arrayBuffer2list(argLen, char_args);
171                arrayBuffer::arrayList::const_iterator cit2 = arr.begin();
172                int i=1;
173
174                for (;cit2!=arr.end();cit2++,i++) {
175                        if (NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_ALLOW_NASTY_META, NRPE_SETTINGS_ALLOW_NASTY_META_DEFAULT) == 0) {
176                                if ((*cit2).find_first_of(NASTY_METACHARS) != std::string::npos) {
177                                        NSC_LOG_ERROR("Request string contained illegal metachars!");
178                                        return NSCAPI::returnIgnored;
179                                }
180                        }
181                        strEx::replace(args, "$ARG" + strEx::itos(i) + "$", (*cit2));
182                }
183        }
184        if (cd.type == inject) {
185                strEx::token t = strEx::getToken(args, ' ');
186                std::string s = t.second;
187                std::string sTarget;
188
189                std::string::size_type p = 0;
190                while(true) {
191                        std::string::size_type pStart = p;
192                        std::string::size_type pEnd = std::string::npos;
193                        if (s[p] == '\"') {
194                                pStart++;
195                                while (true) {
196                                        p = s.find(' ', ++p);
197                                        if (p == std::string::npos)
198                                                break;
199                                        if ((p>1)&&(s[p-1]=='\"')&&(((p>2)&&(s[p-2]!='\\'))||(p==2)))
200                                                break;
201                                }
202                                if (p != std::string::npos)
203                                        pEnd = p-1;
204                                else
205                                        pEnd = s.length()-1;
206                                if (p != std::string::npos) {
207                                        p++;
208                                }
209                        } else {
210                                pEnd = p = s.find(' ', ++p);
211                                if (p != std::string::npos) {
212                                        p = s.find_first_not_of(' ', p);
213                                }
214                        }
215                        if (!sTarget.empty())
216                                sTarget += "!";
217                        if (p == std::string::npos) {
218                                if (pEnd == std::string::npos)
219                                        sTarget += s.substr(pStart);
220                                else
221                                        sTarget += s.substr(pStart, pEnd-pStart);
222                                break;
223                        }
224                        sTarget += s.substr(pStart,pEnd-pStart);
225                        //p++;
226                }
227                return NSCModuleHelper::InjectSplitAndCommand(t.first, sTarget, '!', message, perf);
228        } else if (cd.type == script) {
229                return executeNRPECommand(args, message, perf);
230        } else if (cd.type == script_dir) {
231                std::string args = arrayBuffer::arrayBuffer2string(char_args, argLen, " ");
232                std::string cmd = scriptDirectory_ + command.c_str() + " " +args;
233                return executeNRPECommand(cmd, message, perf);
234        } else {
235                NSC_LOG_ERROR_STD("Unknown script type: " + command.c_str());
236                return NSCAPI::critical;
237        }
238
239}
240#define MAX_INPUT_BUFFER 1024
241
242int NRPEListener::executeNRPECommand(std::string command, std::string &msg, std::string &perf)
243{
244        NSCAPI::nagiosReturn result;
245        PROCESS_INFORMATION pi;
246        STARTUPINFO si;
247        HANDLE hChildOutR, hChildOutW, hChildInR, hChildInW;
248        SECURITY_ATTRIBUTES sec;
249        DWORD dwstate, dwexitcode;
250        int retval;
251
252
253        // Set up members of SECURITY_ATTRIBUTES structure.
254
255        sec.nLength = sizeof(SECURITY_ATTRIBUTES);
256        sec.bInheritHandle = TRUE;
257        sec.lpSecurityDescriptor = NULL;
258
259        // Create Pipes
260        CreatePipe(&hChildInR, &hChildInW, &sec, 0);
261        CreatePipe(&hChildOutR, &hChildOutW, &sec, 0);
262
263        // Set up members of STARTUPINFO structure.
264
265        ZeroMemory(&si, sizeof(STARTUPINFO));
266        si.cb = sizeof(STARTUPINFO);
267        si.dwFlags = STARTF_USESTDHANDLES;
268        si.hStdInput = hChildInR;
269        si.hStdOutput = hChildOutW;
270        si.hStdError = hChildOutW;
271
272
273        // CreateProcess doesn't work with a const command
274        char *cmd = new char[command.length()+1];
275        strncpy_s(cmd, command.length()+1, command.c_str(), command.length());
276        cmd[command.length()] = 0;
277
278        // Create the child process.
279        BOOL processOK = CreateProcess(NULL, cmd,        // command line
280                NULL, // process security attributes
281                NULL, // primary thread security attributes
282                TRUE, // handles are inherited
283                0,    // creation flags
284                NULL, // use parent's environment
285                NULL, // use parent's current directory
286                &si,  // STARTUPINFO pointer
287                &pi); // receives PROCESS_INFORMATION
288        delete [] cmd;
289
290        if (processOK) {
291                dwstate = WaitForSingleObject(pi.hProcess, 1000*timeout);
292                CloseHandle(hChildInR);
293                CloseHandle(hChildInW);
294                CloseHandle(hChildOutW);
295
296                if (dwstate == WAIT_TIMEOUT) {
297                        TerminateProcess(pi.hProcess, 5);
298                        msg = "The check (" + command + ") didn't respond within the timeout period (" + strEx::itos(timeout) + "s)!";
299                        result = NSCAPI::returnUNKNOWN;
300                } else {
301                        DWORD dwread;
302                        char *buf = new char[MAX_INPUT_BUFFER+1];
303                        retval = ReadFile(hChildOutR, buf, MAX_INPUT_BUFFER, &dwread, NULL);
304                        if (!retval || dwread == 0) {
305                                msg = "No output available from command...";
306                        } else {
307                                buf[dwread] = 0;
308                                msg = buf;
309                                //strEx::token t = strEx::getToken(msg, '\n');
310                                strEx::token t = strEx::getToken(msg, '|');
311                                msg = t.first;
312                                perf = t.second;
313                        }
314                        delete [] buf;
315                        if (GetExitCodeProcess(pi.hProcess, &dwexitcode) == 0) {
316                                NSC_LOG_ERROR("Failed to get commands (" + command + ") return code: " + error::lookup::last_error());
317                                dwexitcode = NSCAPI::returnUNKNOWN;
318                        }
319                        if (!NSCHelper::isNagiosReturnCode(dwexitcode)) {
320                                NSC_LOG_ERROR("The command (" + command + ") returned an invalid return code: " + strEx::itos(dwexitcode));
321                                dwexitcode = NSCAPI::returnUNKNOWN;
322                        }
323                        result = NSCHelper::int2nagios(dwexitcode);
324                }
325                CloseHandle(pi.hThread);
326                CloseHandle(pi.hProcess);
327                CloseHandle(hChildOutR);
328        }
329        else {
330                msg = "NRPE_NT failed to create process, exiting...";
331                result = NSCAPI::returnUNKNOWN;
332                CloseHandle(hChildInR);
333                CloseHandle(hChildInW);
334                CloseHandle(hChildOutW);
335                CloseHandle(pi.hThread);
336                CloseHandle(pi.hProcess);
337                CloseHandle(hChildOutR);
338        }
339        return result;
340}
341void NRPEListener::onClose()
342{}
343
344void NRPEListener::onAccept(simpleSocket::Socket *client)
345{
346        if (!allowedHosts.inAllowedHosts(client->getAddr())) {
347                NSC_LOG_ERROR("Unauthorize access from: " + client->getAddrString());
348                client->close();
349                return;
350        }
351        try {
352                simpleSocket::DataBuffer block;
353                int i;
354                int maxWait = socketTimeout_*10;
355                for (i=0;i<maxWait;i++) {
356                        bool lastReadRet = client->readAll(block, 1048);
357                        if (block.getLength() >= NRPEPacket::getBufferLength())
358                                break;
359                        if (!lastReadRet) {
360                                NSC_LOG_MESSAGE("Could not read NRPE packet from socket :(");
361                                client->close();
362                                return;
363                        }
364                        Sleep(100);
365                }
366                if (i >= maxWait) {
367                        NSC_LOG_ERROR_STD("Timeout reading NRPE-packet (increase socket_timeout)");
368                        client->close();
369                        return;
370                }
371                if (block.getLength() == NRPEPacket::getBufferLength()) {
372                        try {
373                                NRPEPacket out = handlePacket(NRPEPacket(block.getBuffer(), block.getLength()));
374                                block.copyFrom(out.getBuffer(), out.getBufferLength());
375                        } catch (NRPEPacket::NRPEPacketException e) {
376                                NSC_LOG_ERROR_STD("NRPESocketException: " + e.getMessage());
377                                client->close();
378                                return;
379                        }
380                        client->send(block);
381                }
382        } catch (simpleSocket::SocketException e) {
383                NSC_LOG_ERROR_STD("SocketException: " + e.getMessage());
384        } catch (NRPEException e) {
385                NSC_LOG_ERROR_STD("NRPEException: " + e.getMessage());
386        }
387        client->close();
388}
389
390NRPEPacket NRPEListener::handlePacket(NRPEPacket p) {
391        if (p.getType() != NRPEPacket::queryPacket) {
392                NSC_LOG_ERROR("Request is not a query.");
393                throw NRPEException("Invalid query type");
394        }
395        if (p.getVersion() != NRPEPacket::version2) {
396                NSC_LOG_ERROR("Request had unsupported version.");
397                throw NRPEException("Invalid version");
398        }
399        if (!p.verifyCRC()) {
400                NSC_LOG_ERROR("Request had invalid checksum.");
401                throw NRPEException("Invalid checksum");
402        }
403        strEx::token cmd = strEx::getToken(p.getPayload(), '!');
404        if (cmd.first == "_NRPE_CHECK") {
405                return NRPEPacket(NRPEPacket::responsePacket, NRPEPacket::version2, NSCAPI::returnOK, "I ("SZVERSION") seem to be doing fine...");
406        }
407        std::string msg, perf;
408
409        if (NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_ALLOW_ARGUMENTS, NRPE_SETTINGS_ALLOW_ARGUMENTS_DEFAULT) == 0) {
410                if (!cmd.second.empty()) {
411                        NSC_LOG_ERROR("Request contained arguments (not currently allowed, check the allow_arguments option).");
412                        throw NRPEException("Request contained arguments (not currently allowed, check the allow_arguments option).");
413                }
414        }
415        if (NSCModuleHelper::getSettingsInt(NRPE_SECTION_TITLE, NRPE_SETTINGS_ALLOW_NASTY_META, NRPE_SETTINGS_ALLOW_NASTY_META_DEFAULT) == 0) {
416                if (cmd.first.find_first_of(NASTY_METACHARS) != std::string::npos) {
417                        NSC_LOG_ERROR("Request command contained illegal metachars!");
418                        throw NRPEException("Request command contained illegal metachars!");
419                }
420                if (cmd.second.find_first_of(NASTY_METACHARS) != std::string::npos) {
421                        NSC_LOG_ERROR("Request arguments contained illegal metachars!");
422                        throw NRPEException("Request command contained illegal metachars!");
423                }
424        }
425
426        NSCAPI::nagiosReturn ret = -3;
427        try {
428                ret = NSCModuleHelper::InjectSplitAndCommand(cmd.first, cmd.second, '!', msg, perf);
429        } catch (...) {
430                return NRPEPacket(NRPEPacket::responsePacket, NRPEPacket::version2, NSCAPI::returnUNKNOWN, "UNKNOWN: Internal exception");
431        }
432        switch (ret) {
433                case NSCAPI::returnInvalidBufferLen:
434                        msg = "UNKNOWN: Return buffer to small to handle this command.";
435                        ret = NSCAPI::returnUNKNOWN;
436                        break;
437                case NSCAPI::returnIgnored:
438                        msg = "UNKNOWN: No handler for that command";
439                        ret = NSCAPI::returnUNKNOWN;
440                        break;
441                case NSCAPI::returnOK:
442                case NSCAPI::returnWARN:
443                case NSCAPI::returnCRIT:
444                case NSCAPI::returnUNKNOWN:
445                        break;
446                default:
447                        msg = "UNKNOWN: Internal error.";
448                        ret = NSCAPI::returnUNKNOWN;
449        }
450        if (perf.empty()||noPerfData_) {
451                return NRPEPacket(NRPEPacket::responsePacket, NRPEPacket::version2, ret, msg);
452        } else {
453                return NRPEPacket(NRPEPacket::responsePacket, NRPEPacket::version2, ret, msg + "|" + perf);
454        }
455}
456
457NSC_WRAPPERS_MAIN_DEF(gNRPEListener);
458NSC_WRAPPERS_IGNORE_MSG_DEF();
459NSC_WRAPPERS_HANDLE_CMD_DEF(gNRPEListener);
460NSC_WRAPPERS_HANDLE_CONFIGURATION(gNRPEListener);
461
462
463MODULE_SETTINGS_START(NRPEListener, "NRPE Listsner configuration", "...")
464
465PAGE("NRPE Listsner configuration")
466
467ITEM_EDIT_TEXT("port", "This is the port the NRPEListener.dll will listen to.")
468ITEM_MAP_TO("basic_ini_text_mapper")
469OPTION("section", "NRPE")
470OPTION("key", "port")
471OPTION("default", "5666")
472ITEM_END()
473
474ITEM_CHECK_BOOL("allow_arguments", "This option determines whether or not the NRPE daemon will allow clients to specify arguments to commands that are executed.")
475ITEM_MAP_TO("basic_ini_bool_mapper")
476OPTION("section", "NRPE")
477OPTION("key", "allow_arguments")
478OPTION("default", "false")
479OPTION("true_value", "1")
480OPTION("false_value", "0")
481ITEM_END()
482
483ITEM_CHECK_BOOL("allow_nasty_meta_chars", "This might have security implications (depending on what you do with the options)")
484ITEM_MAP_TO("basic_ini_bool_mapper")
485OPTION("section", "NRPE")
486OPTION("key", "allow_nasty_meta_chars")
487OPTION("default", "false")
488OPTION("true_value", "1")
489OPTION("false_value", "0")
490ITEM_END()
491
492ITEM_CHECK_BOOL("use_ssl", "This option will enable SSL encryption on the NRPE data socket (this increases security somwhat.")
493ITEM_MAP_TO("basic_ini_bool_mapper")
494OPTION("section", "NRPE")
495OPTION("key", "use_ssl")
496OPTION("default", "true")
497OPTION("true_value", "1")
498OPTION("false_value", "0")
499ITEM_END()
500
501PAGE_END()
502ADVANCED_PAGE("Access configuration")
503
504ITEM_EDIT_OPTIONAL_LIST("Allow connection from:", "This is the hosts that will be allowed to poll performance data from the NRPE server.")
505OPTION("disabledCaption", "Use global settings (defined previously)")
506OPTION("enabledCaption", "Specify hosts for NRPE server")
507OPTION("listCaption", "Add all IP addresses (not hosts) which should be able to connect:")
508OPTION("separator", ",")
509OPTION("disabled", "")
510ITEM_MAP_TO("basic_ini_text_mapper")
511OPTION("section", "NRPE")
512OPTION("key", "allowed_hosts")
513OPTION("default", "")
514ITEM_END()
515
516PAGE_END()
517MODULE_SETTINGS_END()
Note: See TracBrowser for help on using the repository browser.