#!/usr/bin/env python # -*- encoding: utf-8; py-indent-offset: 4 -*- # +------------------------------------------------------------------+ # | ____ _ _ __ __ _ __ | # | / ___| |__ ___ ___| | __ | \/ | |/ / | # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / | # | | |___| | | | __/ (__| < | | | | . \ | # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ | # | | # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de | # +------------------------------------------------------------------+ # # This file is part of Check_MK. # The official homepage is at http://mathias-kettner.de/check_mk. # # check_mk is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation in version 2. check_mk is distributed # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with- # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. See the GNU General Public License for more de- # tails. You should have received a copy of the GNU General Public # License along with GNU Make; see the file COPYING. If not, write # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301 USA. import os, sys, stat opt_debug = "-d" in sys.argv or "--debug" in sys.argv # The following settings are tried to be autodetected target_values = { 'apache_config_dir' : "Configuration directory of Apache", 'cgiurl' : "URL of Nagios CGI programs", 'check_icmp_path' : "Path to check_icmp Plugin", 'htdocsdir' : "Directory of Nagios' static web pages", 'htpasswd_file' : "File of Nagios' HTTP users and passwords", 'livestatus_in_nagioscfg' : "Wether nagios.cfg loads livestatus module", 'nagconfdir' : "Directory of Nagios objects (see cfg_dir)", 'nagiosaddconf' : "Snippet to add to nagios.cfg", 'nagios_auth_name' : "HTTP Basic AuthName for Nagios", 'nagios_binary' : "Absolute path to Nagios binary itself", 'nagios_version' : "Nagios version", 'nagios_config_file': "Absolute path to nagios.cfg", 'nagios_startscript': "Nagios startskript (usually in /etc/init.d)", 'nagios_status_file': "Absolute path to Nagios' status.dat", 'nagiosurl' : "Base-URL of Nagios web pages", 'nagiosuser' : "System user running the Nagios process", 'nagpipe' : "Absolute path to Nagios' command pipe (nagios.cmd)", 'check_result_path' : "Absolute path to Nagios' checkresults directory", 'pnp_url' : "URL of PNP4Nagios", 'pnpconffile' : "PNP4Nagios configuration file for its PHP pages", 'pnphtdocsdir' : "PNP4Nagios www document root directory", 'pnptemplates' : "directory of PHP templates for PNP4Nagios", 'rrddir' : "Base directory where RRDs are stored", 'wwwgroup' : "Common group of Nagios and Apache", 'wwwuser' : "System user apache runs with", } # Ich suche nach Prozessen mit folgenden Kriterien: # - Der Text 'nagios' im Namen # - eines der Worte muss '-d' oder '--daemon' sein. # - Das letzte Wort ist der Name einer existierenden Datei # Beispiel: # /usr/sbin/nagios3 -d /etc/nagios3/nagios.cfg class Sorry(Exception): def __init__(self, reason): self.reason = reason super(Sorry, self).__init__(reason) def find_pid_and_configfile(): procs = os.popen("ps ax -o pid,ppid,user,command").readlines() pids = [] for line in procs: if line.find('nagios.cfg') >= 0 or line.find('icinga.cfg') >= 0: pids.append(line.split()[0]) for line in procs: if line.find('nagios.cfg') >= 0 or line.find('icinga.cfg') >= 0: try: words = line.split() if '-d' in words or '--daemon' in words: pid = words[0] ppid = words[1] user = words[2] configfile = words[-1] if ppid in pids: continue # this is not the main thread. It has # another process as parent! if os.path.exists(configfile): return int(pid), user, configfile except Exception, e: if opt_debug: raise raise Sorry("Cannot find Nagios/Icinga process. Is it running?") def find_apache_properties(nagiosuser, nagios_htdocs_dir): wwwuser = None # Search in running processes for Apache for line in os.popen("ps ax -o pid,user,command").readlines(): parts = line.split() if len(parts) >= 3 and \ (parts[2].endswith("/apache2") or \ parts[2].endswith("/httpd") or \ parts[2].endswith("/httpd2-prefork") or \ parts[2].endswith("/httpd2-worker") ) and \ parts[1] != 'root': wwwuser = parts[1] apache_pid = int(parts[0]) break if not wwwuser: raise Exception("Cannot find Apache process. Is it running?") def scan_apacheconf(apache_conffile): confdirs = [] if apache_conffile[0] != '/': apache_conffile = httpd_root + "/" + apache_conffile confdirs = [] for line in file(apache_conffile): parts = line.strip().split() if len(parts) == 2 and parts[0].lower() == "include": if parts[1].endswith("/") or parts[1].endswith("/*.conf"): confdir = "/".join(parts[1].split("/")[:-1]) if confdir[0] != "/": confdir = httpd_root + "/" + confdir if not os.path.exists(confdir): continue confdirs.append(confdir) # put at front of list else: try: confdirs += scan_apacheconf(parts[1]) # recursive scan except: pass return confdirs # Find binary try: nagios_htpasswd_file = None nagios_auth_name = None apache_binary = process_executable(apache_pid) apache_conffile = None apache_confdir = None httpd_root = "" for line in os.popen("%s -V 2>&1" % apache_binary): parts = line.split() if parts[0] == "-D" and len(parts) > 1: p = parts[1].split("=") if len(p) == 2 and p[0] == "SERVER_CONFIG_FILE": apache_conffile = p[1].replace('"', "") elif len(p) == 2 and p[0] == "HTTPD_ROOT": httpd_root = p[1].replace('"', "") if apache_conffile: confdirs = scan_apacheconf(apache_conffile) if len(confdirs) > 0: apache_confdir = confdirs[0] for dir in confdirs: if dir.endswith("/conf.d"): apache_confdir = dir # Search for Nagios configuration. We are interested # in the authentication configuration. Most Nagios # installations use HTTP basic auth with a htpasswd # user file. We want to use that user file for the # check_mk web pages. def remove_quotes(text): try: if text[0] in '"\'': text = text[1:] if text[-1] in '"\'': text = text[:-1] except: pass return text auth_files = [] auth_names = [] try: for confdir in confdirs: for fn in os.listdir(confdir): file_good = False conffile = file(confdir + "/" + fn) try: new_auth_names = [] new_auth_files = [] for line in conffile: if nagios_htdocs_dir in line: file_good = True parts = line.split() if len(parts) == 2 and parts[0].lower() == "authuserfile": path = remove_quotes(parts[1]) if os.path.exists(path): new_auth_files.append(path) if len(parts) > 1 and parts[0].lower() == "authname": parts = line.split(None, 1) new_auth_names.append(remove_quotes(parts[1].strip())) try: if len(parts) > 1 and parts[0].lower().startswith("", "").replace('"', "") cleanedup = cleanedup[9:] dir = cleanedup.strip() if os.path.exists(dir) and os.path.exists(dir + "/application/config/config.php"): result['pnphtdocsdir'] = dir result['pnptemplates'] = dir + "/templates" except Exception,e : pass if file_good: auth_names += new_auth_names auth_files += new_auth_files except Exception, e: pass if len(auth_files) > 0: nagios_htpasswd_file = auth_files[0] if len(auth_names) > 0: nagios_auth_name = auth_names[0] except: if opt_debug: raise pass except: if opt_debug: raise apache_confdir = None nagios_htpasswd_file = None www_groups = os.popen("id -nG " + wwwuser).read().split() nagios_groups = os.popen("id -nG " + nagiosuser).read().split() common_groups = [ g for g in www_groups if g in nagios_groups ] if len(common_groups) > 1: if 'nagios' in common_groups: common_group = 'nagios' elif 'icinga' in common_groups: common_group = 'icinga' else: common_group = common_groups[0] elif len(common_groups) == 1: common_group = common_groups[0] else: common_group = None return wwwuser, common_group, apache_confdir, nagios_htpasswd_file, nagios_auth_name def process_environment(pid): # Umgebung des Prozesses bestimmen. Ich brauch das nicht, # aber der folgende Code ist einfach cool, oder? try: env = {} for line in file("/proc/%d/environ" % pid).read().split("\0"): if '=' in line: var, value = entry.split('=', 1) env[var] = value return env except: raise Sorry("Cannot get environment of process %d. Aren't you root?" % pid) def process_executable(pid): try: return os.readlink("/proc/%d/exe" % pid).split(" ", 1)[0] except: raise Sorry("Cannot get executable of process %d. Aren't you root?" % pid) def open_files(pid): try: # Liste der offenen Dateien. Das ist schon nuetzlicher, # denn hier sieht man z.B. die Commandpipe procpath = "/proc/%d/fd" % pid return [ os.readlink(procpath + "/" + entry) for entry in os.listdir(procpath) ] except: raise Sorry("Cannot get open files of process %d. Aren't you root?" % pid) def find_pipes(filenames): pipes = [] for f in filenames: try: mode = os.stat(f)[stat.ST_MODE] if stat.S_ISFIFO(mode): pipes.append(f) except: pass return pipes def parse_nagios_config(configfile): conf = [] for line in file(configfile): line = line.strip() if line == "" or line[0] == '#': continue try: key, value = line.split('=', 1) conf.append((key, value)) except: pass # ignore invalid line (as Nagios seems to do) return conf def detect_pnp(): global result # Jetzt will ich noch das Verzeichnis fuer die Schablonen # von PNP finden. Ich erkenne es daran, dass es ein Verzeichnis # ist, in dem 'templates' und 'templates.dist' liegen. Dieses # Verzeichnis liegt hoffentlich innerhalb der Webseite von # Nagios selbst. Dieser Pfad ist in cgi.cfg festgelegt. Das ganze # klappt nur bei PNP 0.4 if 'pnptemplates' not in result: try: found = [] def func(arg, dirname, names): if 'templates' in names and 'templates.dist' in names: found.append(dirname + "/templates") os.path.walk(cgiconf['physical_html_path'], func, None) result['pnptemplates'] = found[0] if 'pnphtdocsdir' not in result: result['pnphtdocsdir'] = result['pnptemplates'].rsplit('/', 1)[0] except: pass # Suche die Konfigurationsdatei von PNP4Nagios. Denn ich will # den Eintrag finden, der auf die RRDs zeigt. Den braucht # check_mk für das direkte Eintragen in die RRD-Datenbanken try: pnppath = os.path.dirname(result['pnptemplates']) index_php = pnppath + "/index.php" for line in file(index_php): line = line.strip() # $config = "/usr/local/nagios/etc/pnp/config"; if line.startswith('$config =') and line.endswith('";'): pnpconffile = line.split('"')[1] + ".php" result['pnpconffile'] = pnpconffile result['pnpconfdir'] = pnpconffile.rsplit("/", 1)[0] break except: pass try: # For PNP 0.6 if 'pnpconffile' not in result: kohanaconf = result['pnphtdocsdir'] + "/application/config/config.php" if os.path.exists(kohanaconf): for line in file(kohanaconf): line = line.strip() if not line.startswith("#") and "pnp_etc_path" in line: last = line.split('=')[-1].strip() dir = last.replace("'", "").replace(";", "").replace('"', "") if os.path.exists(dir): result['pnpconfdir'] = dir result['pnpconffile'] = dir + "/config.php" except: pass try: for line in file(result['pnpconffile']): line = line.strip() if line.startswith("$conf['rrdbase']") and line.endswith('";'): rrddir = line.split('"')[1] if rrddir.endswith('/'): rrddir = rrddir[:-1] result['rrddir'] = rrddir elif (line.startswith("$conf['base_url']") or line.startswith("$conf['pnp_base']")) \ and line.endswith(";"): pnp_url = line.split('"')[1] if not pnp_url.endswith("/"): pnp_url += "/" result["pnp_url"] = pnp_url except: pass def detect_omd(): site = os.getenv("OMD_SITE") root = os.getenv("OMD_ROOT") if not site or not root: return None else: return { 'apache_config_dir' : root + "/etc/apache/conf.d", 'cgiurl' : "/" + site + "/nagios/cgi-bin/", 'check_icmp_path' : root + "/lib/nagios/plugins/check_icmp", 'htdocsdir' : root + "/share/nagios/htdocs", 'htpasswd_file' : root + "/etc/htpasswd", 'livestatus_in_nagioscfg' : False, 'nagconfdir' : root + "/etc/nagios/conf.d", 'nagiosaddconf' : "", 'nagios_auth_name' : "OMD Monitoring Site " + site, 'nagios_binary' : root + "/bin/nagios", 'nagios_config_file' : root + "/tmp/nagios/nagios.cfg", 'nagios_startscript' : root + "/etc/init.d/nagios", 'nagios_status_file' : root + "/var/nagios/status.dat", 'nagiosurl' : "/" + site + "/nagios/", 'nagiosuser' : site, 'nagpipe' : root + "/tmp/run/nagios.cmd", 'check_result_path' : root + "/tmp/nagios/checkresults", 'pnp_url' : "/" + site + "/pnp4nagios/", 'pnpconffile' : root + "/etc/pnp4nagios/config.php", 'pnphtdocsdir' : root + "/share/pnp4nagios/htdocs", 'pnptemplates' : root + "/local/share/check_mk/pnp-templates", 'rrddir' : root + "/var/pnp4nagios/perfdata", 'wwwgroup' : site, 'wwwuser' : site, } # _ # _ __ ___ __ _(_)_ __ # | '_ ` _ \ / _` | | '_ \ # | | | | | | (_| | | | | | # |_| |_| |_|\__,_|_|_| |_| # try: result = detect_omd() if not result: result = {} pid, nagiosuser, configfile = find_pid_and_configfile() nagios_dir = os.path.dirname(configfile) result['nagios_config_file'] = configfile result['nagiosuser'] = nagiosuser pipes = find_pipes(open_files(pid)) if len(pipes) > 0: result['nagpipe'] = pipes[0] # Path to executable result['nagios_binary'] = process_executable(pid) # Nagios version result['nagios_version'] = "" for line in os.popen(result["nagios_binary"]+ " --version 2>/dev/null"): if line.startswith("Nagios Core") or line.startswith("Icinga Core"): result['nagios_version'] = line.split()[2] # Path to startscript for path in [ '/etc/init.d/nagios', '/etc/init.d/nagios3', '/etc/init.d/icinga' ]: if os.path.exists(path): result['nagios_startscript'] = path break nagconf = parse_nagios_config(configfile) nagconf_dict = dict(nagconf) if "check_result_path" in nagconf_dict: result['check_result_path'] = nagconf_dict['check_result_path'] try: cgifile = os.path.dirname(configfile) + "/cgi.cfg" cgiconf = dict(parse_nagios_config(cgifile)) result['htdocsdir'] = cgiconf['physical_html_path'] except: cgiconf = {} # Suche nach cfg_dir Direktiven. Wir suchen # einen flauschigen Platz fuer unsere Konfigdateien cfg_dirs = [ value for key, value in nagconf if key == 'cfg_dir' ] if len(cfg_dirs) > 0: # Wenn es mehrere gibt, bevorzuge ich das, das im gleichen # Verzeichnis, wie die Nagios-Konfigdatei selbst liegt. # Debian legt ein cfg_dir fuer die Plugins an.... if len(cfg_dirs) == 1: result['nagconfdir'] = cfg_dirs[0] else: dir = os.path.dirname(configfile) for d in cfg_dirs: if os.path.dirname(d) == dir: result['nagconfdir'] = d break else: result['nagconfdir'] = cfg_dirs[0] else: # Mist. Kein cfg_dir in nagios.cfg. Das ist z.B. bei # der immer noch verbreiteten Defaultkonfig der Fall. # Wir legen einfach selbst eins fest und hängen das # eigenmächtig hinten an die Config an nagconfdir = nagios_dir + "/check_mk.d" result['nagconfdir'] = nagconfdir result['nagiosaddconf'] = "cfg_dir=" + nagconfdir # Find path to status.dat, the Nagios status file. We # need that for the check_mk web pages. Normally the # path is configured in nagios.cfg. If no - we still # have a chance by parsing the output of nagios3stats. nagios_status_file = nagconf_dict.get("status_file") if not nagios_status_file: for stats_name in [ "stats", "tats" ]: try: stats_bin = result['nagios_binary'] + stats_name for line in os.popen(stats_bin + " 2>/dev/null"): if line.startswith("Status File:"): parts = line.split() nagios_status_file = parts[-1] break elif line.startswith("Error reading status file"): parts = line.split() nagios_status_file = parts[-1][1:-1] break except: pass if nagios_status_file: result['nagios_status_file'] = nagios_status_file # Ermittle $USER1$ Variablen, da sie in den Plugin-Pfaden # auftauchen koennen. uservars = {} try: for line in file(nagconf_dict['resource_file']): line = line.strip() if line.startswith('$') and '=' in line: varname, value = line.split('=', 1) uservars[varname.strip()] = value.strip() except: pass # Suche nach einem Eintrag zum Laden des livestatus # Moduls. Er darf auch auskommentiert sein. Dann lassen # wir den Benutzer damit in Ruhe found = False for line in file(configfile): if "broker_module=" in line and "/livestatus.o" in line: found = True break result['livestatus_in_nagioscfg'] = found # Jetzt wird's schwieriger: Ich suche nach check_icmp. # Ich will keinen find machen, da das erstens ewig # dauern kann und zweitens eventl. eine falsche Stelle # findet, z.B. innerhalb eines ausgepackten und kompilierten # Quellcodes der nagios-plugins. Daher suche ich in # allen Objektdateien von Nagios nach command_line. # Damit ermittle ich alle Verzeichnisse, in denen Plugins # liegen. Dort suche ich dann nach check_icmp. Zur Sicherheit # suche ich aber auch unter '/usr/lib/nagios' und '/usr/local/nagios/libexec' # und '/usr/local/nagios/plugins' found = [] for dir in cfg_dirs: os.path.walk(dir, lambda x,dirname,names: found.append((dirname, names)), None) plugin_paths = [] for dirname, names in found: for name in names: if name.endswith(".cfg"): path = dirname + "/" + name try: for line in file(path): if line.strip() == '': continue parts = line.strip().split() if parts[0] == "command_line": path = parts[1] for var, value in uservars.items(): path = path.replace(var, value) if path.startswith('/') and path not in plugin_paths: plugin_paths.append(path) except: pass for dir in plugin_paths + \ [ '/usr/lib/nagios/plugins', '/usr/lib64/nagios/plugins', '/usr/local/nagios/libexec', '/usr/local/nagios/plugins' ]: try: mode = os.stat(dir)[stat.ST_MODE] if not stat.S_ISDIR(mode): dir = os.path.dirname(dir) filenames = os.listdir(dir) for filename in filenames: if filename == 'check_icmp': result['check_icmp_path'] = dir + '/' + filename break except: pass # Die Basis-Url fuer Nagios ist leider auch nicht immer # gleich try: result['nagiosurl'] = cgiconf['url_html_path'] result['cgiurl'] = result['nagiosurl'] + "/cgi-bin" except: pass # Suche eine Gruppe, die Nagios mit dem Apache gemeinsam # hat. Diese brauchen wir z.B. für logwatch try: wwwuser, wwwgroup, apache_confdir, nagios_htpasswd_file, nagios_auth_name = \ find_apache_properties(nagiosuser, result['htdocsdir']) if wwwuser: result['wwwuser'] = wwwuser if wwwgroup: result['wwwgroup'] = wwwgroup if apache_confdir: result['apache_config_dir'] = apache_confdir if nagios_htpasswd_file: result['htpasswd_file'] = nagios_htpasswd_file if nagios_auth_name: result['nagios_auth_name'] = nagios_auth_name except Exception, e: sys.stderr.write("\033[1;41;35m Cannot determine Apache properties. \033[0m\n" "Reason: %s\n" % e) detect_pnp() print "# Result of autodetection" for var, value in result.items(): print descr = target_values.get(var) if descr: print "# %s" % descr else: print "# (unknown value)" print "%s='%s'" % (var, value) for var, descr in target_values.items(): if var not in result: print print "# %s" % descr print "# NOT DETECTED: %s" % var except Sorry, e: sys.stderr.write("\033[1;41;35m Sorry: %s \033[0m\n" % e.reason) sys.exit(1) except Exception, e: if opt_debug: raise else: sys.stderr.write("* Sorry, something unexpected happened: %s\n\n" % e) import traceback traceback.print_exc() sys.exit(1)