#!/usr/bin/env python
# Templated Emails with included Graphs
# This script creates a very beautiful mail in multipart format with
# attached graphs and such neat stuff. Sweet!
#
# Argument 1: Full system path to the pnp4nagios index.php for fetching
#             the graphs. Usually auto configured in OMD.
# Argument 2: HTTP-URL-Prefix to open multisite. When provided, several
#             links are added to the mail.

import os, re, sys, subprocess, pystache
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.image import MIMEImage

opt_debug = '-d' in sys.argv

class GraphException(Exception):
    pass

def prepare_contents(context):
    renderer = pystache.Renderer(search_dirs=[
        context["OMD_ROOT"] + "/local/share/check_mk/notifications/mailstache_templates",
        context["OMD_ROOT"] + "/share/check_mk/notifications/mailstache_templates"
    ])

    if context['WHAT'] == 'HOST':
        tmpl_subj = renderer.load_template("host_subject").decode("utf-8")
        tmpl_txt  = renderer.load_template("host_txt").decode("utf-8")
        tmpl_html = renderer.load_template("host_html").decode("utf-8")
    else:
        tmpl_subj = renderer.load_template("service_subject").decode("utf-8")
        tmpl_txt  = renderer.load_template("service_txt").decode("utf-8")
        tmpl_html = renderer.load_template("service_html").decode("utf-8")

    context["SUBJECT"] = renderer.render(tmpl_subj, context)

    # Render text
    result_txt  = renderer.render(tmpl_txt, context)

    # Change newlines to HTML
    context["LONGHOSTOUTPUT"]    = context["LONGHOSTOUTPUT"].replace("\\n", "<br />")
    context["LONGSERVICEOUTPUT"] = context["LONGSERVICEOUTPUT"].replace("\\n", "<br />")

    # Render HTML
    result_html = renderer.render(tmpl_html, context)

    return result_txt, result_html

def multipart_mail(target, subject, content_txt, content_html, attach = []):
    m = MIMEMultipart('related', _charset='utf-8')

    alt = MIMEMultipart('alternative')

    # The plain text part
    txt = MIMEText(content_txt, 'plain', _charset='utf-8')
    alt.attach(txt)

    # The html text part
    html = MIMEText(content_html, 'html', _charset='utf-8')
    alt.attach(html)

    m.attach(alt)

    # Add all attachments
    for what, name, contents, how in attach:
        if what == 'img':
            part = MIMEImage(contents, name = name)
        else:
            part = MIMEApplication(contents, name = name)
        part.add_header('Content-ID', '<%s>' % name)
        # how must be inline or attachment
        part.add_header('Content-Disposition', how, filename = name)
        m.attach(part)

    m['Subject'] = subject
    m['To']      = target

    return m

def send_mail(m, target):
    p = subprocess.Popen(["/usr/sbin/sendmail", "-i", target ], stdin = subprocess.PIPE)
    p.communicate(m.as_string())
    #with open("/tmp/mailout.eml", "wt") as f:
    #    f.write(m.as_string())
    return True

def fetch_pnp_data(context, params):
    try:
        # Autodetect the path in OMD environments
        path = "%s/share/pnp4nagios/htdocs/index.php" % context['OMD_ROOT']
        php_save_path = "-d session.save_path=%s/tmp/php/session" % context['OMD_ROOT']
    except:
        # Non-omd environment - use plugin argument 1
        path = context.get('PARAMETER_1', '')
        php_save_path = "" # Using default path

    if not os.path.exists(path):
        raise GraphException('Unable to locate pnp4nagios index.php (%s)' % path)

    return os.popen('REMOTE_USER="%s" php %s %s "%s"' % (context['CONTACTNAME'], php_save_path, path, params)).read()

def fetch_num_sources(context):
    svc_desc = context['WHAT'] == 'HOST' and '_HOST_' or context['SERVICEDESC']
    infos = fetch_pnp_data(context, '/json?host=%s&srv=%s&view=0' %
                                     (context['HOSTNAME'], svc_desc))
    if not infos.startswith('[{'):
        raise GraphException('Unable to fetch graph infos, got: "%s"' % infos)

    return infos.count('source=')

def fetch_graph(context, source, view = 1):
    svc_desc = context['WHAT'] == 'HOST' and '_HOST_' or context['SERVICEDESC']
    graph = fetch_pnp_data(context, '/image?host=%s&srv=%s&view=%d&source=%d' %
                                    (context['HOSTNAME'], svc_desc, view, source))

    if graph[:8] != '\x89PNG\r\n\x1a\n':
        raise GraphException('Unable to fetch the graph, got: "%s"' % graph)

    return graph

# Borrowed from share/check_mk/web/plugins/views/painters.py
def notes_matching_pattern_entries(dirs, item):
    from fnmatch import fnmatch
    matching = []
    for dir in dirs:
        if os.path.isdir(dir):
            entries = filter(lambda d: d[0] != '.', os.listdir(dir))
            entries.sort()
            entries.reverse()
            for pattern in entries:
                if pattern[0] == '.':
                    continue
                if fnmatch(item, pattern):
                    matching.append(dir + "/" + pattern)
    return matching

# Borrowed and modified from share/check_mk/web/plugins/views/painters.py
def get_custom_notes(context):
    host = context['HOSTNAME']
    svc = context.get('SERVICEDESC')

    files_svc = []
    if svc:
        notes_dir = context["OMD_ROOT"] + "/etc/check_mk/notes/services"
        dirs = notes_matching_pattern_entries([notes_dir], host)
        files_svc = notes_matching_pattern_entries(dirs, svc)
        files_svc.sort()
        files_svc.reverse()

    dirs = [ context["OMD_ROOT"] + "/etc/check_mk/notes/hosts" ]
    files_host = notes_matching_pattern_entries(dirs, host)
    files_host.sort()
    files_host.reverse()

    def replace_tags(text):
        base_url = ""
        if context.get('PARAMETER_2'):
            base_url = context['PARAMETER_2'].rstrip('/')
        return text\
            .replace(u'$URL_PREFIX$',     base_url)\
            .replace(u'$SITE$',           context['OMD_SITE'])\
            .replace(u'$HOSTNAME$',       host)\
            .replace(u'$HOSTNAME_LOWER$', host.lower())\
            .replace(u'$HOSTNAME_UPPER$', host.upper())\
            .replace(u'$HOSTNAME_TITLE$', host[0].upper() + host[1:].lower())\
            .replace(u'$HOSTADDRESS$',    context['HOSTADDRESS'])\
            .replace(u'$SERVICEOUTPUT$',  context.get('SERVICEOUTPUT', ""))\
            .replace(u'$HOSTOUTPUT$',     context.get('HOSTOUTPUT', ""))\
            .replace(u'$SERVICEDESC$',    context.get('SERVICEDESC', ""))

    contents_host = []
    contents_svc  = []

    for f in files_host:
        contents_host.append(replace_tags(unicode(file(f).read(), "utf-8").strip()))

    for f in files_svc:
        contents_svc.append(replace_tags(unicode(file(f).read(), "utf-8").strip()))

    result = { "HOST": [], "SERVICE": [] }
    result["HOST"] = "\n<hr />\n".join(contents_host)
    result["SERVICE"] = "\n<hr />\n".join(contents_svc)

    return result

def main():
    # gather all options from env
    context = dict([
        (var[7:], os.environ.get(var))
        for (var, value)
        in os.environ.items()
        if var.startswith("NOTIFY_")])

    context['HOSTNOTES']       = os.environ.get("NAGIOS_HOSTNOTES").decode("utf-8")
    context['HOSTNOTESURL']    = os.environ.get("NAGIOS_HOSTNOTESURL").decode("utf-8")
    context['SERVICENOTES']    = os.environ.get("NAGIOS_SERVICENOTES").decode("utf-8")
    context['SERVICENOTESURL'] = os.environ.get("NAGIOS_SERVICENOTESURL").decode("utf-8")
    context['HOSTOUTPUT']      = os.environ.get("NAGIOS_HOSTOUTPUT").decode("utf-8")
    context['SERVICEOUTPUT']   = os.environ.get("NAGIOS_SERVICEOUTPUT").decode("utf-8")
    context['LONGHOSTOUTPUT']  = os.environ.get("NAGIOS_LONGHOSTOUTPUT").decode("utf-8")
    context['LONGSERVICEOUTPUT'] = os.environ.get("NAGIOS_LONGSERVICEOUTPUT").decode("utf-8")

    notes = get_custom_notes(context)
    context['HOSTCUSTOMNOTES'] = notes["HOST"]
    context['SERVICECUSTOMNOTES'] = notes["SERVICE"]

    # Fetch graphs for this object. It first tries to detect how many sources
    # are available for this object. Then it loops through all sources and
    # fetches retrieves the images. If a problem occurs, it is printed to
    # stderr (-> notify.log) and the graph is not added to the mail.
    try:
        num_sources = fetch_num_sources(context)
    except GraphException, e:
        sys.stderr.write('Unable to fetch graph infos: %s\n' % e)
        num_sources = 0

    # If argument 2 is given, we know the base url to the installation and can add
    # links to hosts and services. ubercomfortable!
    if context.get('PARAMETER_2'):
        base_url = context['PARAMETER_2'].rstrip('/')
        host_url = base_url + context['HOSTURL']

        context['BASE_URL']       = context['PARAMETER_2']
        context['HOST_URL']       = host_url
        context['LINKEDHOSTNAME'] = '<a href="%s">%s</a>' % (host_url, context['HOSTNAME'])
        context['HOSTLINK']       = '\nLink:     %s' % host_url

        if context['WHAT'] == 'SERVICE':
            service_url = base_url + context['SERVICEURL']
            context['SERVICE_URL']       = service_url
            context['LINKEDSERVICEDESC'] = '<a href="%s">%s</a>' % (service_url, context['SERVICEDESC'])
            context['SERVICELINK']       = '\nLink:     %s' % service_url
    else:
        context['LINKEDHOSTNAME']    = context['HOSTNAME']
        context['LINKEDSERVICEDESC'] = context.get('SERVICEDESC', '')
        context['HOSTLINK']          = ''
        context['SERVICELINK']       = ''

    attachments = []
    graph_code = ''
    for source in range(0, num_sources):
        try:
            content = fetch_graph(context, source)
        except GraphException, e:
            sys.stderr.write('Unable to fetch graph: %s\n' % e)
            continue

        if context['WHAT'] == 'HOST':
            svc_desc = '_HOST_'
        else:
            svc_desc = context['SERVICEDESC'].replace(' ', '_')
            # replace forbidden windows characters < > ? " : | \ / *
            for token in ["<", ">", "?", "\"", ":", "|", "\\", "/", "*"] :
                svc_desc = svc_desc.replace(token, "x%s" % ord(token))
        name = '%s-%s-%d.png' % (context['HOSTNAME'], svc_desc, source)

        attachments.append(('img', name, content, 'inline'))

        context['GRAPH_%d' % source] = name
        graph_code += '<img src="cid:%s" />' % name

    if graph_code:
        context['GRAPH_CODE'] = (
            '<tr><th colspan=2>Graphs</th></tr>'
            '<tr class="even0"><td colspan=2 class=graphs>%s</td></tr>' % graph_code
        )
    else:
        context['GRAPH_CODE'] = ''

    # Prepare the mail contents (also SUBJECT)
    content_txt, content_html = prepare_contents(context)

    # Create the mail and send it
    m = multipart_mail(context['CONTACTEMAIL'], context['SUBJECT'], content_txt,
                       content_html, attachments)
    send_mail(m, context['CONTACTEMAIL'])

main()
